From 85239ed7cf30cab0333bd46310e8ddc1cf13868f Mon Sep 17 00:00:00 2001 From: DrKLO Date: Wed, 25 May 2016 14:49:47 -0700 Subject: [PATCH] Update to 3.9.0 --- TMessagesProj/build.gradle | 6 +- TMessagesProj/jni/Android.mk | 7 +- TMessagesProj/jni/SqliteWrapper.cpp | 241 + TMessagesProj/jni/jni.c | 5 - .../jni/libyuv/include/libyuv/compare_row.h | 9 +- .../jni/libyuv/include/libyuv/cpu_id.h | 2 +- .../libyuv/include/libyuv/planar_functions.h | 12 +- .../jni/libyuv/include/libyuv/rotate_row.h | 33 +- TMessagesProj/jni/libyuv/include/libyuv/row.h | 133 +- .../jni/libyuv/include/libyuv/scale_row.h | 68 +- .../jni/libyuv/include/libyuv/version.h | 2 +- TMessagesProj/jni/libyuv/source/convert.cc | 8 +- .../jni/libyuv/source/convert_argb.cc | 18 +- .../jni/libyuv/source/convert_from.cc | 14 +- .../jni/libyuv/source/convert_from_argb.cc | 175 +- .../jni/libyuv/source/convert_to_argb.cc | 12 +- TMessagesProj/jni/libyuv/source/cpu_id.cc | 27 +- .../jni/libyuv/source/planar_functions.cc | 90 +- TMessagesProj/jni/libyuv/source/rotate.cc | 36 +- TMessagesProj/jni/libyuv/source/rotate_any.cc | 8 +- .../jni/libyuv/source/rotate_mips.cc | 6 +- .../jni/libyuv/source/rotate_neon.cc | 8 +- .../jni/libyuv/source/rotate_neon64.cc | 8 +- TMessagesProj/jni/libyuv/source/row_any.cc | 15 +- TMessagesProj/jni/libyuv/source/row_common.cc | 68 +- TMessagesProj/jni/libyuv/source/row_gcc.cc | 263 +- TMessagesProj/jni/libyuv/source/row_mips.cc | 18 +- TMessagesProj/jni/libyuv/source/row_neon.cc | 84 +- TMessagesProj/jni/libyuv/source/row_neon64.cc | 48 +- TMessagesProj/jni/libyuv/source/row_win.cc | 131 +- TMessagesProj/jni/libyuv/source/scale.cc | 113 +- TMessagesProj/jni/libyuv/source/scale_any.cc | 21 + TMessagesProj/jni/libyuv/source/scale_argb.cc | 27 +- .../jni/libyuv/source/scale_common.cc | 38 +- TMessagesProj/jni/libyuv/source/scale_gcc.cc | 60 +- TMessagesProj/jni/libyuv/source/scale_mips.cc | 42 +- TMessagesProj/jni/libyuv/source/scale_neon.cc | 30 +- .../jni/libyuv/source/scale_neon64.cc | 30 +- TMessagesProj/jni/libyuv/source/scale_win.cc | 2 +- TMessagesProj/jni/sqlite.c | 11 - TMessagesProj/jni/sqlite.h | 10 - TMessagesProj/jni/sqlite/sqlite3.c | 32558 +++++++++++----- TMessagesProj/jni/sqlite/sqlite3.h | 1859 +- TMessagesProj/jni/sqlite_cursor.c | 79 - TMessagesProj/jni/sqlite_database.c | 42 - TMessagesProj/jni/sqlite_statement.c | 120 - TMessagesProj/jni/tgnet/Datacenter.cpp | 2 +- TMessagesProj/jni/tgnet/MTProtoScheme.cpp | 2 + TMessagesProj/jni/tgnet/MTProtoScheme.h | 3 +- TMessagesProj/src/main/AndroidManifest.xml | 17 + .../org/telegram/PhoneFormat/PhoneFormat.java | 64 +- .../org/telegram/SQLite/SQLiteCursor.java | 23 +- .../SQLite/SQLitePreparedStatement.java | 8 +- .../telegram/messenger/AndroidUtilities.java | 199 +- .../telegram/messenger/ApplicationLoader.java | 13 +- .../org/telegram/messenger/BuildVars.java | 4 +- .../telegram/messenger/FileLoadOperation.java | 29 +- .../org/telegram/messenger/ImageReceiver.java | 14 +- .../telegram/messenger/LocaleController.java | 2 +- .../telegram/messenger/MediaController.java | 95 +- .../org/telegram/messenger/MessageObject.java | 56 +- .../messenger/MessagesController.java | 173 +- .../telegram/messenger/MessagesStorage.java | 378 +- .../messenger/MusicBrowserService.java | 593 + .../messenger/NativeCrashManager.java | 2 - .../org/telegram/messenger/NativeLoader.java | 10 +- .../messenger/NotificationCenter.java | 6 + .../messenger/NotificationsController.java | 43 +- .../telegram/messenger/OpenChatReceiver.java | 36 + .../telegram/messenger/SecretChatHelper.java | 35 +- .../messenger/SendMessagesHelper.java | 95 +- .../messenger/TgChooserTargetService.java | 1 - .../org/telegram/messenger/UserConfig.java | 4 + .../telegram/messenger/browser/Browser.java | 6 +- .../telegram/messenger/query/BotQuery.java | 14 +- .../messenger/query/MessagesQuery.java | 25 +- .../telegram/messenger/query/SearchQuery.java | 410 + .../messenger/query/SharedMediaQuery.java | 12 +- .../messenger/query/StickersQuery.java | 6 +- .../support/widget/RecyclerView.java | 6 +- .../telegram/tgnet/ConnectionsManager.java | 3 + .../org/telegram/tgnet/NativeByteBuffer.java | 12 +- .../main/java/org/telegram/tgnet/TLRPC.java | 707 +- .../org/telegram/ui/ActionBar/ActionBar.java | 69 +- .../ui/ActionBar/ActionBarLayout.java | 36 +- .../ui/ActionBar/ActionBarMenuItem.java | 4 + .../telegram/ui/ActionBar/BaseFragment.java | 4 + .../telegram/ui/ActionBar/BottomSheet.java | 1011 +- .../ui/ActionBar/DrawerLayoutContainer.java | 1 - .../telegram/ui/ActionBar/SimpleTextView.java | 19 +- .../java/org/telegram/ui/ActionBar/Theme.java | 28 +- .../Adapters/BaseSearchAdapterRecycler.java | 4 +- .../ui/Adapters/ChatActivityAdapter.java | 396 - .../ui/Adapters/DialogsSearchAdapter.java | 202 +- .../telegram/ui/Adapters/MentionsAdapter.java | 208 +- .../telegram/ui/Adapters/StickersAdapter.java | 44 +- .../org/telegram/ui/CacheControlActivity.java | 8 +- .../org/telegram/ui/Cells/ChatBaseCell.java | 1234 - .../telegram/ui/Cells/ChatMessageCell.java | 2171 +- .../telegram/ui/Cells/ContextLinkCell.java | 14 +- .../org/telegram/ui/Cells/DialogCell.java | 56 +- .../telegram/ui/Cells/GreySectionCell.java | 4 +- .../org/telegram/ui/Cells/HintDialogCell.java | 175 + .../org/telegram/ui/Cells/MentionCell.java | 9 +- .../ui/Cells/PhotoAttachCameraCell.java | 6 +- .../telegram/ui/Cells/ProfileSearchCell.java | 2 +- .../telegram/ui/Cells/ShareDialogCell.java | 9 +- .../java/org/telegram/ui/Cells/UserCell.java | 10 +- .../java/org/telegram/ui/ChatActivity.java | 1089 +- .../ui/Components/ChatActivityEnterView.java | 124 +- .../ui/Components/ChatAttachAlert.java | 1369 + .../ui/Components/ChatAttachView.java | 611 - .../org/telegram/ui/Components/EmojiView.java | 58 +- .../ui/Components/EmptyTextProgressView.java | 5 + .../Components/ExtendedGridLayoutManager.java | 224 + .../ui/Components/FlowLayoutManager.java | 529 - .../PhotoViewerCaptionEnterView.java | 5 - .../ui/Components/ProgressCircleView.java | 28 + .../ui/Components/RecyclerListView.java | 5 + .../telegram/ui/Components/ShareAlert.java | 69 +- .../SizeNotifierFrameLayoutPhoto.java | 4 +- .../telegram/ui/Components/StickersAlert.java | 55 +- .../ui/Components/TypingDotsDrawable.java | 20 +- .../ui/Components/URLSpanUserMention.java | 27 + .../ui/Components/WebFrameLayout.java | 11 - .../java/org/telegram/ui/DialogsActivity.java | 85 +- .../java/org/telegram/ui/LaunchActivity.java | 40 +- .../org/telegram/ui/LocationActivity.java | 8 +- .../java/org/telegram/ui/PhotoViewer.java | 15 +- .../ui/PopupNotificationActivity.java | 4 +- .../java/org/telegram/ui/ProfileActivity.java | 60 +- .../org/telegram/ui/SettingsActivity.java | 2 +- .../org/telegram/ui/WallpapersActivity.java | 3 + .../src/main/res/drawable-hdpi/book_bot.png | Bin 0 -> 3825 bytes .../main/res/drawable-hdpi/book_channel.png | Bin 0 -> 3110 bytes .../src/main/res/drawable-hdpi/book_group.png | Bin 0 -> 3563 bytes .../src/main/res/drawable-hdpi/book_logo.png | Bin 0 -> 1474 bytes .../src/main/res/drawable-hdpi/book_user.png | Bin 0 -> 3161 bytes .../main/res/drawable-hdpi/instant_camera.png | Bin 0 -> 2450 bytes .../src/main/res/drawable-hdpi/scroll_tip.png | Bin 0 -> 1139 bytes .../src/main/res/drawable-mdpi/boog_group.png | Bin 0 -> 2553 bytes .../src/main/res/drawable-mdpi/book_bot.png | Bin 0 -> 2826 bytes .../main/res/drawable-mdpi/book_channel.png | Bin 0 -> 2301 bytes .../src/main/res/drawable-mdpi/book_logo.png | Bin 0 -> 1279 bytes .../src/main/res/drawable-mdpi/book_user.png | Bin 0 -> 2303 bytes .../main/res/drawable-mdpi/instant_camera.png | Bin 0 -> 1639 bytes .../src/main/res/drawable-mdpi/scroll_tip.png | Bin 0 -> 1112 bytes .../src/main/res/drawable-xhdpi/book_bot.png | Bin 0 -> 4759 bytes .../main/res/drawable-xhdpi/book_channel.png | Bin 0 -> 3787 bytes .../main/res/drawable-xhdpi/book_group.png | Bin 0 -> 4390 bytes .../src/main/res/drawable-xhdpi/book_logo.png | Bin 0 -> 1635 bytes .../src/main/res/drawable-xhdpi/book_user.png | Bin 0 -> 3972 bytes .../res/drawable-xhdpi/instant_camera.png | Bin 0 -> 2883 bytes .../main/res/drawable-xhdpi/scroll_tip.png | Bin 0 -> 1316 bytes .../src/main/res/drawable-xxhdpi/book_bot.png | Bin 0 -> 6717 bytes .../main/res/drawable-xxhdpi/book_channel.png | Bin 0 -> 5326 bytes .../main/res/drawable-xxhdpi/book_group.png | Bin 0 -> 6359 bytes .../main/res/drawable-xxhdpi/book_logo.png | Bin 0 -> 2056 bytes .../main/res/drawable-xxhdpi/book_user.png | Bin 0 -> 5470 bytes .../res/drawable-xxhdpi/instant_camera.png | Bin 0 -> 4196 bytes .../main/res/drawable-xxhdpi/scroll_tip.png | Bin 0 -> 1531 bytes .../main/res/drawable-xxxhdpi/book_logo.png | Bin 0 -> 2434 bytes .../src/main/res/drawable/ic_player.png | Bin 0 -> 2047 bytes .../src/main/res/values-ar/strings.xml | 16 +- .../src/main/res/values-de/strings.xml | 16 +- .../src/main/res/values-es/strings.xml | 28 +- .../src/main/res/values-it/strings.xml | 38 +- .../src/main/res/values-ko/strings.xml | 16 +- .../src/main/res/values-nl/strings.xml | 28 +- .../src/main/res/values-pt-rBR/strings.xml | 28 +- .../src/main/res/values-pt-rPT/strings.xml | 28 +- TMessagesProj/src/main/res/values/strings.xml | 26 +- .../src/main/res/xml/automotive_app_desc.xml | 1 + build.gradle | 4 +- 174 files changed, 33705 insertions(+), 16403 deletions(-) create mode 100755 TMessagesProj/jni/SqliteWrapper.cpp delete mode 100755 TMessagesProj/jni/sqlite.c delete mode 100755 TMessagesProj/jni/sqlite.h delete mode 100755 TMessagesProj/jni/sqlite_cursor.c delete mode 100755 TMessagesProj/jni/sqlite_database.c delete mode 100755 TMessagesProj/jni/sqlite_statement.c create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Adapters/ChatActivityAdapter.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/FlowLayoutManager.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/book_bot.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/book_channel.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/book_group.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/book_logo.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/book_user.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/instant_camera.png create mode 100755 TMessagesProj/src/main/res/drawable-hdpi/scroll_tip.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/boog_group.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/book_bot.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/book_channel.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/book_logo.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/book_user.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/instant_camera.png create mode 100755 TMessagesProj/src/main/res/drawable-mdpi/scroll_tip.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/book_bot.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/book_channel.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/book_group.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/book_logo.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/book_user.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/instant_camera.png create mode 100755 TMessagesProj/src/main/res/drawable-xhdpi/scroll_tip.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/book_bot.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/book_channel.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/book_group.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/book_logo.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/book_user.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/instant_camera.png create mode 100755 TMessagesProj/src/main/res/drawable-xxhdpi/scroll_tip.png create mode 100644 TMessagesProj/src/main/res/drawable-xxxhdpi/book_logo.png create mode 100644 TMessagesProj/src/main/res/drawable/ic_player.png diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 682d05af2..84dc35852 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -8,7 +8,7 @@ dependencies { compile 'com.android.support:support-v4:23.3.0' compile "com.google.android.gms:play-services-gcm:8.4.0" compile "com.google.android.gms:play-services-maps:8.4.0" - compile 'net.hockeyapp.android:HockeySDK:3.6.+' + compile 'net.hockeyapp.android:HockeySDK:4.0.+' compile 'com.googlecode.mp4parser:isoparser:1.0.+' } @@ -63,7 +63,7 @@ android { } } - defaultConfig.versionCode = 787 + defaultConfig.versionCode = 803 sourceSets.main { jniLibs.srcDir 'libs' @@ -114,7 +114,7 @@ android { defaultConfig { minSdkVersion 9 targetSdkVersion 23 - versionName "3.8.1" + versionName "3.9.0" } } diff --git a/TMessagesProj/jni/Android.mk b/TMessagesProj/jni/Android.mk index e5ca2a519..d680d15d2 100755 --- a/TMessagesProj/jni/Android.mk +++ b/TMessagesProj/jni/Android.mk @@ -235,7 +235,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false -LOCAL_MODULE := tmessages.21 +LOCAL_MODULE := tmessages.22 LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS @@ -521,15 +521,12 @@ endif LOCAL_SRC_FILES += \ ./jni.c \ -./sqlite_cursor.c \ -./sqlite_database.c \ -./sqlite_statement.c \ -./sqlite.c \ ./audio.c \ ./utils.c \ ./image.c \ ./video.c \ ./gifvideo.cpp \ +./SqliteWrapper.cpp \ ./TgNetWrapper.cpp \ ./NativeLoader.cpp diff --git a/TMessagesProj/jni/SqliteWrapper.cpp b/TMessagesProj/jni/SqliteWrapper.cpp new file mode 100755 index 000000000..67cf62315 --- /dev/null +++ b/TMessagesProj/jni/SqliteWrapper.cpp @@ -0,0 +1,241 @@ +#include "sqlite/sqlite3.h" +#include "tgnet/NativeByteBuffer.h" +#include "tgnet/BuffersStorage.h" + +void throw_sqlite3_exception(JNIEnv *env, sqlite3 *handle, int errcode) { + if (SQLITE_OK == errcode) { + errcode = sqlite3_errcode(handle); + } + const char *errmsg = sqlite3_errmsg(handle); + jclass exClass = env->FindClass("org/telegram/SQLite/SQLiteException"); + env->ThrowNew(exClass, errmsg); +} + +extern "C" { + +int Java_org_telegram_SQLite_SQLitePreparedStatement_step(JNIEnv *env, jobject object, int statementHandle) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_step(handle); + if (errcode == SQLITE_ROW) { + return 0; + } else if(errcode == SQLITE_DONE) { + return 1; + } else if(errcode == SQLITE_BUSY) { + return -1; + } + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); +} + +int Java_org_telegram_SQLite_SQLitePreparedStatement_prepare(JNIEnv *env, jobject object, int sqliteHandle, jstring sql) { + sqlite3 *handle = (sqlite3 *) sqliteHandle; + + char const *sqlStr = env->GetStringUTFChars(sql, 0); + + sqlite3_stmt *stmt_handle; + + int errcode = sqlite3_prepare_v2(handle, sqlStr, -1, &stmt_handle, 0); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, handle, errcode); + } + + if (sqlStr != 0) { + env->ReleaseStringUTFChars(sql, sqlStr); + } + + return (int) stmt_handle; +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_reset(JNIEnv *env, jobject object, int statementHandle) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_reset(handle); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_finalize(JNIEnv *env, jobject object, int statementHandle) { + sqlite3_finalize((sqlite3_stmt *) statementHandle); +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindByteBuffer(JNIEnv *env, jobject object, int statementHandle, int index, jobject value, int length) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + void *buf = env->GetDirectBufferAddress(value); + + int errcode = sqlite3_bind_blob(handle, index, buf, length, SQLITE_STATIC); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindString(JNIEnv *env, jobject object, int statementHandle, int index, jstring value) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + char const *valueStr = env->GetStringUTFChars(value, 0); + + int errcode = sqlite3_bind_text(handle, index, valueStr, -1, SQLITE_TRANSIENT); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } + + if (valueStr != 0) { + env->ReleaseStringUTFChars(value, valueStr); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindInt(JNIEnv *env, jobject object, int statementHandle, int index, int value) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_bind_int(handle, index, value); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindLong(JNIEnv *env, jobject object, int statementHandle, int index, long long value) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_bind_int64(handle, index, value); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindDouble(JNIEnv *env, jobject object, int statementHandle, int index, double value) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_bind_double(handle, index, value); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindNull(JNIEnv *env, jobject object, int statementHandle, int index) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + + int errcode = sqlite3_bind_null(handle, index); + if (SQLITE_OK != errcode) { + throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); + } +} + +void Java_org_telegram_SQLite_SQLiteDatabase_closedb(JNIEnv *env, jobject object, int sqliteHandle) { + sqlite3 *handle = (sqlite3 *)sqliteHandle; + int err = sqlite3_close(handle); + if (SQLITE_OK != err) { + throw_sqlite3_exception(env, handle, err); + } +} + +void Java_org_telegram_SQLite_SQLiteDatabase_beginTransaction(JNIEnv *env, jobject object, int sqliteHandle) { + sqlite3 *handle = (sqlite3 *)sqliteHandle; + sqlite3_exec(handle, "BEGIN", 0, 0, 0); +} + +void Java_org_telegram_SQLite_SQLiteDatabase_commitTransaction(JNIEnv *env, jobject object, int sqliteHandle) { + sqlite3 *handle = (sqlite3 *)sqliteHandle; + sqlite3_exec(handle, "COMMIT", 0, 0, 0); +} + +int Java_org_telegram_SQLite_SQLiteDatabase_opendb(JNIEnv *env, jobject object, jstring fileName, jstring tempDir) { + char const *fileNameStr = env->GetStringUTFChars(fileName, 0); + char const *tempDirStr = env->GetStringUTFChars(tempDir, 0); + + if (sqlite3_temp_directory != 0) { + sqlite3_free(sqlite3_temp_directory); + } + sqlite3_temp_directory = sqlite3_mprintf("%s", tempDirStr); + + sqlite3 *handle = 0; + int err = sqlite3_open(fileNameStr, &handle); + if (SQLITE_OK != err) { + throw_sqlite3_exception(env, handle, err); + } + if (fileNameStr != 0) { + env->ReleaseStringUTFChars(fileName, fileNameStr); + } + if (tempDirStr != 0) { + env->ReleaseStringUTFChars(tempDir, tempDirStr); + } + return (int)handle; +} + +int Java_org_telegram_SQLite_SQLiteCursor_columnType(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + return sqlite3_column_type(handle, columnIndex); +} + +int Java_org_telegram_SQLite_SQLiteCursor_columnIsNull(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + int valType = sqlite3_column_type(handle, columnIndex); + return SQLITE_NULL == valType; +} + +int Java_org_telegram_SQLite_SQLiteCursor_columnIntValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + int valType = sqlite3_column_type(handle, columnIndex); + if (SQLITE_NULL == valType) { + return 0; + } + return sqlite3_column_int(handle, columnIndex); +} + +long long Java_org_telegram_SQLite_SQLiteCursor_columnLongValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + int valType = sqlite3_column_type(handle, columnIndex); + if (SQLITE_NULL == valType) { + return 0; + } + return sqlite3_column_int64(handle, columnIndex); +} + +double Java_org_telegram_SQLite_SQLiteCursor_columnDoubleValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + int valType = sqlite3_column_type(handle, columnIndex); + if (SQLITE_NULL == valType) { + return 0; + } + return sqlite3_column_double(handle, columnIndex); +} + +jstring Java_org_telegram_SQLite_SQLiteCursor_columnStringValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + const char *str = (const char *) sqlite3_column_text(handle, columnIndex); + if (str != 0) { + return env->NewStringUTF(str); + } + return 0; +} + +jbyteArray Java_org_telegram_SQLite_SQLiteCursor_columnByteArrayValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + const jbyte *buf = (const jbyte *) sqlite3_column_blob(handle, columnIndex); + int length = sqlite3_column_bytes(handle, columnIndex); + if (buf != nullptr && length > 0) { + jbyteArray result = env->NewByteArray(length); + env->SetByteArrayRegion(result, 0, length, buf); + return result; + } + return nullptr; +} + +int Java_org_telegram_SQLite_SQLiteCursor_columnByteBufferValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; + int length = sqlite3_column_bytes(handle, columnIndex); + if (length <= 0) { + return 0; + } + NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(length); + if (buffer == nullptr) { + return 0; + } + const char *buf = (const char *) sqlite3_column_blob(handle, columnIndex); + if (buf == nullptr) { + return 0; + } + memcpy(buffer->bytes(), buf, length); + return (int) buffer; +} + +} diff --git a/TMessagesProj/jni/jni.c b/TMessagesProj/jni/jni.c index 711b91c90..1589a61e9 100644 --- a/TMessagesProj/jni/jni.c +++ b/TMessagesProj/jni/jni.c @@ -7,7 +7,6 @@ #include #include #include "utils.h" -#include "sqlite.h" #include "image.h" int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env); @@ -21,10 +20,6 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { return -1; } - if (sqliteOnJNILoad(vm, reserved, env) == -1) { - return -1; - } - if (imageOnJNILoad(vm, reserved, env) == -1) { return -1; } diff --git a/TMessagesProj/jni/libyuv/include/libyuv/compare_row.h b/TMessagesProj/jni/libyuv/include/libyuv/compare_row.h index 4562da047..38a957b2c 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/compare_row.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/compare_row.h @@ -22,6 +22,12 @@ extern "C" { (defined(__i386__) && !defined(__SSE2__)) #define LIBYUV_DISABLE_X86 #endif +// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define LIBYUV_DISABLE_X86 +#endif +#endif // Visual C 2012 required for AVX2. #if defined(_M_IX86) && !defined(__clang__) && \ @@ -36,7 +42,8 @@ extern "C" { #endif // clang >= 3.4 #endif // __clang__ -#if defined(_M_IX86) && (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) +#if !defined(LIBYUV_DISABLE_X86) && \ + defined(_M_IX86) && (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) #define HAS_HASHDJB2_AVX2 #endif diff --git a/TMessagesProj/jni/libyuv/include/libyuv/cpu_id.h b/TMessagesProj/jni/libyuv/include/libyuv/cpu_id.h index a83edd501..2ccc3e7dd 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/cpu_id.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/cpu_id.h @@ -41,7 +41,7 @@ static const int kCpuHasAVX3 = 0x2000; // These flags are only valid on MIPS processors. static const int kCpuHasMIPS = 0x10000; -static const int kCpuHasMIPS_DSPR2 = 0x20000; +static const int kCpuHasDSPR2 = 0x20000; // Internal function used to auto-init. LIBYUV_API diff --git a/TMessagesProj/jni/libyuv/include/libyuv/planar_functions.h b/TMessagesProj/jni/libyuv/include/libyuv/planar_functions.h index 9d30225d4..9c19a59df 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/planar_functions.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/planar_functions.h @@ -384,12 +384,6 @@ int ARGBUnattenuate(const uint8* src_argb, int src_stride_argb, uint8* dst_argb, int dst_stride_argb, int width, int height); -// Convert MJPG to ARGB. -LIBYUV_API -int MJPGToARGB(const uint8* sample, size_t sample_size, - uint8* argb, int argb_stride, - int w, int h, int dw, int dh); - // Internal function - do not call directly. // Computes table of cumulative sum for image where the value is the sum // of all values above and to the left of the entry. Used by ARGBBlur. @@ -453,6 +447,12 @@ int I420Interpolate(const uint8* src0_y, int src0_stride_y, (defined(__i386__) && !defined(__SSE2__)) #define LIBYUV_DISABLE_X86 #endif +// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define LIBYUV_DISABLE_X86 +#endif +#endif // The following are available on all x86 platforms: #if !defined(LIBYUV_DISABLE_X86) && \ (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) diff --git a/TMessagesProj/jni/libyuv/include/libyuv/rotate_row.h b/TMessagesProj/jni/libyuv/include/libyuv/rotate_row.h index e3838295c..ebc487f9a 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/rotate_row.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/rotate_row.h @@ -22,7 +22,12 @@ extern "C" { (defined(__i386__) && !defined(__SSE2__)) #define LIBYUV_DISABLE_X86 #endif - +// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define LIBYUV_DISABLE_X86 +#endif +#endif // The following are available for Visual C and clangcl 32 bit: #if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) #define HAS_TRANSPOSEWX8_SSSE3 @@ -51,8 +56,8 @@ extern "C" { #if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ defined(__mips__) && \ defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_TRANSPOSEWX8_MIPS_DSPR2 -#define HAS_TRANSPOSEUVWX8_MIPS_DSPR2 +#define HAS_TRANSPOSEWX8_DSPR2 +#define HAS_TRANSPOSEUVWX8_DSPR2 #endif // defined(__mips__) void TransposeWxH_C(const uint8* src, int src_stride, @@ -66,10 +71,10 @@ void TransposeWx8_SSSE3(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); void TransposeWx8_Fast_SSSE3(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); -void TransposeWx8_MIPS_DSPR2(const uint8* src, int src_stride, +void TransposeWx8_DSPR2(const uint8* src, int src_stride, + uint8* dst, int dst_stride, int width); +void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_MIPS_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); void TransposeWx8_Any_NEON(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); @@ -77,8 +82,8 @@ void TransposeWx8_Any_SSSE3(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); void TransposeWx8_Fast_Any_SSSE3(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_MIPS_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); +void TransposeWx8_Any_DSPR2(const uint8* src, int src_stride, + uint8* dst, int dst_stride, int width); void TransposeUVWxH_C(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, @@ -94,9 +99,9 @@ void TransposeUVWx8_SSE2(const uint8* src, int src_stride, void TransposeUVWx8_NEON(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_MIPS_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); +void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, + uint8* dst_a, int dst_stride_a, + uint8* dst_b, int dst_stride_b, int width); void TransposeUVWx8_Any_SSE2(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, @@ -104,9 +109,9 @@ void TransposeUVWx8_Any_SSE2(const uint8* src, int src_stride, void TransposeUVWx8_Any_NEON(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_MIPS_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); +void TransposeUVWx8_Any_DSPR2(const uint8* src, int src_stride, + uint8* dst_a, int dst_stride_a, + uint8* dst_b, int dst_stride_b, int width); #ifdef __cplusplus } // extern "C" diff --git a/TMessagesProj/jni/libyuv/include/libyuv/row.h b/TMessagesProj/jni/libyuv/include/libyuv/row.h index f7620eea1..b5d9aaa17 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/row.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/row.h @@ -41,6 +41,12 @@ extern "C" { (defined(__i386__) && !defined(__SSE2__)) #define LIBYUV_DISABLE_X86 #endif +// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define LIBYUV_DISABLE_X86 +#endif +#endif // True if compiling for SSSE3 as a requirement. #if defined(__SSSE3__) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 3)) #define LIBYUV_SSSE3_ONLY @@ -93,7 +99,6 @@ extern "C" { #define HAS_ARGBTORGB24ROW_SSSE3 #define HAS_ARGBTORGB565DITHERROW_SSE2 #define HAS_ARGBTORGB565ROW_SSE2 -#define HAS_ARGBTOUV422ROW_SSSE3 #define HAS_ARGBTOUV444ROW_SSSE3 #define HAS_ARGBTOUVJROW_SSSE3 #define HAS_ARGBTOUVROW_SSSE3 @@ -105,17 +110,6 @@ extern "C" { #define HAS_COPYROW_SSE2 #define HAS_H422TOARGBROW_SSSE3 #define HAS_I400TOARGBROW_SSE2 -// The following functions fail on gcc/clang 32 bit with fpic and framepointer. -// caveat: clangcl uses row_win.cc which works. -#if defined(NDEBUG) || !(defined(_DEBUG) && defined(__i386__)) || \ - !defined(__i386__) || defined(_MSC_VER) -// TODO(fbarchard): fix build error on x86 debug -// https://code.google.com/p/libyuv/issues/detail?id=524 -#define HAS_I411TOARGBROW_SSSE3 -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_SSSE3 -#endif #define HAS_I422TOARGB1555ROW_SSSE3 #define HAS_I422TOARGB4444ROW_SSSE3 #define HAS_I422TOARGBROW_SSSE3 @@ -129,7 +123,6 @@ extern "C" { #define HAS_J422TOARGBROW_SSSE3 #define HAS_MERGEUVROW_SSE2 #define HAS_MIRRORROW_SSSE3 -#define HAS_MIRRORROW_UV_SSSE3 #define HAS_MIRRORUVROW_SSSE3 #define HAS_NV12TOARGBROW_SSSE3 #define HAS_NV12TORGB565ROW_SSSE3 @@ -173,6 +166,7 @@ extern "C" { #define HAS_ARGBSHADEROW_SSE2 #define HAS_ARGBSUBTRACTROW_SSE2 #define HAS_ARGBUNATTENUATEROW_SSE2 +#define HAS_BLENDPLANEROW_SSSE3 #define HAS_COMPUTECUMULATIVESUMROW_SSE2 #define HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 #define HAS_INTERPOLATEROW_SSSE3 @@ -182,7 +176,18 @@ extern "C" { #define HAS_SOBELXROW_SSE2 #define HAS_SOBELXYROW_SSE2 #define HAS_SOBELYROW_SSE2 -#define HAS_BLENDPLANEROW_SSSE3 + +// The following functions fail on gcc/clang 32 bit with fpic and framepointer. +// caveat: clangcl uses row_win.cc which works. +#if defined(NDEBUG) || !(defined(_DEBUG) && defined(__i386__)) || \ + !defined(__i386__) || defined(_MSC_VER) +// TODO(fbarchard): fix build error on x86 debug +// https://code.google.com/p/libyuv/issues/detail?id=524 +#define HAS_I411TOARGBROW_SSSE3 +// TODO(fbarchard): fix build error on android_full_debug=1 +// https://code.google.com/p/libyuv/issues/detail?id=517 +#define HAS_I422ALPHATOARGBROW_SSSE3 +#endif #endif // The following are available on all x86 platforms, but @@ -196,6 +201,7 @@ extern "C" { #define HAS_ARGBPOLYNOMIALROW_AVX2 #define HAS_ARGBSHUFFLEROW_AVX2 #define HAS_ARGBTORGB565DITHERROW_AVX2 +#define HAS_ARGBTOUVJROW_AVX2 #define HAS_ARGBTOUVROW_AVX2 #define HAS_ARGBTOYJROW_AVX2 #define HAS_ARGBTOYROW_AVX2 @@ -207,15 +213,20 @@ extern "C" { // https://code.google.com/p/libyuv/issues/detail?id=517 #define HAS_I422ALPHATOARGBROW_AVX2 #endif -#define HAS_I444TOARGBROW_AVX2 +#define HAS_I411TOARGBROW_AVX2 +#define HAS_I422TOARGB1555ROW_AVX2 +#define HAS_I422TOARGB4444ROW_AVX2 #define HAS_I422TOARGBROW_AVX2 #define HAS_I422TORGB24ROW_AVX2 +#define HAS_I422TORGB565ROW_AVX2 #define HAS_I422TORGBAROW_AVX2 +#define HAS_I444TOARGBROW_AVX2 #define HAS_INTERPOLATEROW_AVX2 #define HAS_J422TOARGBROW_AVX2 #define HAS_MERGEUVROW_AVX2 #define HAS_MIRRORROW_AVX2 #define HAS_NV12TOARGBROW_AVX2 +#define HAS_NV12TORGB565ROW_AVX2 #define HAS_NV21TOARGBROW_AVX2 #define HAS_SPLITUVROW_AVX2 #define HAS_UYVYTOARGBROW_AVX2 @@ -245,12 +256,7 @@ extern "C" { #define HAS_ARGBTOARGB1555ROW_AVX2 #define HAS_ARGBTOARGB4444ROW_AVX2 #define HAS_ARGBTORGB565ROW_AVX2 -#define HAS_I411TOARGBROW_AVX2 -#define HAS_I422TOARGB1555ROW_AVX2 -#define HAS_I422TOARGB4444ROW_AVX2 -#define HAS_I422TORGB565ROW_AVX2 #define HAS_J400TOARGBROW_AVX2 -#define HAS_NV12TORGB565ROW_AVX2 #define HAS_RGB565TOARGBROW_AVX2 #endif @@ -264,7 +270,6 @@ extern "C" { // The following are available on Neon platforms: #if !defined(LIBYUV_DISABLE_NEON) && \ (defined(__aarch64__) || defined(__ARM_NEON__) || defined(LIBYUV_NEON)) -#define HAS_I422ALPHATOARGBROW_NEON #define HAS_ABGRTOUVROW_NEON #define HAS_ABGRTOYROW_NEON #define HAS_ARGB1555TOARGBROW_NEON @@ -281,7 +286,6 @@ extern "C" { #define HAS_ARGBTORGB565DITHERROW_NEON #define HAS_ARGBTORGB565ROW_NEON #define HAS_ARGBTOUV411ROW_NEON -#define HAS_ARGBTOUV422ROW_NEON #define HAS_ARGBTOUV444ROW_NEON #define HAS_ARGBTOUVJROW_NEON #define HAS_ARGBTOUVROW_NEON @@ -292,6 +296,7 @@ extern "C" { #define HAS_COPYROW_NEON #define HAS_I400TOARGBROW_NEON #define HAS_I411TOARGBROW_NEON +#define HAS_I422ALPHATOARGBROW_NEON #define HAS_I422TOARGB1555ROW_NEON #define HAS_I422TOARGB4444ROW_NEON #define HAS_I422TOARGBROW_NEON @@ -357,11 +362,11 @@ extern "C" { (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) #define HAS_COPYROW_MIPS #if defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_I422TOARGBROW_MIPS_DSPR2 -#define HAS_INTERPOLATEROW_MIPS_DSPR2 -#define HAS_MIRRORROW_MIPS_DSPR2 -#define HAS_MIRRORUVROW_MIPS_DSPR2 -#define HAS_SPLITUVROW_MIPS_DSPR2 +#define HAS_I422TOARGBROW_DSPR2 +#define HAS_INTERPOLATEROW_DSPR2 +#define HAS_MIRRORROW_DSPR2 +#define HAS_MIRRORUVROW_DSPR2 +#define HAS_SPLITUVROW_DSPR2 #endif #endif @@ -648,8 +653,6 @@ void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width); void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width); void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV422Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, @@ -712,8 +715,8 @@ void ARGB4444ToYRow_Any_NEON(const uint8* src_argb4444, uint8* dst_y, void ARGBToUVRow_AVX2(const uint8* src_argb, int src_stride_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); +void ARGBToUVJRow_AVX2(const uint8* src_argb, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width); void ARGBToUVRow_SSSE3(const uint8* src_argb, int src_stride_argb, uint8* dst_u, uint8* dst_v, int width); void ARGBToUVJRow_SSSE3(const uint8* src_argb, int src_stride_argb, @@ -724,6 +727,10 @@ void ABGRToUVRow_SSSE3(const uint8* src_abgr, int src_stride_abgr, uint8* dst_u, uint8* dst_v, int width); void RGBAToUVRow_SSSE3(const uint8* src_rgba, int src_stride_rgba, uint8* dst_u, uint8* dst_v, int width); +void ARGBToUVRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width); +void ARGBToUVJRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width); void ARGBToUVRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, uint8* dst_u, uint8* dst_v, int width); void ARGBToUVJRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, @@ -736,8 +743,6 @@ void RGBAToUVRow_Any_SSSE3(const uint8* src_rgba, int src_stride_rgba, uint8* dst_u, uint8* dst_v, int width); void ARGBToUV444Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV422Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); void ARGBToUV411Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); void ARGBToUVRow_Any_NEON(const uint8* src_argb, int src_stride_argb, @@ -788,24 +793,15 @@ void ARGBToUV444Row_SSSE3(const uint8* src_argb, void ARGBToUV444Row_Any_SSSE3(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV422Row_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV422Row_Any_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - void ARGBToUV444Row_C(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV422Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); void ARGBToUV411Row_C(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJ422Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); void MirrorRow_AVX2(const uint8* src, uint8* dst, int width); void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width); void MirrorRow_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_MIPS_DSPR2(const uint8* src, uint8* dst, int width); +void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width); void MirrorRow_C(const uint8* src, uint8* dst, int width); void MirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); void MirrorRow_Any_SSSE3(const uint8* src, uint8* dst, int width); @@ -816,10 +812,9 @@ void MirrorUVRow_SSSE3(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void MirrorUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); +void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, + int width); +void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width); void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width); @@ -836,16 +831,16 @@ void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void SplitUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); +void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, + int width); void SplitUVRow_Any_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); void SplitUVRow_Any_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); void SplitUVRow_Any_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void SplitUVRow_Any_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); +void SplitUVRow_Any_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, + int width); void MergeUVRow_C(const uint8* src_u, const uint8* src_v, uint8* dst_uv, int width); @@ -1625,18 +1620,18 @@ void UYVYToARGBRow_Any_NEON(const uint8* src_uyvy, uint8* dst_argb, const struct YuvConstants* yuvconstants, int width); -void I422ToARGBRow_MIPS_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_MIPS_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); +void I422ToARGBRow_DSPR2(const uint8* src_y, + const uint8* src_u, + const uint8* src_v, + uint8* dst_argb, + const struct YuvConstants* yuvconstants, + int width); +void I422ToARGBRow_DSPR2(const uint8* src_y, + const uint8* src_u, + const uint8* src_v, + uint8* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, @@ -1846,9 +1841,9 @@ void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, void InterpolateRow_NEON(uint8* dst_ptr, const uint8* src_ptr, ptrdiff_t src_stride_ptr, int width, int source_y_fraction); -void InterpolateRow_MIPS_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); +void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, + ptrdiff_t src_stride_ptr, int width, + int source_y_fraction); void InterpolateRow_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, ptrdiff_t src_stride_ptr, int width, int source_y_fraction); @@ -1858,9 +1853,9 @@ void InterpolateRow_Any_SSSE3(uint8* dst_ptr, const uint8* src_ptr, void InterpolateRow_Any_AVX2(uint8* dst_ptr, const uint8* src_ptr, ptrdiff_t src_stride_ptr, int width, int source_y_fraction); -void InterpolateRow_Any_MIPS_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); +void InterpolateRow_Any_DSPR2(uint8* dst_ptr, const uint8* src_ptr, + ptrdiff_t src_stride_ptr, int width, + int source_y_fraction); void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, ptrdiff_t src_stride_ptr, diff --git a/TMessagesProj/jni/libyuv/include/libyuv/scale_row.h b/TMessagesProj/jni/libyuv/include/libyuv/scale_row.h index 45127bda3..df699e6c2 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/scale_row.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/scale_row.h @@ -23,6 +23,12 @@ extern "C" { (defined(__i386__) && !defined(__SSE2__)) #define LIBYUV_DISABLE_X86 #endif +// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define LIBYUV_DISABLE_X86 +#endif +#endif // GCC >= 4.7.0 required for AVX2. #if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) @@ -90,10 +96,10 @@ extern "C" { // The following are available on Mips platforms: #if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ defined(__mips__) && defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_SCALEROWDOWN2_MIPS_DSPR2 -#define HAS_SCALEROWDOWN4_MIPS_DSPR2 -#define HAS_SCALEROWDOWN34_MIPS_DSPR2 -#define HAS_SCALEROWDOWN38_MIPS_DSPR2 +#define HAS_SCALEROWDOWN2_DSPR2 +#define HAS_SCALEROWDOWN4_DSPR2 +#define HAS_SCALEROWDOWN34_DSPR2 +#define HAS_SCALEROWDOWN38_DSPR2 #endif // Scale ARGB vertically with bilinear interpolation. @@ -146,6 +152,8 @@ void ScaleRowDown2Linear_16_C(const uint16* src_ptr, ptrdiff_t src_stride, uint16* dst, int dst_width); void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst, int dst_width); +void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, uint16* dst, int dst_width); void ScaleRowDown4_C(const uint8* src_ptr, ptrdiff_t src_stride, @@ -269,13 +277,17 @@ void ScaleRowDown2_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, void ScaleRowDown2Linear_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width); void ScaleRowDown2Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); + uint8* dst_ptr, int dst_width); +void ScaleRowDown2Box_Odd_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width); void ScaleRowDown2_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width); void ScaleRowDown2Linear_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width); void ScaleRowDown2Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); + uint8* dst_ptr, int dst_width); +void ScaleRowDown2Box_Odd_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width); void ScaleRowDown4_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width); void ScaleRowDown4Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, @@ -431,6 +443,8 @@ void ScaleRowDown2Linear_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst, int dst_width); void ScaleRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst, int dst_width); +void ScaleRowDown2Box_Odd_NEON(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); void ScaleRowDown4_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width); void ScaleRowDown4Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, @@ -460,28 +474,26 @@ void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, void ScaleFilterCols_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, int dst_width, int x, int dx); - -void ScaleRowDown2_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_0_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown38_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_2_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_MIPS_DSPR2(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); +void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* d, int dst_width); +void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* d, int dst_width); +void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width); +void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width); +void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width); #ifdef __cplusplus } // extern "C" diff --git a/TMessagesProj/jni/libyuv/include/libyuv/version.h b/TMessagesProj/jni/libyuv/include/libyuv/version.h index ab0f88d9e..f1e6ae2f2 100644 --- a/TMessagesProj/jni/libyuv/include/libyuv/version.h +++ b/TMessagesProj/jni/libyuv/include/libyuv/version.h @@ -11,6 +11,6 @@ #ifndef INCLUDE_LIBYUV_VERSION_H_ // NOLINT #define INCLUDE_LIBYUV_VERSION_H_ -#define LIBYUV_VERSION 1561 +#define LIBYUV_VERSION 1586 #endif // INCLUDE_LIBYUV_VERSION_H_ NOLINT diff --git a/TMessagesProj/jni/libyuv/source/convert.cc b/TMessagesProj/jni/libyuv/source/convert.cc index 5dc279f8a..e332bc505 100644 --- a/TMessagesProj/jni/libyuv/source/convert.cc +++ b/TMessagesProj/jni/libyuv/source/convert.cc @@ -303,14 +303,14 @@ static int X420ToI420(const uint8* src_y, } } #endif -#if defined(HAS_SPLITUVROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_SPLITUVROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_uv, 4) && IS_ALIGNED(src_stride_uv, 4) && IS_ALIGNED(dst_u, 4) && IS_ALIGNED(dst_stride_u, 4) && IS_ALIGNED(dst_v, 4) && IS_ALIGNED(dst_stride_v, 4)) { - SplitUVRow = SplitUVRow_Any_MIPS_DSPR2; + SplitUVRow = SplitUVRow_Any_DSPR2; if (IS_ALIGNED(halfwidth, 16)) { - SplitUVRow = SplitUVRow_MIPS_DSPR2; + SplitUVRow = SplitUVRow_DSPR2; } } #endif diff --git a/TMessagesProj/jni/libyuv/source/convert_argb.cc b/TMessagesProj/jni/libyuv/source/convert_argb.cc index cf3d72289..e586f7043 100644 --- a/TMessagesProj/jni/libyuv/source/convert_argb.cc +++ b/TMessagesProj/jni/libyuv/source/convert_argb.cc @@ -92,13 +92,13 @@ static int I420ToARGBMatrix(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422TOARGBROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422TOARGBROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_MIPS_DSPR2; + I422ToARGBRow = I422ToARGBRow_DSPR2; } #endif @@ -262,13 +262,13 @@ static int I422ToARGBMatrix(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422TOARGBROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422TOARGBROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_MIPS_DSPR2; + I422ToARGBRow = I422ToARGBRow_DSPR2; } #endif @@ -607,13 +607,13 @@ static int I420AlphaToARGBMatrix(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422ALPHATOARGBROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422ALPHATOARGBROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422AlphaToARGBRow = I422AlphaToARGBRow_MIPS_DSPR2; + I422AlphaToARGBRow = I422AlphaToARGBRow_DSPR2; } #endif #if defined(HAS_ARGBATTENUATEROW_SSSE3) diff --git a/TMessagesProj/jni/libyuv/source/convert_from.cc b/TMessagesProj/jni/libyuv/source/convert_from.cc index 9c138d936..3bc9eb1be 100644 --- a/TMessagesProj/jni/libyuv/source/convert_from.cc +++ b/TMessagesProj/jni/libyuv/source/convert_from.cc @@ -445,7 +445,7 @@ int I420ToNV21(const uint8* src_y, int src_stride_y, return I420ToNV12(src_y, src_stride_y, src_v, src_stride_v, src_u, src_stride_u, - dst_y, src_stride_y, + dst_y, dst_stride_y, dst_vu, dst_stride_vu, width, height); } @@ -498,13 +498,13 @@ static int I420ToRGBAMatrix(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422TORGBAROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422TORGBAROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_rgba, 4) && IS_ALIGNED(dst_stride_rgba, 4)) { - I422ToRGBARow = I422ToRGBARow_MIPS_DSPR2; + I422ToRGBARow = I422ToRGBARow_DSPR2; } #endif @@ -888,12 +888,12 @@ int I420ToRGB565Dither(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422TOARGBROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422TOARGBROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2)) { - I422ToARGBRow = I422ToARGBRow_MIPS_DSPR2; + I422ToARGBRow = I422ToARGBRow_DSPR2; } #endif #if defined(HAS_ARGBTORGB565DITHERROW_SSE2) diff --git a/TMessagesProj/jni/libyuv/source/convert_from_argb.cc b/TMessagesProj/jni/libyuv/source/convert_from_argb.cc index 6796343c0..2a8682b7e 100644 --- a/TMessagesProj/jni/libyuv/source/convert_from_argb.cc +++ b/TMessagesProj/jni/libyuv/source/convert_from_argb.cc @@ -109,13 +109,16 @@ int ARGBToI422(const uint8* src_argb, int src_stride_argb, uint8* dst_v, int dst_stride_v, int width, int height) { int y; - void (*ARGBToUV422Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUV422Row_C; + void (*ARGBToUVRow)(const uint8* src_argb0, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = ARGBToYRow_C; - if (!src_argb || !dst_y || !dst_u || !dst_v || width <= 0 || height == 0) { + if (!src_argb || + !dst_y || !dst_u || !dst_v || + width <= 0 || height == 0) { return -1; } + // Negative height means invert the image. if (height < 0) { height = -height; src_argb = src_argb + (height - 1) * src_stride_argb; @@ -130,34 +133,22 @@ int ARGBToI422(const uint8* src_argb, int src_stride_argb, height = 1; src_stride_argb = dst_stride_y = dst_stride_u = dst_stride_v = 0; } -#if defined(HAS_ARGBTOUV422ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUV422Row = ARGBToUV422Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOUV422ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUV422Row = ARGBToUV422Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_NEON; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) +#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { + ARGBToUVRow = ARGBToUVRow_Any_SSSE3; ARGBToYRow = ARGBToYRow_Any_SSSE3; if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_SSSE3; ARGBToYRow = ARGBToYRow_SSSE3; } } #endif -#if defined(HAS_ARGBTOYROW_AVX2) +#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { + ARGBToUVRow = ARGBToUVRow_Any_AVX2; ARGBToYRow = ARGBToYRow_Any_AVX2; if (IS_ALIGNED(width, 32)) { + ARGBToUVRow = ARGBToUVRow_AVX2; ARGBToYRow = ARGBToYRow_AVX2; } } @@ -170,9 +161,17 @@ int ARGBToI422(const uint8* src_argb, int src_stride_argb, } } #endif +#if defined(HAS_ARGBTOUVROW_NEON) + if (TestCpuFlag(kCpuHasNEON)) { + ARGBToUVRow = ARGBToUVRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_NEON; + } + } +#endif for (y = 0; y < height; ++y) { - ARGBToUV422Row(src_argb, dst_u, dst_v, width); + ARGBToUVRow(src_argb, 0, dst_u, dst_v, width); ARGBToYRow(src_argb, dst_y, width); src_argb += src_stride_argb; dst_y += dst_stride_y; @@ -478,8 +477,8 @@ int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, uint8* dst_yuy2, int dst_stride_yuy2, int width, int height) { int y; - void (*ARGBToUV422Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUV422Row_C; + void (*ARGBToUVRow)(const uint8* src_argb, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = ARGBToYRow_C; void (*I422ToYUY2Row)(const uint8* src_y, const uint8* src_u, @@ -502,34 +501,22 @@ int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, height = 1; src_stride_argb = dst_stride_yuy2 = 0; } -#if defined(HAS_ARGBTOUV422ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUV422Row = ARGBToUV422Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOUV422ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUV422Row = ARGBToUV422Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_NEON; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) +#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { + ARGBToUVRow = ARGBToUVRow_Any_SSSE3; ARGBToYRow = ARGBToYRow_Any_SSSE3; if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_SSSE3; ARGBToYRow = ARGBToYRow_SSSE3; } } #endif -#if defined(HAS_ARGBTOYROW_AVX2) +#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { + ARGBToUVRow = ARGBToUVRow_Any_AVX2; ARGBToYRow = ARGBToYRow_Any_AVX2; if (IS_ALIGNED(width, 32)) { + ARGBToUVRow = ARGBToUVRow_AVX2; ARGBToYRow = ARGBToYRow_AVX2; } } @@ -542,7 +529,14 @@ int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, } } #endif - +#if defined(HAS_ARGBTOUVROW_NEON) + if (TestCpuFlag(kCpuHasNEON)) { + ARGBToUVRow = ARGBToUVRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_NEON; + } + } +#endif #if defined(HAS_I422TOYUY2ROW_SSE2) if (TestCpuFlag(kCpuHasSSE2)) { I422ToYUY2Row = I422ToYUY2Row_Any_SSE2; @@ -567,7 +561,7 @@ int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, uint8* row_v = row_u + ((width + 63) & ~63) / 2; for (y = 0; y < height; ++y) { - ARGBToUV422Row(src_argb, row_u, row_v, width); + ARGBToUVRow(src_argb, 0, row_u, row_v, width); ARGBToYRow(src_argb, row_y, width); I422ToYUY2Row(row_y, row_u, row_v, dst_yuy2, width); src_argb += src_stride_argb; @@ -585,8 +579,8 @@ int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, uint8* dst_uyvy, int dst_stride_uyvy, int width, int height) { int y; - void (*ARGBToUV422Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUV422Row_C; + void (*ARGBToUVRow)(const uint8* src_argb, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) = ARGBToUVRow_C; void (*ARGBToYRow)(const uint8* src_argb, uint8* dst_y, int width) = ARGBToYRow_C; void (*I422ToUYVYRow)(const uint8* src_y, const uint8* src_u, @@ -609,34 +603,22 @@ int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, height = 1; src_stride_argb = dst_stride_uyvy = 0; } -#if defined(HAS_ARGBTOUV422ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUV422Row = ARGBToUV422Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOUV422ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUV422Row = ARGBToUV422Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUV422Row = ARGBToUV422Row_NEON; - } - } -#endif -#if defined(HAS_ARGBTOYROW_SSSE3) +#if defined(HAS_ARGBTOYROW_SSSE3) && defined(HAS_ARGBTOUVROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { + ARGBToUVRow = ARGBToUVRow_Any_SSSE3; ARGBToYRow = ARGBToYRow_Any_SSSE3; if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_SSSE3; ARGBToYRow = ARGBToYRow_SSSE3; } } #endif -#if defined(HAS_ARGBTOYROW_AVX2) +#if defined(HAS_ARGBTOYROW_AVX2) && defined(HAS_ARGBTOUVROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { + ARGBToUVRow = ARGBToUVRow_Any_AVX2; ARGBToYRow = ARGBToYRow_Any_AVX2; if (IS_ALIGNED(width, 32)) { + ARGBToUVRow = ARGBToUVRow_AVX2; ARGBToYRow = ARGBToYRow_AVX2; } } @@ -649,7 +631,14 @@ int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, } } #endif - +#if defined(HAS_ARGBTOUVROW_NEON) + if (TestCpuFlag(kCpuHasNEON)) { + ARGBToUVRow = ARGBToUVRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + ARGBToUVRow = ARGBToUVRow_NEON; + } + } +#endif #if defined(HAS_I422TOUYVYROW_SSE2) if (TestCpuFlag(kCpuHasSSE2)) { I422ToUYVYRow = I422ToUYVYRow_Any_SSE2; @@ -674,7 +663,7 @@ int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, uint8* row_v = row_u + ((width + 63) & ~63) / 2; for (y = 0; y < height; ++y) { - ARGBToUV422Row(src_argb, row_u, row_v, width); + ARGBToUVRow(src_argb, 0, row_u, row_v, width); ARGBToYRow(src_argb, row_y, width); I422ToUYVYRow(row_y, row_u, row_v, dst_uyvy, width); src_argb += src_stride_argb; @@ -1157,21 +1146,24 @@ int ARGBToJ420(const uint8* src_argb, int src_stride_argb, return 0; } -// ARGB little endian (bgra in memory) to J422 +// Convert ARGB to J422. (JPeg full range I422). LIBYUV_API int ARGBToJ422(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, + uint8* dst_yj, int dst_stride_yj, uint8* dst_u, int dst_stride_u, uint8* dst_v, int dst_stride_v, int width, int height) { int y; - void (*ARGBToUVJ422Row)(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) = ARGBToUVJ422Row_C; - void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_y, int width) = + void (*ARGBToUVJRow)(const uint8* src_argb0, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) = ARGBToUVJRow_C; + void (*ARGBToYJRow)(const uint8* src_argb, uint8* dst_yj, int width) = ARGBToYJRow_C; - if (!src_argb || !dst_y || !dst_u || !dst_v || width <= 0 || height == 0) { + if (!src_argb || + !dst_yj || !dst_u || !dst_v || + width <= 0 || height == 0) { return -1; } + // Negative height means invert the image. if (height < 0) { height = -height; src_argb = src_argb + (height - 1) * src_stride_argb; @@ -1179,34 +1171,19 @@ int ARGBToJ422(const uint8* src_argb, int src_stride_argb, } // Coalesce rows. if (src_stride_argb == width * 4 && - dst_stride_y == width && + dst_stride_yj == width && dst_stride_u * 2 == width && dst_stride_v * 2 == width) { width *= height; height = 1; - src_stride_argb = dst_stride_y = dst_stride_u = dst_stride_v = 0; + src_stride_argb = dst_stride_yj = dst_stride_u = dst_stride_v = 0; } -#if defined(HAS_ARGBTOUVJ422ROW_SSSE3) - if (TestCpuFlag(kCpuHasSSSE3)) { - ARGBToUVJ422Row = ARGBToUVJ422Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJ422Row = ARGBToUVJ422Row_SSSE3; - } - } -#endif -#if defined(HAS_ARGBTOUVJ422ROW_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ARGBToUVJ422Row = ARGBToUVJ422Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { - ARGBToUVJ422Row = ARGBToUVJ422Row_NEON; - } - } -#endif - -#if defined(HAS_ARGBTOYJROW_SSSE3) +#if defined(HAS_ARGBTOYJROW_SSSE3) && defined(HAS_ARGBTOUVJROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { + ARGBToUVJRow = ARGBToUVJRow_Any_SSSE3; ARGBToYJRow = ARGBToYJRow_Any_SSSE3; if (IS_ALIGNED(width, 16)) { + ARGBToUVJRow = ARGBToUVJRow_SSSE3; ARGBToYJRow = ARGBToYJRow_SSSE3; } } @@ -1227,12 +1204,20 @@ int ARGBToJ422(const uint8* src_argb, int src_stride_argb, } } #endif +#if defined(HAS_ARGBTOUVJROW_NEON) + if (TestCpuFlag(kCpuHasNEON)) { + ARGBToUVJRow = ARGBToUVJRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + ARGBToUVJRow = ARGBToUVJRow_NEON; + } + } +#endif for (y = 0; y < height; ++y) { - ARGBToUVJ422Row(src_argb, dst_u, dst_v, width); - ARGBToYJRow(src_argb, dst_y, width); + ARGBToUVJRow(src_argb, 0, dst_u, dst_v, width); + ARGBToYJRow(src_argb, dst_yj, width); src_argb += src_stride_argb; - dst_y += dst_stride_y; + dst_yj += dst_stride_yj; dst_u += dst_stride_u; dst_v += dst_stride_v; } diff --git a/TMessagesProj/jni/libyuv/source/convert_to_argb.cc b/TMessagesProj/jni/libyuv/source/convert_to_argb.cc index af829fbd3..7533f5010 100644 --- a/TMessagesProj/jni/libyuv/source/convert_to_argb.cc +++ b/TMessagesProj/jni/libyuv/source/convert_to_argb.cc @@ -23,7 +23,7 @@ namespace libyuv { extern "C" { #endif -// Convert camera sample to I420 with cropping, rotation and vertical flip. +// Convert camera sample to ARGB with cropping, rotation and vertical flip. // src_width is used for source stride computation // src_height is used to compute location of planes, and indicate inversion // sample_size is measured in bytes and is the size of the frame. @@ -51,8 +51,8 @@ int ConvertToARGB(const uint8* sample, size_t sample_size, // also enable temporary buffer. LIBYUV_BOOL need_buf = (rotation && format != FOURCC_ARGB) || crop_argb == sample; - uint8* tmp_argb = crop_argb; - int tmp_argb_stride = argb_stride; + uint8* dest_argb = crop_argb; + int dest_argb_stride = argb_stride; uint8* rotate_buffer = NULL; int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height; @@ -66,13 +66,13 @@ int ConvertToARGB(const uint8* sample, size_t sample_size, } if (need_buf) { - int argb_size = crop_width * abs_crop_height * 4; + int argb_size = crop_width * 4 * abs_crop_height; rotate_buffer = (uint8*)malloc(argb_size); if (!rotate_buffer) { return 1; // Out of memory runtime error. } crop_argb = rotate_buffer; - argb_stride = crop_width; + argb_stride = crop_width * 4; } switch (format) { @@ -291,7 +291,7 @@ int ConvertToARGB(const uint8* sample, size_t sample_size, if (need_buf) { if (!r) { r = ARGBRotate(crop_argb, argb_stride, - tmp_argb, tmp_argb_stride, + dest_argb, dest_argb_stride, crop_width, abs_crop_height, rotation); } free(rotate_buffer); diff --git a/TMessagesProj/jni/libyuv/source/cpu_id.cc b/TMessagesProj/jni/libyuv/source/cpu_id.cc index ff7bdbd92..84927ebc3 100644 --- a/TMessagesProj/jni/libyuv/source/cpu_id.cc +++ b/TMessagesProj/jni/libyuv/source/cpu_id.cc @@ -10,12 +10,12 @@ #include "libyuv/cpu_id.h" -#if defined(_MSC_VER) && !defined(__clang__) +#if defined(_MSC_VER) #include // For __cpuidex() #endif #if !defined(__pnacl__) && !defined(__CLR_VER) && \ !defined(__native_client__) && (defined(_M_IX86) || defined(_M_X64)) && \ - defined(_MSC_VER) && !defined(__clang__) && (_MSC_FULL_VER >= 160040219) + defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) #include // For _xgetbv() #endif @@ -36,7 +36,8 @@ extern "C" { // For functions that use the stack and have runtime checks for overflow, // use SAFEBUFFERS to avoid additional check. -#if (defined(_MSC_VER) && !defined(__clang__)) && (_MSC_FULL_VER >= 160040219) +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) && \ + !defined(__clang__) #define SAFEBUFFERS __declspec(safebuffers) #else #define SAFEBUFFERS @@ -48,9 +49,9 @@ extern "C" { !defined(__pnacl__) && !defined(__CLR_VER) LIBYUV_API void CpuId(uint32 info_eax, uint32 info_ecx, uint32* cpu_info) { -#if defined(_MSC_VER) && !defined(__clang__) +#if defined(_MSC_VER) // Visual C version uses intrinsic or inline x86 assembly. -#if (_MSC_FULL_VER >= 160040219) +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) __cpuidex((int*)(cpu_info), info_eax, info_ecx); #elif defined(_M_IX86) __asm { @@ -71,7 +72,7 @@ void CpuId(uint32 info_eax, uint32 info_ecx, uint32* cpu_info) { } #endif // GCC version uses inline x86 assembly. -#else // defined(_MSC_VER) && !defined(__clang__) +#else // defined(_MSC_VER) uint32 info_ebx, info_edx; asm volatile ( #if defined( __i386__) && defined(__PIC__) @@ -89,7 +90,7 @@ void CpuId(uint32 info_eax, uint32 info_ecx, uint32* cpu_info) { cpu_info[1] = info_ebx; cpu_info[2] = info_ecx; cpu_info[3] = info_edx; -#endif // defined(_MSC_VER) && !defined(__clang__) +#endif // defined(_MSC_VER) } #else // (defined(_M_IX86) || defined(_M_X64) ... LIBYUV_API @@ -117,7 +118,7 @@ void CpuId(uint32 eax, uint32 ecx, uint32* cpu_info) { // X86 CPUs have xgetbv to detect OS saves high parts of ymm registers. int GetXCR0() { uint32 xcr0 = 0u; -#if (_MSC_FULL_VER >= 160040219) +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 160040219) xcr0 = (uint32)(_xgetbv(0)); // VS2010 SP1 required. #elif defined(__i386__) || defined(__x86_64__) asm(".byte 0x0f, 0x01, 0xd0" : "=a" (xcr0) : "c" (0) : "%edx"); @@ -251,15 +252,11 @@ int InitCpuFlags(void) { #endif #if defined(__mips__) && defined(__linux__) #if defined(__mips_dspr2) - cpu_info |= kCpuHasMIPS_DSPR2; + cpu_info |= kCpuHasDSPR2; #endif cpu_info |= kCpuHasMIPS; - - if (getenv("LIBYUV_DISABLE_MIPS")) { - cpu_info &= ~kCpuHasMIPS; - } - if (getenv("LIBYUV_DISABLE_MIPS_DSPR2")) { - cpu_info &= ~kCpuHasMIPS_DSPR2; + if (getenv("LIBYUV_DISABLE_DSPR2")) { + cpu_info &= ~kCpuHasDSPR2; } #endif #if defined(__arm__) || defined(__aarch64__) diff --git a/TMessagesProj/jni/libyuv/source/planar_functions.cc b/TMessagesProj/jni/libyuv/source/planar_functions.cc index 536e1d528..73fa7d284 100644 --- a/TMessagesProj/jni/libyuv/source/planar_functions.cc +++ b/TMessagesProj/jni/libyuv/source/planar_functions.cc @@ -255,11 +255,11 @@ void MirrorPlane(const uint8* src_y, int src_stride_y, } #endif // TODO(fbarchard): Mirror on mips handle unaligned memory. -#if defined(HAS_MIRRORROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_MIRRORROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(dst_y, 4) && IS_ALIGNED(dst_stride_y, 4)) { - MirrorRow = MirrorRow_MIPS_DSPR2; + MirrorRow = MirrorRow_DSPR2; } #endif @@ -677,7 +677,7 @@ int I420Blend(const uint8* src_y0, int src_stride_y0, #if defined(HAS_BLENDPLANEROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { - BlendPlaneRow = BlendPlaneRow_Any_SSSE3; + BlendPlaneRow = BlendPlaneRow_Any_SSSE3; if (IS_ALIGNED(halfwidth, 8)) { BlendPlaneRow = BlendPlaneRow_SSSE3; } @@ -685,33 +685,45 @@ int I420Blend(const uint8* src_y0, int src_stride_y0, #endif #if defined(HAS_BLENDPLANEROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { - BlendPlaneRow = BlendPlaneRow_Any_AVX2; + BlendPlaneRow = BlendPlaneRow_Any_AVX2; if (IS_ALIGNED(halfwidth, 32)) { BlendPlaneRow = BlendPlaneRow_AVX2; } } #endif + if (!IS_ALIGNED(width, 2)) { + ScaleRowDown2 = ScaleRowDown2Box_Odd_C; + } #if defined(HAS_SCALEROWDOWN2_NEON) if (TestCpuFlag(kCpuHasNEON)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_NEON; - if (IS_ALIGNED(halfwidth, 16)) { - ScaleRowDown2 = ScaleRowDown2Box_NEON; + ScaleRowDown2 = ScaleRowDown2Box_Odd_NEON; + if (IS_ALIGNED(width, 2)) { + ScaleRowDown2 = ScaleRowDown2Box_Any_NEON; + if (IS_ALIGNED(halfwidth, 16)) { + ScaleRowDown2 = ScaleRowDown2Box_NEON; + } } } #endif #if defined(HAS_SCALEROWDOWN2_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_SSSE3; - if (IS_ALIGNED(halfwidth, 16)) { - ScaleRowDown2 = ScaleRowDown2Box_SSSE3; + ScaleRowDown2 = ScaleRowDown2Box_Odd_SSSE3; + if (IS_ALIGNED(width, 2)) { + ScaleRowDown2 = ScaleRowDown2Box_Any_SSSE3; + if (IS_ALIGNED(halfwidth, 16)) { + ScaleRowDown2 = ScaleRowDown2Box_SSSE3; + } } } #endif #if defined(HAS_SCALEROWDOWN2_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { - ScaleRowDown2 = ScaleRowDown2Box_Any_AVX2; - if (IS_ALIGNED(halfwidth, 32)) { - ScaleRowDown2 = ScaleRowDown2Box_AVX2; + ScaleRowDown2 = ScaleRowDown2Box_Odd_AVX2; + if (IS_ALIGNED(width, 2)) { + ScaleRowDown2 = ScaleRowDown2Box_Any_AVX2; + if (IS_ALIGNED(halfwidth, 32)) { + ScaleRowDown2 = ScaleRowDown2Box_AVX2; + } } } #endif @@ -974,13 +986,13 @@ static int I422ToRGBAMatrix(const uint8* src_y, int src_stride_y, } } #endif -#if defined(HAS_I422TORGBAROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 4) && +#if defined(HAS_I422TORGBAROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_rgba, 4) && IS_ALIGNED(dst_stride_rgba, 4)) { - I422ToRGBARow = I422ToRGBARow_MIPS_DSPR2; + I422ToRGBARow = I422ToRGBARow_DSPR2; } #endif @@ -1894,19 +1906,18 @@ int InterpolatePlane(const uint8* src0, int src_stride0, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src0, 4) && IS_ALIGNED(src_stride0, 4) && IS_ALIGNED(src1, 4) && IS_ALIGNED(src_stride1, 4) && IS_ALIGNED(dst, 4) && IS_ALIGNED(dst_stride, 4) && IS_ALIGNED(width, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } #endif for (y = 0; y < height; ++y) { - InterpolateRow(dst, src0, src1 - src0, - width, interpolation); + InterpolateRow(dst, src0, src1 - src0, width, interpolation); src0 += src_stride0; src1 += src_stride1; dst += dst_stride; @@ -2412,6 +2423,9 @@ int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y, return 0; } +// TODO(fbarchard): Consider if width is even Y channel can be split +// directly. A SplitUVRow_Odd function could copy the remaining chroma. + LIBYUV_API int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, uint8* dst_y, int dst_stride_y, @@ -2486,22 +2500,24 @@ int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, { int awidth = halfwidth * 2; - // 2 rows of uv - align_buffer_64(rows, awidth * 2); + // row of y and 2 rows of uv + align_buffer_64(rows, awidth * 3); for (y = 0; y < height - 1; y += 2) { // Split Y from UV. - SplitUVRow(src_yuy2, dst_y, rows, awidth); - SplitUVRow(src_yuy2 + src_stride_yuy2, dst_y + dst_stride_y, - rows + awidth, awidth); - InterpolateRow(dst_uv, rows, awidth, awidth, 128); + SplitUVRow(src_yuy2, rows, rows + awidth, awidth); + memcpy(dst_y, rows, width); + SplitUVRow(src_yuy2 + src_stride_yuy2, rows, rows + awidth * 2, awidth); + memcpy(dst_y + dst_stride_y, rows, width); + InterpolateRow(dst_uv, rows + awidth, awidth, awidth, 128); src_yuy2 += src_stride_yuy2 * 2; dst_y += dst_stride_y * 2; dst_uv += dst_stride_uv; } if (height & 1) { // Split Y from UV. - SplitUVRow(src_yuy2, dst_y, dst_uv, awidth); + SplitUVRow(src_yuy2, rows, dst_uv, awidth); + memcpy(dst_y, rows, width); } free_aligned_buffer_64(rows); } @@ -2582,22 +2598,24 @@ int UYVYToNV12(const uint8* src_uyvy, int src_stride_uyvy, { int awidth = halfwidth * 2; - // 2 rows of uv - align_buffer_64(rows, awidth * 2); + // row of y and 2 rows of uv + align_buffer_64(rows, awidth * 3); for (y = 0; y < height - 1; y += 2) { // Split Y from UV. - SplitUVRow(src_uyvy, rows, dst_y, awidth); - SplitUVRow(src_uyvy + src_stride_uyvy, rows + awidth, - dst_y + dst_stride_y, awidth); - InterpolateRow(dst_uv, rows, awidth, awidth, 128); + SplitUVRow(src_uyvy, rows + awidth, rows, awidth); + memcpy(dst_y, rows, width); + SplitUVRow(src_uyvy + src_stride_uyvy, rows + awidth * 2, rows, awidth); + memcpy(dst_y + dst_stride_y, rows, width); + InterpolateRow(dst_uv, rows + awidth, awidth, awidth, 128); src_uyvy += src_stride_uyvy * 2; dst_y += dst_stride_y * 2; dst_uv += dst_stride_uv; } if (height & 1) { // Split Y from UV. - SplitUVRow(src_uyvy, dst_uv, dst_y, awidth); + SplitUVRow(src_uyvy, dst_uv, rows, awidth); + memcpy(dst_y, rows, width); } free_aligned_buffer_64(rows); } diff --git a/TMessagesProj/jni/libyuv/source/rotate.cc b/TMessagesProj/jni/libyuv/source/rotate.cc index 31e04af9c..01ea5c407 100644 --- a/TMessagesProj/jni/libyuv/source/rotate.cc +++ b/TMessagesProj/jni/libyuv/source/rotate.cc @@ -49,13 +49,13 @@ void TransposePlane(const uint8* src, int src_stride, } } #endif -#if defined(HAS_TRANSPOSEWX8_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2)) { +#if defined(HAS_TRANSPOSEWX8_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2)) { if (IS_ALIGNED(width, 4) && IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - TransposeWx8 = TransposeWx8_Fast_MIPS_DSPR2; + TransposeWx8 = TransposeWx8_Fast_DSPR2; } else { - TransposeWx8 = TransposeWx8_MIPS_DSPR2; + TransposeWx8 = TransposeWx8_DSPR2; } } #endif @@ -134,11 +134,11 @@ void RotatePlane180(const uint8* src, int src_stride, } #endif // TODO(fbarchard): Mirror on mips handle unaligned memory. -#if defined(HAS_MIRRORROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_MIRRORROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst, 4) && IS_ALIGNED(dst_stride, 4)) { - MirrorRow = MirrorRow_MIPS_DSPR2; + MirrorRow = MirrorRow_DSPR2; } #endif #if defined(HAS_COPYROW_SSE2) @@ -203,10 +203,10 @@ void TransposeUV(const uint8* src, int src_stride, } } #endif -#if defined(HAS_TRANSPOSEUVWX8_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(width, 2) && +#if defined(HAS_TRANSPOSEUVWX8_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(width, 2) && IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - TransposeUVWx8 = TransposeUVWx8_MIPS_DSPR2; + TransposeUVWx8 = TransposeUVWx8_DSPR2; } #endif @@ -267,22 +267,22 @@ void RotateUV180(const uint8* src, int src_stride, uint8* dst_b, int dst_stride_b, int width, int height) { int i; - void (*MirrorRowUV)(const uint8* src, uint8* dst_u, uint8* dst_v, int width) = + void (*MirrorUVRow)(const uint8* src, uint8* dst_u, uint8* dst_v, int width) = MirrorUVRow_C; #if defined(HAS_MIRRORUVROW_NEON) if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 8)) { - MirrorRowUV = MirrorUVRow_NEON; + MirrorUVRow = MirrorUVRow_NEON; } #endif -#if defined(HAS_MIRRORROW_UV_SSSE3) +#if defined(HAS_MIRRORUVROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3) && IS_ALIGNED(width, 16)) { - MirrorRowUV = MirrorUVRow_SSSE3; + MirrorUVRow = MirrorUVRow_SSSE3; } #endif -#if defined(HAS_MIRRORUVROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_MIRRORUVROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src, 4) && IS_ALIGNED(src_stride, 4)) { - MirrorRowUV = MirrorUVRow_MIPS_DSPR2; + MirrorUVRow = MirrorUVRow_DSPR2; } #endif @@ -290,7 +290,7 @@ void RotateUV180(const uint8* src, int src_stride, dst_b += dst_stride_b * (height - 1); for (i = 0; i < height; ++i) { - MirrorRowUV(src, dst_a, dst_b, width); + MirrorUVRow(src, dst_a, dst_b, width); src += src_stride; dst_a -= dst_stride_a; dst_b -= dst_stride_b; diff --git a/TMessagesProj/jni/libyuv/source/rotate_any.cc b/TMessagesProj/jni/libyuv/source/rotate_any.cc index d12bad5dc..31a74c315 100644 --- a/TMessagesProj/jni/libyuv/source/rotate_any.cc +++ b/TMessagesProj/jni/libyuv/source/rotate_any.cc @@ -38,8 +38,8 @@ TANY(TransposeWx8_Any_SSSE3, TransposeWx8_SSSE3, 7) #ifdef HAS_TRANSPOSEWX8_FAST_SSSE3 TANY(TransposeWx8_Fast_Any_SSSE3, TransposeWx8_Fast_SSSE3, 15) #endif -#ifdef HAS_TRANSPOSEWX8_MIPS_DSPR2 -TANY(TransposeWx8_Any_MIPS_DSPR2, TransposeWx8_MIPS_DSPR2, 7) +#ifdef HAS_TRANSPOSEWX8_DSPR2 +TANY(TransposeWx8_Any_DSPR2, TransposeWx8_DSPR2, 7) #endif #undef TANY @@ -64,8 +64,8 @@ TUVANY(TransposeUVWx8_Any_NEON, TransposeUVWx8_NEON, 7) #ifdef HAS_TRANSPOSEUVWX8_SSE2 TUVANY(TransposeUVWx8_Any_SSE2, TransposeUVWx8_SSE2, 7) #endif -#ifdef HAS_TRANSPOSEUVWX8_MIPS_DSPR2 -TUVANY(TransposeUVWx8_Any_MIPS_DSPR2, TransposeUVWx8_MIPS_DSPR2, 7) +#ifdef HAS_TRANSPOSEUVWX8_DSPR2 +TUVANY(TransposeUVWx8_Any_DSPR2, TransposeUVWx8_DSPR2, 7) #endif #undef TUVANY diff --git a/TMessagesProj/jni/libyuv/source/rotate_mips.cc b/TMessagesProj/jni/libyuv/source/rotate_mips.cc index efe6bd909..23e89fbad 100644 --- a/TMessagesProj/jni/libyuv/source/rotate_mips.cc +++ b/TMessagesProj/jni/libyuv/source/rotate_mips.cc @@ -22,7 +22,7 @@ extern "C" { defined(__mips_dsp) && (__mips_dsp_rev >= 2) && \ (_MIPS_SIM == _MIPS_SIM_ABI32) -void TransposeWx8_MIPS_DSPR2(const uint8* src, int src_stride, +void TransposeWx8_DSPR2(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width) { __asm__ __volatile__ ( ".set push \n" @@ -106,7 +106,7 @@ void TransposeWx8_MIPS_DSPR2(const uint8* src, int src_stride, ); } -void TransposeWx8_Fast_MIPS_DSPR2(const uint8* src, int src_stride, +void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width) { __asm__ __volatile__ ( ".set noat \n" @@ -308,7 +308,7 @@ void TransposeWx8_Fast_MIPS_DSPR2(const uint8* src, int src_stride, ); } -void TransposeUVWx8_MIPS_DSPR2(const uint8* src, int src_stride, +void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, uint8* dst_b, int dst_stride_b, int width) { diff --git a/TMessagesProj/jni/libyuv/source/rotate_neon.cc b/TMessagesProj/jni/libyuv/source/rotate_neon.cc index 9e4ecd80d..1c22b472b 100644 --- a/TMessagesProj/jni/libyuv/source/rotate_neon.cc +++ b/TMessagesProj/jni/libyuv/source/rotate_neon.cc @@ -27,7 +27,7 @@ static uvec8 kVTbl4x4Transpose = void TransposeWx8_NEON(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width) { - const uint8* src_temp = NULL; + const uint8* src_temp; asm volatile ( // loops are on blocks of 8. loop will stop when // counter gets to or below 0. starting the counter @@ -229,7 +229,7 @@ void TransposeWx8_NEON(const uint8* src, int src_stride, "4: \n" - : "+r"(src_temp), // %0 + : "=&r"(src_temp), // %0 "+r"(src), // %1 "+r"(src_stride), // %2 "+r"(dst), // %3 @@ -247,7 +247,7 @@ void TransposeUVWx8_NEON(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, uint8* dst_b, int dst_stride_b, int width) { - const uint8* src_temp = NULL; + const uint8* src_temp; asm volatile ( // loops are on blocks of 8. loop will stop when // counter gets to or below 0. starting the counter @@ -512,7 +512,7 @@ void TransposeUVWx8_NEON(const uint8* src, int src_stride, "4: \n" - : "+r"(src_temp), // %0 + : "=&r"(src_temp), // %0 "+r"(src), // %1 "+r"(src_stride), // %2 "+r"(dst_a), // %3 diff --git a/TMessagesProj/jni/libyuv/source/rotate_neon64.cc b/TMessagesProj/jni/libyuv/source/rotate_neon64.cc index f52c082b3..1ab448f3a 100644 --- a/TMessagesProj/jni/libyuv/source/rotate_neon64.cc +++ b/TMessagesProj/jni/libyuv/source/rotate_neon64.cc @@ -26,7 +26,7 @@ static uvec8 kVTbl4x4Transpose = void TransposeWx8_NEON(const uint8* src, int src_stride, uint8* dst, int dst_stride, int width) { - const uint8* src_temp = NULL; + const uint8* src_temp; int64 width64 = (int64) width; // Work around clang 3.4 warning. asm volatile ( // loops are on blocks of 8. loop will stop when @@ -235,7 +235,7 @@ void TransposeWx8_NEON(const uint8* src, int src_stride, "4: \n" - : "+r"(src_temp), // %0 + : "=&r"(src_temp), // %0 "+r"(src), // %1 "+r"(dst), // %2 "+r"(width64) // %3 @@ -255,7 +255,7 @@ void TransposeUVWx8_NEON(const uint8* src, int src_stride, uint8* dst_a, int dst_stride_a, uint8* dst_b, int dst_stride_b, int width) { - const uint8* src_temp = NULL; + const uint8* src_temp; int64 width64 = (int64) width; // Work around clang 3.4 warning. asm volatile ( // loops are on blocks of 8. loop will stop when @@ -520,7 +520,7 @@ void TransposeUVWx8_NEON(const uint8* src, int src_stride, "4: \n" - : "+r"(src_temp), // %0 + : "=&r"(src_temp), // %0 "+r"(src), // %1 "+r"(dst_a), // %2 "+r"(dst_b), // %3 diff --git a/TMessagesProj/jni/libyuv/source/row_any.cc b/TMessagesProj/jni/libyuv/source/row_any.cc index 5e5f435a6..29b7a343d 100644 --- a/TMessagesProj/jni/libyuv/source/row_any.cc +++ b/TMessagesProj/jni/libyuv/source/row_any.cc @@ -596,8 +596,8 @@ ANY11T(InterpolateRow_Any_SSSE3, InterpolateRow_SSSE3, 1, 1, 15) #ifdef HAS_INTERPOLATEROW_NEON ANY11T(InterpolateRow_Any_NEON, InterpolateRow_NEON, 1, 1, 15) #endif -#ifdef HAS_INTERPOLATEROW_MIPS_DSPR2 -ANY11T(InterpolateRow_Any_MIPS_DSPR2, InterpolateRow_MIPS_DSPR2, 1, 1, 3) +#ifdef HAS_INTERPOLATEROW_DSPR2 +ANY11T(InterpolateRow_Any_DSPR2, InterpolateRow_DSPR2, 1, 1, 3) #endif #undef ANY11T @@ -705,8 +705,8 @@ ANY12(SplitUVRow_Any_AVX2, SplitUVRow_AVX2, 0, 2, 0, 31) #ifdef HAS_SPLITUVROW_NEON ANY12(SplitUVRow_Any_NEON, SplitUVRow_NEON, 0, 2, 0, 15) #endif -#ifdef HAS_SPLITUVROW_MIPS_DSPR2 -ANY12(SplitUVRow_Any_MIPS_DSPR2, SplitUVRow_MIPS_DSPR2, 0, 2, 0, 15) +#ifdef HAS_SPLITUVROW_DSPR2 +ANY12(SplitUVRow_Any_DSPR2, SplitUVRow_DSPR2, 0, 2, 0, 15) #endif #ifdef HAS_ARGBTOUV444ROW_SSSE3 ANY12(ARGBToUV444Row_Any_SSSE3, ARGBToUV444Row_SSSE3, 0, 4, 0, 15) @@ -715,16 +715,12 @@ ANY12(ARGBToUV444Row_Any_SSSE3, ARGBToUV444Row_SSSE3, 0, 4, 0, 15) ANY12(YUY2ToUV422Row_Any_AVX2, YUY2ToUV422Row_AVX2, 1, 4, 1, 31) ANY12(UYVYToUV422Row_Any_AVX2, UYVYToUV422Row_AVX2, 1, 4, 1, 31) #endif -#ifdef HAS_ARGBTOUV422ROW_SSSE3 -ANY12(ARGBToUV422Row_Any_SSSE3, ARGBToUV422Row_SSSE3, 0, 4, 1, 15) -#endif #ifdef HAS_YUY2TOUV422ROW_SSE2 ANY12(YUY2ToUV422Row_Any_SSE2, YUY2ToUV422Row_SSE2, 1, 4, 1, 15) ANY12(UYVYToUV422Row_Any_SSE2, UYVYToUV422Row_SSE2, 1, 4, 1, 15) #endif #ifdef HAS_YUY2TOUV422ROW_NEON ANY12(ARGBToUV444Row_Any_NEON, ARGBToUV444Row_NEON, 0, 4, 0, 7) -ANY12(ARGBToUV422Row_Any_NEON, ARGBToUV422Row_NEON, 0, 4, 1, 15) ANY12(ARGBToUV411Row_Any_NEON, ARGBToUV411Row_NEON, 0, 4, 2, 31) ANY12(YUY2ToUV422Row_Any_NEON, YUY2ToUV422Row_NEON, 1, 4, 1, 15) ANY12(UYVYToUV422Row_Any_NEON, UYVYToUV422Row_NEON, 1, 4, 1, 15) @@ -760,6 +756,9 @@ ANY12(UYVYToUV422Row_Any_NEON, UYVYToUV422Row_NEON, 1, 4, 1, 15) #ifdef HAS_ARGBTOUVROW_AVX2 ANY12S(ARGBToUVRow_Any_AVX2, ARGBToUVRow_AVX2, 0, 4, 31) #endif +#ifdef HAS_ARGBTOUVJROW_AVX2 +ANY12S(ARGBToUVJRow_Any_AVX2, ARGBToUVJRow_AVX2, 0, 4, 31) +#endif #ifdef HAS_ARGBTOUVROW_SSSE3 ANY12S(ARGBToUVRow_Any_SSSE3, ARGBToUVRow_SSSE3, 0, 4, 15) ANY12S(ARGBToUVJRow_Any_SSSE3, ARGBToUVJRow_SSSE3, 0, 4, 15) diff --git a/TMessagesProj/jni/libyuv/source/row_common.cc b/TMessagesProj/jni/libyuv/source/row_common.cc index c820cdf1f..0c47e1016 100644 --- a/TMessagesProj/jni/libyuv/source/row_common.cc +++ b/TMessagesProj/jni/libyuv/source/row_common.cc @@ -433,28 +433,6 @@ void NAME ## ToUVJRow_C(const uint8* src_rgb0, int src_stride_rgb, \ MAKEROWYJ(ARGB, 2, 1, 0, 4) #undef MAKEROWYJ -void ARGBToUVJ422Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 ab = (src_argb[0] + src_argb[4]) >> 1; - uint8 ag = (src_argb[1] + src_argb[5]) >> 1; - uint8 ar = (src_argb[2] + src_argb[6]) >> 1; - dst_u[0] = RGBToUJ(ar, ag, ab); - dst_v[0] = RGBToVJ(ar, ag, ab); - src_argb += 8; - dst_u += 1; - dst_v += 1; - } - if (width & 1) { - uint8 ab = src_argb[0]; - uint8 ag = src_argb[1]; - uint8 ar = src_argb[2]; - dst_u[0] = RGBToUJ(ar, ag, ab); - dst_v[0] = RGBToVJ(ar, ag, ab); - } -} - void RGB565ToYRow_C(const uint8* src_rgb565, uint8* dst_y, int width) { int x; for (x = 0; x < width; ++x) { @@ -658,28 +636,6 @@ void ARGBToUV444Row_C(const uint8* src_argb, } } -void ARGBToUV422Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width) { - int x; - for (x = 0; x < width - 1; x += 2) { - uint8 ab = (src_argb[0] + src_argb[4]) >> 1; - uint8 ag = (src_argb[1] + src_argb[5]) >> 1; - uint8 ar = (src_argb[2] + src_argb[6]) >> 1; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - src_argb += 8; - dst_u += 1; - dst_v += 1; - } - if (width & 1) { - uint8 ab = src_argb[0]; - uint8 ag = src_argb[1]; - uint8 ar = src_argb[2]; - dst_u[0] = RGBToU(ar, ag, ab); - dst_v[0] = RGBToV(ar, ag, ab); - } -} - void ARGBToUV411Row_C(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width) { int x; @@ -2191,7 +2147,7 @@ void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, } // Blend 2 rows into 1. -static void HalfRow_C(const uint8* src_uv, int src_uv_stride, +static void HalfRow_C(const uint8* src_uv, ptrdiff_t src_uv_stride, uint8* dst_uv, int width) { int x; for (x = 0; x < width; ++x) { @@ -2199,7 +2155,7 @@ static void HalfRow_C(const uint8* src_uv, int src_uv_stride, } } -static void HalfRow_16_C(const uint16* src_uv, int src_uv_stride, +static void HalfRow_16_C(const uint16* src_uv, ptrdiff_t src_uv_stride, uint16* dst_uv, int width) { int x; for (x = 0; x < width; ++x) { @@ -2220,7 +2176,7 @@ void InterpolateRow_C(uint8* dst_ptr, const uint8* src_ptr, return; } if (y1_fraction == 128) { - HalfRow_C(src_ptr, (int)(src_stride), dst_ptr, width); + HalfRow_C(src_ptr, src_stride, dst_ptr, width); return; } for (x = 0; x < width - 1; x += 2) { @@ -2250,7 +2206,7 @@ void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, return; } if (source_y_fraction == 128) { - HalfRow_16_C(src_ptr, (int)(src_stride), dst_ptr, width); + HalfRow_16_C(src_ptr, src_stride, dst_ptr, width); return; } for (x = 0; x < width - 1; x += 2) { @@ -2539,7 +2495,11 @@ void I422ToRGB565Row_AVX2(const uint8* src_y, while (width > 0) { int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); +#if defined(HAS_ARGBTORGB565ROW_AVX2) ARGBToRGB565Row_AVX2(row, dst_rgb565, twidth); +#else + ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); +#endif src_y += twidth; src_u += twidth / 2; src_v += twidth / 2; @@ -2561,7 +2521,11 @@ void I422ToARGB1555Row_AVX2(const uint8* src_y, while (width > 0) { int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); +#if defined(HAS_ARGBTOARGB1555ROW_AVX2) ARGBToARGB1555Row_AVX2(row, dst_argb1555, twidth); +#else + ARGBToARGB1555Row_SSE2(row, dst_argb1555, twidth); +#endif src_y += twidth; src_u += twidth / 2; src_v += twidth / 2; @@ -2583,7 +2547,11 @@ void I422ToARGB4444Row_AVX2(const uint8* src_y, while (width > 0) { int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; I422ToARGBRow_AVX2(src_y, src_u, src_v, row, yuvconstants, twidth); +#if defined(HAS_ARGBTOARGB4444ROW_AVX2) ARGBToARGB4444Row_AVX2(row, dst_argb4444, twidth); +#else + ARGBToARGB4444Row_SSE2(row, dst_argb4444, twidth); +#endif src_y += twidth; src_u += twidth / 2; src_v += twidth / 2; @@ -2627,7 +2595,11 @@ void NV12ToRGB565Row_AVX2(const uint8* src_y, while (width > 0) { int twidth = width > MAXTWIDTH ? MAXTWIDTH : width; NV12ToARGBRow_AVX2(src_y, src_uv, row, yuvconstants, twidth); +#if defined(HAS_ARGBTORGB565ROW_AVX2) ARGBToRGB565Row_AVX2(row, dst_rgb565, twidth); +#else + ARGBToRGB565Row_SSE2(row, dst_rgb565, twidth); +#endif src_y += twidth; src_uv += twidth; dst_rgb565 += twidth * 2; diff --git a/TMessagesProj/jni/libyuv/source/row_gcc.cc b/TMessagesProj/jni/libyuv/source/row_gcc.cc index 80b2a95aa..866bded79 100644 --- a/TMessagesProj/jni/libyuv/source/row_gcc.cc +++ b/TMessagesProj/jni/libyuv/source/row_gcc.cc @@ -1023,6 +1023,67 @@ void ARGBToUVRow_AVX2(const uint8* src_argb0, int src_stride_argb, } #endif // HAS_ARGBTOUVROW_AVX2 +#ifdef HAS_ARGBTOUVJROW_AVX2 +void ARGBToUVJRow_AVX2(const uint8* src_argb0, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) { + asm volatile ( + "vbroadcastf128 %5,%%ymm5 \n" + "vbroadcastf128 %6,%%ymm6 \n" + "vbroadcastf128 %7,%%ymm7 \n" + "sub %1,%2 \n" + LABELALIGN + "1: \n" + "vmovdqu " MEMACCESS(0) ",%%ymm0 \n" + "vmovdqu " MEMACCESS2(0x20,0) ",%%ymm1 \n" + "vmovdqu " MEMACCESS2(0x40,0) ",%%ymm2 \n" + "vmovdqu " MEMACCESS2(0x60,0) ",%%ymm3 \n" + VMEMOPREG(vpavgb,0x00,0,4,1,ymm0,ymm0) // vpavgb (%0,%4,1),%%ymm0,%%ymm0 + VMEMOPREG(vpavgb,0x20,0,4,1,ymm1,ymm1) + VMEMOPREG(vpavgb,0x40,0,4,1,ymm2,ymm2) + VMEMOPREG(vpavgb,0x60,0,4,1,ymm3,ymm3) + "lea " MEMLEA(0x80,0) ",%0 \n" + "vshufps $0x88,%%ymm1,%%ymm0,%%ymm4 \n" + "vshufps $0xdd,%%ymm1,%%ymm0,%%ymm0 \n" + "vpavgb %%ymm4,%%ymm0,%%ymm0 \n" + "vshufps $0x88,%%ymm3,%%ymm2,%%ymm4 \n" + "vshufps $0xdd,%%ymm3,%%ymm2,%%ymm2 \n" + "vpavgb %%ymm4,%%ymm2,%%ymm2 \n" + + "vpmaddubsw %%ymm7,%%ymm0,%%ymm1 \n" + "vpmaddubsw %%ymm7,%%ymm2,%%ymm3 \n" + "vpmaddubsw %%ymm6,%%ymm0,%%ymm0 \n" + "vpmaddubsw %%ymm6,%%ymm2,%%ymm2 \n" + "vphaddw %%ymm3,%%ymm1,%%ymm1 \n" + "vphaddw %%ymm2,%%ymm0,%%ymm0 \n" + "vpaddw %%ymm5,%%ymm0,%%ymm0 \n" + "vpaddw %%ymm5,%%ymm1,%%ymm1 \n" + "vpsraw $0x8,%%ymm1,%%ymm1 \n" + "vpsraw $0x8,%%ymm0,%%ymm0 \n" + "vpacksswb %%ymm0,%%ymm1,%%ymm0 \n" + "vpermq $0xd8,%%ymm0,%%ymm0 \n" + "vpshufb %8,%%ymm0,%%ymm0 \n" + + "vextractf128 $0x0,%%ymm0," MEMACCESS(1) " \n" + VEXTOPMEM(vextractf128,1,ymm0,0x0,1,2,1) // vextractf128 $1,%%ymm0,(%1,%2,1) + "lea " MEMLEA(0x10,1) ",%1 \n" + "sub $0x20,%3 \n" + "jg 1b \n" + "vzeroupper \n" + : "+r"(src_argb0), // %0 + "+r"(dst_u), // %1 + "+r"(dst_v), // %2 + "+rm"(width) // %3 + : "r"((intptr_t)(src_stride_argb)), // %4 + "m"(kAddUVJ128), // %5 + "m"(kARGBToVJ), // %6 + "m"(kARGBToUJ), // %7 + "m"(kShufARGBToUV_AVX) // %8 + : "memory", "cc", NACL_R14 + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" + ); +} +#endif // HAS_ARGBTOUVJROW_AVX2 + #ifdef HAS_ARGBTOUVJROW_SSSE3 void ARGBToUVJRow_SSSE3(const uint8* src_argb0, int src_stride_argb, uint8* dst_u, uint8* dst_v, int width) { @@ -1144,59 +1205,6 @@ void ARGBToUV444Row_SSSE3(const uint8* src_argb, uint8* dst_u, uint8* dst_v, } #endif // HAS_ARGBTOUV444ROW_SSSE3 -#ifdef HAS_ARGBTOUV422ROW_SSSE3 -void ARGBToUV422Row_SSSE3(const uint8* src_argb0, - uint8* dst_u, uint8* dst_v, int width) { - asm volatile ( - "movdqa %4,%%xmm3 \n" - "movdqa %5,%%xmm4 \n" - "movdqa %6,%%xmm5 \n" - "sub %1,%2 \n" - LABELALIGN - "1: \n" - "movdqu " MEMACCESS(0) ",%%xmm0 \n" - "movdqu " MEMACCESS2(0x10,0) ",%%xmm1 \n" - "movdqu " MEMACCESS2(0x20,0) ",%%xmm2 \n" - "movdqu " MEMACCESS2(0x30,0) ",%%xmm6 \n" - "lea " MEMLEA(0x40,0) ",%0 \n" - "movdqa %%xmm0,%%xmm7 \n" - "shufps $0x88,%%xmm1,%%xmm0 \n" - "shufps $0xdd,%%xmm1,%%xmm7 \n" - "pavgb %%xmm7,%%xmm0 \n" - "movdqa %%xmm2,%%xmm7 \n" - "shufps $0x88,%%xmm6,%%xmm2 \n" - "shufps $0xdd,%%xmm6,%%xmm7 \n" - "pavgb %%xmm7,%%xmm2 \n" - "movdqa %%xmm0,%%xmm1 \n" - "movdqa %%xmm2,%%xmm6 \n" - "pmaddubsw %%xmm4,%%xmm0 \n" - "pmaddubsw %%xmm4,%%xmm2 \n" - "pmaddubsw %%xmm3,%%xmm1 \n" - "pmaddubsw %%xmm3,%%xmm6 \n" - "phaddw %%xmm2,%%xmm0 \n" - "phaddw %%xmm6,%%xmm1 \n" - "psraw $0x8,%%xmm0 \n" - "psraw $0x8,%%xmm1 \n" - "packsswb %%xmm1,%%xmm0 \n" - "paddb %%xmm5,%%xmm0 \n" - "movlps %%xmm0," MEMACCESS(1) " \n" - MEMOPMEM(movhps,xmm0,0x00,1,2,1) // movhps %%xmm0,(%1,%2,1) - "lea " MEMLEA(0x8,1) ",%1 \n" - "sub $0x10,%3 \n" - "jg 1b \n" - : "+r"(src_argb0), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+rm"(width) // %3 - : "m"(kARGBToV), // %4 - "m"(kARGBToU), // %5 - "m"(kAddUV128) // %6 - : "memory", "cc", NACL_R14 - "xmm0", "xmm1", "xmm2", "xmm6", "xmm7" - ); -} -#endif // HAS_ARGBTOUV422ROW_SSSE3 - void BGRAToYRow_SSSE3(const uint8* src_bgra, uint8* dst_y, int width) { asm volatile ( "movdqa %4,%%xmm5 \n" @@ -1484,7 +1492,7 @@ void RGBAToUVRow_SSSE3(const uint8* src_rgba0, int src_stride_rgba, #if defined(HAS_I422TOARGBROW_SSSE3) || defined(HAS_I422TOARGBROW_AVX2) -// Read 8 UV from 411 +// Read 8 UV from 444 #define READYUV444 \ "movq " MEMACCESS([u_buf]) ",%%xmm0 \n" \ MEMOPREG(movq, 0x00, [u_buf], [v_buf], 1, xmm1) \ @@ -1528,7 +1536,7 @@ void RGBAToUVRow_SSSE3(const uint8* src_rgba0, int src_stride_rgba, #define READYUV411_TEMP \ "movzwl " MEMACCESS([u_buf]) ",%[temp] \n" \ "movd %[temp],%%xmm0 \n" \ - MEMOPARG(movzwl,0x00,[u_buf],[v_buf],1,[temp]) " \n" \ + MEMOPARG(movzwl, 0x00, [u_buf], [v_buf], 1, [temp]) " \n" \ "movd %[temp],%%xmm1 \n" \ "lea " MEMLEA(0x2, [u_buf]) ",%[u_buf] \n" \ "punpcklbw %%xmm1,%%xmm0 \n" \ @@ -1803,7 +1811,7 @@ void OMITFP I411ToARGBRow_SSSE3(const uint8* y_buf, uint8* dst_argb, const struct YuvConstants* yuvconstants, int width) { - int temp = 0; + int temp; asm volatile ( YUVTORGB_SETUP(yuvconstants) "sub %[u_buf],%[v_buf] \n" @@ -1815,15 +1823,15 @@ void OMITFP I411ToARGBRow_SSSE3(const uint8* y_buf, STOREARGB "subl $0x8,%[width] \n" "jg 1b \n" - : [y_buf]"+r"(y_buf), // %[y_buf] - [u_buf]"+r"(u_buf), // %[u_buf] - [v_buf]"+r"(v_buf), // %[v_buf] + : [y_buf]"+r"(y_buf), // %[y_buf] + [u_buf]"+r"(u_buf), // %[u_buf] + [v_buf]"+r"(v_buf), // %[v_buf] [dst_argb]"+r"(dst_argb), // %[dst_argb] - [temp]"+r"(temp), // %[temp] + [temp]"=&r"(temp), // %[temp] #if defined(__i386__) && defined(__pic__) - [width]"+m"(width) // %[width] + [width]"+m"(width) // %[width] #else - [width]"+rm"(width) // %[width] + [width]"+rm"(width) // %[width] #endif : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] : "memory", "cc", NACL_R14 YUVTORGB_REGS @@ -2005,6 +2013,20 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8* y_buf, "vpermq $0xd8,%%ymm5,%%ymm5 \n" \ "lea " MEMLEA(0x10, [a_buf]) ",%[a_buf] \n" +// Read 4 UV from 411, upsample to 16 UV. +#define READYUV411_AVX2 \ + "vmovd " MEMACCESS([u_buf]) ",%%xmm0 \n" \ + MEMOPREG(vmovd, 0x00, [u_buf], [v_buf], 1, xmm1) \ + "lea " MEMLEA(0x4, [u_buf]) ",%[u_buf] \n" \ + "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" \ + "vpunpcklwd %%ymm0,%%ymm0,%%ymm0 \n" \ + "vpermq $0xd8,%%ymm0,%%ymm0 \n" \ + "vpunpckldq %%ymm0,%%ymm0,%%ymm0 \n" \ + "vmovdqu " MEMACCESS([y_buf]) ",%%xmm4 \n" \ + "vpermq $0xd8,%%ymm4,%%ymm4 \n" \ + "vpunpcklbw %%ymm4,%%ymm4,%%ymm4 \n" \ + "lea " MEMLEA(0x10, [y_buf]) ",%[y_buf] \n" + // Read 8 UV from NV12, upsample to 16 UV. #define READNV12_AVX2 \ "vmovdqu " MEMACCESS([uv_buf]) ",%%xmm0 \n" \ @@ -2071,7 +2093,7 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8* y_buf, "vpackuswb %%ymm2,%%ymm2,%%ymm2 \n" #define YUVTORGB_REGS_AVX2 \ "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", -#else// Convert 16 pixels: 16 UV and 16 Y. +#else // Convert 16 pixels: 16 UV and 16 Y. #define YUVTORGB_SETUP_AVX2(yuvconstants) #define YUVTORGB_AVX2(yuvconstants) \ "vpmaddubsw " MEMACCESS2(64, [yuvconstants]) ",%%ymm0,%%ymm2 \n" \ @@ -2120,7 +2142,7 @@ void OMITFP I444ToARGBRow_AVX2(const uint8* y_buf, asm volatile ( YUVTORGB_SETUP_AVX2(yuvconstants) "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" + "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" LABELALIGN "1: \n" READYUV444_AVX2 @@ -2141,6 +2163,39 @@ void OMITFP I444ToARGBRow_AVX2(const uint8* y_buf, } #endif // HAS_I444TOARGBROW_AVX2 +#ifdef HAS_I411TOARGBROW_AVX2 +// 16 pixels +// 4 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). +void OMITFP I411ToARGBRow_AVX2(const uint8* y_buf, + const uint8* u_buf, + const uint8* v_buf, + uint8* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + asm volatile ( + YUVTORGB_SETUP_AVX2(yuvconstants) + "sub %[u_buf],%[v_buf] \n" + "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" + LABELALIGN + "1: \n" + READYUV411_AVX2 + YUVTORGB_AVX2(yuvconstants) + STOREARGB_AVX2 + "sub $0x10,%[width] \n" + "jg 1b \n" + "vzeroupper \n" + : [y_buf]"+r"(y_buf), // %[y_buf] + [u_buf]"+r"(u_buf), // %[u_buf] + [v_buf]"+r"(v_buf), // %[v_buf] + [dst_argb]"+r"(dst_argb), // %[dst_argb] + [width]"+rm"(width) // %[width] + : [yuvconstants]"r"(yuvconstants) // %[yuvconstants] + : "memory", "cc", NACL_R14 YUVTORGB_REGS_AVX2 + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" + ); +} +#endif // HAS_I411TOARGBROW_AVX2 + #if defined(HAS_I422TOARGBROW_AVX2) // 16 pixels // 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 ARGB (64 bytes). @@ -2153,7 +2208,7 @@ void OMITFP I422ToARGBRow_AVX2(const uint8* y_buf, asm volatile ( YUVTORGB_SETUP_AVX2(yuvconstants) "sub %[u_buf],%[v_buf] \n" - "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" + "vpcmpeqb %%ymm5,%%ymm5,%%ymm5 \n" LABELALIGN "1: \n" READYUV422_AVX2 @@ -2521,7 +2576,7 @@ void MirrorRow_AVX2(const uint8* src, uint8* dst, int width) { } #endif // HAS_MIRRORROW_AVX2 -#ifdef HAS_MIRRORROW_UV_SSSE3 +#ifdef HAS_MIRRORUVROW_SSSE3 // Shuffle table for reversing the bytes of UV channels. static uvec8 kShuffleMirrorUV = { 14u, 12u, 10u, 8u, 6u, 4u, 2u, 0u, 15u, 13u, 11u, 9u, 7u, 5u, 3u, 1u @@ -2552,7 +2607,7 @@ void MirrorUVRow_SSSE3(const uint8* src, uint8* dst_u, uint8* dst_v, "xmm0", "xmm1" ); } -#endif // HAS_MIRRORROW_UV_SSSE3 +#endif // HAS_MIRRORUVROW_SSSE3 #ifdef HAS_ARGBMIRRORROW_SSE2 @@ -2953,7 +3008,7 @@ void ARGBCopyYToAlphaRow_AVX2(const uint8* src, uint8* dst, int width) { #ifdef HAS_SETROW_X86 void SetRow_X86(uint8* dst, uint8 v8, int width) { size_t width_tmp = (size_t)(width >> 2); - const uint32 v32 = v8 * 0x01010101; // Duplicate byte to all bytes. + const uint32 v32 = v8 * 0x01010101u; // Duplicate byte to all bytes. asm volatile ( "rep stosl " MEMSTORESTRING(eax,0) " \n" : "+D"(dst), // %0 @@ -3677,7 +3732,7 @@ void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width) { // Unattenuate 4 pixels at a time. void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width) { - uintptr_t alpha = 0; + uintptr_t alpha; asm volatile ( // 4 pixel loop. LABELALIGN @@ -3708,10 +3763,10 @@ void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, "lea " MEMLEA(0x10,1) ",%1 \n" "sub $0x4,%2 \n" "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width), // %2 - "+r"(alpha) // %3 + : "+r"(src_argb), // %0 + "+r"(dst_argb), // %1 + "+r"(width), // %2 + "=&r"(alpha) // %3 : "r"(fixed_invtbl8) // %4 : "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" @@ -3727,7 +3782,7 @@ static const uvec8 kUnattenShuffleAlpha_AVX2 = { // Unattenuate 8 pixels at a time. void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width) { - uintptr_t alpha = 0; + uintptr_t alpha; asm volatile ( "sub %0,%1 \n" "vbroadcastf128 %5,%%ymm5 \n" @@ -3776,10 +3831,10 @@ void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, "sub $0x8,%2 \n" "jg 1b \n" "vzeroupper \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+r"(width), // %2 - "+r"(alpha) // %3 + : "+r"(src_argb), // %0 + "+r"(dst_argb), // %1 + "+r"(width), // %2 + "=&r"(alpha) // %3 : "r"(fixed_invtbl8), // %4 "m"(kUnattenShuffleAlpha_AVX2) // %5 : "memory", "cc", NACL_R14 @@ -4704,7 +4759,7 @@ LIBYUV_API void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, uint8* dst_argb, const float* src_dudv, int width) { intptr_t src_argb_stride_temp = src_argb_stride; - intptr_t temp = 0; + intptr_t temp; asm volatile ( "movq " MEMACCESS(3) ",%%xmm2 \n" "movq " MEMACCESS2(0x08,3) ",%%xmm7 \n" @@ -4776,7 +4831,7 @@ void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, "+r"(dst_argb), // %2 "+r"(src_dudv), // %3 "+rm"(width), // %4 - "+r"(temp) // %5 + "=&r"(temp) // %5 : : "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" @@ -5002,7 +5057,7 @@ void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, // For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA. void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, const uint8* shuffler, int width) { - uintptr_t pixel_temp = 0u; + uintptr_t pixel_temp; asm volatile ( "pxor %%xmm5,%%xmm5 \n" "mov " MEMACCESS(4) ",%k2 \n" @@ -5107,11 +5162,11 @@ void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, "jg 3012b \n" "99: \n" - : "+r"(src_argb), // %0 - "+r"(dst_argb), // %1 - "+d"(pixel_temp), // %2 + : "+r"(src_argb), // %0 + "+r"(dst_argb), // %1 + "=&d"(pixel_temp), // %2 "+r"(width) // %3 - : "r"(shuffler) // %4 + : "r"(shuffler) // %4 : "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm5" ); @@ -5288,7 +5343,7 @@ void ARGBPolynomialRow_AVX2(const uint8* src_argb, // Tranform ARGB pixels with color table. void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { - uintptr_t pixel_temp = 0u; + uintptr_t pixel_temp; asm volatile ( // 1 pixel loop. LABELALIGN @@ -5308,10 +5363,10 @@ void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, "mov %b1," MEMACCESS2(-0x1,0) " \n" "dec %2 \n" "jg 1b \n" - : "+r"(dst_argb), // %0 - "+d"(pixel_temp), // %1 - "+r"(width) // %2 - : "r"(table_argb) // %3 + : "+r"(dst_argb), // %0 + "=&d"(pixel_temp), // %1 + "+r"(width) // %2 + : "r"(table_argb) // %3 : "memory", "cc"); } #endif // HAS_ARGBCOLORTABLEROW_X86 @@ -5319,7 +5374,7 @@ void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, #ifdef HAS_RGBCOLORTABLEROW_X86 // Tranform RGB pixels with color table. void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { - uintptr_t pixel_temp = 0u; + uintptr_t pixel_temp; asm volatile ( // 1 pixel loop. LABELALIGN @@ -5336,10 +5391,10 @@ void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { "mov %b1," MEMACCESS2(-0x2,0) " \n" "dec %2 \n" "jg 1b \n" - : "+r"(dst_argb), // %0 - "+d"(pixel_temp), // %1 - "+r"(width) // %2 - : "r"(table_argb) // %3 + : "+r"(dst_argb), // %0 + "=&d"(pixel_temp), // %1 + "+r"(width) // %2 + : "r"(table_argb) // %3 : "memory", "cc"); } #endif // HAS_RGBCOLORTABLEROW_X86 @@ -5349,8 +5404,8 @@ void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width) { void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width, const uint8* luma, uint32 lumacoeff) { - uintptr_t pixel_temp = 0u; - uintptr_t table_temp = 0u; + uintptr_t pixel_temp; + uintptr_t table_temp; asm volatile ( "movd %6,%%xmm3 \n" "pshufd $0x0,%%xmm3,%%xmm3 \n" @@ -5432,13 +5487,13 @@ void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, "lea " MEMLEA(0x10,3) ",%3 \n" "sub $0x4,%4 \n" "jg 1b \n" - : "+d"(pixel_temp), // %0 - "+a"(table_temp), // %1 - "+r"(src_argb), // %2 - "+r"(dst_argb), // %3 - "+rm"(width) // %4 - : "r"(luma), // %5 - "rm"(lumacoeff) // %6 + : "=&d"(pixel_temp), // %0 + "=&a"(table_temp), // %1 + "+r"(src_argb), // %2 + "+r"(dst_argb), // %3 + "+rm"(width) // %4 + : "r"(luma), // %5 + "rm"(lumacoeff) // %6 : "memory", "cc", "xmm0", "xmm3", "xmm4", "xmm5" ); } diff --git a/TMessagesProj/jni/libyuv/source/row_mips.cc b/TMessagesProj/jni/libyuv/source/row_mips.cc index d12cf6ab7..ca6ecce94 100644 --- a/TMessagesProj/jni/libyuv/source/row_mips.cc +++ b/TMessagesProj/jni/libyuv/source/row_mips.cc @@ -375,12 +375,12 @@ void CopyRow_MIPS(const uint8* src, uint8* dst, int count) { } #endif // HAS_COPYROW_MIPS -// MIPS DSPR2 functions +// DSPR2 functions #if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips_dsp) && \ (__mips_dsp_rev >= 2) && \ (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) -void SplitUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, +void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width) { __asm__ __volatile__ ( ".set push \n" @@ -446,7 +446,7 @@ void SplitUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, ); } -void MirrorRow_MIPS_DSPR2(const uint8* src, uint8* dst, int width) { +void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -496,10 +496,10 @@ void MirrorRow_MIPS_DSPR2(const uint8* src, uint8* dst, int width) { ); } -void MirrorUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, +void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width) { - int x = 0; - int y = 0; + int x; + int y; __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -579,7 +579,7 @@ void MirrorUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, [dst_u] "+r" (dst_u), [dst_v] "+r" (dst_v), [x] "=&r" (x), - [y] "+r" (y) + [y] "=&r" (y) : [width] "r" (width) : "t0", "t1", "t2", "t3", "t4", "t5", "t7", "t8", "t9" @@ -653,7 +653,7 @@ void MirrorUVRow_MIPS_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, "addu.ph $t1, $t1, $s5 \n" // TODO(fbarchard): accept yuv conversion constants. -void I422ToARGBRow_MIPS_DSPR2(const uint8* y_buf, +void I422ToARGBRow_DSPR2(const uint8* y_buf, const uint8* u_buf, const uint8* v_buf, uint8* rgb_buf, @@ -716,7 +716,7 @@ void I422ToARGBRow_MIPS_DSPR2(const uint8* y_buf, } // Bilinear filter 8x2 -> 8x1 -void InterpolateRow_MIPS_DSPR2(uint8* dst_ptr, const uint8* src_ptr, +void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, ptrdiff_t src_stride, int dst_width, int source_y_fraction) { int y0_fraction = 256 - source_y_fraction; diff --git a/TMessagesProj/jni/libyuv/source/row_neon.cc b/TMessagesProj/jni/libyuv/source/row_neon.cc index 5b4ff3b5a..91d6aa857 100644 --- a/TMessagesProj/jni/libyuv/source/row_neon.cc +++ b/TMessagesProj/jni/libyuv/source/row_neon.cc @@ -317,16 +317,11 @@ void I422ToRGB24Row_NEON(const uint8* src_y, } #define ARGBTORGB565 \ - "vshr.u8 d20, d20, #3 \n" /* B */ \ - "vshr.u8 d21, d21, #2 \n" /* G */ \ - "vshr.u8 d22, d22, #3 \n" /* R */ \ - "vmovl.u8 q8, d20 \n" /* B */ \ - "vmovl.u8 q9, d21 \n" /* G */ \ - "vmovl.u8 q10, d22 \n" /* R */ \ - "vshl.u16 q9, q9, #5 \n" /* G */ \ - "vshl.u16 q10, q10, #11 \n" /* R */ \ - "vorr q0, q8, q9 \n" /* BG */ \ - "vorr q0, q0, q10 \n" /* BGR */ + "vshll.u8 q0, d22, #8 \n" /* R */ \ + "vshll.u8 q8, d21, #8 \n" /* G */ \ + "vshll.u8 q9, d20, #8 \n" /* B */ \ + "vsri.16 q0, q8, #5 \n" /* RG */ \ + "vsri.16 q0, q9, #11 \n" /* RGB */ void I422ToRGB565Row_NEON(const uint8* src_y, const uint8* src_u, @@ -359,19 +354,13 @@ void I422ToRGB565Row_NEON(const uint8* src_y, } #define ARGBTOARGB1555 \ - "vshr.u8 q10, q10, #3 \n" /* B */ \ - "vshr.u8 d22, d22, #3 \n" /* R */ \ - "vshr.u8 d23, d23, #7 \n" /* A */ \ - "vmovl.u8 q8, d20 \n" /* B */ \ - "vmovl.u8 q9, d21 \n" /* G */ \ - "vmovl.u8 q10, d22 \n" /* R */ \ - "vmovl.u8 q11, d23 \n" /* A */ \ - "vshl.u16 q9, q9, #5 \n" /* G */ \ - "vshl.u16 q10, q10, #10 \n" /* R */ \ - "vshl.u16 q11, q11, #15 \n" /* A */ \ - "vorr q0, q8, q9 \n" /* BG */ \ - "vorr q1, q10, q11 \n" /* RA */ \ - "vorr q0, q0, q1 \n" /* BGRA */ + "vshll.u8 q0, d23, #8 \n" /* A */ \ + "vshll.u8 q8, d22, #8 \n" /* R */ \ + "vshll.u8 q9, d21, #8 \n" /* G */ \ + "vshll.u8 q10, d20, #8 \n" /* B */ \ + "vsri.16 q0, q8, #1 \n" /* AR */ \ + "vsri.16 q0, q9, #6 \n" /* ARG */ \ + "vsri.16 q0, q10, #11 \n" /* ARGB */ void I422ToARGB1555Row_NEON(const uint8* src_y, const uint8* src_u, @@ -1374,55 +1363,6 @@ void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, ); } -// 16x1 pixels -> 8x1. width is number of argb pixels. e.g. 16. -void ARGBToUV422Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - "vmov.s16 q10, #112 / 2 \n" // UB / VR 0.875 coefficient - "vmov.s16 q11, #74 / 2 \n" // UG -0.5781 coefficient - "vmov.s16 q12, #38 / 2 \n" // UR -0.2969 coefficient - "vmov.s16 q13, #18 / 2 \n" // VB -0.1406 coefficient - "vmov.s16 q14, #94 / 2 \n" // VG -0.7344 coefficient - "vmov.u16 q15, #0x8080 \n" // 128.5 - "1: \n" - MEMACCESS(0) - "vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels. - MEMACCESS(0) - "vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels. - - "vpaddl.u8 q0, q0 \n" // B 16 bytes -> 8 shorts. - "vpaddl.u8 q1, q1 \n" // G 16 bytes -> 8 shorts. - "vpaddl.u8 q2, q2 \n" // R 16 bytes -> 8 shorts. - - "subs %3, %3, #16 \n" // 16 processed per loop. - "vmul.s16 q8, q0, q10 \n" // B - "vmls.s16 q8, q1, q11 \n" // G - "vmls.s16 q8, q2, q12 \n" // R - "vadd.u16 q8, q8, q15 \n" // +128 -> unsigned - - "vmul.s16 q9, q2, q10 \n" // R - "vmls.s16 q9, q1, q14 \n" // G - "vmls.s16 q9, q0, q13 \n" // B - "vadd.u16 q9, q9, q15 \n" // +128 -> unsigned - - "vqshrn.u16 d0, q8, #8 \n" // 16 bit to 8 bit U - "vqshrn.u16 d1, q9, #8 \n" // 16 bit to 8 bit V - - MEMACCESS(1) - "vst1.8 {d0}, [%1]! \n" // store 8 pixels U. - MEMACCESS(2) - "vst1.8 {d1}, [%2]! \n" // store 8 pixels V. - "bgt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "q0", "q1", "q2", "q3", - "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" - ); -} - // 32x1 pixels -> 8x1. width is number of argb pixels. e.g. 32. void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, int width) { diff --git a/TMessagesProj/jni/libyuv/source/row_neon64.cc b/TMessagesProj/jni/libyuv/source/row_neon64.cc index 6fe5a1080..ee42af12e 100644 --- a/TMessagesProj/jni/libyuv/source/row_neon64.cc +++ b/TMessagesProj/jni/libyuv/source/row_neon64.cc @@ -323,8 +323,8 @@ void I422ToRGB24Row_NEON(const uint8* src_y, #define ARGBTORGB565 \ "shll v0.8h, v22.8b, #8 \n" /* R */ \ - "shll v20.8h, v20.8b, #8 \n" /* B */ \ "shll v21.8h, v21.8b, #8 \n" /* G */ \ + "shll v20.8h, v20.8b, #8 \n" /* B */ \ "sri v0.8h, v21.8h, #5 \n" /* RG */ \ "sri v0.8h, v20.8h, #11 \n" /* RGB */ @@ -363,8 +363,8 @@ void I422ToRGB565Row_NEON(const uint8* src_y, #define ARGBTOARGB1555 \ "shll v0.8h, v23.8b, #8 \n" /* A */ \ "shll v22.8h, v22.8b, #8 \n" /* R */ \ - "shll v20.8h, v20.8b, #8 \n" /* B */ \ "shll v21.8h, v21.8b, #8 \n" /* G */ \ + "shll v20.8h, v20.8b, #8 \n" /* B */ \ "sri v0.8h, v22.8h, #1 \n" /* AR */ \ "sri v0.8h, v21.8h, #6 \n" /* ARG */ \ "sri v0.8h, v20.8h, #11 \n" /* ARGB */ @@ -1477,50 +1477,6 @@ void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, "movi v24.8h, #47, lsl #0 \n" /* VG coefficient (-0.7344) / 2 */ \ "movi v25.16b, #0x80 \n" /* 128.5 (0x8080 in 16-bit) */ -// 16x1 pixels -> 8x1. width is number of argb pixels. e.g. 16. -#ifdef HAS_ARGBTOUV422ROW_NEON -void ARGBToUV422Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width) { - asm volatile ( - RGBTOUV_SETUP_REG - "1: \n" - MEMACCESS(0) - "ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load 16 pixels. - - "uaddlp v0.8h, v0.16b \n" // B 16 bytes -> 8 shorts. - "uaddlp v1.8h, v1.16b \n" // G 16 bytes -> 8 shorts. - "uaddlp v2.8h, v2.16b \n" // R 16 bytes -> 8 shorts. - - "subs %w3, %w3, #16 \n" // 16 processed per loop. - "mul v3.8h, v0.8h, v20.8h \n" // B - "mls v3.8h, v1.8h, v21.8h \n" // G - "mls v3.8h, v2.8h, v22.8h \n" // R - "add v3.8h, v3.8h, v25.8h \n" // +128 -> unsigned - - "mul v4.8h, v2.8h, v20.8h \n" // R - "mls v4.8h, v1.8h, v24.8h \n" // G - "mls v4.8h, v0.8h, v23.8h \n" // B - "add v4.8h, v4.8h, v25.8h \n" // +128 -> unsigned - - "uqshrn v0.8b, v3.8h, #8 \n" // 16 bit to 8 bit U - "uqshrn v1.8b, v4.8h, #8 \n" // 16 bit to 8 bit V - - MEMACCESS(1) - "st1 {v0.8b}, [%1], #8 \n" // store 8 pixels U. - MEMACCESS(2) - "st1 {v1.8b}, [%2], #8 \n" // store 8 pixels V. - "b.gt 1b \n" - : "+r"(src_argb), // %0 - "+r"(dst_u), // %1 - "+r"(dst_v), // %2 - "+r"(width) // %3 - : - : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", - "v20", "v21", "v22", "v23", "v24", "v25" - ); -} -#endif // HAS_ARGBTOUV422ROW_NEON - // 32x1 pixels -> 8x1. width is number of argb pixels. e.g. 32. #ifdef HAS_ARGBTOUV411ROW_NEON void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, diff --git a/TMessagesProj/jni/libyuv/source/row_win.cc b/TMessagesProj/jni/libyuv/source/row_win.cc index 5cb5d1e4f..a8c16c3c1 100644 --- a/TMessagesProj/jni/libyuv/source/row_win.cc +++ b/TMessagesProj/jni/libyuv/source/row_win.cc @@ -1505,7 +1505,7 @@ void ARGBToUVJRow_SSSE3(const uint8* src_argb0, int src_stride_argb, pmaddubsw xmm3, xmm6 phaddw xmm0, xmm2 phaddw xmm1, xmm3 - paddw xmm0, xmm5 // +.5 rounding -> unsigned + paddw xmm0, xmm5 // +.5 rounding -> unsigned paddw xmm1, xmm5 psraw xmm0, 8 psraw xmm1, 8 @@ -1590,6 +1590,73 @@ void ARGBToUVRow_AVX2(const uint8* src_argb0, int src_stride_argb, } #endif // HAS_ARGBTOUVROW_AVX2 +#ifdef HAS_ARGBTOUVJROW_AVX2 +__declspec(naked) +void ARGBToUVJRow_AVX2(const uint8* src_argb0, int src_stride_argb, + uint8* dst_u, uint8* dst_v, int width) { + __asm { + push esi + push edi + mov eax, [esp + 8 + 4] // src_argb + mov esi, [esp + 8 + 8] // src_stride_argb + mov edx, [esp + 8 + 12] // dst_u + mov edi, [esp + 8 + 16] // dst_v + mov ecx, [esp + 8 + 20] // width + vbroadcastf128 ymm5, xmmword ptr kAddUV128 + vbroadcastf128 ymm6, xmmword ptr kARGBToV + vbroadcastf128 ymm7, xmmword ptr kARGBToU + sub edi, edx // stride from u to v + + convertloop: + /* step 1 - subsample 32x2 argb pixels to 16x1 */ + vmovdqu ymm0, [eax] + vmovdqu ymm1, [eax + 32] + vmovdqu ymm2, [eax + 64] + vmovdqu ymm3, [eax + 96] + vpavgb ymm0, ymm0, [eax + esi] + vpavgb ymm1, ymm1, [eax + esi + 32] + vpavgb ymm2, ymm2, [eax + esi + 64] + vpavgb ymm3, ymm3, [eax + esi + 96] + lea eax, [eax + 128] + vshufps ymm4, ymm0, ymm1, 0x88 + vshufps ymm0, ymm0, ymm1, 0xdd + vpavgb ymm0, ymm0, ymm4 // mutated by vshufps + vshufps ymm4, ymm2, ymm3, 0x88 + vshufps ymm2, ymm2, ymm3, 0xdd + vpavgb ymm2, ymm2, ymm4 // mutated by vshufps + + // step 2 - convert to U and V + // from here down is very similar to Y code except + // instead of 32 different pixels, its 16 pixels of U and 16 of V + vpmaddubsw ymm1, ymm0, ymm7 // U + vpmaddubsw ymm3, ymm2, ymm7 + vpmaddubsw ymm0, ymm0, ymm6 // V + vpmaddubsw ymm2, ymm2, ymm6 + vphaddw ymm1, ymm1, ymm3 // mutates + vphaddw ymm0, ymm0, ymm2 + vpaddw ymm1, ymm1, ymm5 // +.5 rounding -> unsigned + vpaddw ymm0, ymm0, ymm5 + vpsraw ymm1, ymm1, 8 + vpsraw ymm0, ymm0, 8 + vpacksswb ymm0, ymm1, ymm0 // mutates + vpermq ymm0, ymm0, 0xd8 // For vpacksswb + vpshufb ymm0, ymm0, ymmword ptr kShufARGBToUV_AVX // for vshufps/vphaddw + + // step 3 - store 16 U and 16 V values + vextractf128 [edx], ymm0, 0 // U + vextractf128 [edx + edi], ymm0, 1 // V + lea edx, [edx + 16] + sub ecx, 32 + jg convertloop + + pop edi + pop esi + vzeroupper + ret + } +} +#endif // HAS_ARGBTOUVJROW_AVX2 + __declspec(naked) void ARGBToUV444Row_SSSE3(const uint8* src_argb0, uint8* dst_u, uint8* dst_v, int width) { @@ -1647,64 +1714,6 @@ void ARGBToUV444Row_SSSE3(const uint8* src_argb0, } } -__declspec(naked) -void ARGBToUV422Row_SSSE3(const uint8* src_argb0, - uint8* dst_u, uint8* dst_v, int width) { - __asm { - push edi - mov eax, [esp + 4 + 4] // src_argb - mov edx, [esp + 4 + 8] // dst_u - mov edi, [esp + 4 + 12] // dst_v - mov ecx, [esp + 4 + 16] // width - movdqa xmm5, xmmword ptr kAddUV128 - movdqa xmm6, xmmword ptr kARGBToV - movdqa xmm7, xmmword ptr kARGBToU - sub edi, edx // stride from u to v - - convertloop: - /* step 1 - subsample 16x2 argb pixels to 8x1 */ - movdqu xmm0, [eax] - movdqu xmm1, [eax + 16] - movdqu xmm2, [eax + 32] - movdqu xmm3, [eax + 48] - lea eax, [eax + 64] - movdqa xmm4, xmm0 - shufps xmm0, xmm1, 0x88 - shufps xmm4, xmm1, 0xdd - pavgb xmm0, xmm4 - movdqa xmm4, xmm2 - shufps xmm2, xmm3, 0x88 - shufps xmm4, xmm3, 0xdd - pavgb xmm2, xmm4 - - // step 2 - convert to U and V - // from here down is very similar to Y code except - // instead of 16 different pixels, its 8 pixels of U and 8 of V - movdqa xmm1, xmm0 - movdqa xmm3, xmm2 - pmaddubsw xmm0, xmm7 // U - pmaddubsw xmm2, xmm7 - pmaddubsw xmm1, xmm6 // V - pmaddubsw xmm3, xmm6 - phaddw xmm0, xmm2 - phaddw xmm1, xmm3 - psraw xmm0, 8 - psraw xmm1, 8 - packsswb xmm0, xmm1 - paddb xmm0, xmm5 // -> unsigned - - // step 3 - store 8 U and 8 V values - movlps qword ptr [edx], xmm0 // U - movhps qword ptr [edx + edi], xmm0 // V - lea edx, [edx + 8] - sub ecx, 16 - jg convertloop - - pop edi - ret - } -} - __declspec(naked) void BGRAToUVRow_SSSE3(const uint8* src_argb0, int src_stride_argb, uint8* dst_u, uint8* dst_v, int width) { @@ -3154,7 +3163,7 @@ void MirrorRow_AVX2(const uint8* src, uint8* dst, int width) { } #endif // HAS_MIRRORROW_AVX2 -#ifdef HAS_MIRRORROW_UV_SSSE3 +#ifdef HAS_MIRRORUVROW_SSSE3 // Shuffle table for reversing the bytes of UV channels. static const uvec8 kShuffleMirrorUV = { 14u, 12u, 10u, 8u, 6u, 4u, 2u, 0u, 15u, 13u, 11u, 9u, 7u, 5u, 3u, 1u @@ -3187,7 +3196,7 @@ void MirrorUVRow_SSSE3(const uint8* src, uint8* dst_u, uint8* dst_v, ret } } -#endif // HAS_MIRRORROW_UV_SSSE3 +#endif // HAS_MIRRORUVROW_SSSE3 #ifdef HAS_ARGBMIRRORROW_SSE2 __declspec(naked) diff --git a/TMessagesProj/jni/libyuv/source/scale.cc b/TMessagesProj/jni/libyuv/source/scale.cc index 595314f35..36e3fe528 100644 --- a/TMessagesProj/jni/libyuv/source/scale.cc +++ b/TMessagesProj/jni/libyuv/source/scale.cc @@ -85,12 +85,12 @@ static void ScalePlaneDown2(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN2_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(src_ptr, 4) && +#if defined(HAS_SCALEROWDOWN2_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(row_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { ScaleRowDown2 = filtering ? - ScaleRowDown2Box_MIPS_DSPR2 : ScaleRowDown2_MIPS_DSPR2; + ScaleRowDown2Box_DSPR2 : ScaleRowDown2_DSPR2; } #endif @@ -135,12 +135,12 @@ static void ScalePlaneDown2_16(int src_width, int src_height, ScaleRowDown2Box_16_SSE2); } #endif -#if defined(HAS_SCALEROWDOWN2_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(src_ptr, 4) && +#if defined(HAS_SCALEROWDOWN2_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(row_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { ScaleRowDown2 = filtering ? - ScaleRowDown2Box_16_MIPS_DSPR2 : ScaleRowDown2_16_MIPS_DSPR2; + ScaleRowDown2Box_16_DSPR2 : ScaleRowDown2_16_DSPR2; } #endif @@ -200,12 +200,12 @@ static void ScalePlaneDown4(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN4_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(row_stride, 4) && +#if defined(HAS_SCALEROWDOWN4_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(row_stride, 4) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { ScaleRowDown4 = filtering ? - ScaleRowDown4Box_MIPS_DSPR2 : ScaleRowDown4_MIPS_DSPR2; + ScaleRowDown4Box_DSPR2 : ScaleRowDown4_DSPR2; } #endif @@ -245,12 +245,12 @@ static void ScalePlaneDown4_16(int src_width, int src_height, ScaleRowDown4_16_SSE2; } #endif -#if defined(HAS_SCALEROWDOWN4_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(row_stride, 4) && +#if defined(HAS_SCALEROWDOWN4_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(row_stride, 4) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { ScaleRowDown4 = filtering ? - ScaleRowDown4Box_16_MIPS_DSPR2 : ScaleRowDown4_16_MIPS_DSPR2; + ScaleRowDown4Box_16_DSPR2 : ScaleRowDown4_16_DSPR2; } #endif @@ -325,16 +325,16 @@ static void ScalePlaneDown34(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN34_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && (dst_width % 24 == 0) && +#if defined(HAS_SCALEROWDOWN34_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 24 == 0) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_MIPS_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_MIPS_DSPR2; + ScaleRowDown34_0 = ScaleRowDown34_DSPR2; + ScaleRowDown34_1 = ScaleRowDown34_DSPR2; } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_MIPS_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_MIPS_DSPR2; + ScaleRowDown34_0 = ScaleRowDown34_0_Box_DSPR2; + ScaleRowDown34_1 = ScaleRowDown34_1_Box_DSPR2; } } #endif @@ -404,16 +404,16 @@ static void ScalePlaneDown34_16(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN34_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && (dst_width % 24 == 0) && +#if defined(HAS_SCALEROWDOWN34_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 24 == 0) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { if (!filtering) { - ScaleRowDown34_0 = ScaleRowDown34_16_MIPS_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_16_MIPS_DSPR2; + ScaleRowDown34_0 = ScaleRowDown34_16_DSPR2; + ScaleRowDown34_1 = ScaleRowDown34_16_DSPR2; } else { - ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_MIPS_DSPR2; - ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_MIPS_DSPR2; + ScaleRowDown34_0 = ScaleRowDown34_0_Box_16_DSPR2; + ScaleRowDown34_1 = ScaleRowDown34_1_Box_16_DSPR2; } } #endif @@ -517,16 +517,16 @@ static void ScalePlaneDown38(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN38_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && (dst_width % 12 == 0) && +#if defined(HAS_SCALEROWDOWN38_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 12 == 0) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_MIPS_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_MIPS_DSPR2; + ScaleRowDown38_3 = ScaleRowDown38_DSPR2; + ScaleRowDown38_2 = ScaleRowDown38_DSPR2; } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_MIPS_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_MIPS_DSPR2; + ScaleRowDown38_3 = ScaleRowDown38_3_Box_DSPR2; + ScaleRowDown38_2 = ScaleRowDown38_2_Box_DSPR2; } } #endif @@ -595,16 +595,16 @@ static void ScalePlaneDown38_16(int src_width, int src_height, } } #endif -#if defined(HAS_SCALEROWDOWN38_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && (dst_width % 12 == 0) && +#if defined(HAS_SCALEROWDOWN38_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && (dst_width % 12 == 0) && IS_ALIGNED(src_ptr, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_ptr, 4) && IS_ALIGNED(dst_stride, 4)) { if (!filtering) { - ScaleRowDown38_3 = ScaleRowDown38_16_MIPS_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_16_MIPS_DSPR2; + ScaleRowDown38_3 = ScaleRowDown38_16_DSPR2; + ScaleRowDown38_2 = ScaleRowDown38_16_DSPR2; } else { - ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_MIPS_DSPR2; - ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_MIPS_DSPR2; + ScaleRowDown38_3 = ScaleRowDown38_3_Box_16_DSPR2; + ScaleRowDown38_2 = ScaleRowDown38_2_Box_16_DSPR2; } } #endif @@ -659,7 +659,6 @@ static void ScaleAddCols2_C(int dst_width, int boxheight, int x, int dx, int i; int scaletbl[2]; int minboxwidth = dx >> 16; - int* scaleptr = scaletbl - minboxwidth; int boxwidth; scaletbl[0] = 65536 / (MIN1(minboxwidth) * boxheight); scaletbl[1] = 65536 / (MIN1(minboxwidth + 1) * boxheight); @@ -667,7 +666,8 @@ static void ScaleAddCols2_C(int dst_width, int boxheight, int x, int dx, int ix = x >> 16; x += dx; boxwidth = MIN1((x >> 16) - ix); - *dst_ptr++ = SumPixels(boxwidth, src_ptr + ix) * scaleptr[boxwidth] >> 16; + *dst_ptr++ = SumPixels(boxwidth, src_ptr + ix) * + scaletbl[boxwidth - minboxwidth] >> 16; } } @@ -676,7 +676,6 @@ static void ScaleAddCols2_16_C(int dst_width, int boxheight, int x, int dx, int i; int scaletbl[2]; int minboxwidth = dx >> 16; - int* scaleptr = scaletbl - minboxwidth; int boxwidth; scaletbl[0] = 65536 / (MIN1(minboxwidth) * boxheight); scaletbl[1] = 65536 / (MIN1(minboxwidth + 1) * boxheight); @@ -684,8 +683,8 @@ static void ScaleAddCols2_16_C(int dst_width, int boxheight, int x, int dx, int ix = x >> 16; x += dx; boxwidth = MIN1((x >> 16) - ix); - *dst_ptr++ = - SumPixels_16(boxwidth, src_ptr + ix) * scaleptr[boxwidth] >> 16; + *dst_ptr++ = SumPixels_16(boxwidth, src_ptr + ix) * + scaletbl[boxwidth - minboxwidth] >> 16; } } @@ -899,11 +898,11 @@ void ScalePlaneBilinearDown(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2)) { - InterpolateRow = InterpolateRow_Any_MIPS_DSPR2; +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2)) { + InterpolateRow = InterpolateRow_Any_DSPR2; if (IS_ALIGNED(src_width, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } } #endif @@ -1003,11 +1002,11 @@ void ScalePlaneBilinearDown_16(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2)) { - InterpolateRow = InterpolateRow_Any_16_MIPS_DSPR2; +#if defined(HAS_INTERPOLATEROW_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2)) { + InterpolateRow = InterpolateRow_Any_16_DSPR2; if (IS_ALIGNED(src_width, 4)) { - InterpolateRow = InterpolateRow_16_MIPS_DSPR2; + InterpolateRow = InterpolateRow_16_DSPR2; } } #endif @@ -1088,11 +1087,11 @@ void ScalePlaneBilinearUp(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2)) { - InterpolateRow = InterpolateRow_Any_MIPS_DSPR2; +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2)) { + InterpolateRow = InterpolateRow_Any_DSPR2; if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } } #endif @@ -1227,11 +1226,11 @@ void ScalePlaneBilinearUp_16(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2)) { - InterpolateRow = InterpolateRow_Any_16_MIPS_DSPR2; +#if defined(HAS_INTERPOLATEROW_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2)) { + InterpolateRow = InterpolateRow_Any_16_DSPR2; if (IS_ALIGNED(dst_width, 4)) { - InterpolateRow = InterpolateRow_16_MIPS_DSPR2; + InterpolateRow = InterpolateRow_16_DSPR2; } } #endif diff --git a/TMessagesProj/jni/libyuv/source/scale_any.cc b/TMessagesProj/jni/libyuv/source/scale_any.cc index 1da4dacde..ed76a9e4c 100644 --- a/TMessagesProj/jni/libyuv/source/scale_any.cc +++ b/TMessagesProj/jni/libyuv/source/scale_any.cc @@ -55,12 +55,29 @@ CANY(ScaleARGBFilterCols_Any_NEON, ScaleARGBFilterCols_NEON, dst_ptr + n * BPP, r); \ } +// Fixed scale down for odd source width. Used by I420Blend subsampling. +// Since dst_width is (width + 1) / 2, this function scales one less pixel +// and copies the last pixel. +#define SDODD(NAMEANY, SCALEROWDOWN_SIMD, SCALEROWDOWN_C, FACTOR, BPP, MASK) \ + void NAMEANY(const uint8* src_ptr, ptrdiff_t src_stride, \ + uint8* dst_ptr, int dst_width) { \ + int r = (int)((unsigned int)(dst_width - 1) % (MASK + 1)); \ + int n = dst_width - r; \ + if (n > 0) { \ + SCALEROWDOWN_SIMD(src_ptr, src_stride, dst_ptr, n); \ + } \ + SCALEROWDOWN_C(src_ptr + (n * FACTOR) * BPP, src_stride, \ + dst_ptr + n * BPP, r); \ + } + #ifdef HAS_SCALEROWDOWN2_SSSE3 SDANY(ScaleRowDown2_Any_SSSE3, ScaleRowDown2_SSSE3, ScaleRowDown2_C, 2, 1, 15) SDANY(ScaleRowDown2Linear_Any_SSSE3, ScaleRowDown2Linear_SSSE3, ScaleRowDown2Linear_C, 2, 1, 15) SDANY(ScaleRowDown2Box_Any_SSSE3, ScaleRowDown2Box_SSSE3, ScaleRowDown2Box_C, 2, 1, 15) +SDODD(ScaleRowDown2Box_Odd_SSSE3, ScaleRowDown2Box_SSSE3, + ScaleRowDown2Box_Odd_C, 2, 1, 15) #endif #ifdef HAS_SCALEROWDOWN2_AVX2 SDANY(ScaleRowDown2_Any_AVX2, ScaleRowDown2_AVX2, ScaleRowDown2_C, 2, 1, 31) @@ -68,6 +85,8 @@ SDANY(ScaleRowDown2Linear_Any_AVX2, ScaleRowDown2Linear_AVX2, ScaleRowDown2Linear_C, 2, 1, 31) SDANY(ScaleRowDown2Box_Any_AVX2, ScaleRowDown2Box_AVX2, ScaleRowDown2Box_C, 2, 1, 31) +SDODD(ScaleRowDown2Box_Odd_AVX2, ScaleRowDown2Box_AVX2, ScaleRowDown2Box_Odd_C, + 2, 1, 31) #endif #ifdef HAS_SCALEROWDOWN2_NEON SDANY(ScaleRowDown2_Any_NEON, ScaleRowDown2_NEON, ScaleRowDown2_C, 2, 1, 15) @@ -75,6 +94,8 @@ SDANY(ScaleRowDown2Linear_Any_NEON, ScaleRowDown2Linear_NEON, ScaleRowDown2Linear_C, 2, 1, 15) SDANY(ScaleRowDown2Box_Any_NEON, ScaleRowDown2Box_NEON, ScaleRowDown2Box_C, 2, 1, 15) +SDODD(ScaleRowDown2Box_Odd_NEON, ScaleRowDown2Box_NEON, + ScaleRowDown2Box_Odd_C, 2, 1, 15) #endif #ifdef HAS_SCALEROWDOWN4_SSSE3 SDANY(ScaleRowDown4_Any_SSSE3, ScaleRowDown4_SSSE3, ScaleRowDown4_C, 4, 1, 7) diff --git a/TMessagesProj/jni/libyuv/source/scale_argb.cc b/TMessagesProj/jni/libyuv/source/scale_argb.cc index adddf9db5..17f51ae9b 100644 --- a/TMessagesProj/jni/libyuv/source/scale_argb.cc +++ b/TMessagesProj/jni/libyuv/source/scale_argb.cc @@ -234,12 +234,12 @@ static void ScaleARGBBilinearDown(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4)) { - InterpolateRow = InterpolateRow_Any_MIPS_DSPR2; + InterpolateRow = InterpolateRow_Any_DSPR2; if (IS_ALIGNED(clip_src_width, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } } #endif @@ -324,10 +324,10 @@ static void ScaleARGBBilinearUp(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } #endif if (src_width >= 32768) { @@ -465,13 +465,13 @@ static void ScaleYUVToARGBBilinearUp(int src_width, int src_height, } } #endif -#if defined(HAS_I422TOARGBROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && IS_ALIGNED(src_width, 4) && +#if defined(HAS_I422TOARGBROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_width, 4) && IS_ALIGNED(src_y, 4) && IS_ALIGNED(src_stride_y, 4) && IS_ALIGNED(src_u, 2) && IS_ALIGNED(src_stride_u, 2) && IS_ALIGNED(src_v, 2) && IS_ALIGNED(src_stride_v, 2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - I422ToARGBRow = I422ToARGBRow_MIPS_DSPR2; + I422ToARGBRow = I422ToARGBRow_DSPR2; } #endif @@ -502,10 +502,10 @@ static void ScaleYUVToARGBBilinearUp(int src_width, int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride_argb, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } #endif @@ -835,7 +835,6 @@ int YUVToARGBScaleClip(const uint8* src_y, int src_stride_y, int dst_width, int dst_height, int clip_x, int clip_y, int clip_width, int clip_height, enum FilterMode filtering) { - uint8* argb_buffer = (uint8*)malloc(src_width * src_height * 4); int r; I420ToARGB(src_y, src_stride_y, diff --git a/TMessagesProj/jni/libyuv/source/scale_common.cc b/TMessagesProj/jni/libyuv/source/scale_common.cc index f5c908d0b..d3992df2e 100644 --- a/TMessagesProj/jni/libyuv/source/scale_common.cc +++ b/TMessagesProj/jni/libyuv/source/scale_common.cc @@ -103,6 +103,28 @@ void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, } } +void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { + const uint8* s = src_ptr; + const uint8* t = src_ptr + src_stride; + int x; + dst_width -= 1; + for (x = 0; x < dst_width - 1; x += 2) { + dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; + dst[1] = (s[2] + s[3] + t[2] + t[3] + 2) >> 2; + dst += 2; + s += 4; + t += 4; + } + if (dst_width & 1) { + dst[0] = (s[0] + s[1] + t[0] + t[1] + 2) >> 2; + dst += 1; + s += 2; + t += 2; + } + dst[0] = (s[0] + t[0] + 1) >> 1; +} + void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, uint16* dst, int dst_width) { const uint16* s = src_ptr; @@ -900,13 +922,13 @@ void ScalePlaneVertical(int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_Any_MIPS_DSPR2; + InterpolateRow = InterpolateRow_Any_DSPR2; if (IS_ALIGNED(dst_width_bytes, 4)) { - InterpolateRow = InterpolateRow_MIPS_DSPR2; + InterpolateRow = InterpolateRow_DSPR2; } } #endif @@ -974,13 +996,13 @@ void ScalePlaneVertical_16(int src_height, } } #endif -#if defined(HAS_INTERPOLATEROW_16_MIPS_DSPR2) - if (TestCpuFlag(kCpuHasMIPS_DSPR2) && +#if defined(HAS_INTERPOLATEROW_16_DSPR2) + if (TestCpuFlag(kCpuHasDSPR2) && IS_ALIGNED(src_argb, 4) && IS_ALIGNED(src_stride, 4) && IS_ALIGNED(dst_argb, 4) && IS_ALIGNED(dst_stride, 4)) { - InterpolateRow = InterpolateRow_Any_16_MIPS_DSPR2; + InterpolateRow = InterpolateRow_Any_16_DSPR2; if (IS_ALIGNED(dst_width_bytes, 4)) { - InterpolateRow = InterpolateRow_16_MIPS_DSPR2; + InterpolateRow = InterpolateRow_16_DSPR2; } } #endif diff --git a/TMessagesProj/jni/libyuv/source/scale_gcc.cc b/TMessagesProj/jni/libyuv/source/scale_gcc.cc index a1ae4e277..400f2fde9 100644 --- a/TMessagesProj/jni/libyuv/source/scale_gcc.cc +++ b/TMessagesProj/jni/libyuv/source/scale_gcc.cc @@ -316,7 +316,7 @@ void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, uint8* dst_ptr, int dst_width) { - intptr_t stridex3 = 0; + intptr_t stridex3; asm volatile ( "pcmpeqb %%xmm4,%%xmm4 \n" "psrlw $0xf,%%xmm4 \n" @@ -361,7 +361,7 @@ void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, : "+r"(src_ptr), // %0 "+r"(dst_ptr), // %1 "+r"(dst_width), // %2 - "+r"(stridex3) // %3 + "=&r"(stridex3) // %3 : "r"((intptr_t)(src_stride)) // %4 : "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5" @@ -824,7 +824,7 @@ void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width) { // Bilinear column filtering. SSSE3 version. void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, int dst_width, int x, int dx) { - intptr_t x0 = 0, x1 = 0, temp_pixel = 0; + intptr_t x0, x1, temp_pixel; asm volatile ( "movd %6,%%xmm2 \n" "movd %7,%%xmm3 \n" @@ -880,14 +880,14 @@ void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, "movd %%xmm0,%k2 \n" "mov %b2," MEMACCESS(0) " \n" "99: \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+a"(temp_pixel), // %2 - "+r"(x0), // %3 - "+r"(x1), // %4 - "+rm"(dst_width) // %5 - : "rm"(x), // %6 - "rm"(dx) // %7 + : "+r"(dst_ptr), // %0 + "+r"(src_ptr), // %1 + "=&a"(temp_pixel), // %2 + "=&r"(x0), // %3 + "=&r"(x1), // %4 + "+rm"(dst_width) // %5 + : "rm"(x), // %6 + "rm"(dx) // %7 : "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" ); @@ -998,7 +998,7 @@ void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, int src_stepx, uint8* dst_argb, int dst_width) { intptr_t src_stepx_x4 = (intptr_t)(src_stepx); - intptr_t src_stepx_x12 = 0; + intptr_t src_stepx_x12; asm volatile ( "lea " MEMLEA3(0x00,1,4) ",%1 \n" "lea " MEMLEA4(0x00,1,1,2) ",%4 \n" @@ -1016,11 +1016,11 @@ void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, "lea " MEMLEA(0x10,2) ",%2 \n" "sub $0x4,%3 \n" "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stepx_x4), // %1 - "+r"(dst_argb), // %2 - "+r"(dst_width), // %3 - "+r"(src_stepx_x12) // %4 + : "+r"(src_argb), // %0 + "+r"(src_stepx_x4), // %1 + "+r"(dst_argb), // %2 + "+r"(dst_width), // %3 + "=&r"(src_stepx_x12) // %4 :: "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3" ); @@ -1032,7 +1032,7 @@ void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, ptrdiff_t src_stride, int src_stepx, uint8* dst_argb, int dst_width) { intptr_t src_stepx_x4 = (intptr_t)(src_stepx); - intptr_t src_stepx_x12 = 0; + intptr_t src_stepx_x12; intptr_t row1 = (intptr_t)(src_stride); asm volatile ( "lea " MEMLEA3(0x00,1,4) ",%1 \n" @@ -1061,12 +1061,12 @@ void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, "lea " MEMLEA(0x10,2) ",%2 \n" "sub $0x4,%3 \n" "jg 1b \n" - : "+r"(src_argb), // %0 - "+r"(src_stepx_x4), // %1 - "+r"(dst_argb), // %2 - "+rm"(dst_width), // %3 - "+r"(src_stepx_x12), // %4 - "+r"(row1) // %5 + : "+r"(src_argb), // %0 + "+r"(src_stepx_x4), // %1 + "+r"(dst_argb), // %2 + "+rm"(dst_width), // %3 + "=&r"(src_stepx_x12), // %4 + "+r"(row1) // %5 :: "memory", "cc", NACL_R14 "xmm0", "xmm1", "xmm2", "xmm3" ); @@ -1074,7 +1074,7 @@ void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, int dst_width, int x, int dx) { - intptr_t x0 = 0, x1 = 0; + intptr_t x0, x1; asm volatile ( "movd %5,%%xmm2 \n" "movd %6,%%xmm3 \n" @@ -1127,8 +1127,8 @@ void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, MEMOPREG(movd,0x00,3,0,4,xmm0) // movd (%3,%0,4),%%xmm0 "movd %%xmm0," MEMACCESS(2) " \n" "99: \n" - : "+a"(x0), // %0 - "+d"(x1), // %1 + : "=&a"(x0), // %0 + "=&d"(x1), // %1 "+r"(dst_argb), // %2 "+r"(src_argb), // %3 "+r"(dst_width) // %4 @@ -1179,7 +1179,7 @@ static uvec8 kShuffleFractions = { // Bilinear row filtering combines 4x2 -> 4x1. SSSE3 version void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, int dst_width, int x, int dx) { - intptr_t x0 = 0, x1 = 0; + intptr_t x0, x1; asm volatile ( "movdqa %0,%%xmm4 \n" "movdqa %1,%%xmm5 \n" @@ -1242,8 +1242,8 @@ void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, : "+r"(dst_argb), // %0 "+r"(src_argb), // %1 "+rm"(dst_width), // %2 - "+r"(x0), // %3 - "+r"(x1) // %4 + "=&r"(x0), // %3 + "=&r"(x1) // %4 : "rm"(x), // %5 "rm"(dx) // %6 : "memory", "cc", NACL_R14 diff --git a/TMessagesProj/jni/libyuv/source/scale_mips.cc b/TMessagesProj/jni/libyuv/source/scale_mips.cc index 2298a74b9..ae953073f 100644 --- a/TMessagesProj/jni/libyuv/source/scale_mips.cc +++ b/TMessagesProj/jni/libyuv/source/scale_mips.cc @@ -21,8 +21,8 @@ extern "C" { defined(__mips_dsp) && (__mips_dsp_rev >= 2) && \ (_MIPS_SIM == _MIPS_SIM_ABI32) -void ScaleRowDown2_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { __asm__ __volatile__( ".set push \n" ".set noreorder \n" @@ -77,8 +77,8 @@ void ScaleRowDown2_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown2Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { const uint8* t = src_ptr + src_stride; __asm__ __volatile__ ( @@ -176,8 +176,8 @@ void ScaleRowDown2Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown4_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -231,8 +231,8 @@ void ScaleRowDown4_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown4Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { intptr_t stride = src_stride; const uint8* s1 = src_ptr + stride; const uint8* s2 = s1 + stride; @@ -310,8 +310,8 @@ void ScaleRowDown4Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown34_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -356,8 +356,8 @@ void ScaleRowDown34_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown34_0_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { +void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* d, int dst_width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -412,8 +412,8 @@ void ScaleRowDown34_0_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown34_1_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width) { +void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* d, int dst_width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -464,8 +464,8 @@ void ScaleRowDown34_1_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown38_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width) { +void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst, int dst_width) { __asm__ __volatile__ ( ".set push \n" ".set noreorder \n" @@ -510,8 +510,8 @@ void ScaleRowDown38_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown38_2_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { +void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width) { intptr_t stride = src_stride; const uint8* t = src_ptr + stride; const int c = 0x2AAA; @@ -563,9 +563,9 @@ void ScaleRowDown38_2_Box_MIPS_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, ); } -void ScaleRowDown38_3_Box_MIPS_DSPR2(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width) { +void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, + ptrdiff_t src_stride, + uint8* dst_ptr, int dst_width) { intptr_t stride = src_stride; const uint8* s1 = src_ptr + stride; stride += stride; diff --git a/TMessagesProj/jni/libyuv/source/scale_neon.cc b/TMessagesProj/jni/libyuv/source/scale_neon.cc index 10856cf84..95f3362a4 100644 --- a/TMessagesProj/jni/libyuv/source/scale_neon.cc +++ b/TMessagesProj/jni/libyuv/source/scale_neon.cc @@ -532,7 +532,7 @@ void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, uint16* dst_ptr, int src_width, int src_height) { - const uint8* src_tmp = NULL; + const uint8* src_tmp; asm volatile ( "1: \n" "mov %0, %1 \n" @@ -552,12 +552,12 @@ void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, "add %1, %1, #16 \n" "subs %4, %4, #16 \n" // 16 processed per loop "bgt 1b \n" - : "+r"(src_tmp), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_ptr), // %2 - "+r"(src_stride), // %3 - "+r"(src_width), // %4 - "+r"(src_height) // %5 + : "=&r"(src_tmp), // %0 + "+r"(src_ptr), // %1 + "+r"(dst_ptr), // %2 + "+r"(src_stride), // %3 + "+r"(src_width), // %4 + "+r"(src_height) // %5 : : "memory", "cc", "r12", "q0", "q1", "q2", "q3" // Clobber List ); @@ -909,7 +909,7 @@ void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, int dst_width, int x, int dx) { - int tmp = 0; + int tmp; const uint8* src_tmp = src_argb; asm volatile ( "1: \n" @@ -926,13 +926,13 @@ void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, "vst1.32 {q0, q1}, [%0]! \n" // store pixels "subs %2, %2, #8 \n" // 8 processed per loop "bgt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width), // %2 - "+r"(x), // %3 - "+r"(dx), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 + : "+r"(dst_argb), // %0 + "+r"(src_argb), // %1 + "+r"(dst_width), // %2 + "+r"(x), // %3 + "+r"(dx), // %4 + "=&r"(tmp), // %5 + "+r"(src_tmp) // %6 : : "memory", "cc", "q0", "q1" ); diff --git a/TMessagesProj/jni/libyuv/source/scale_neon64.cc b/TMessagesProj/jni/libyuv/source/scale_neon64.cc index 1d5519357..3a62db5b8 100644 --- a/TMessagesProj/jni/libyuv/source/scale_neon64.cc +++ b/TMessagesProj/jni/libyuv/source/scale_neon64.cc @@ -547,7 +547,7 @@ void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, uint16* dst_ptr, int src_width, int src_height) { - const uint8* src_tmp = NULL; + const uint8* src_tmp; asm volatile ( "1: \n" "mov %0, %1 \n" @@ -567,12 +567,12 @@ void ScaleAddRows_NEON(const uint8* src_ptr, ptrdiff_t src_stride, "add %1, %1, #16 \n" "subs %w4, %w4, #16 \n" // 16 processed per loop "b.gt 1b \n" - : "+r"(src_tmp), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_ptr), // %2 - "+r"(src_stride), // %3 - "+r"(src_width), // %4 - "+r"(src_height) // %5 + : "=&r"(src_tmp), // %0 + "+r"(src_ptr), // %1 + "+r"(dst_ptr), // %2 + "+r"(src_stride), // %3 + "+r"(src_width), // %4 + "+r"(src_height) // %5 : : "memory", "cc", "w12", "v0", "v1", "v2", "v3" // Clobber List ); @@ -931,7 +931,7 @@ void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, int64 dst_width64 = (int64) dst_width; // Work around ios 64 bit warning. int64 x64 = (int64) x; int64 dx64 = (int64) dx; - int64 tmp64 = 0; + int64 tmp64; asm volatile ( "1: \n" LOAD1_DATA32_LANE(v0, 0) @@ -947,13 +947,13 @@ void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, "st1 {v0.4s, v1.4s}, [%0], #32 \n" // store pixels "subs %w2, %w2, #8 \n" // 8 processed per loop "b.gt 1b \n" - : "+r"(dst_argb), // %0 - "+r"(src_argb), // %1 - "+r"(dst_width64), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "+r"(tmp64), // %5 - "+r"(src_tmp) // %6 + : "+r"(dst_argb), // %0 + "+r"(src_argb), // %1 + "+r"(dst_width64), // %2 + "+r"(x64), // %3 + "+r"(dx64), // %4 + "=&r"(tmp64), // %5 + "+r"(src_tmp) // %6 : : "memory", "cc", "v0", "v1" ); diff --git a/TMessagesProj/jni/libyuv/source/scale_win.cc b/TMessagesProj/jni/libyuv/source/scale_win.cc index 5ab4fa0cc..21b1ed923 100644 --- a/TMessagesProj/jni/libyuv/source/scale_win.cc +++ b/TMessagesProj/jni/libyuv/source/scale_win.cc @@ -289,7 +289,7 @@ void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, vpmaddubsw ymm3, ymm3, ymm4 vpaddw ymm0, ymm0, ymm2 // vertical add vpaddw ymm1, ymm1, ymm3 - vpsrlw ymm0, ymm0, 1 + vpsrlw ymm0, ymm0, 1 // (x + 2) / 4 = (x / 2 + 1) / 2 vpsrlw ymm1, ymm1, 1 vpavgw ymm0, ymm0, ymm5 // (x + 1) / 2 vpavgw ymm1, ymm1, ymm5 diff --git a/TMessagesProj/jni/sqlite.c b/TMessagesProj/jni/sqlite.c deleted file mode 100755 index 124b3df2f..000000000 --- a/TMessagesProj/jni/sqlite.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "sqlite/sqlite3.h" -#include "sqlite.h" - -void throw_sqlite3_exception(JNIEnv *env, sqlite3 *handle, int errcode) { - if (SQLITE_OK == errcode) { - errcode = sqlite3_errcode(handle); - } - const char *errmsg = sqlite3_errmsg(handle); - jclass exClass = (*env)->FindClass(env, "org/telegram/SQLite/SQLiteException"); - (*env)->ThrowNew(env, exClass, errmsg); -} diff --git a/TMessagesProj/jni/sqlite.h b/TMessagesProj/jni/sqlite.h deleted file mode 100755 index af19fcb85..000000000 --- a/TMessagesProj/jni/sqlite.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef sqlite_h -#define sqlite_h - -#include -#include "sqlite/sqlite3.h" - -void throw_sqlite3_exception(JNIEnv* env, sqlite3 *handle, int errcode); -jint sqliteOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env); - -#endif diff --git a/TMessagesProj/jni/sqlite/sqlite3.c b/TMessagesProj/jni/sqlite/sqlite3.c index 486eefee4..e19867890 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.c +++ b/TMessagesProj/jni/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.9.2. By combining all the individual C code files into this +** version 3.13.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 @@ -17,7 +17,6 @@ ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. */ -#define SQLITE_THREADSAFE 0 #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE @@ -38,9 +37,44 @@ ** Internal interface definitions for SQLite. ** */ -#ifndef _SQLITEINT_H_ +#ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ +/* Special Comments: +** +** Some comments have special meaning to the tools that measure test +** coverage: +** +** NO_TEST - The branches on this line are not +** measured by branch coverage. This is +** used on lines of code that actually +** implement parts of coverage testing. +** +** OPTIMIZATION-IF-TRUE - This branch is allowed to alway be false +** and the correct answer is still obtained, +** though perhaps more slowly. +** +** OPTIMIZATION-IF-FALSE - This branch is allowed to alway be true +** and the correct answer is still obtained, +** though perhaps more slowly. +** +** PREVENTS-HARMLESS-OVERREAD - This branch prevents a buffer overread +** that would be harmless and undetectable +** if it did occur. +** +** In all cases, the special comment must be enclosed in the usual +** slash-asterisk...asterisk-slash comment marks, with no spaces between the +** asterisks and the comment text. +*/ + +/* +** 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 @@ -122,6 +156,9 @@ #else /* This is not VxWorks. */ #define OS_VXWORKS 0 +#define HAVE_FCHOWN 1 +#define HAVE_READLINK 1 +#define HAVE_LSTAT 1 #endif /* defined(_WRS_KERNEL) */ /************** End of vxworks.h *********************************************/ @@ -326,9 +363,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.9.2" -#define SQLITE_VERSION_NUMBER 3009002 -#define SQLITE_SOURCE_ID "2015-11-02 18:31:45 bda77dda9697c463c3d0704014d51627fceee328" +#define SQLITE_VERSION "3.13.0" +#define SQLITE_VERSION_NUMBER 3013000 +#define SQLITE_SOURCE_ID "2016-05-18 10:57:30 fc49f556e48970561d7ab6a2f24fdd7d9eb81ff2" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -562,7 +599,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** from [sqlite3_malloc()] and passed back through the 5th parameter. ** To avoid memory leaks, the application should invoke [sqlite3_free()] ** on error message strings returned through the 5th parameter of -** of sqlite3_exec() after the error message string is no longer needed. +** 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 ** NULL before returning. @@ -693,6 +730,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_exec( #define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) #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_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) @@ -1008,8 +1046,13 @@ struct sqlite3_io_methods { **
  • [[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 -** connection. See the [sqlite3_file_control()] documentation for -** additional information. +** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER]. +** +**
  • [[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 +** the [rollback journal] or the [write-ahead log]) for a particular database +** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** **
  • [[SQLITE_FCNTL_SYNC_OMITTED]] ** No longer in use. @@ -1096,6 +1139,15 @@ struct sqlite3_io_methods { ** pointer in case this file-control is not implemented. This file-control ** is intended for diagnostic use only. ** +**
  • [[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 +** 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. +** **
  • [[SQLITE_FCNTL_PRAGMA]] ** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding @@ -1214,6 +1266,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_WAL_BLOCK 24 #define SQLITE_FCNTL_ZIPVFS 25 #define SQLITE_FCNTL_RBU 26 +#define SQLITE_FCNTL_VFS_POINTER 27 +#define SQLITE_FCNTL_JOURNAL_POINTER 28 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1426,7 +1480,7 @@ struct sqlite3_vfs { const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. - ** New fields may be appended in figure versions. The iVersion + ** New fields may be appended in future versions. The iVersion ** value will increment whenever this happens. */ }; @@ -1813,29 +1867,34 @@ struct sqlite3_mem_methods { ** ** ** [[SQLITE_CONFIG_PAGECACHE]]
    SQLITE_CONFIG_PAGECACHE
    -**
    ^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +**
    ^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 should not be used if an application-define page -** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] -** configuration option. +** This configuration option is a no-op if an application-define 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, the size of each page buffer (sz), and the number of pages (N). +** 8-byte aligned memory (pMem), the size of each page cache line (sz), +** and the number of cache lines (N). ** The sz argument should be the size of the largest database page ** (a power of two between 512 and 65536) plus some extra bytes for each ** page header. ^The number of extra bytes needed by the page header -** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option -** to [sqlite3_config()]. +** can be determined using [SQLITE_CONFIG_PCACHE_HDRSZ]. ** ^It is harmless, apart from the wasted memory, -** for the sz parameter to be larger than necessary. The first -** argument should pointer to an 8-byte aligned block of memory that -** is at least sz*N bytes of memory, otherwise subsequent behavior is -** undefined. -** ^SQLite will use the memory provided by the first argument to satisfy its -** memory needs for the first N pages that it adds to cache. ^If additional -** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space.
    +** for the sz parameter to be larger than necessary. The pMem +** argument must be either a NULL pointer or a pointer to an 8-byte +** 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 +** 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 +** 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 +** additional cache line. ** ** [[SQLITE_CONFIG_HEAP]]
    SQLITE_CONFIG_HEAP
    **
    ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer @@ -2013,6 +2072,20 @@ struct sqlite3_mem_methods { ** is enabled (using the [PRAGMA threads] command) and the amount of content ** to be sorted exceeds the page size times the minimum of the ** [PRAGMA cache_size] setting and this value. +** +** [[SQLITE_CONFIG_STMTJRNL_SPILL]] +**
    SQLITE_CONFIG_STMTJRNL_SPILL +**
    ^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which +** becomes the [statement journal] spill-to-disk threshold. +** [Statement journals] are held in memory until their size (in bytes) +** exceeds this threshold, at which point they are written to disk. +** Or if the threshold is -1, statement journals are always held +** exclusively in memory. +** Since many statement journals never become large, setting the spill +** threshold to a value such as 64KiB can greatly reduce the amount of +** I/O required to support statement rollback. +** The default value for this setting is controlled by the +** [SQLITE_STMTJRNL_SPILL] compile-time option. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -2040,6 +2113,7 @@ struct sqlite3_mem_methods { #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 */ /* ** CAPI3REF: Database Connection Configuration Options @@ -2097,11 +2171,43 @@ 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.
    ** +**
    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
    +**
    ^This option is used to enable or disable the two-argument +** version of 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 +** positive to enable fts3_tokenizer() 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 fts3_tokenizer 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.
    +** +**
    SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
    +**
    ^This option is used to enable or disable the [sqlite3_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()]. +** 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 argment to +** this interface is 0, then both the C-API and the SQL function are disabled. +** 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 disabled or enabled following this call. The second parameter may +** be a NULL pointer, in which case the new setting is not reported back. +**
    +** ** */ -#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ -#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ -#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ +#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ +#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* */ /* @@ -4604,8 +4710,8 @@ SQLITE_API unsigned int SQLITE_STDCALL sqlite3_value_subtype(sqlite3_value*); ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer ** then sqlite3_value_free(V) is a harmless no-op. */ -SQLITE_API SQLITE_EXPERIMENTAL sqlite3_value *SQLITE_STDCALL sqlite3_value_dup(const sqlite3_value*); -SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_value_free(sqlite3_value*); +SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_value_dup(const sqlite3_value*); +SQLITE_API void SQLITE_STDCALL sqlite3_value_free(sqlite3_value*); /* ** CAPI3REF: Obtain Aggregate Function Context @@ -5351,7 +5457,7 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), ** ^The sqlite3_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. +** a [rowid table]. ** ^Any callback set by a previous call to this function ** for the same database connection is overridden. ** @@ -5390,8 +5496,8 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), ** on the same [database connection] D, or NULL for ** the first call on D. ** -** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] -** interfaces. +** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()], +** and [sqlite3_preupdate_hook()] interfaces. */ SQLITE_API void *SQLITE_STDCALL sqlite3_update_hook( sqlite3*, @@ -5638,9 +5744,18 @@ SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( ** should free this memory by calling [sqlite3_free()]. ** ** ^Extension loading must be enabled using -** [sqlite3_enable_load_extension()] prior to calling this API, +** [sqlite3_enable_load_extension()] or +** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL) +** prior to calling this API, ** otherwise an error will be returned. ** +** Security warning: 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 +** 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 SQLITE_STDCALL sqlite3_load_extension( @@ -5663,6 +5778,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_load_extension( ** ^Call the sqlite3_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],..) +** to enable or disable only the C-API. +** +** Security warning: It is recommended that extension loading +** be disabled 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 SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff); @@ -5824,6 +5950,17 @@ struct sqlite3_module { ** ^Information about the ORDER BY clause is stored in aOrderBy[]. ** ^Each term of aOrderBy records a column of the ORDER BY clause. ** +** 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), +** 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 +** non-zero. +** ** The [xBestIndex] method must fill aConstraintUsage[] with information ** about what parameters to pass to xFilter. ^If argvIndex>0 then ** the right-hand side of the corresponding aConstraint[] is evaluated @@ -5879,7 +6016,7 @@ struct sqlite3_index_info { /* Inputs */ int nConstraint; /* Number of entries in aConstraint */ struct sqlite3_index_constraint { - int iColumn; /* Column on left-hand side of 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 */ @@ -5903,6 +6040,8 @@ struct sqlite3_index_info { sqlite3_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 */ }; /* @@ -5918,12 +6057,15 @@ struct sqlite3_index_info { ** 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_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 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -6787,7 +6929,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_status64( ** The value written into the *pCurrent parameter is undefined.)^ ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
    SQLITE_STATUS_PARSER_STACK
    -**
    This parameter records the deepest parser stack. It is only +**
    The *pHighwater parameter records the deepest parser stack. +** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
    )^ ** ** @@ -7284,7 +7427,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** ^A call to sqlite3_backup_init() will fail, returning NULL, if ** there is already a read or read-write transaction open on the ** destination database. ** @@ -7573,18 +7716,43 @@ SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *, const char *, int); /* ** CAPI3REF: String Globbing * -** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches -** the glob pattern P, and it returns non-zero if string X does not match -** the glob pattern P. ^The definition of glob pattern matching used in +** ^The [sqlite3_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 used by SQLite. ^The sqlite3_strglob(P,X) function is case -** sensitive. +** SQL dialect understood by SQLite. ^The [sqlite3_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()]. +** +** See also: [sqlite3_strlike()]. */ SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlob, const char *zStr); +/* +** CAPI3REF: String LIKE Matching +* +** ^The [sqlite3_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" +** 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 +** insensitive - equivalent upper and lower case ASCII characters match +** one another. +** +** ^The [sqlite3_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()]. +** +** See also: [sqlite3_strglob()]. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc); + /* ** CAPI3REF: Error Logging Interface ** @@ -7640,7 +7808,7 @@ SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...) ** 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 -** those overwrite any prior [sqlite3_wal_hook()] settings. +** overwrite any prior [sqlite3_wal_hook()] settings. */ SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook( sqlite3*, @@ -8005,6 +8173,277 @@ SQLITE_API int SQLITE_STDCALL sqlite3_stmt_scanstatus( */ SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_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 +** 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)] +** interface flushes caches for all schemas - "main", "temp", and +** any [attached] databases. +** +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained +** immediately and there is a busy-handler callback configured, it is invoked +** in the usual manner. ^If the required lock still cannot be obtained, then +** the database is skipped and an attempt made to flush any dirty pages +** belonging to the next (if any) database. ^If any databases are skipped +** because locks cannot be obtained, but no other error occurs, this +** function returns SQLITE_BUSY. +** +** ^If any other error occurs while flushing dirty pages to disk (for +** 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. +** +** ^This function does not set the database handle error code or message +** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_db_cacheflush(sqlite3*); + +/* +** CAPI3REF: The pre-update hook. +** +** ^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 +** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation +** on a [rowid table]. +** ^At most one preupdate hook may be registered at a time on a single +** [database connection]; each call to [sqlite3_preupdate_hook()] overrides +** the previous setting. +** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] +** with a NULL pointer as the second parameter. +** ^The third parameter to [sqlite3_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 second parameter to the preupdate callback is a pointer to +** the [database connection] that registered the preupdate hook. +** ^The third parameter to the preupdate callback is one of the constants +** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to indentify the +** kind of update operation that is about to occur. +** ^(The fourth parameter to the preupdate callback is the name of the +** database within the database connection that is being modified. This +** will be "main" for the main database or "temp" for TEMP tables or +** the name given after the AS keyword in the [ATTACH] statement for attached +** 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 +** 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 +** [database connection] pointer that is different from the one supplied +** to the preupdate callback results in undefined and probably undesirable +** behavior. +** +** ^The [sqlite3_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 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 +** 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 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 +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_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()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL void *SQLITE_STDCALL sqlite3_preupdate_hook( + sqlite3 *db, + void(*xPreUpdate)( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *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) */ + ), + void* +); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_count(sqlite3 *); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); + +/* +** CAPI3REF: Low-level system error code +** +** ^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 +** called to get back the underlying "errno" that caused the problem, such +** as ENOSPC, EAUTH, EISDIR, and so forth. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_system_errno(sqlite3*); + +/* +** CAPI3REF: Database Snapshot +** KEYWORDS: {snapshot} +** EXPERIMENTAL +** +** An instance of the snapshot object records the state of a [WAL mode] +** database for some specific point in history. +** +** In [WAL mode], multiple [database connections] that are open on the +** same database file can each be reading a different historical version +** of the database file. When a [database connection] begins a read +** transaction, that connection sees an unchanging copy of the database +** as it existed for the point in time when the transaction first started. +** 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 +** 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; + +/* +** CAPI3REF: Record A Database Snapshot +** EXPERIMENTAL +** +** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a +** new [sqlite3_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()] +** to avoid a memory leak. +** +** The [sqlite3_snapshot_get()] interface is only available when the +** SQLITE_ENABLE_SNAPSHOT compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_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 +** 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 +** I/O on that database connection, or if the database entered [WAL mode] +** after the most recent I/O on the database connection.)^ +** (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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); + +/* +** CAPI3REF: Destroy a snapshot +** EXPERIMENTAL +** +** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P. +** The application must eventually free every [sqlite3_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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_snapshot_free(sqlite3_snapshot*); + +/* +** CAPI3REF: Compare the ages of two snapshot handles. +** EXPERIMENTAL +** +** The sqlite3_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 +** 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 +** wal file was last deleted, the value returned by this function +** is undefined. +** +** 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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_cmp( + sqlite3_snapshot *p1, + sqlite3_snapshot *p2 +); /* ** Undo the hack that converts floating point types to integer for @@ -8019,6 +8458,7 @@ SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); #endif #endif /* _SQLITE3_H_ */ +/******** Begin file sqlite3rtree.h *********/ /* ** 2010 August 30 ** @@ -8136,6 +8576,1287 @@ struct sqlite3_rtree_query_info { #endif /* ifndef _SQLITE3RTREE_H_ */ +/******** End of sqlite3rtree.h *********/ +/******** Begin file sqlite3session.h *********/ + +#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) +#define __SQLITESESSION_H_ 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +extern "C" { +#endif + + +/* +** CAPI3REF: Session Object Handle +*/ +typedef struct sqlite3_session sqlite3_session; + +/* +** CAPI3REF: Changeset Iterator Handle +*/ +typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; + +/* +** CAPI3REF: Create A New Session Object +** +** 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 +** returned. If an error occurs, *ppSession is set to NULL and an SQLite +** error code (e.g. SQLITE_NOMEM) is returned. +** +** It is possible to create multiple session objects attached to a single +** database handle. +** +** Session objects created using this function should be deleted using the +** [sqlite3session_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 +** are undefined. +** +** Because the session module uses the [sqlite3_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 +** which a pre-update hook is already defined. The results of attempting +** either of these things are undefined. +** +** The session object will be used to create changesets for tables in +** database zDb, where zDb is either "main", or "temp", or the name of an +** 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 */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +); + +/* +** CAPI3REF: Delete A Session Object +** +** Delete a session object previously allocated using +** [sqlite3session_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. +*/ +void sqlite3session_delete(sqlite3_session *pSession); + + +/* +** CAPI3REF: Enable Or Disable A Session Object +** +** 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 +** details regarding how enabling and disabling a session object affects +** the eventual changesets. +** +** Passing zero to this function disables the session. Passing a value +** greater than zero enables it. Passing a value less than zero is a +** no-op, and may be used to query the current state of the session. +** +** 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); + +/* +** CAPI3REF: Set Or Clear the Indirect Change Flag +** +** Each change recorded by a session object is marked as either direct or +** indirect. A change is marked as indirect if either: +** +**
      +**
    • The session object "indirect" flag is set when the change is +** made, or +**
    • The change is made by an SQL trigger or foreign key action +** instead of directly as a result of a users SQL statement. +**
    +** +** If a single row is affected by more than one operation within a session, +** then the change is considered indirect if all operations meet the criteria +** for an indirect change above, or direct otherwise. +** +** This function is used to set, clear or query the session object indirect +** flag. If the second argument passed to this function is zero, then the +** indirect flag is cleared. If it is greater than zero, the indirect flag +** is set. Passing a value less than zero does not modify the current value +** of the indirect flag, and may be used to query the current state of the +** indirect flag for the specified session object. +** +** 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); + +/* +** CAPI3REF: Attach A Table To A Session Object +** +** 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. +** +** 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 +** executing "CREATE TABLE" statements) after this call is made, changes for +** the new tables are also recorded. +** +** Changes can only be recorded for tables that have a PRIMARY KEY explicitly +** defined as part of their CREATE TABLE statement. It does not matter if the +** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY +** KEY may consist of a single column, or may be a composite key. +** +** It is not an error if the named table does not exist in the database. Nor +** is it an error if the named table does not have a PRIMARY KEY. However, +** no changes will be recorded in either of these scenarios. +** +** Changes are not recorded for individual rows that have NULL values stored +** in one or more of their PRIMARY KEY columns. +** +** 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 */ + const char *zTab /* Table name */ +); + +/* +** CAPI3REF: Set a table filter on a Session Object. +** +** The second argument (xFilter) is the "filter callback". For changes to rows +** in tables that are not attached to the Session oject, 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 +** attached, xFilter will not be called again. +*/ +void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); + +/* +** CAPI3REF: Generate A Changeset From A Session Object +** +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset +** and *pnChangeset to the size of the changeset in bytes before returning +** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to +** zero and return an SQLite error code. +** +** A changeset consists of zero or more INSERT, UPDATE and/or DELETE changes, +** each representing a change to a single row of an attached table. An INSERT +** change contains the values of each field of a new database row. A DELETE +** contains the original values of each field of a deleted database row. An +** UPDATE change contains the original values of each field of an updated +** database row along with the updated values for each updated non-primary-key +** column. It is not possible for an UPDATE change to represent a change that +** modifies the values of primary key columns. If such a change is made, it +** is represented in a changeset as a DELETE followed by an INSERT. +** +** Changes are not recorded for rows that have NULL values stored in one or +** more of their PRIMARY KEY columns. If such a row is inserted or deleted, +** no corresponding change is present in the changesets returned by this +** function. If an existing row with one or more NULL values stored in +** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL, +** only an INSERT is appears in the changeset. Similarly, if an existing row +** with non-NULL PRIMARY KEY values is updated so that one or more of its +** PRIMARY KEY columns are set to NULL, the resulting changeset contains a +** 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()] +** API. +** +** Within a changeset generated by this function, all changes related to a +** single table are grouped together. In other words, when iterating through +** 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 +** 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()]. +** +**

    Changeset Generation

    +** +** Once a table has been attached to a session object, the session object +** records the primary key values of all new rows inserted into the table. +** It also records the original primary key and other column values of any +** deleted or updated rows. For each unique primary key value, data is only +** recorded once - the first time a row with said primary key is inserted, +** updated or deleted in the lifetime of the session. +** +** There is one exception to the previous paragraph: when a row is inserted, +** updated or deleted, if one or more of its primary key columns contain a +** NULL value, no record of the change is made. +** +** The session object therefore accumulates two types of records - those +** that consist of primary key values only (created when the user inserts +** a new record) and those that consist of the primary key values and the +** original values of other table columns (created when the users deletes +** or updates a record). +** +** When this function is called, the requested changeset is created using +** both the accumulated records and the current contents of the database +** file. Specifically: +** +**
      +**
    • For each record generated by an insert, the database is queried +** for a row with a matching primary key. If one is found, an INSERT +** change is added to the changeset. If no such row is found, no change +** is added to the changeset. +** +**
    • For each record generated by an update or delete, the database is +** queried for a row with a matching primary key. If such a row is +** found and one or more of the non-primary key fields have been +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE +** change is added to the changeset. If there is a row with a matching +** primary key in the database, but all fields contain their original +** values, no change is added to the changeset. +**
    +** +** This means, amongst other things, that if a row is inserted and then later +** deleted while a session object is active, neither the insert nor the delete +** will be present in the changeset. Or if a row is deleted and then later a +** row with the same primary key values inserted while a session object is +** 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), +** 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 +** is inserted while a session object is enabled, then later deleted while +** the same session object is disabled, no INSERT record will appear in the +** changeset, even though the delete took place while the session was disabled. +** Or, if one field of a row is updated while a session is disabled, and +** 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 *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +); + +/* +** CAPI3REF: Load The Difference Between Tables Into A 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 +** does not have a primary key, this function is a no-op (but does not return +** an error). +** +** Argument zFromDb must be the name of a database ("main", "temp" etc.) +** attached to the same database handle as the session object that contains +** a table compatible with the table attached to the session by this function. +** A table is considered compatible if it: +** +**
      +**
    • Has the same name, +**
    • Has the same set of columns declared in the same order, and +**
    • Has the same PRIMARY KEY definition. +**
    +** +** If the tables are not compatible, SQLITE_SCHEMA is returned. If the tables +** are compatible but do not have any PRIMARY KEY columns, it is not an error +** but no changes are added to the session object. As with other session +** APIs, tables without PRIMARY KEYs are simply ignored. +** +** This function adds a set of changes to the session object that could be +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session +** object (call this the "to-table"). Specifically: +** +**
      +**
    • For each row (primary key) that exists in the to-table but not in +** the from-table, an INSERT record is added to the session object. +** +**
    • For each row (primary key) that exists in the to-table but not in +** the from-table, a DELETE record is added to the session object. +** +**
    • For each row (primary key) that exists in both tables, but features +** different in each, an UPDATE record is added to the session. +**
    +** +** To clarify, if this function is called and then a changeset constructed +** using [sqlite3session_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 +** 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(). +*/ +int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFromDb, + const char *zTbl, + char **pzErrMsg +); + + +/* +** CAPI3REF: Generate A Patchset From A Session Object +** +** The differences between a patchset and a changeset are that: +** +**
      +**
    • DELETE records consist of the primary key fields only. The +** original values of other fields are omitted. +**
    • The original values of any modified fields are omitted from +** UPDATE records. +**
    +** +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_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. +** +** 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 +** 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 +** 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 */ +); + +/* +** CAPI3REF: Test if a changeset has recorded any changes. +** +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or +** 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 +** 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 +** changeset containing zero changes. +*/ +int sqlite3session_isempty(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset +** +** 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 +** is returned. Otherwise, if an error occurs, *pp is set to zero and an +** SQLite error code is returned. +** +** The following functions can be used to advance and query a changeset +** iterator created by this function: +** +**
      +**
    • [sqlite3changeset_next()] +**
    • [sqlite3changeset_op()] +**
    • [sqlite3changeset_new()] +**
    • [sqlite3changeset_old()] +**
    +** +** It is the responsibility of the caller to eventually destroy the iterator +** by passing it to [sqlite3changeset_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 +** 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 visted +** 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. +*/ +int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset /* Pointer to blob containing changeset */ +); + + +/* +** CAPI3REF: Advance A Changeset Iterator +** +** 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 +** is returned and the call has no effect. +** +** Immediately after an iterator is created by sqlite3changeset_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. +** Otherwise, if all changes in the changeset have already been visited, +** SQLITE_DONE is returned. +** +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** SQLITE_NOMEM. +*/ +int sqlite3changeset_next(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Obtain The Current Operation From A Changeset Iterator +** +** 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 +** 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 +** 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 +** is an indirect change, or false (0) otherwise. See the documentation for +** [sqlite3session_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. +** +** If no error occurs, SQLITE_OK is returned. If an error does occur, an +** 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 */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ +); + +/* +** CAPI3REF: Obtain The Primary Key Definition Of A Table +** +** For each modified table, a changeset includes the following: +** +**
      +**
    • The number of columns in the table, and +**
    • Which of those columns make up the tables PRIMARY KEY. +**
    +** +** This function is used to find which columns comprise the PRIMARY KEY of +** the table modified by the change that iterator pIter currently points to. +** If successful, *pabPK is set to point to an array of nCol entries, where +** nCol is the number of columns in the table. Elements of *pabPK are set to +** 0x01 if the corresponding column is part of the tables primary key, or +** 0x00 if it is not. +** +** If argumet pnCol is not NULL, then *pnCol is set to the number of columns +** in the table. +** +** If this function is called when the iterator does not point to a valid +** entry, SQLITE_MISUSE is returned and the output variables zeroed. Otherwise, +** SQLITE_OK is returned and the output variables populated as described +** above. +*/ +int sqlite3changeset_pk( + sqlite3_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 +** +** 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. +** 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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** 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. +** +** 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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain new.* Values From A Changeset Iterator +** +** 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. +** 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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** 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 +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete +** triggers. +** +** 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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator +** +** This function should only be used with iterator objects passed to a +** conflict-handler callback by [sqlite3changeset_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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** "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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +); + +/* +** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations +** +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +); + + +/* +** CAPI3REF: Finalize A Changeset Iterator +** +** This function is used to finalize an iterator allocated with +** [sqlite3changeset_start()]. +** +** This function should only be called on iterators created using the +** [sqlite3changeset_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 +** 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 +** 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() ){ +** // Do something with change. +** } +** rc = sqlite3changeset_finalize(); +** if( rc!=SQLITE_OK ){ +** // An error has occurred +** } +*/ +int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Invert A Changeset +** +** This function is used to "invert" a changeset object. Applying an inverted +** changeset to a database reverses the effects of applying the uninverted +** changeset. Specifically: +** +**
      +**
    • Each DELETE change is changed to an INSERT, and +**
    • Each INSERT change is changed to a DELETE, and +**
    • For each UPDATE change, the old.* and new.* values are exchanged. +**
    +** +** This function does not change the order in which changes appear within +** the changeset. It merely reverses the sense of each individual change. +** +** If successful, a pointer to a buffer containing the inverted changeset +** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and +** 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() +** 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 nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); + +/* +** CAPI3REF: Concatenate Two Changeset Objects +** +** This function is used to concatenate two changesets, A and B, into a +** single changeset. The result is a changeset equivalent to applying +** 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 +** 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); +** if( rc==SQLITE_OK ){ +** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); +** }else{ +** *ppOut = 0; +** *pnOut = 0; +** } +** +** Refer to the sqlite3_changegroup documentation below for details. +*/ +int sqlite3changeset_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 */ + void *pB, /* Pointer to buffer containing changeset B */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Buffer containing output changeset */ +); + + +/* +** Changegroup handle. +*/ +typedef struct sqlite3_changegroup sqlite3_changegroup; + +/* +** CAPI3REF: Combine two or more changesets into a single changeset. +** +** An sqlite3_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 +** should eventually free the returned object using a call to +** sqlite3changegroup_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: +** +**
      +**
    • It is created using a call to sqlite3changegroup_new(). +** +**
    • Zero or more changesets (or patchsets) are added to the object +** by calling sqlite3changegroup_add(). +** +**
    • The result of combining all input changesets together is obtained +** by the application via a call to sqlite3changegroup_output(). +** +**
    • The object is deleted using a call to sqlite3changegroup_delete(). +**
    +** +** 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(). +*/ +int sqlite3changegroup_new(sqlite3_changegroup **pp); + +/* +** Add all changes within the changeset (or patchset) in buffer pData (size +** nData bytes) to the changegroup. +** +** If the buffer contains a patchset, then all prior calls to this function +** on the same changegroup object must also have specified patchsets. Or, if +** the buffer contains a changeset, so must have the earlier calls to this +** function. Otherwise, SQLITE_ERROR is returned and no changes are added +** to the changegroup. +** +** Rows within the changeset and changegroup are identified by the values in +** their PRIMARY KEY columns. A change in the changeset is considered to +** apply to the same row as a change already present in the changegroup if +** the two rows have the same primary key. +** +** Changes to rows that that do not already appear in the changegroup are +** simply copied into it. Or, if both the new changeset and the changegroup +** contain changes that apply to a single row, the final contents of the +** changegroup depends on the type of each change, as follows: +** +** +** +** +**
    Existing Change New Change Output Change +**
    INSERT INSERT +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    INSERT UPDATE +** The INSERT change remains in the changegroup. The values in the +** INSERT change are modified as if the row was inserted by the +** existing change and then updated according to the new change. +**
    INSERT DELETE +** The existing INSERT is removed from the changegroup. The DELETE is +** not added. +**
    UPDATE INSERT +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    UPDATE UPDATE +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once +** by the existing change and then again by the new change. +**
    UPDATE DELETE +** The existing UPDATE is replaced by the new DELETE within the +** changegroup. +**
    DELETE INSERT +** If one or more of the column values in the row inserted by the +** new change differ from those in the row deleted by the existing +** change, the existing DELETE is replaced by an UPDATE within the +** changegroup. Otherwise, if the inserted row is exactly the same +** as the deleted row, the existing DELETE is simply discarded. +**
    DELETE UPDATE +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    DELETE DELETE +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    +** +** If the new changeset contains changes to a table that is already present +** in the changegroup, then the number of columns and the position of the +** primary key columns for the table must be consistent. If this is not the +** 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. +** +** If no error occurs, SQLITE_OK is returned. +*/ +int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** 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 +** 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 +** changes for tables that do not appear in the first changeset, they are +** appended onto the end of the output changeset, again in the order in +** which they are first encountered. +** +** If an error occurs, an SQLite error code is returned and the output +** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK +** 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(). +*/ +int sqlite3changegroup_output( + sqlite3_changegroup*, + int *pnData, /* OUT: Size of output buffer in bytes */ + void **ppData /* OUT: Pointer to output buffer */ +); + +/* +** Delete a changegroup object. +*/ +void sqlite3changegroup_delete(sqlite3_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. +** +** The fourth argument (xFilter) passed to this function 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. +** +** 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 +** considered compatible if all of the following are true: +** +**
      +**
    • The table has the same name as the name recorded in the +** changeset, and +**
    • The table has the same number of columns as recorded in the +** changeset, and +**
    • The table has primary key columns in the same position as +** recorded in the changeset. +**
    +** +** 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 +** 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 +** invoked. A description of exactly when the conflict handler is invoked for +** each type of change is below. +** +** Unlike the xFilter argument, xConflict may not be passed NULL. The results +** of passing anything other than a valid function pointer as the xConflict +** argument are undefined. +** +** Each time the conflict handler function is invoked, it must return one +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned +** 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 +** returned by each invocation of the conflict-handler function. Refer to +** the documentation for the three +** [SQLITE_CHANGESET_OMIT|available return values] for details. +** +**
    +**
    DELETE Changes
    +** For each DELETE change, this 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 deleted from 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 the original +** row value stored in the changeset, the conflict-handler function is +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the DELETE operation is attempted, but SQLite returns SQLITE_CONSTRAINT +** (which can only happen if a foreign key constraint is violated), the +** conflict-handler function is invoked with [SQLITE_CHANGESET_CONSTRAINT] +** passed as the second argument. This includes the case where the DELETE +** operation is attempted because an earlier call to the conflict handler +** function returned [SQLITE_CHANGESET_REPLACE]. +** +**
    INSERT Changes
    +** For each INSERT change, an attempt is made to insert the new row into +** the database. +** +** If the attempt to insert the row fails because the database already +** contains a row with the same primary key values, the conflict handler +** function is invoked with the second argument set to +** [SQLITE_CHANGESET_CONFLICT]. +** +** If the attempt to insert the row fails because of some other constraint +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** +**
    UPDATE Changes
    +** For each UPDATE change, this 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. +** +** 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 +** 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. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. +** This includes the case where the UPDATE operation is attempted after +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +**
    +** +** 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 +** resolution strategy. +** +** All changes made by this function 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 */ + 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 */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); + +/* +** CAPI3REF: Constants Passed To The Conflict Handler +** +** Values that may be passed as the second argument to a conflict-handler. +** +**
    +**
    SQLITE_CHANGESET_DATA
    +** The conflict handler is invoked with CHANGESET_DATA as the second argument +** when processing a DELETE or UPDATE change if a row with the required +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the +** expected "before" values. +** +** The conflicting row, in this case, is the database row with the matching +** primary key. +** +**
    SQLITE_CHANGESET_NOTFOUND
    +** The conflict handler is invoked with CHANGESET_NOTFOUND as the second +** argument when processing a DELETE or UPDATE change if a row with the +** 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. +** +**
    SQLITE_CHANGESET_CONFLICT
    +** CHANGESET_CONFLICT is passed as the second argument to the conflict +** handler while processing an INSERT change if the operation would result +** in duplicate primary key values. +** +** The conflicting row in this case is the database row with the matching +** primary key. +** +**
    SQLITE_CHANGESET_FOREIGN_KEY
    +** If foreign key handling is enabled, and applying a changeset leaves the +** database in a state containing foreign key violations, the conflict +** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument +** exactly once before the changeset is committed. If the conflict handler +** returns CHANGESET_OMIT, the changes, including those that caused the +** foreign key constraint violation, are committed. Or, if it returns +** 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(). +** +**
    SQLITE_CHANGESET_CONSTRAINT
    +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** 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. +** +**
    +*/ +#define SQLITE_CHANGESET_DATA 1 +#define SQLITE_CHANGESET_NOTFOUND 2 +#define SQLITE_CHANGESET_CONFLICT 3 +#define SQLITE_CHANGESET_CONSTRAINT 4 +#define SQLITE_CHANGESET_FOREIGN_KEY 5 + +/* +** CAPI3REF: Constants Returned By The Conflict Handler +** +** A conflict handler callback must return one of the following three values. +** +**
    +**
    SQLITE_CHANGESET_OMIT
    +** If a conflict handler returns this value no special action is taken. The +** change that caused the conflict is not applied. The session module +** continues to the next change in the changeset. +** +**
    SQLITE_CHANGESET_REPLACE
    +** 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. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict +** handler, then the conflicting row is either updated or deleted, depending +** on the type of change. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict +** handler, then the conflicting row is removed from the database and a +** second attempt to apply the change is made. If this second attempt fails, +** the original row is restored to the database before continuing. +** +**
    SQLITE_CHANGESET_ABORT
    +** If this value is returned, any changes applied so far are rolled back +** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. +**
    +*/ +#define SQLITE_CHANGESET_OMIT 0 +#define SQLITE_CHANGESET_REPLACE 1 +#define SQLITE_CHANGESET_ABORT 2 + +/* +** CAPI3REF: Streaming Versions of API functions. +** +** The six streaming API xxx_strm() functions serve similar purposes to the +** corresponding non-streaming API functions: +** +** +** +**
    Streaming functionNon-streaming equivalent
    sqlite3changeset_apply_str[sqlite3changeset_apply] +**
    sqlite3changeset_concat_str[sqlite3changeset_concat] +**
    sqlite3changeset_invert_str[sqlite3changeset_invert] +**
    sqlite3changeset_start_str[sqlite3changeset_start] +**
    sqlite3session_changeset_str[sqlite3session_changeset] +**
    sqlite3session_patchset_str[sqlite3session_patchset] +**
    +** +** 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(). +** 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. +** +** In order to avoid this problem, instead of a single large buffer, input +** is passed to a streaming API functions by way of a callback function that +** the sessions module invokes to incrementally request input data as it is +** required. In all cases, a pair of API function parameters such as +** +**
    +**        int nChangeset,
    +**        void *pChangeset,
    +**  
    +** +** Is replaced by: +** +**
    +**        int (*xInput)(void *pIn, void *pData, int *pnData),
    +**        void *pIn,
    +**  
    +** +** Each time the xInput callback is invoked by the sessions module, the first +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** error code should be returned. In all cases, if an xInput callback returns +** 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 +** 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 +** immediately fail with the same error code as returned by xInput. +** +** Similarly, streaming API functions that return changesets (or patchsets) +** return them in chunks by way of a callback function instead of via a +** pointer to a single large buffer. In this case, a pair of parameters such +** as: +** +**
    +**        int *pnChangeset,
    +**        void **ppChangeset,
    +**  
    +** +** Is replaced by: +** +**
    +**        int (*xOutput)(void *pOut, const void *pData, int nData),
    +**        void *pOut
    +**  
    +** +** The xOutput callback is invoked zero or more times to return data to +** the application. The first parameter passed to each call is a copy of the +** pOut pointer supplied by the application. The second parameter, pData, +** points to a buffer nData bytes in size containing the chunk of output +** data being returned. If the xOutput callback successfully processes the +** supplied data, it should return SQLITE_OK to indicate success. Otherwise, +** it should return some other SQLite error code. In this case processing +** is immediately abandoned and the streaming API function returns a copy +** of the xOutput error code to the application. +** +** The sessions module never invokes an xOutput callback with the third +** 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 (*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 */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3changeset_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 (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3changegroup_add_strm(sqlite3_changegroup*, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +int sqlite3changegroup_output_strm(sqlite3_changegroup*, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); + + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +} +#endif + +#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ + +/******** End of sqlite3session.h *********/ +/******** Begin file fts5.h *********/ /* ** 2014 May 31 ** @@ -8221,6 +9942,9 @@ struct Fts5PhraseIter { ** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** +** This function may be quite inefficient if used with an FTS5 table +** created with the "columnsize=0" option. +** ** xColumnText: ** This function attempts to retrieve the text of column iCol of the ** current document. If successful, (*pz) is set to point to a buffer @@ -8241,15 +9965,29 @@ struct Fts5PhraseIter { ** the query within the current row. Return 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. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always returns 0. +** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value ** output by xInstCount(). ** +** 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. ** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** ** xRowid: ** Returns the rowid of the current row. ** @@ -8263,11 +10001,13 @@ struct Fts5PhraseIter { ** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid ** ** with $p set to a phrase equivalent to the phrase iPhrase of the -** current query is executed. For each row visited, the callback function -** passed as the fourth argument is invoked. The context and API objects -** passed to the callback function may be used to access the properties of -** each matched row. Invoking Api.xUserData() returns a copy of the pointer -** passed as the third argument to pUserData. +** current query is executed. Any column filter that applies to +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback +** function may be used to access the properties of each matched row. +** Invoking Api.xUserData() returns a copy of the pointer passed as +** the third argument to pUserData. ** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. @@ -8333,7 +10073,7 @@ struct Fts5PhraseIter { ** Fts5PhraseIter iter; ** int iCol, iOff; ** for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); -** iOff>=0; +** iCol>=0; ** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) ** ){ ** // An instance of phrase iPhrase at offset iOff of column iCol @@ -8341,13 +10081,51 @@ struct Fts5PhraseIter { ** ** The Fts5PhraseIter structure is defined above. Applications should not ** modify this structure directly - it should only be used as shown above -** with the xPhraseFirst() and xPhraseNext() API methods. +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** ** xPhraseNext() ** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 1 */ + int iVersion; /* Currently always set to 3 */ void *(*xUserData)(Fts5Context*); @@ -8377,8 +10155,11 @@ struct Fts5ExtensionApi { int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); void *(*xGetAuxdata)(Fts5Context*, int bClear); - void (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); }; /* @@ -8656,6 +10437,7 @@ struct fts5_api { #endif /* _FTS5_H */ +/******** End of fts5.h *********/ /************** End of sqlite3.h *********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -8773,13 +10555,13 @@ struct fts5_api { ** The suggested maximum number of in-memory pages to use for ** the main database table and for temporary tables. ** -** IMPLEMENTATION-OF: R-31093-59126 The default suggested cache size -** is 2000 pages. +** IMPLEMENTATION-OF: R-30185-15359 The default suggested cache size is -2000, +** which means the cache size is limited to 2048000 bytes of memory. ** IMPLEMENTATION-OF: R-48205-43578 The default suggested cache size can be ** altered using the SQLITE_DEFAULT_CACHE_SIZE compile-time options. */ #ifndef SQLITE_DEFAULT_CACHE_SIZE -# define SQLITE_DEFAULT_CACHE_SIZE 2000 +# define SQLITE_DEFAULT_CACHE_SIZE -2000 #endif /* @@ -8792,8 +10574,9 @@ struct fts5_api { /* ** The maximum number of attached databases. This must be between 0 -** and 62. The upper bound on 62 is because a 64-bit integer bitmap -** is used internally to track attached databases. +** and 125. The upper bound of 125 is because the attached databases are +** counted using a signed 8-bit integer which has a maximum value of 127 +** and we have to allow 2 extra counts for the "main" and "temp" databases. */ #ifndef SQLITE_MAX_ATTACHED # define SQLITE_MAX_ATTACHED 10 @@ -8828,7 +10611,7 @@ struct fts5_api { ** The default size of a database page. */ #ifndef SQLITE_DEFAULT_PAGE_SIZE -# define SQLITE_DEFAULT_PAGE_SIZE 1024 +# define SQLITE_DEFAULT_PAGE_SIZE 4096 #endif #if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE # undef SQLITE_DEFAULT_PAGE_SIZE @@ -8909,7 +10692,7 @@ struct fts5_api { ** to the next, so we have developed the following set of #if statements ** to generate appropriate macros for a wide range of compilers. ** -** The correct "ANSI" way to do this is to use the intptr_t type. +** The correct "ANSI" way to do this is to use the intptr_t type. ** Unfortunately, that typedef is not available on all compilers, or ** if it is available, it requires an #include of specific headers ** that vary from one machine to the next. @@ -9061,7 +10844,7 @@ struct fts5_api { ** is set. Thus NDEBUG becomes an opt-in rather than an opt-out ** feature. */ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif #if defined(NDEBUG) && defined(SQLITE_DEBUG) @@ -9076,7 +10859,7 @@ struct fts5_api { #endif /* -** The testcase() macro is used to aid in coverage testing. When +** The testcase() macro is used to aid in coverage testing. When ** doing coverage testing, the condition inside the argument to ** testcase() must be evaluated both true and false in order to ** get full branch coverage. The testcase() macro is inserted @@ -9122,7 +10905,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int); #endif /* -** The ALWAYS and NEVER macros surround boolean expressions which +** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such ** expressions could be omitted from the code completely. But they ** are included in a few cases in order to enhance the resilience @@ -9136,7 +10919,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int); ** be true and false so that the unreachable code they specify will ** not be counted as untested code. */ -#if defined(SQLITE_COVERAGE_TEST) +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) @@ -9147,6 +10930,21 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # define NEVER(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 +** during a normal build. The following macro can be used to disable tests +** that are always false except when SQLITE_TEST_REALLOC_STRESS is set. +*/ +#if defined(SQLITE_TEST_REALLOC_STRESS) +# define ONLY_IF_REALLOC_STRESS(X) (X) +#elif !defined(NDEBUG) +# define ONLY_IF_REALLOC_STRESS(X) ((X)?(assert(0),1):0) +#else +# define ONLY_IF_REALLOC_STRESS(X) (0) +#endif + /* ** Declarations used for tracing the operating system interfaces. */ @@ -9173,6 +10971,13 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # undef SQLITE_NEED_ERR_NAME #endif +/* +** SQLITE_ENABLE_EXPLAIN_COMMENTS is incompatible with SQLITE_OMIT_EXPLAIN +*/ +#ifdef SQLITE_OMIT_EXPLAIN +# undef SQLITE_ENABLE_EXPLAIN_COMMENTS +#endif + /* ** Return true (non-zero) if the input is an integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() @@ -9319,76 +11124,76 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_AS 24 #define TK_WITHOUT 25 #define TK_COMMA 26 -#define TK_ID 27 -#define TK_INDEXED 28 -#define TK_ABORT 29 -#define TK_ACTION 30 -#define TK_AFTER 31 -#define TK_ANALYZE 32 -#define TK_ASC 33 -#define TK_ATTACH 34 -#define TK_BEFORE 35 -#define TK_BY 36 -#define TK_CASCADE 37 -#define TK_CAST 38 -#define TK_COLUMNKW 39 -#define TK_CONFLICT 40 -#define TK_DATABASE 41 -#define TK_DESC 42 -#define TK_DETACH 43 -#define TK_EACH 44 -#define TK_FAIL 45 -#define TK_FOR 46 -#define TK_IGNORE 47 -#define TK_INITIALLY 48 -#define TK_INSTEAD 49 -#define TK_LIKE_KW 50 -#define TK_MATCH 51 -#define TK_NO 52 -#define TK_KEY 53 -#define TK_OF 54 -#define TK_OFFSET 55 -#define TK_PRAGMA 56 -#define TK_RAISE 57 -#define TK_RECURSIVE 58 -#define TK_REPLACE 59 -#define TK_RESTRICT 60 -#define TK_ROW 61 -#define TK_TRIGGER 62 -#define TK_VACUUM 63 -#define TK_VIEW 64 -#define TK_VIRTUAL 65 -#define TK_WITH 66 -#define TK_REINDEX 67 -#define TK_RENAME 68 -#define TK_CTIME_KW 69 -#define TK_ANY 70 -#define TK_OR 71 -#define TK_AND 72 -#define TK_IS 73 -#define TK_BETWEEN 74 -#define TK_IN 75 -#define TK_ISNULL 76 -#define TK_NOTNULL 77 -#define TK_NE 78 -#define TK_EQ 79 -#define TK_GT 80 -#define TK_LE 81 -#define TK_LT 82 -#define TK_GE 83 -#define TK_ESCAPE 84 -#define TK_BITAND 85 -#define TK_BITOR 86 -#define TK_LSHIFT 87 -#define TK_RSHIFT 88 -#define TK_PLUS 89 -#define TK_MINUS 90 -#define TK_STAR 91 -#define TK_SLASH 92 -#define TK_REM 93 -#define TK_CONCAT 94 -#define TK_COLLATE 95 -#define TK_BITNOT 96 +#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 @@ -9442,16 +11247,25 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_TO_REAL 147 #define TK_ISNOT 148 #define TK_END_OF_FILE 149 -#define TK_ILLEGAL 150 -#define TK_SPACE 151 -#define TK_UNCLOSED_STRING 152 -#define TK_FUNCTION 153 -#define TK_COLUMN 154 -#define TK_AGG_FUNCTION 155 -#define TK_AGG_COLUMN 156 -#define TK_UMINUS 157 -#define TK_UPLUS 158 -#define TK_REGISTER 159 +#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_ASTERISK 158 +#define TK_SPAN 159 +#define TK_SPACE 160 +#define TK_ILLEGAL 161 + +/* 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 */ /************** End of parse.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -9483,7 +11297,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 -** afterward. Having this macro allows us to cause the C compiler +** afterward. Having this macro allows us to cause the C compiler ** to omit code used by TEMP tables without messy #ifndef statements. */ #ifdef SQLITE_OMIT_TEMPDB @@ -9522,7 +11336,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** If no value has been provided for SQLITE_MAX_WORKER_THREADS, or if -** SQLITE_TEMP_STORE is set to 3 (never use temporary files), set it +** SQLITE_TEMP_STORE is set to 3 (never use temporary files), set it ** to zero. */ #if SQLITE_TEMP_STORE==3 || SQLITE_THREADSAFE==0 @@ -9550,7 +11364,6 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); # define SQLITE_DEFAULT_PCACHE_INITSZ 100 #endif - /* ** GCC does not define the offsetof() macro so we'll have to do it ** ourselves. @@ -9562,8 +11375,12 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** Macros to compute minimum and maximum of two numbers. */ -#define MIN(A,B) ((A)<(B)?(A):(B)) -#define MAX(A,B) ((A)>(B)?(A):(B)) +#ifndef MIN +# define MIN(A,B) ((A)<(B)?(A):(B)) +#endif +#ifndef MAX +# define MAX(A,B) ((A)>(B)?(A):(B)) +#endif /* ** Swap two objects of type TYPE. @@ -9671,7 +11488,7 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ ** 4 -> 20 1000 -> 99 1048576 -> 200 ** 10 -> 33 1024 -> 100 4294967296 -> 320 ** -** The LogEst can be negative to indicate fractional values. +** The LogEst can be negative to indicate fractional values. ** Examples: ** ** 0.5 -> -10 0.1 -> -33 0.0625 -> -40 @@ -9692,6 +11509,27 @@ typedef INT16_TYPE LogEst; # endif #endif +/* The uptr type is an unsigned integer large enough to hold a pointer +*/ +#if defined(HAVE_STDINT_H) + typedef uintptr_t uptr; +#elif SQLITE_PTRSIZE==4 + typedef u32 uptr; +#else + typedef u64 uptr; +#endif + +/* +** The SQLITE_WITHIN(P,S,E) macro checks to see if pointer P points to +** something between S (inclusive) and E (exclusive). +** +** In other words, S is a buffer and E is a pointer to the first byte after +** the end of buffer S. This macro returns true if P points to something +** contained within the buffer S. +*/ +#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) + + /* ** Macros to determine whether the machine is big or little endian, ** and whether or not that determination is run-time or compile-time. @@ -9701,11 +11539,6 @@ typedef INT16_TYPE LogEst; ** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined ** at run-time. */ -#ifdef SQLITE_AMALGAMATION -SQLITE_PRIVATE const int sqlite3one = 1; -#else -SQLITE_PRIVATE const int sqlite3one; -#endif #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) || \ @@ -9723,6 +11556,11 @@ SQLITE_PRIVATE const int sqlite3one; # define SQLITE_UTF16NATIVE SQLITE_UTF16BE #endif #if !defined(SQLITE_BYTEORDER) +# ifdef SQLITE_AMALGAMATION + const int sqlite3one = 1; +# else + extern const int sqlite3one; +# endif # define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ # define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) # define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) @@ -9737,7 +11575,7 @@ SQLITE_PRIVATE const int sqlite3one; #define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) #define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) -/* +/* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. */ @@ -9776,10 +11614,6 @@ SQLITE_PRIVATE const int sqlite3one; */ #ifdef __APPLE__ # include -# if TARGET_OS_IPHONE -# undef SQLITE_MAX_MMAP_SIZE -# define SQLITE_MAX_MMAP_SIZE 0 -# endif #endif #ifndef SQLITE_MAX_MMAP_SIZE # if defined(__linux__) \ @@ -9835,7 +11669,7 @@ SQLITE_PRIVATE const int sqlite3one; /* ** An instance of the following structure is used to store the busy-handler -** callback for a given sqlite handle. +** callback for a given sqlite handle. ** ** The sqlite.busyHandler member of the sqlite struct contains the busy ** callback for the database handle. Each pager opened via the sqlite @@ -9880,9 +11714,9 @@ struct BusyHandler { /* ** The following value as a destructor means to use sqlite3DbFree(). -** The sqlite3DbFree() 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 +** The sqlite3DbFree() 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. */ @@ -9909,16 +11743,16 @@ struct BusyHandler { SQLITE_API int SQLITE_STDCALL sqlite3_wsd_init(int N, int J); SQLITE_API void *SQLITE_STDCALL sqlite3_wsd_find(void *K, int L); #else - #define SQLITE_WSD + #define SQLITE_WSD #define GLOBAL(t,v) v #define sqlite3GlobalConfig sqlite3Config #endif /* ** The following macros are used to suppress compiler warnings and to -** make it clear to human readers when a function parameter is deliberately +** make it clear to human readers when a function parameter is deliberately ** left unused within the body of a function. This usually happens when -** a function is called via a function pointer. For example the +** a function is called via a function pointer. For example the ** implementation of an SQL aggregate step callback may not use the ** parameter indicating the number of arguments passed to the aggregate, ** if it knows that this is enforced elsewhere. @@ -9961,6 +11795,7 @@ typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; typedef struct Parse Parse; +typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; @@ -9984,7 +11819,7 @@ typedef struct WhereInfo WhereInfo; typedef struct With With; /* -** Defer sourcing vdbe.h and btree.h until after the "u8" and +** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. */ @@ -10055,11 +11890,11 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(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 sqlite3BtreeSyncDisabled(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); @@ -10142,8 +11977,37 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); #define BTREE_DATA_VERSION 15 /* A virtual meta-value */ /* -** Values that may be OR'd together to form the second argument of an -** sqlite3BtreeCursorHints() call. +** Kinds of hints that can be passed into the sqlite3BtreeCursorHint() +** interface. +** +** BTREE_HINT_RANGE (arguments: Expr*, Mem*) +** +** The first argument is an Expr* (which is guaranteed to be constant for +** the lifetime of the cursor) that defines constraints on which rows +** might be fetched with this cursor. The Expr* tree may contain +** TK_REGISTER nodes that refer to values stored in the array of registers +** passed as the second parameter. In other words, if Expr.op==TK_REGISTER +** then the value of the node is the value in Mem[pExpr.iTable]. Any +** TK_COLUMN node in the expression tree refers to the Expr.iColumn-th +** column of the b-tree of the cursor. The Expr tree will not contain +** any function calls nor subqueries nor references to b-trees other than +** the cursor being hinted. +** +** The design of the _RANGE hint is aid b-tree implementations that try +** to prefetch content from remote machines - to provide those +** implementations with limits on what needs to be prefetched and thereby +** reduce network bandwidth. +** +** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by +** standard SQLite. The other hints are provided for extentions that use +** the SQLite parser and code generator but substitute their own storage +** engine. +*/ +#define BTREE_HINT_RANGE 0 /* Range constraints on queries */ + +/* +** Values that may be OR'd together to form the argument to the +** BTREE_HINT_FLAGS hint for sqlite3BtreeCursorHint(): ** ** 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. @@ -10157,6 +12021,32 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); #define BTREE_BULKLOAD 0x00000001 /* Used to full index in sorted order */ #define BTREE_SEEK_EQ 0x00000002 /* EQ seeks only - no range seeks */ +/* +** Flags passed as the third argument to sqlite3BtreeCursor(). +** +** 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 +** (BTREE_WRCSR). If the BTREE_FORDELETE bit is set, then the cursor will +** only be used by SQLite for the following: +** +** * to seek to and then delete specific entries, and/or +** +** * to read values that will be used to create keys that other +** BTREE_FORDELETE cursors will seek to and delete. +** +** The BTREE_FORDELETE flag is an optimization hint. It is not used by +** by this, the native b-tree engine of SQLite, but it is available to +** alternative storage engines that might be substituted in place of this +** b-tree system. For alternative storage engines in which a delete of +** the main table row automatically deletes corresponding index rows, +** the FORDELETE flag hint allows those alternative storage engines to +** skip a lot of work. Namely: FORDELETE cursors may treat all SEEK +** and DELETE operations as no-ops, and any READ operation against a +** FORDELETE cursor may return a null row: 0x01 0x00. +*/ +#define BTREE_WRCSR 0x00000004 /* read-write cursor */ +#define BTREE_FORDELETE 0x00000008 /* Cursor is for seek/delete only */ + SQLITE_PRIVATE int sqlite3BtreeCursor( Btree*, /* BTree containing table to open */ int iTable, /* Index of root page */ @@ -10166,6 +12056,10 @@ SQLITE_PRIVATE int sqlite3BtreeCursor( ); SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor*, unsigned); +#ifdef SQLITE_ENABLE_CURSOR_HINTS +SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor*, int, ...); +#endif SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( @@ -10177,7 +12071,12 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( ); SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); -SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, int); +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); + +/* Allowed flags for the 2nd argument to sqlite3BtreeDelete() */ +#define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ +#define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ + SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, const void *pData, int nData, int nZero, int bias, int seekResult); @@ -10200,10 +12099,7 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *); SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); -SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); -#ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); -#endif SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt); SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); @@ -10232,15 +12128,17 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); #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*); #else # define sqlite3BtreeEnter(X) # define sqlite3BtreeEnterAll(X) +# define sqlite3BtreeSharable(X) 0 +# define sqlite3BtreeEnterCursor(X) #endif #if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE -SQLITE_PRIVATE int sqlite3BtreeSharable(Btree*); SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*); -SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*); #ifndef NDEBUG @@ -10251,9 +12149,7 @@ SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3*,int,Schema*); #endif #else -# define sqlite3BtreeSharable(X) 0 # define sqlite3BtreeLeave(X) -# define sqlite3BtreeEnterCursor(X) # define sqlite3BtreeLeaveCursor(X) # define sqlite3BtreeLeaveAll(X) @@ -10312,7 +12208,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 opflags; /* Mask of the OPFLG_* flags in opcodes.h */ + u8 notUsed1; u8 p5; /* Fifth parameter is an unsigned character */ int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ @@ -10331,6 +12227,10 @@ struct VdbeOp { KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ int *ai; /* Used when p4type is P4_INTARRAY */ SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ + Table *pTab; /* Used when p4type is P4_TABLE */ +#ifdef SQLITE_ENABLE_CURSOR_HINTS + Expr *pExpr; /* Used when p4type is P4_EXPR */ +#endif int (*xAdvance)(BtCursor *, int *); } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS @@ -10381,6 +12281,7 @@ typedef struct VdbeOpList VdbeOpList; #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 */ @@ -10391,7 +12292,8 @@ typedef struct VdbeOpList VdbeOpList; #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_FUNCCTX (-20) /* P4 is a pointer to an sqlite3_context object */ +#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 */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -10433,202 +12335,210 @@ typedef struct VdbeOpList VdbeOpList; /************** Include opcodes.h in the middle of vdbe.h ********************/ /************** Begin file opcodes.h *****************************************/ /* Automatically generated. Do not edit */ -/* See the mkopcodeh.awk script for details */ -#define OP_Savepoint 1 -#define OP_AutoCommit 2 -#define OP_Transaction 3 -#define OP_SorterNext 4 -#define OP_PrevIfOpen 5 -#define OP_NextIfOpen 6 -#define OP_Prev 7 -#define OP_Next 8 -#define OP_Checkpoint 9 -#define OP_JournalMode 10 -#define OP_Vacuum 11 -#define OP_VFilter 12 /* synopsis: iplan=r[P3] zplan='P4' */ -#define OP_VUpdate 13 /* synopsis: data=r[P3@P2] */ -#define OP_Goto 14 -#define OP_Gosub 15 -#define OP_Return 16 -#define OP_InitCoroutine 17 -#define OP_EndCoroutine 18 +/* See the tool/mkopcodeh.tcl script for details */ +#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_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */ -#define OP_Yield 20 -#define OP_HaltIfNull 21 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 22 -#define OP_Integer 23 /* synopsis: r[P2]=P1 */ -#define OP_Int64 24 /* synopsis: r[P2]=P4 */ -#define OP_String 25 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 26 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 27 /* synopsis: r[P1]=NULL */ -#define OP_Blob 28 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 29 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 30 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 31 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 32 /* synopsis: r[P2]=r[P1] */ -#define OP_ResultRow 33 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 34 -#define OP_Function0 35 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_Function 36 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_AddImm 37 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_MustBeInt 38 -#define OP_RealAffinity 39 -#define OP_Cast 40 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 41 -#define OP_Compare 42 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_Jump 43 -#define OP_Once 44 -#define OP_If 45 -#define OP_IfNot 46 -#define OP_Column 47 /* synopsis: r[P3]=PX */ -#define OP_Affinity 48 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 49 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 50 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 51 -#define OP_SetCookie 52 -#define OP_ReopenIdx 53 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenRead 54 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenWrite 55 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenAutoindex 56 /* synopsis: nColumn=P2 */ -#define OP_OpenEphemeral 57 /* synopsis: nColumn=P2 */ -#define OP_SorterOpen 58 -#define OP_SequenceTest 59 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ -#define OP_OpenPseudo 60 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 61 -#define OP_ColumnsUsed 62 -#define OP_SeekLT 63 /* synopsis: key=r[P3@P4] */ -#define OP_SeekLE 64 /* synopsis: key=r[P3@P4] */ -#define OP_SeekGE 65 /* synopsis: key=r[P3@P4] */ -#define OP_SeekGT 66 /* synopsis: key=r[P3@P4] */ -#define OP_Seek 67 /* synopsis: intkey=r[P2] */ -#define OP_NoConflict 68 /* synopsis: key=r[P3@P4] */ -#define OP_NotFound 69 /* synopsis: key=r[P3@P4] */ -#define OP_Found 70 /* synopsis: key=r[P3@P4] */ -#define OP_Or 71 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ -#define OP_And 72 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ -#define OP_NotExists 73 /* synopsis: intkey=r[P3] */ -#define OP_Sequence 74 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NewRowid 75 /* synopsis: r[P2]=rowid */ -#define OP_IsNull 76 /* same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ -#define OP_NotNull 77 /* same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ -#define OP_Ne 78 /* same as TK_NE, synopsis: if r[P1]!=r[P3] goto P2 */ -#define OP_Eq 79 /* same as TK_EQ, synopsis: if r[P1]==r[P3] goto P2 */ -#define OP_Gt 80 /* same as TK_GT, synopsis: if r[P1]>r[P3] goto P2 */ -#define OP_Le 81 /* same as TK_LE, synopsis: if r[P1]<=r[P3] goto P2 */ -#define OP_Lt 82 /* same as TK_LT, synopsis: if r[P1]=r[P3] goto P2 */ -#define OP_Insert 84 /* synopsis: intkey=r[P3] data=r[P2] */ -#define OP_BitAnd 85 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ -#define OP_BitOr 86 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ -#define OP_ShiftLeft 87 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<>r[P1] */ -#define OP_Add 89 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ -#define OP_Subtract 90 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ -#define OP_Multiply 91 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ -#define OP_Divide 92 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ -#define OP_Remainder 93 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ -#define OP_Concat 94 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ -#define OP_InsertInt 95 /* synopsis: intkey=P3 data=r[P2] */ -#define OP_BitNot 96 /* same as TK_BITNOT, synopsis: r[P1]= ~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_NotExists 32 /* synopsis: intkey=r[P3] */ +#define OP_Last 33 +#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[P1]!=r[P3] goto P2 */ +#define OP_Eq 37 /* same as TK_EQ, synopsis: if r[P1]==r[P3] goto P2 */ +#define OP_Gt 38 /* same as TK_GT, synopsis: if r[P1]>r[P3] goto P2 */ +#define OP_Le 39 /* same as TK_LE, synopsis: if r[P1]<=r[P3] goto P2 */ +#define OP_Lt 40 /* same as TK_LT, synopsis: if r[P1]=r[P3] goto P2 */ +#define OP_SorterSort 42 +#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_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_Sort 53 +#define OP_BitNot 54 /* same as TK_BITNOT, synopsis: r[P1]= ~r[P1] */ +#define OP_Rewind 55 +#define OP_IdxLE 56 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGT 57 /* synopsis: key=r[P3@P4] */ +#define OP_IdxLT 58 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGE 59 /* synopsis: key=r[P3@P4] */ +#define OP_RowSetRead 60 /* synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 61 /* synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 62 +#define OP_FkIfZero 63 /* synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IfPos 64 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 65 /* synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 */ +#define OP_DecrJumpZero 66 /* synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 67 +#define OP_VNext 68 +#define OP_Init 69 /* synopsis: Start at P2 */ +#define OP_Return 70 +#define OP_EndCoroutine 71 +#define OP_HaltIfNull 72 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 73 +#define OP_Integer 74 /* synopsis: r[P2]=P1 */ +#define OP_Int64 75 /* synopsis: r[P2]=P4 */ +#define OP_String 76 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_Null 77 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 78 /* synopsis: r[P1]=NULL */ +#define OP_Blob 79 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 80 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 81 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 82 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 83 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 84 /* synopsis: r[P2]=r[P1] */ +#define OP_ResultRow 85 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 86 +#define OP_Function0 87 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_Function 88 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_AddImm 89 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 90 +#define OP_Cast 91 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 92 +#define OP_Compare 93 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_Column 94 /* synopsis: r[P3]=PX */ +#define OP_Affinity 95 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 96 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ #define OP_String8 97 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_Delete 98 -#define OP_ResetCount 99 -#define OP_SorterCompare 100 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 101 /* synopsis: r[P2]=data */ -#define OP_RowKey 102 /* synopsis: r[P2]=key */ -#define OP_RowData 103 /* synopsis: r[P2]=data */ -#define OP_Rowid 104 /* synopsis: r[P2]=rowid */ -#define OP_NullRow 105 -#define OP_Last 106 -#define OP_SorterSort 107 -#define OP_Sort 108 -#define OP_Rewind 109 -#define OP_SorterInsert 110 -#define OP_IdxInsert 111 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 112 /* synopsis: key=r[P2@P3] */ -#define OP_IdxRowid 113 /* synopsis: r[P2]=rowid */ -#define OP_IdxLE 114 /* synopsis: key=r[P3@P4] */ -#define OP_IdxGT 115 /* synopsis: key=r[P3@P4] */ -#define OP_IdxLT 116 /* synopsis: key=r[P3@P4] */ -#define OP_IdxGE 117 /* synopsis: key=r[P3@P4] */ -#define OP_Destroy 118 -#define OP_Clear 119 -#define OP_ResetSorter 120 -#define OP_CreateIndex 121 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_CreateTable 122 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_ParseSchema 123 -#define OP_LoadAnalysis 124 -#define OP_DropTable 125 -#define OP_DropIndex 126 -#define OP_DropTrigger 127 -#define OP_IntegrityCk 128 -#define OP_RowSetAdd 129 /* synopsis: rowset(P1)=r[P2] */ -#define OP_RowSetRead 130 /* synopsis: r[P3]=rowset(P1) */ -#define OP_RowSetTest 131 /* synopsis: if r[P3] in rowset(P1) goto P2 */ -#define OP_Program 132 +#define OP_Count 98 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 99 +#define OP_SetCookie 100 +#define OP_ReopenIdx 101 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenRead 102 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 103 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenAutoindex 104 /* synopsis: nColumn=P2 */ +#define OP_OpenEphemeral 105 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 106 +#define OP_SequenceTest 107 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 108 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 109 +#define OP_ColumnsUsed 110 +#define OP_Sequence 111 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 112 /* synopsis: r[P2]=rowid */ +#define OP_Insert 113 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_InsertInt 114 /* synopsis: intkey=P3 data=r[P2] */ +#define OP_Delete 115 +#define OP_ResetCount 116 +#define OP_SorterCompare 117 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 118 /* synopsis: r[P2]=data */ +#define OP_RowKey 119 /* synopsis: r[P2]=key */ +#define OP_RowData 120 /* synopsis: r[P2]=data */ +#define OP_Rowid 121 /* synopsis: r[P2]=rowid */ +#define OP_NullRow 122 +#define OP_SorterInsert 123 +#define OP_IdxInsert 124 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 125 /* synopsis: key=r[P2@P3] */ +#define OP_Seek 126 /* synopsis: Move P3 to P1.rowid */ +#define OP_IdxRowid 127 /* synopsis: r[P2]=rowid */ +#define OP_Destroy 128 +#define OP_Clear 129 +#define OP_ResetSorter 130 +#define OP_CreateIndex 131 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_CreateTable 132 /* synopsis: r[P2]=root iDb=P1 */ #define OP_Real 133 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_Param 134 -#define OP_FkCounter 135 /* synopsis: fkctr[P1]+=P2 */ -#define OP_FkIfZero 136 /* synopsis: if fkctr[P1]==0 goto P2 */ -#define OP_MemMax 137 /* synopsis: r[P1]=max(r[P1],r[P2]) */ -#define OP_IfPos 138 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ -#define OP_SetIfNotPos 139 /* synopsis: if r[P1]<=0 then r[P2]=P3 */ -#define OP_IfNotZero 140 /* synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 */ -#define OP_DecrJumpZero 141 /* synopsis: if (--r[P1])==0 goto P2 */ -#define OP_JumpZeroIncr 142 /* synopsis: if (r[P1]++)==0 ) goto P2 */ -#define OP_AggStep0 143 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep 144 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggFinal 145 /* synopsis: accum=r[P1] N=P2 */ -#define OP_IncrVacuum 146 -#define OP_Expire 147 -#define OP_TableLock 148 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 149 -#define OP_VCreate 150 -#define OP_VDestroy 151 -#define OP_VOpen 152 -#define OP_VColumn 153 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VNext 154 +#define OP_ParseSchema 134 +#define OP_LoadAnalysis 135 +#define OP_DropTable 136 +#define OP_DropIndex 137 +#define OP_DropTrigger 138 +#define OP_IntegrityCk 139 +#define OP_RowSetAdd 140 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 141 +#define OP_FkCounter 142 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 143 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 144 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggStep0 145 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep 146 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggFinal 147 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 148 +#define OP_TableLock 149 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 150 +#define OP_VCreate 151 +#define OP_VDestroy 152 +#define OP_VOpen 153 +#define OP_VColumn 154 /* synopsis: r[P3]=vcolumn(P2) */ #define OP_VRename 155 #define OP_Pagecount 156 #define OP_MaxPgcnt 157 -#define OP_Init 158 /* synopsis: Start at P2 */ +#define OP_CursorHint 158 #define OP_Noop 159 #define OP_Explain 160 - /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c ** are encoded into bitvectors as follows: */ -#define OPFLG_JUMP 0x0001 /* jump: P2 holds jmp target */ -#define OPFLG_IN1 0x0002 /* in1: P1 is an input */ -#define OPFLG_IN2 0x0004 /* in2: P2 is an input */ -#define OPFLG_IN3 0x0008 /* in3: P3 is an input */ -#define OPFLG_OUT2 0x0010 /* out2: P2 is an output */ -#define OPFLG_OUT3 0x0020 /* out3: P3 is an output */ +#define OPFLG_JUMP 0x01 /* jump: P2 holds jmp target */ +#define OPFLG_IN1 0x02 /* in1: P1 is an input */ +#define OPFLG_IN2 0x04 /* in2: P2 is an input */ +#define OPFLG_IN3 0x08 /* in3: P3 is an input */ +#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, 0x00, 0x01, 0x01, 0x01, 0x01,\ -/* 8 */ 0x01, 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01,\ -/* 16 */ 0x02, 0x01, 0x02, 0x12, 0x03, 0x08, 0x00, 0x10,\ -/* 24 */ 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00,\ -/* 32 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02,\ -/* 40 */ 0x02, 0x00, 0x00, 0x01, 0x01, 0x03, 0x03, 0x00,\ -/* 48 */ 0x00, 0x00, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00,\ -/* 56 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,\ -/* 64 */ 0x09, 0x09, 0x09, 0x04, 0x09, 0x09, 0x09, 0x26,\ -/* 72 */ 0x26, 0x09, 0x10, 0x10, 0x03, 0x03, 0x0b, 0x0b,\ -/* 80 */ 0x0b, 0x0b, 0x0b, 0x0b, 0x00, 0x26, 0x26, 0x26,\ -/* 88 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x00,\ -/* 96 */ 0x12, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 104 */ 0x10, 0x00, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04,\ -/* 112 */ 0x00, 0x10, 0x01, 0x01, 0x01, 0x01, 0x10, 0x00,\ -/* 120 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 128 */ 0x00, 0x06, 0x23, 0x0b, 0x01, 0x10, 0x10, 0x00,\ -/* 136 */ 0x01, 0x04, 0x03, 0x06, 0x03, 0x03, 0x03, 0x00,\ -/* 144 */ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 152 */ 0x00, 0x00, 0x01, 0x00, 0x10, 0x10, 0x01, 0x00,\ +/* 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, 0x01, 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, 0x23, 0x0b, 0x01, 0x01,\ +/* 64 */ 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x02, 0x02,\ +/* 72 */ 0x08, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10,\ +/* 80 */ 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00,\ +/* 88 */ 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,\ +/* 96 */ 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ +/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ +/* 112 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 120 */ 0x00, 0x10, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10,\ +/* 128 */ 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00,\ +/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\ +/* 144 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\ /* 160 */ 0x00,} +/* The sqlite3P2Values() 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 69 /* Maximum JUMP opcode */ + /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ @@ -10647,7 +12557,13 @@ 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 int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno); +SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(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); @@ -10655,7 +12571,7 @@ 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 void sqlite3VdbeChangeToNoop(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*); @@ -10663,6 +12579,7 @@ 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*); @@ -10843,7 +12760,11 @@ typedef struct PgHdr DbPage; #define PAGER_LOCKINGMODE_EXCLUSIVE 1 /* -** Numeric constants that encode the journalmode. +** Numeric constants that encode the journalmode. +** +** The numeric values encoded here (other than PAGER_JOURNALMODE_QUERY) +** are exposed in the API via the "PRAGMA journal_mode" command and +** therefore cannot be changed without a compatibility break. */ #define PAGER_JOURNALMODE_QUERY (-1) /* Query the value of journalmode */ #define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ @@ -10854,22 +12775,28 @@ typedef struct PgHdr DbPage; #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ /* -** Flags that make up the mask passed to sqlite3PagerAcquire(). +** Flags that make up the mask passed to sqlite3PagerGet(). */ #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ /* ** Flags for sqlite3PagerSetFlags() +** +** Value constraints (enforced via assert()): +** PAGER_FULLFSYNC == SQLITE_FullFSync +** PAGER_CKPT_FULLFSYNC == SQLITE_CkptFullFSync +** PAGER_CACHE_SPILL == SQLITE_CacheSpill */ #define PAGER_SYNCHRONOUS_OFF 0x01 /* PRAGMA synchronous=OFF */ #define PAGER_SYNCHRONOUS_NORMAL 0x02 /* PRAGMA synchronous=NORMAL */ #define PAGER_SYNCHRONOUS_FULL 0x03 /* PRAGMA synchronous=FULL */ -#define PAGER_SYNCHRONOUS_MASK 0x03 /* Mask for three values above */ -#define PAGER_FULLFSYNC 0x04 /* PRAGMA fullfsync=ON */ -#define PAGER_CKPT_FULLFSYNC 0x08 /* PRAGMA checkpoint_fullfsync=ON */ -#define PAGER_CACHESPILL 0x10 /* PRAGMA cache_spill=ON */ -#define PAGER_FLAGS_MASK 0x1c /* All above except SYNCHRONOUS */ +#define PAGER_SYNCHRONOUS_EXTRA 0x04 /* PRAGMA synchronous=EXTRA */ +#define PAGER_SYNCHRONOUS_MASK 0x07 /* Mask for four values above */ +#define PAGER_FULLFSYNC 0x08 /* PRAGMA fullfsync=ON */ +#define PAGER_CKPT_FULLFSYNC 0x10 /* PRAGMA checkpoint_fullfsync=ON */ +#define PAGER_CACHESPILL 0x20 /* PRAGMA cache_spill=ON */ +#define PAGER_FLAGS_MASK 0x38 /* All above except SYNCHRONOUS */ /* ** The remainder of this file contains the declarations of the functions @@ -10898,6 +12825,7 @@ 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); @@ -10907,10 +12835,10 @@ 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*); /* Functions used to obtain and release page references. */ -SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); -#define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) +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*); @@ -10942,6 +12870,10 @@ 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); +# ifdef SQLITE_ENABLE_SNAPSHOT +SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot); +# endif #endif #ifdef SQLITE_ENABLE_ZIPVFS @@ -10956,10 +12888,10 @@ SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); #endif SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int); -SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*); +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 int sqlite3PagerNosync(Pager*); SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); @@ -11024,7 +12956,7 @@ struct PgHdr { sqlite3_pcache_page *pPage; /* Pcache object page handle */ void *pData; /* Page data */ void *pExtra; /* Extra content */ - PgHdr *pDirty; /* Transient list of dirty pages */ + 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 */ #ifdef SQLITE_CHECK_PAGES @@ -11049,9 +12981,10 @@ struct PgHdr { #define PGHDR_WRITEABLE 0x004 /* Journaled and ready to modify */ #define PGHDR_NEED_SYNC 0x008 /* Fsync the rollback journal before ** writing this page to the database */ -#define PGHDR_NEED_READ 0x010 /* Content is unread */ -#define PGHDR_DONT_WRITE 0x020 /* Do not write content to disk */ -#define PGHDR_MMAP 0x040 /* This is an mmap page object */ +#define PGHDR_DONT_WRITE 0x010 /* Do not write content to disk */ +#define PGHDR_MMAP 0x020 /* This is an mmap page object */ + +#define PGHDR_WAL_APPEND 0x040 /* Appended to wal file */ /* Initialize and shutdown the page cache subsystem */ SQLITE_PRIVATE int sqlite3PcacheInitialize(void); @@ -11095,6 +13028,7 @@ 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*); /* Change a page number. Used by incr-vacuum. */ SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr*, Pgno); @@ -11133,6 +13067,11 @@ SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*); SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)); #endif +#if defined(SQLITE_DEBUG) +/* Check invariants on a PgHdr object */ +SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr*); +#endif + /* Set and get the suggested cache-size for the specified pager-cache. ** ** If no global maximum is configured, then the system attempts to limit @@ -11144,6 +13083,13 @@ SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *, int); SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *); #endif +/* Set or get the suggested spill-size for the specified pager-cache. +** +** 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); + /* Free up as much memory as possible from the page cache */ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache*); @@ -11162,11 +13108,13 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); SQLITE_PRIVATE int sqlite3HeaderSizePcache(void); SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void); +/* Number of dirty pages as a percentage of the configured cache size */ +SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*); + #endif /* _PCACHE_H_ */ /************** End of pcache.h **********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ - /************** Include os.h in the middle of sqliteInt.h ********************/ /************** Begin file os.h **********************************************/ /* @@ -11392,7 +13340,7 @@ SQLITE_PRIVATE int sqlite3OsInit(void); /* ** Functions for accessing sqlite3_file methods */ -SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*); +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); @@ -11429,6 +13377,7 @@ SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_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*); /* @@ -11436,7 +13385,7 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); ** sqlite3_malloc() to obtain space for the file-handle structure. */ SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); -SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); +SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); #endif /* _SQLITE_OS_H_ */ @@ -11518,6 +13467,36 @@ SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); /************** End of mutex.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ +/* The SQLITE_EXTRA_DURABLE compile-time option used to set the default +** synchronous setting to EXTRA. It is no longer supported. +*/ +#ifdef SQLITE_EXTRA_DURABLE +# warning Use SQLITE_DEFAULT_SYNCHRONOUS=3 instead of SQLITE_EXTRA_DURABLE +# define SQLITE_DEFAULT_SYNCHRONOUS 3 +#endif + +/* +** Default synchronous levels. +** +** Note that (for historcal reasons) the PAGER_SYNCHRONOUS_* macros differ +** from the SQLITE_DEFAULT_SYNCHRONOUS value by 1. +** +** PAGER_SYNCHRONOUS DEFAULT_SYNCHRONOUS +** OFF 1 0 +** NORMAL 2 1 +** FULL 3 2 +** EXTRA 4 3 +** +** The "PRAGMA synchronous" statement also uses the zero-based numbers. +** In other words, the zero-based numbers are used for all external interfaces +** and the one-based values are used internally. +*/ +#ifndef SQLITE_DEFAULT_SYNCHRONOUS +# define SQLITE_DEFAULT_SYNCHRONOUS (PAGER_SYNCHRONOUS_FULL-1) +#endif +#ifndef SQLITE_DEFAULT_WAL_SYNCHRONOUS +# define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS +#endif /* ** Each database file to be accessed by the system is an instance @@ -11530,6 +13509,7 @@ struct Db { char *zName; /* Name of this database */ Btree *pBt; /* The B*Tree structure for this database file */ u8 safety_level; /* How aggressive at syncing data to disk */ + u8 bSyncSet; /* True if "PRAGMA synchronous=N" has been run */ Schema *pSchema; /* Pointer to database schema (possibly shared) */ }; @@ -11540,7 +13520,7 @@ struct Db { ** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. -** +** ** Schema objects are automatically deallocated when the last Btree that ** references them is destroyed. The TEMP Schema is manually freed by ** sqlite3_close(). @@ -11565,7 +13545,7 @@ struct Schema { }; /* -** These macros can be used to test, set, or clear bits in the +** These macros can be used to test, set, or clear bits in the ** Db.pSchema->flags field. */ #define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->schemaFlags&(P))==(P)) @@ -11614,8 +13594,8 @@ struct Schema { ** lookaside allocations are not used to construct the schema objects. */ struct Lookaside { + u32 bDisable; /* Only operate the lookaside when zero */ u16 sz; /* Size of each buffer in bytes */ - u8 bEnabled; /* False to disable new lookaside allocations */ u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ int nOut; /* Number of buffers currently checked out */ int mxOut; /* Highwater mark for nOut */ @@ -11629,13 +13609,15 @@ struct LookasideSlot { }; /* -** A hash table for function definitions. +** 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.pHash chain. +** Collisions are on the FuncDef.u.pHash chain. */ +#define SQLITE_FUNC_HASH_SZ 23 struct FuncDefHash { - FuncDef *a[23]; /* Hash table for functions */ + FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */ }; #ifdef SQLITE_USER_AUTHENTICATION @@ -11693,11 +13675,13 @@ struct sqlite3 { unsigned int openFlags; /* Flags passed to sqlite3_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 */ u16 dbOptFlags; /* Flags to enable/disable optimizations */ u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ + u8 bBenignMalloc; /* Do not require OOMs if true */ u8 dfltLockMode; /* Default locking-mode for attached dbs */ signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ u8 suppressErr; /* Do not issue error messages if true */ @@ -11727,12 +13711,19 @@ struct sqlite3 { void *pTraceArg; /* Argument to the trace function */ void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ - void *pCommitArg; /* Argument to xCommitCallback() */ + void *pCommitArg; /* Argument to xCommitCallback() */ int (*xCommitCallback)(void*); /* Invoked at every commit. */ - void *pRollbackArg; /* Argument to xRollbackCallback() */ + void *pRollbackArg; /* Argument to xRollbackCallback() */ void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); +#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 + ); + PreUpdate *pPreUpdate; /* Context for active pre-update callback */ +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifndef SQLITE_OMIT_WAL int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; @@ -11762,7 +13753,7 @@ struct sqlite3 { VTable **aVTrans; /* Virtual tables with open transactions */ VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ #endif - FuncDefHash aFunc; /* Hash table of connection functions */ + Hash aFunc; /* Hash table of connection functions */ Hash aCollSeq; /* All collating sequences */ BusyHandler busyHandler; /* Busy callback */ Db aDbStatic[2]; /* Static space for the 2 default backends */ @@ -11774,8 +13765,8 @@ struct sqlite3 { i64 nDeferredImmCons; /* Net deferred immediate constraints */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY - /* The following variables are all protected by the STATIC_MASTER - ** mutex, not by sqlite3.mutex. They are used by code in notify.c. + /* The following variables are all protected by the STATIC_MASTER + ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** ** When X.pUnlockConnection==Y, that means that X is waiting for Y to ** unlock so that it can proceed. @@ -11803,13 +13794,18 @@ struct sqlite3 { /* ** Possible values for the sqlite3.flags. +** +** Value constraints (enforced via assert()): +** SQLITE_FullFSync == PAGER_FULLFSYNC +** 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_FullFSync 0x00000004 /* Use full fsync on the backend */ -#define SQLITE_CkptFullFSync 0x00000008 /* Use full fsync for checkpoint */ -#define SQLITE_CacheSpill 0x00000010 /* OK to spill pager cache */ -#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */ +#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 */ @@ -11830,12 +13826,14 @@ struct sqlite3 { #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_EnableTrigger 0x00800000 /* True to enable triggers */ -#define SQLITE_DeferFKs 0x01000000 /* Defer all FK constraints */ -#define SQLITE_QueryOnly 0x02000000 /* Disable database changes */ -#define SQLITE_VdbeEQP 0x04000000 /* Debug EXPLAIN QUERY PLAN */ -#define SQLITE_Vacuum 0x08000000 /* Currently in a VACUUM */ -#define SQLITE_CellSizeCk 0x10000000 /* Check btree cell sizes on load */ +#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) */ /* @@ -11855,6 +13853,7 @@ struct sqlite3 { #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_AllOpts 0xffff /* All optimizations */ /* @@ -11888,28 +13887,33 @@ struct sqlite3 { /* ** Each SQL function is defined by an instance of the following -** structure. A pointer to this structure is stored in the sqlite.aFunc -** hash table. When multiple functions have the same name, the hash table -** points to a linked list of these structures. +** structure. For global built-in functions (ex: substr(), max(), count()) +** a pointer to this structure is held in the sqlite3BuiltinFunctions object. +** For per-connection application-defined functions, a pointer to this +** structure is held in the db->aHash hash table. +** +** The u.pHash field is used by the global built-ins. The u.pDestructor +** field is used by per-connection app-def functions. */ struct FuncDef { - i16 nArg; /* Number of arguments. -1 means unlimited */ + i8 nArg; /* Number of arguments. -1 means unlimited */ u16 funcFlags; /* Some combination of SQLITE_FUNC_* */ void *pUserData; /* User data parameter */ FuncDef *pNext; /* Next function with same name */ - void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ - void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */ - void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */ - char *zName; /* SQL name of the function. */ - FuncDef *pHash; /* Next with a different name but the same hash */ - FuncDestructor *pDestructor; /* Reference counted destructor function */ + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ + void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ + const char *zName; /* SQL name of the function. */ + union { + FuncDef *pHash; /* Next with a different name but the same hash */ + FuncDestructor *pDestructor; /* Reference counted destructor function */ + } u; }; /* ** This structure encapsulates a user-function destructor callback (as ** configured using create_function_v2()) and a reference counter. When ** create_function_v2() is called to create a function with a destructor, -** a single object of this type is allocated. FuncDestructor.nRef is set to +** a single object of this type is allocated. FuncDestructor.nRef is set to ** the number of FuncDef objects created (either 1 or 3, depending on whether ** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor ** member of each of the new FuncDef objects is set to point to the allocated @@ -11927,8 +13931,16 @@ struct FuncDestructor { /* ** Possible values for FuncDef.flags. Note that the _LENGTH and _TYPEOF -** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. There +** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. And +** SQLITE_FUNC_CONSTANT must be the same as SQLITE_DETERMINISTIC. There ** are assert() statements in the code to verify this. +** +** Value constraints (enforced via assert()): +** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg +** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG +** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** 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 */ @@ -11950,10 +13962,10 @@ struct FuncDestructor { ** used to create the initializers for the FuncDef structures. ** ** FUNCTION(zName, nArg, iArg, bNC, xFunc) -** Used to create a scalar function definition of a function zName +** 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 (sqlite3_user_data()) for the function. If ** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. ** ** VFUNCTION(zName, nArg, iArg, bNC, xFunc) @@ -11972,8 +13984,8 @@ struct FuncDestructor { ** 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 +** 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 ** FuncDef.flags variable is set to the value passed as the flags @@ -11981,28 +13993,28 @@ struct FuncDestructor { */ #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, 0, #zName, 0, 0} + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 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, 0, #zName, 0, 0} + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 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, 0, #zName, 0, 0} + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 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, 0, #zName, 0, 0} + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 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, 0, #zName, 0, 0} + pArg, 0, xFunc, 0, #zName, } #define LIKEFUNC(zName, nArg, arg, flags) \ {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ - (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} + (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, 0, xStep,xFinal,#zName,0,0} + 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, 0, xStep,xFinal,#zName,0,0} + SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}} /* ** All current savepoints are stored in a linked list starting at @@ -12044,14 +14056,12 @@ struct Module { ** of this structure. */ struct Column { - char *zName; /* Name of this column */ + char *zName; /* Name of this column, \000, then the type */ Expr *pDflt; /* Default value of this column */ - char *zDflt; /* Original text of the default value */ - char *zType; /* Data type for this column */ 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 this column. INT==1 */ + u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */ u8 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; @@ -12059,6 +14069,7 @@ struct Column { */ #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 */ /* ** A "Collating Sequence" is defined by an instance of the following @@ -12089,7 +14100,7 @@ struct CollSeq { ** ** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and ** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve -** the speed a little by numbering the values consecutively. +** the speed a little by numbering the values consecutively. ** ** But rather than start with 0 or 1, we begin with 'A'. That way, ** when multiple affinity types are concatenated into a string and @@ -12108,7 +14119,7 @@ struct CollSeq { /* ** The SQLITE_AFF_MASK values masks off the significant bits of an -** affinity value. +** affinity value. */ #define SQLITE_AFF_MASK 0x47 @@ -12128,20 +14139,20 @@ struct CollSeq { /* ** An object of this type is created for each virtual table present in -** the database schema. +** 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 ** 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 -** database connections, even when the rest of the in-memory database +** instance of the sqlite3_vtab* handle used to access the virtual table +** implementation. sqlite3_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 ** during initialization internally. This database connection handle may -** then be used by the virtual table implementation to access real tables -** within the database. So that they appear as part of the callers -** transaction, these accesses need to be made via the same database +** then be used by the virtual table implementation to access real tables +** within the database. So that they appear as part of the callers +** transaction, these accesses need to be made via the same database ** connection as that used to execute SQL operations on the virtual table. ** ** All VTable objects that correspond to a single table in a shared @@ -12153,19 +14164,19 @@ struct CollSeq { ** sqlite3_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 +** schema is being reloaded for some reason), the VTable objects are not +** deleted and the sqlite3_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 +** corresponding sqlite3 structure. They are then deleted/xDisconnected ** next time a statement is prepared using said sqlite3*. This is done ** to avoid deadlock issues involving multiple sqlite3.mutex mutexes. ** Refer to comments above function sqlite3VtabUnlockList() 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 +** The memory for objects of this type is always allocated by +** sqlite3DbMalloc(), using the connection handle stored in VTable.db as ** the first argument. */ struct VTable { @@ -12218,7 +14229,7 @@ struct Table { /* ** Allowed values for Table.tabFlags. ** -** TF_OOOHidden applies to virtual tables that have hidden columns that are +** TF_OOOHidden applies to tables or view that have hidden columns that are ** 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 @@ -12241,12 +14252,28 @@ struct Table { */ #ifndef SQLITE_OMIT_VIRTUALTABLE # define IsVirtual(X) (((X)->tabFlags & TF_Virtual)!=0) -# define IsHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) #else # define IsVirtual(X) 0 -# define IsHiddenColumn(X) 0 #endif +/* +** Macros to determine if a column is hidden. IsOrdinaryHiddenColumn() +** only works for non-virtual tables (ordinary tables and views) and is +** always false unless SQLITE_ENABLE_HIDDEN_COLUMNS is defined. The +** IsHiddenColumn() macro is general purpose. +*/ +#if defined(SQLITE_ENABLE_HIDDEN_COLUMNS) +# define IsHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +# define IsOrdinaryHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +#elif !defined(SQLITE_OMIT_VIRTUALTABLE) +# define IsHiddenColumn(X) (((X)->colFlags & COLFLAG_HIDDEN)!=0) +# define IsOrdinaryHiddenColumn(X) 0 +#else +# define IsHiddenColumn(X) 0 +# define IsOrdinaryHiddenColumn(X) 0 +#endif + + /* Does the table have a rowid */ #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) @@ -12317,7 +14344,7 @@ struct FKey { ** key is set to NULL. CASCADE means that a DELETE or UPDATE of the ** referenced table row is propagated into the row that holds the ** foreign key. -** +** ** The following symbolic values are used to record which type ** of action to take. */ @@ -12338,7 +14365,7 @@ struct FKey { /* ** An instance of the following structure is passed as the first -** argument to sqlite3VdbeKeyCompare and is used to control the +** argument to sqlite3VdbeKeyCompare and is used to control the ** comparison of the two index keys. ** ** Note that aSortOrder[] and aColl[] have nField+1 slots. There @@ -12356,9 +14383,8 @@ struct KeyInfo { }; /* -** An instance of the following structure holds information about a -** single index record that has already been parsed out into individual -** values. +** This object holds a record which has been parsed out into individual +** fields, for the purposes of doing a comparison. ** ** A record is an object that contains one or more fields of data. ** Records are used to store the content of a table row and to store @@ -12366,20 +14392,40 @@ struct KeyInfo { ** the OP_MakeRecord opcode of the VDBE and is disassembled by the ** OP_Column opcode. ** -** This structure holds a record that has already been disassembled -** into its constituent fields. +** An instance of this object serves as a "key" for doing a search on +** an index b+tree. The goal of the search is to find the entry that +** is closed to the key described by this object. This object might hold +** just a prefix of the key. The number of fields is given by +** pKeyInfo->nField. ** -** The r1 and r2 member variables are only used by the optimized comparison -** functions vdbeRecordCompareInt() and vdbeRecordCompareString(). +** The r1 and r2 fields are the values to return if this key is less than +** or greater than a key in the btree, respectively. These are normally +** -1 and +1 respectively, but might be inverted to +1 and -1 if the b-tree +** is in DESC order. +** +** The key comparison functions actually return default_rc when they find +** an equals comparison. default_rc can be -1, 0, or +1. If there are +** multiple entries in the b-tree with the same key (when only looking +** at the first pKeyInfo->nFields,) then default_rc can be set to -1 to +** cause the search to find the last match, or +1 to cause the search to +** find the first match. +** +** The key comparison functions will set eqSeen to true if they ever +** get and equal results when comparing this structure to a b-tree record. +** When default_rc!=0, the search might end up on the record immediately +** before the first match or immediately after the last match. The +** eqSeen field will indicate whether or not an exact match exists in the +** b-tree. */ struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ + Mem *aMem; /* Values */ 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) */ - Mem *aMem; /* Values */ - int r1; /* Value to return if (lhs > rhs) */ - int r2; /* Value to return if (rhs < lhs) */ + i8 r1; /* Value to return if (lhs > rhs) */ + i8 r2; /* Value to return if (rhs < lhs) */ + u8 eqSeen; /* True if an equality comparison has been seen */ }; @@ -12397,7 +14443,7 @@ struct UnpackedRecord { ** In the Table structure describing Ex1, nCol==3 because there are ** three columns in the table. In the Index structure describing ** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. -** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the ** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. ** The second column to be indexed (c1) has an index of 0 in ** Ex1.aCol[], hence Ex2.aiColumn[1]==0. @@ -12405,7 +14451,7 @@ struct UnpackedRecord { ** The Index.onError field determines whether or not the indexed columns ** must be unique and what to do if they are not. When Index.onError=OE_None, ** it means this is not a unique index. Otherwise it is a unique index -** and the value of Index.onError indicate the which conflict resolution +** and the value of Index.onError indicate the which conflict resolution ** algorithm to employ whenever an attempt is made to insert a non-unique ** element. ** @@ -12426,7 +14472,7 @@ struct Index { Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ - char **azColl; /* Array of collation sequence names for index */ + const char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ ExprList *aColExpr; /* Column expressions */ int tnum; /* DB Page containing root of this index */ @@ -12470,7 +14516,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_stat3 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. */ @@ -12565,9 +14611,9 @@ typedef int ynVar; ** to represent the greater-than-or-equal-to operator in the expression ** tree. ** -** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, +** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, ** or TK_STRING), then Expr.token contains the text of the SQL literal. If -** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** the expression is a variable (TK_VARIABLE), then Expr.token contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), ** then Expr.token contains the name of the function. ** @@ -12578,7 +14624,7 @@ typedef int ynVar; ** a CASE expression or an IN expression of the form " IN (, ...)". ** Expr.x.pSelect is used if the expression is a sub-select or an expression of ** the form " IN (SELECT ...)". If the EP_xIsSelect bit is set in the -** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is +** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is ** valid. ** ** An expression of the form ID or ID.ID refers to a column in a table. @@ -12589,8 +14635,8 @@ typedef int ynVar; ** value is also stored in the Expr.iAgg column in the aggregate so that ** it can be accessed after all aggregates are computed. ** -** If the expression is an unbound variable marker (a question mark -** character '?' in the original SQL) then the Expr.iTable holds the index +** If the expression is an unbound variable marker (a question mark +** character '?' in the original SQL) then the Expr.iTable holds the index ** number for that variable. ** ** If the expression is a subquery then Expr.iColumn holds an integer @@ -12629,7 +14675,7 @@ struct Expr { /* If the EP_TokenOnly flag is set in the Expr.flags mask, then no ** space is allocated for the fields below this point. An attempt to - ** access them will result in a segfault or malfunction. + ** access them will result in a segfault or malfunction. *********************************************************************/ Expr *pLeft; /* Left subnode */ @@ -12695,7 +14741,7 @@ struct Expr { #define EP_Propagate (EP_Collate|EP_Subquery) /* Propagate these bits up tree */ /* -** These macros can be used to test, set, or clear bits in the +** These macros can be used to test, set, or clear bits in the ** Expr.flags field. */ #define ExprHasProperty(E,P) (((E)->flags&(P))!=0) @@ -12714,8 +14760,8 @@ struct Expr { #endif /* -** Macros to determine the number of bytes required by a normal Expr -** struct, an Expr struct with the EP_Reduced flag set in Expr.flags +** Macros to determine the number of bytes required by a normal Expr +** struct, an Expr struct with the EP_Reduced flag set in Expr.flags ** and an Expr struct with the EP_TokenOnly flag set. */ #define EXPR_FULLSIZE sizeof(Expr) /* Full size */ @@ -12723,7 +14769,7 @@ struct Expr { #define EXPR_TOKENONLYSIZE offsetof(Expr,pLeft) /* Fewer features */ /* -** Flags passed to the sqlite3ExprDup() function. See the header comment +** Flags passed to the sqlite3ExprDup() function. See the header comment ** above sqlite3ExprDup() for details. */ #define EXPRDUP_REDUCE 0x0001 /* Used reduced-size Expr nodes */ @@ -12805,7 +14851,11 @@ struct IdList { ** tables in a join to 32 instead of 64. But it also reduces the size ** of the library by 738 bytes on ix86. */ -typedef u64 Bitmask; +#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". @@ -12817,6 +14867,7 @@ typedef u64 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. @@ -12889,6 +14940,9 @@ struct SrcList { /* ** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin() ** and the WhereInfo.wctrlFlags member. +** +** Value constraints (enforced via assert()): +** WHERE_USE_LIMIT == SF_FixedLimit */ #define WHERE_ORDERBY_NORMAL 0x0000 /* No-op */ #define WHERE_ORDERBY_MIN 0x0001 /* ORDER BY processing for min() func */ @@ -12905,6 +14959,8 @@ struct SrcList { #define WHERE_SORTBYGROUP 0x0800 /* Support sqlite3WhereIsSorted() */ #define WHERE_REOPEN_IDX 0x1000 /* Try to use OP_ReopenIdx */ #define WHERE_ONEPASS_MULTIROW 0x2000 /* ONEPASS is ok with multiple rows */ +#define WHERE_USE_LIMIT 0x4000 /* There is a constant LIMIT clause */ +#define WHERE_SEEK_TABLE 0x8000 /* Do not defer seeks on main table */ /* Allowed return values from sqlite3WhereIsDistinct() */ @@ -12922,12 +14978,12 @@ struct SrcList { ** pEList corresponds to the result set of a SELECT and is NULL for ** other statements. ** -** NameContexts can be nested. When resolving names, the inner-most +** NameContexts can be nested. When resolving names, the inner-most ** context is searched first. If no match is found, the next outer ** context is checked. If there is still no match, the next context ** is checked. This process continues until either a match is found ** or all contexts are check. When a match is found, the nRef member of -** the context containing the match is incremented. +** the context containing the match is incremented. ** ** Each subquery gets a new NameContext. The pNext field points to the ** NameContext in the parent query. Thus the process of scanning the @@ -12948,16 +15004,18 @@ struct NameContext { /* ** Allowed values for the NameContext, ncFlags field. ** -** Note: NC_MinMaxAgg must have the same value as SF_MinMaxAgg and -** SQLITE_FUNC_MINMAX. -** +** Value constraints (all checked via assert()): +** NC_HasAgg == SF_HasAgg +** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** */ #define NC_AllowAgg 0x0001 /* Aggregate functions are allowed here */ -#define NC_HasAgg 0x0002 /* One or more aggregate functions seen */ +#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_PartIdx 0x0010 /* True if resolving a partial index WHERE */ +#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 */ /* @@ -12983,13 +15041,13 @@ struct NameContext { struct Select { ExprList *pEList; /* The fields of the result */ u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ - u16 selFlags; /* Various SF_* values */ + 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 int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ - u64 nSelectRow; /* Estimated number of result rows */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ ExprList *pGroupBy; /* The GROUP BY clause */ @@ -13005,22 +15063,30 @@ struct Select { /* ** Allowed values for Select.selFlags. The "SF" prefix stands for ** "Select Flag". +** +** Value constraints (all checked via assert()) +** SF_HasAgg == NC_HasAgg +** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX +** SF_FixedLimit == WHERE_USE_LIMIT */ -#define SF_Distinct 0x0001 /* Output should be DISTINCT */ -#define SF_All 0x0002 /* Includes the ALL keyword */ -#define SF_Resolved 0x0004 /* Identifiers have been resolved */ -#define SF_Aggregate 0x0008 /* Contains aggregate functions */ -#define SF_UsesEphemeral 0x0010 /* Uses the OpenEphemeral opcode */ -#define SF_Expanded 0x0020 /* sqlite3SelectExpand() called on this */ -#define SF_HasTypeInfo 0x0040 /* FROM subqueries have Table metadata */ -#define SF_Compound 0x0080 /* Part of a compound query */ -#define SF_Values 0x0100 /* Synthesized from VALUES clause */ -#define SF_MultiValue 0x0200 /* Single VALUES term with multiple rows */ -#define SF_NestedFrom 0x0400 /* Part of a parenthesized FROM clause */ -#define SF_MaybeConvert 0x0800 /* Need convertCompoundSelectToSubquery() */ -#define SF_MinMaxAgg 0x1000 /* Aggregate containing min() or max() */ -#define SF_Recursive 0x2000 /* The recursive part of a recursive CTE */ -#define SF_Converted 0x4000 /* By convertCompoundSelectToSubquery() */ +#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 */ /* @@ -13028,7 +15094,7 @@ struct Select { ** by one of the following macros. The "SRT" prefix means "SELECT Result ** Type". ** -** SRT_Union Store results as a key in a temporary index +** SRT_Union Store results as a key in a temporary index ** identified by pDest->iSDParm. ** ** SRT_Except Remove results from the temporary index pDest->iSDParm. @@ -13052,7 +15118,7 @@ struct Select { ** of the query. This destination implies "LIMIT 1". ** ** SRT_Set The result must be a single column. Store each -** row of result as the key in table pDest->iSDParm. +** row of result as the key in table pDest->iSDParm. ** Apply the affinity pDest->affSdst before storing ** results. Used to implement "IN (SELECT ...)". ** @@ -13120,11 +15186,11 @@ struct SelectDest { }; /* -** During code generation of statements that do inserts into AUTOINCREMENT +** During code generation of statements that do inserts into AUTOINCREMENT ** tables, the following information is attached to the Table.u.autoInc.p ** pointer of each autoincrement table to record some side information that ** the code generator needs. We have to keep per-table autoincrement -** information in case inserts are down within triggers. Triggers do not +** information in case inserts are done within triggers. Triggers do not ** normally coordinate their activities, but we do need to coordinate the ** loading and saving of autoincrement information. */ @@ -13143,7 +15209,7 @@ struct AutoincInfo { #endif /* -** At least one instance of the following structure is created for each +** 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 ** Parse.pTriggerPrg and deleted once statement compilation has been @@ -13156,7 +15222,7 @@ struct AutoincInfo { ** values for both pTrigger and orconf. ** ** The TriggerPrg.aColmask[0] variable is set to a mask of old.* columns -** accessed (or set to 0 for triggers fired as a result of INSERT +** accessed (or set to 0 for triggers fired as a result of INSERT ** statements). Similarly, the TriggerPrg.aColmask[1] variable is set to ** a mask of new.* columns used by the program. */ @@ -13197,7 +15263,7 @@ struct TriggerPrg { ** is constant but the second part is reset at the beginning and end of ** each recursion. ** -** The nTableLock and aTableLock variables are only used if the shared-cache +** The nTableLock and aTableLock variables are only used if the shared-cache ** feature is enabled (if sqlite3Tsd()->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 @@ -13216,6 +15282,8 @@ struct Parse { u8 mayAbort; /* True if statement may throw an ABORT exception */ 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[] */ int aTempReg[8]; /* Holding area for temporary registers */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ @@ -13225,6 +15293,7 @@ struct Parse { int nSet; /* Number of sets used so far */ int nOnce; /* Number of OP_Once instructions so far */ int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ + int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */ int ckBase; /* Base register of data during check constraints */ int iSelfTab; /* Table of an index whose exprs are being coded */ @@ -13276,10 +15345,9 @@ struct Parse { ** in the recursive region. ************************************************************************/ - int nVar; /* Number of '?' variables seen in the SQL so far */ + 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 bFreeWith; /* True if pWith should be freed with parser */ u8 explain; /* True if the EXPLAIN flag is found on the query */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ @@ -13306,6 +15374,7 @@ struct Parse { Table *pZombieTab; /* List of Table objects to delete after code gen */ 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 */ }; /* @@ -13328,26 +15397,42 @@ struct AuthContext { /* ** Bitfield flags for P5 value in various opcodes. +** +** Value constraints (enforced via assert()): +** OPFLAG_LENGTHARG == SQLITE_FUNC_LENGTH +** OPFLAG_TYPEOFARG == SQLITE_FUNC_TYPEOF +** OPFLAG_BULKCSR == BTREE_BULKLOAD +** OPFLAG_SEEKEQ == BTREE_SEEK_EQ +** OPFLAG_FORDELETE == BTREE_FORDELETE +** OPFLAG_SAVEPOSITION == BTREE_SAVEPOSITION +** OPFLAG_AUXDELETE == BTREE_AUXDELETE */ -#define OPFLAG_NCHANGE 0x01 /* Set to update db->nChange */ +#define OPFLAG_NCHANGE 0x01 /* OP_Insert: Set to update db->nChange */ + /* Also used in P2 (not P5) of OP_Delete */ #define OPFLAG_EPHEM 0x01 /* OP_Column: Ephemeral output is ok */ #define OPFLAG_LASTROWID 0x02 /* 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 */ #define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ -#define OPFLAG_P2ISREG 0x04 /* P2 to OP_Open** is a register number */ +#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_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ /* * Each trigger present in the database schema is stored as an instance of - * struct Trigger. + * 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 sqlite3* 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 @@ -13373,7 +15458,7 @@ struct Trigger { /* ** A trigger is either a BEFORE or an AFTER trigger. The following constants -** determine which. +** determine which. ** ** If there are multiple triggers, you might of some BEFORE and some AFTER. ** In that cases, the constants below can be ORed together. @@ -13383,15 +15468,15 @@ struct Trigger { /* * An instance of struct TriggerStep is used to store a single SQL statement - * that is a part of a trigger-program. + * that is a part of a trigger-program. * * Instances of struct TriggerStep are stored in a singly linked list (linked - * using the "pNext" member) referenced by the "step_list" member of the + * using the "pNext" member) referenced by the "step_list" member of the * associated struct Trigger instance. The first element of the linked list is * the first step of the trigger-program. - * + * * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or - * "SELECT" statement. The meanings of the other members is determined by the + * "SELECT" statement. The meanings of the other members is determined by the * value of "op" as follows: * * (op == TK_INSERT) @@ -13401,7 +15486,7 @@ struct Trigger { * zTarget -> Dequoted name of the table to insert into. * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then * this stores values to be inserted. Otherwise NULL. - * pIdList -> If this is an INSERT INTO ... () VALUES ... + * pIdList -> If this is an INSERT INTO ... () VALUES ... * statement, then this stores the column-names to be * inserted into. * @@ -13409,7 +15494,7 @@ struct Trigger { * zTarget -> Dequoted name of the table to delete from. * pWhere -> The WHERE clause of the DELETE statement if one is specified. * Otherwise NULL. - * + * * (op == TK_UPDATE) * zTarget -> Dequoted name of the table to update. * pWhere -> The WHERE clause of the UPDATE statement if one is specified. @@ -13417,7 +15502,7 @@ struct Trigger { * pExprList -> A list of the columns to update and the expressions to update * them to. See sqlite3Update() documentation of "pChanges" * argument. - * + * */ struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ @@ -13435,7 +15520,7 @@ struct TriggerStep { /* ** The following structure contains information used by the sqliteFix... ** routines as they walk the parse tree to make database references -** explicit. +** explicit. */ typedef struct DbFixer DbFixer; struct DbFixer { @@ -13455,13 +15540,20 @@ struct StrAccum { sqlite3 *db; /* Optional database for lookaside. Can be NULL */ char *zBase; /* A base allocation. Not from malloc. */ char *zText; /* The string collected so far */ - int nChar; /* Length of the string so far */ - int nAlloc; /* Amount of space allocated in zText */ - int mxAlloc; /* Maximum allowed allocation. 0 for no malloc usage */ + 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 */ + 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 */ + +#define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) + /* ** A pointer to this structure is used to communicate information @@ -13489,6 +15581,7 @@ struct Sqlite3Config { 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 */ @@ -13556,10 +15649,10 @@ struct Sqlite3Config { ** Context pointer passed down through the tree-walk. */ struct Walker { + Parse *pParse; /* Parser context. */ int (*xExprCallback)(Walker*, Expr*); /* Callback for expressions */ int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */ void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ - Parse *pParse; /* Parser context. */ int walkerDepth; /* Number of subqueries */ u8 eCode; /* A small processing code */ union { /* Extra data for callback */ @@ -13568,6 +15661,8 @@ struct Walker { 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 */ } u; }; @@ -13577,6 +15672,7 @@ 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*); /* ** Return code from the parse-tree walking primitives and their @@ -13635,7 +15731,23 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); #define SQLITE_CORRUPT_BKPT sqlite3CorruptError(__LINE__) #define SQLITE_MISUSE_BKPT sqlite3MisuseError(__LINE__) #define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__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__) +#else +# define SQLITE_NOMEM_BKPT SQLITE_NOMEM +# define SQLITE_IOERR_NOMEM_BKPT SQLITE_IOERR_NOMEM +#endif +/* +** FTS3 and FTS4 both require virtual table support +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) +# undef SQLITE_ENABLE_FTS3 +# undef SQLITE_ENABLE_FTS4 +#endif /* ** FTS4 is really an extension for FTS3. It is enabled using the @@ -13668,6 +15780,7 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); # 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)) @@ -13676,6 +15789,7 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); # 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); @@ -13684,8 +15798,9 @@ SQLITE_PRIVATE int sqlite3IsIdChar(u8); /* ** Internal function prototypes */ -#define sqlite3StrICmp sqlite3_stricmp +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); @@ -13694,6 +15809,7 @@ 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); @@ -13723,7 +15839,7 @@ SQLITE_PRIVATE int sqlite3HeapNearlyFull(void); #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) # define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) -# define sqlite3StackFree(D,P) +# define sqlite3StackFree(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) # define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) @@ -13754,7 +15870,7 @@ SQLITE_PRIVATE void sqlite3MemoryBarrier(void); SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int); SQLITE_PRIVATE void sqlite3StatusUp(int, int); SQLITE_PRIVATE void sqlite3StatusDown(int, int); -SQLITE_PRIVATE void sqlite3StatusSet(int, int); +SQLITE_PRIVATE void sqlite3StatusHighwater(int, int); /* Access to mutexes used by sqlite3_status() */ SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void); @@ -13776,10 +15892,8 @@ struct PrintfArguments { sqlite3_value **apArg; /* The argument values */ }; -#define SQLITE_PRINTF_INTERNAL 0x01 -#define SQLITE_PRINTF_SQLFUNC 0x02 -SQLITE_PRIVATE void sqlite3VXPrintf(StrAccum*, u32, const char*, va_list); -SQLITE_PRIVATE void sqlite3XPrintf(StrAccum*, u32, const char*, ...); +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); #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) @@ -13793,12 +15907,14 @@ SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); 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 int sqlite3Dequote(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*); @@ -13807,10 +15923,14 @@ SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int); SQLITE_PRIVATE int sqlite3GetTempRange(Parse*,int); SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse*,int,int); SQLITE_PRIVATE void sqlite3ClearTempRegCache(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*); @@ -13827,20 +15947,24 @@ 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 sqlite3BeginParse(Parse*,int); 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 void sqlite3AddColumn(Parse*,Token*); +#if SQLITE_ENABLE_HIDDEN_COLUMNS +SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*); +#else +# define sqlite3ColumnPropertiesFromName(T,C) /* no-op */ +#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 sqlite3AddColumnType(Parse*,Token*); SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,ExprSpan*); SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*); SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); @@ -13914,7 +16038,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList* 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*,u16,Expr*,Expr*); + Expr*,ExprList*,u32,Expr*,Expr*); SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3*, Select*); SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse*, SrcList*); SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, int); @@ -13926,7 +16050,7 @@ 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 u64 sqlite3WhereOutputRowCount(WhereInfo*); +SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*); @@ -13938,6 +16062,7 @@ SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*, int*); #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); @@ -13947,6 +16072,7 @@ 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*); @@ -13992,6 +16118,9 @@ SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); +#ifdef SQLITE_ENABLE_CURSOR_HINTS +SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); +#endif SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); @@ -14002,9 +16131,9 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, 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*); + 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, int, u8*, 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*); @@ -14021,11 +16150,11 @@ SQLITE_PRIVATE void sqlite3SelectSetName(Select*,const char*); #else # define sqlite3SelectSetName(A,B) #endif -SQLITE_PRIVATE void sqlite3FuncDefInsert(FuncDefHash*, FuncDef*); -SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,u8); -SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(sqlite3*); +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 sqlite3RegisterGlobalFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); @@ -14104,7 +16233,11 @@ SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst); #ifndef SQLITE_OMIT_VIRTUALTABLE SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); #endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ + defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \ + defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); +#endif /* ** Routines to read and write variable-length integers. These used to @@ -14139,6 +16272,7 @@ 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 **); @@ -14171,7 +16305,7 @@ SQLITE_PRIVATE u8 sqlite3GetBoolean(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 void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); @@ -14181,11 +16315,12 @@ SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_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 SQLITE_WSD FuncDefHash sqlite3GlobalFunctions; +SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; #ifndef SQLITE_OMIT_WSD SQLITE_PRIVATE int sqlite3PendingByte; #endif @@ -14220,7 +16355,6 @@ 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 sqlite3MinimumFileFormat(Parse*, int, int); SQLITE_PRIVATE void sqlite3SchemaClear(void *); SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *); SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *); @@ -14231,11 +16365,13 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo*); #endif -SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, +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*), FuncDestructor *pDestructor ); +SQLITE_PRIVATE void sqlite3OomFault(sqlite3*); +SQLITE_PRIVATE void sqlite3OomClear(sqlite3*); SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); @@ -14292,7 +16428,7 @@ SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*); # define sqlite3VtabRollback(X) # define sqlite3VtabCommit(X) # define sqlite3VtabInSync(db) 0 -# define sqlite3VtabLock(X) +# define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK @@ -14350,7 +16486,7 @@ SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8); ** no-op macros if OMIT_FOREIGN_KEY is defined. In this case no foreign ** key functionality is available. If OMIT_TRIGGER is defined but ** OMIT_FOREIGN_KEY is not, only some of the functions are no-oped. In -** this case foreign keys are parsed, but no other functionality is +** this case foreign keys are parsed, but no other functionality is ** provided (enforcement of FK constraints requires the triggers sub-system). */ #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) @@ -14411,19 +16547,14 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void); #define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, 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 sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); -SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *); SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *); -SQLITE_PRIVATE int sqlite3JournalExists(sqlite3_file *p); -#else - #define sqlite3JournalSize(pVfs) ((pVfs)->szOsFile) - #define sqlite3JournalExists(p) 1 #endif +SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p); SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *); -SQLITE_PRIVATE int sqlite3MemJournalSize(void); -SQLITE_PRIVATE int sqlite3IsMemJournal(sqlite3_file *); SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #if SQLITE_MAX_EXPR_DEPTH>0 @@ -14454,7 +16585,7 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *); /* ** If the SQLITE_ENABLE IOTRACE exists then the global variable ** sqlite3IoTrace is a pointer to a printf-like routine used to -** print I/O tracing messages. +** print I/O tracing messages. */ #ifdef SQLITE_ENABLE_IOTRACE # define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; } @@ -14488,7 +16619,7 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); ** that allocations that might have been satisfied by lookaside are not ** passed back to non-lookaside free() routines. Asserts such as the ** example above are placed on the non-lookaside free() routines to verify -** this constraint. +** this constraint. ** ** All of this is no-op for a production build. It only comes into ** play when the SQLITE_MEMDEBUG compile-time option is used. @@ -14595,6 +16726,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { ** isxdigit() 0x08 ** toupper() 0x20 ** SQLite identifier character 0x40 +** Quote character 0x80 ** ** Bit 0x20 is set if the mapped character requires translation to upper ** case. i.e. if the character is a lower-case ASCII character. @@ -14620,7 +16752,7 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17 ........ */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f ........ */ - 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, /* 20..27 !"#$%&' */ + 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27 !"#$%&' */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f ()*+,-./ */ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37 01234567 */ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f 89:;<=>? */ @@ -14628,8 +16760,8 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47 @ABCDEFG */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f HIJKLMNO */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57 PQRSTUVW */ - 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, /* 58..5f XYZ[\]^_ */ - 0x00, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67 `abcdefg */ + 0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f XYZ[\]^_ */ + 0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67 `abcdefg */ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f hijklmno */ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77 pqrstuvw */ 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f xyz{|}~. */ @@ -14684,6 +16816,18 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { # define SQLITE_SORTER_PMASZ 250 #endif +/* Statement journals spill to disk when their size exceeds the following +** threashold (in bytes). 0 means that statement journals are created and +** written to disk immediately (the default behavior for SQLite versions +** before 3.12.0). -1 means always keep the entire statement journal in +** memory. (The statement journal is also always held entirely in memory +** if journal_mode=MEMORY or if temp_store=MEMORY, regardless of this +** setting.) +*/ +#ifndef SQLITE_STMTJRNL_SPILL +# define SQLITE_STMTJRNL_SPILL (64*1024) +#endif + /* ** The following singleton contains the global configuration for ** the SQLite library. @@ -14698,6 +16842,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* neverCorrupt */ 128, /* szLookaside */ 500, /* nLookaside */ + SQLITE_STMTJRNL_SPILL, /* nStmtSpill */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ {0,0,0,0,0,0,0,0,0,0,0,0,0},/* pcache2 */ @@ -14744,7 +16889,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { ** database connections. After initialization, this table is ** read-only. */ -SQLITE_PRIVATE SQLITE_WSD FuncDefHash sqlite3GlobalFunctions; +SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; /* ** Constant tokens for values 0 and 1. @@ -14786,6 +16931,11 @@ SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; */ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; +/* +** Name of the default collating sequence +*/ +SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; + /************** End of global.c **********************************************/ /************** Begin file ctime.c *******************************************/ /* @@ -14853,6 +17003,9 @@ static const char * const azCompileOpt[] = { #if SQLITE_DISABLE_LFS "DISABLE_LFS", #endif +#if SQLITE_ENABLE_8_3_NAMES + "ENABLE_8_3_NAMES", +#endif #if SQLITE_ENABLE_API_ARMOR "ENABLE_API_ARMOR", #endif @@ -14948,6 +17101,9 @@ static const char * const azCompileOpt[] = { #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 @@ -15273,6 +17429,17 @@ SQLITE_API const char *SQLITE_STDCALL sqlite3_compileoption_get(int N){ # define SQLITE_MAX_SCHEMA_RETRY 50 #endif +/* +** VDBE_DISPLAY_P4 is true or false depending on whether or not the +** "explain" P4 display logic is enabled. +*/ +#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \ + || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) +# define VDBE_DISPLAY_P4 1 +#else +# define VDBE_DISPLAY_P4 0 +#endif + /* ** SQL is translated into a sequence of instructions to be ** executed by a virtual machine. Each instruction is an instance @@ -15294,42 +17461,52 @@ typedef struct Explain Explain; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; +/* Types of VDBE cursors */ +#define CURTYPE_BTREE 0 +#define CURTYPE_SORTER 1 +#define CURTYPE_VTAB 2 +#define CURTYPE_PSEUDO 3 + /* -** A cursor is a pointer into a single BTree within a database file. -** The cursor can seek to a BTree entry with a particular key, or -** loop over all entries of the Btree. You can also insert new BTree -** entries or retrieve the key or data from the entry that the cursor -** is currently pointing to. +** A VdbeCursor is an superclass (a wrapper) for various cursor objects: ** -** Cursors can also point to virtual tables, sorters, or "pseudo-tables". -** A pseudo-table is a single-row table implemented by registers. -** -** Every cursor that the virtual machine has open is represented by an -** instance of the following structure. +** * A b-tree cursor +** - In the main database or in an ephemeral database +** - On either an index or a table +** * A sorter +** * A virtual table +** * A one-row "pseudotable" stored in a single register */ +typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { - BtCursor *pCursor; /* The cursor structure of the backend */ - Btree *pBt; /* Separate file holding temporary table */ - KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ - int seekResult; /* Result of previous sqlite3BtreeMoveto() */ - int pseudoTableReg; /* Register holding pseudotable content. */ - i16 nField; /* Number of fields in the header */ - u16 nHdrParsed; /* Number of header fields parsed so far */ -#ifdef SQLITE_DEBUG - u8 seekOp; /* Most recent seek operation on this cursor */ -#endif + 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 */ +#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 isTable:1; /* True if a table requiring integer keys */ - Bool isOrdered:1; /* True if the underlying table is BTREE_UNORDERED */ + Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Pgno pgnoRoot; /* Root page of the open btree cursor */ - sqlite3_vtab_cursor *pVtabCursor; /* The cursor for a virtual table */ + i16 nField; /* Number of fields in the header */ + u16 nHdrParsed; /* Number of header fields parsed so far */ + 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 */ + } 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() */ - VdbeSorter *pSorter; /* Sorter object for OP_SorterOpen cursors */ + VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ + int *aAltMap; /* Mapping from table to index column numbers */ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif @@ -15354,7 +17531,6 @@ struct VdbeCursor { ** static element declared in the structure. nField total array slots for ** aType[] and nField+1 array slots for aOffset[] */ }; -typedef struct VdbeCursor VdbeCursor; /* ** When a sub-program is executed (OP_Program), a structure of this type @@ -15388,6 +17564,7 @@ struct VdbeFrame { VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ void *token; /* Copy of SubProgram.token */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ + AuxData *pAuxData; /* Linked list of auxdata allocations */ int nCursor; /* Number of entries in apCsr */ int pc; /* Program Counter in parent (calling) frame */ int nOp; /* Size of aOp array */ @@ -15465,7 +17642,7 @@ struct Mem { #define MEM_Frame 0x0040 /* Value is a VdbeFrame object */ #define MEM_Undefined 0x0080 /* Value is undefined */ #define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ -#define MEM_TypeMask 0x01ff /* Mask of type bits */ +#define MEM_TypeMask 0x81ff /* Mask of type bits */ /* Whenever Mem contains a valid string or blob representation, one of @@ -15479,11 +17656,18 @@ struct Mem { #define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ #define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ #define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x8000 /* Mem.eSubtype is valid */ #ifdef SQLITE_OMIT_INCRBLOB #undef MEM_Zero #define MEM_Zero 0x0000 #endif +/* Return TRUE if Mem X contains dynamically allocated content - anything +** that needs to be deallocated to avoid a leak. +*/ +#define VdbeMemDynamic(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0) + /* ** Clear any existing type flags from a Mem and replace them with f */ @@ -15600,16 +17784,16 @@ struct Vdbe { #endif u16 nResColumn; /* Number of columns in one row of the result set */ u8 errorAction; /* Recovery action to do in case of an error */ + bft expired:1; /* True if the VM needs to be recompiled */ + bft doingRerun:1; /* True if rerunning after an auto-reprepare */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft explain:2; /* True if EXPLAIN present on SQL command */ bft changeCntOn:1; /* True to update the change-counter */ - bft expired:1; /* True if the VM needs to be recompiled */ 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() */ - bft doingRerun:1; /* True if rerunning after an auto-reprepare */ int nChange; /* Number of db changes made since last reset */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ @@ -15647,22 +17831,42 @@ struct Vdbe { #define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */ #define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */ +/* +** Structure used to store the context required by the +** sqlite3_preupdate_*() API functions. +*/ +struct PreUpdate { + Vdbe *v; + VdbeCursor *pCsr; /* Cursor to read old values from */ + int op; /* One of SQLITE_INSERT, UPDATE, DELETE */ + u8 *aRecord; /* old.* database record */ + KeyInfo keyinfo; + UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ + UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ + int iNewReg; /* Register for new.* values */ + i64 iKey1; /* First key value passed to hook */ + i64 iKey2; /* Second key value passed to hook */ + int iPKey; /* If not negative index of IPK column */ + Mem *aNew; /* Array of new.* values */ +}; + /* ** Function prototypes */ SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...); SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); void sqliteVdbePopStack(Vdbe*,int); -SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor*); +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 u32 sqlite3VdbeSerialType(Mem*, int); +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(Vdbe*, int, int); +SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); @@ -15698,8 +17902,6 @@ 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); -#define VdbeMemDynamic(X) \ - (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0) SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); SQLITE_PRIVATE const char *sqlite3OpcodeName(int); SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); @@ -15707,6 +17909,9 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *, int); SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int); +#endif SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p); SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *); @@ -15718,11 +17923,15 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); -#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 +#if !defined(SQLITE_OMIT_SHARED_CACHE) SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe*); -SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe*); #else # define sqlite3VdbeEnter(X) +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 +SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe*); +#else # define sqlite3VdbeLeave(X) #endif @@ -15760,15 +17969,15 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *); /* ** Variables in which to record status information. */ +#if SQLITE_PTRSIZE>4 +typedef sqlite3_int64 sqlite3StatValueType; +#else +typedef u32 sqlite3StatValueType; +#endif typedef struct sqlite3StatType sqlite3StatType; static SQLITE_WSD struct sqlite3StatType { -#if SQLITE_PTRSIZE>4 - sqlite3_int64 nowValue[10]; /* Current value */ - sqlite3_int64 mxValue[10]; /* Maximum value */ -#else - u32 nowValue[10]; /* Current value */ - u32 mxValue[10]; /* Maximum value */ -#endif + sqlite3StatValueType nowValue[10]; /* Current value */ + sqlite3StatValueType mxValue[10]; /* Maximum value */ } sqlite3Stat = { {0,}, {0,} }; /* @@ -15849,18 +18058,24 @@ SQLITE_PRIVATE void sqlite3StatusDown(int op, int N){ } /* -** Set the value of a status to X. The highwater mark is adjusted if -** necessary. The caller must hold the appropriate mutex. +** Adjust the highwater mark if necessary. +** The caller must hold the appropriate mutex. */ -SQLITE_PRIVATE void sqlite3StatusSet(int op, int X){ +SQLITE_PRIVATE void sqlite3StatusHighwater(int op, int X){ + sqlite3StatValueType newValue; wsdStatInit; + assert( X>=0 ); + newValue = (sqlite3StatValueType)X; assert( op>=0 && op=0 && opwsdStat.mxValue[op] ){ - wsdStat.mxValue[op] = wsdStat.nowValue[op]; + 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; } } @@ -15993,10 +18208,10 @@ SQLITE_API int SQLITE_STDCALL sqlite3_db_status( + pSchema->idxHash.count + pSchema->fkeyHash.count ); - nByte += sqlite3MallocSize(pSchema->tblHash.ht); - nByte += sqlite3MallocSize(pSchema->trigHash.ht); - nByte += sqlite3MallocSize(pSchema->idxHash.ht); - nByte += sqlite3MallocSize(pSchema->fkeyHash.ht); + nByte += sqlite3_msize(pSchema->tblHash.ht); + nByte += sqlite3_msize(pSchema->trigHash.ht); + nByte += sqlite3_msize(pSchema->idxHash.ht); + nByte += sqlite3_msize(pSchema->fkeyHash.ht); for(p=sqliteHashFirst(&pSchema->trigHash); p; p=sqliteHashNext(p)){ sqlite3DeleteTrigger(db, (Trigger*)sqliteHashData(p)); @@ -16134,6 +18349,15 @@ SQLITE_API int SQLITE_STDCALL sqlite3_db_status( #ifndef SQLITE_OMIT_DATETIME_FUNCS +/* +** The MSVC CRT on Windows CE may not have a localtime() function. +** So declare a substitute. The substitute function itself is +** defined in "os_win.c". +*/ +#if !defined(SQLITE_OMIT_LOCALTIME) && defined(_WIN32_WCE) && \ + (!defined(SQLITE_MSVC_LOCALTIME_API) || !SQLITE_MSVC_LOCALTIME_API) +struct tm *__cdecl localtime(const time_t *); +#endif /* ** A structure for holding a single date and time. @@ -16149,38 +18373,54 @@ struct DateTime { 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 */ }; /* -** Convert zDate into one or more integers. Additional arguments -** come in groups of 5 as follows: +** Convert zDate into one or more integers according to the conversion +** specifier zFormat. ** -** N number of digits in the integer -** min minimum allowed value of the integer -** max maximum allowed value of the integer -** nextC first character after the integer -** pVal where to write the integers value. +** zFormat[] contains 4 characters for each integer converted, except for +** the last integer which is specified by three characters. The meaning +** of a four-character format specifiers ABCD is: +** +** A: number of digits to convert. Always "2" or "4". +** B: minimum value. Always "0" or "1". +** C: maximum value, decoded as: +** a: 12 +** b: 14 +** c: 24 +** d: 31 +** e: 59 +** f: 9999 +** D: the separator character, or \000 to indicate this is the +** last number to convert. +** +** Example: To translate an ISO-8601 date YYYY-MM-DD, the format would +** be "40f-21a-20c". The "40f-" indicates the 4-digit year followed by "-". +** The "21a-" indicates the 2-digit month followed by "-". The "20c" indicates +** the 2-digit day which is the last integer in the set. ** -** Conversions continue until one with nextC==0 is encountered. ** The function returns the number of successful conversions. */ -static int getDigits(const char *zDate, ...){ +static int getDigits(const char *zDate, const char *zFormat, ...){ + /* The aMx[] array translates the 3rd character of each format + ** spec into a max size: a b c d e f */ + static const u16 aMx[] = { 12, 14, 24, 31, 59, 9999 }; va_list ap; - int val; - int N; - int min; - int max; - int nextC; - int *pVal; int cnt = 0; - va_start(ap, zDate); + char nextC; + va_start(ap, zFormat); do{ - N = va_arg(ap, int); - min = va_arg(ap, int); - max = va_arg(ap, int); - nextC = va_arg(ap, int); - pVal = va_arg(ap, int*); + char N = zFormat[0] - '0'; + char min = zFormat[1] - '0'; + int val = 0; + u16 max; + + assert( zFormat[2]>='a' && zFormat[2]<='f' ); + max = aMx[zFormat[2] - 'a']; + nextC = zFormat[3]; val = 0; while( N-- ){ if( !sqlite3Isdigit(*zDate) ){ @@ -16189,12 +18429,13 @@ static int getDigits(const char *zDate, ...){ val = val*10 + *zDate - '0'; zDate++; } - if( valmax || (nextC!=0 && nextC!=*zDate) ){ + if( val<(int)min || val>(int)max || (nextC!=0 && nextC!=*zDate) ){ goto end_getDigits; } - *pVal = val; + *va_arg(ap,int*) = val; zDate++; cnt++; + zFormat += 4; }while( nextC ); end_getDigits: va_end(ap); @@ -16235,13 +18476,14 @@ static int parseTimezone(const char *zDate, DateTime *p){ return c!=0; } zDate++; - if( getDigits(zDate, 2, 0, 14, ':', &nHr, 2, 0, 59, 0, &nMn)!=2 ){ + if( getDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){ return 1; } zDate += 5; p->tz = sgn*(nMn + nHr*60); zulu_time: while( sqlite3Isspace(*zDate) ){ zDate++; } + p->tzSet = 1; return *zDate!=0; } @@ -16255,13 +18497,13 @@ zulu_time: static int parseHhMmSs(const char *zDate, DateTime *p){ int h, m, s; double ms = 0.0; - if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){ + if( getDigits(zDate, "20c:20e", &h, &m)!=2 ){ return 1; } zDate += 5; if( *zDate==':' ){ zDate++; - if( getDigits(zDate, 2, 0, 59, 0, &s)!=1 ){ + if( getDigits(zDate, "20e", &s)!=1 ){ return 1; } zDate += 2; @@ -16349,7 +18591,7 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ }else{ neg = 0; } - if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){ + if( getDigits(zDate, "40f-21a-21d", &Y, &M, &D)!=3 ){ return 1; } zDate += 10; @@ -16484,6 +18726,7 @@ static void clearYMD_HMS_TZ(DateTime *p){ p->validTZ = 0; } +#ifndef SQLITE_OMIT_LOCALTIME /* ** On recent Windows platforms, the localtime_s() function is available ** as part of the "Secure CRT". It is essentially equivalent to @@ -16502,7 +18745,6 @@ static void clearYMD_HMS_TZ(DateTime *p){ #define HAVE_LOCALTIME_S 1 #endif -#ifndef SQLITE_OMIT_LOCALTIME /* ** The following routine implements the rough equivalent of localtime_r() ** using whatever operating-system specific localtime facility that @@ -16674,13 +18916,18 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ } #ifndef SQLITE_OMIT_LOCALTIME else if( strcmp(z, "utc")==0 ){ - sqlite3_int64 c1; - computeJD(p); - c1 = localtimeOffset(p, pCtx, &rc); - if( rc==SQLITE_OK ){ - p->iJD -= c1; - clearYMD_HMS_TZ(p); - p->iJD += c1 - localtimeOffset(p, pCtx, &rc); + if( p->tzSet==0 ){ + sqlite3_int64 c1; + computeJD(p); + c1 = localtimeOffset(p, pCtx, &rc); + if( rc==SQLITE_OK ){ + p->iJD -= c1; + clearYMD_HMS_TZ(p); + p->iJD += c1 - localtimeOffset(p, pCtx, &rc); + } + p->tzSet = 1; + }else{ + rc = SQLITE_OK; } } #endif @@ -17028,7 +19275,7 @@ static void strftimeFunc( sqlite3_result_error_toobig(context); return; }else{ - z = sqlite3DbMallocRaw(db, (int)n); + z = sqlite3DbMallocRawNN(db, (int)n); if( z==0 ){ sqlite3_result_error_nomem(context); return; @@ -17197,7 +19444,7 @@ static void currentTimeFunc( ** external linkage. */ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ - static SQLITE_WSD FuncDef aDateTimeFuncs[] = { + static FuncDef aDateTimeFuncs[] = { #ifndef SQLITE_OMIT_DATETIME_FUNCS DFUNCTION(julianday, -1, 0, 0, juliandayFunc ), DFUNCTION(date, -1, 0, 0, dateFunc ), @@ -17213,13 +19460,7 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0, currentTimeFunc), #endif }; - int i; - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); - FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aDateTimeFuncs); - - for(i=0; ipMethods ){ - rc = pId->pMethods->xClose(pId); + pId->pMethods->xClose(pId); pId->pMethods = 0; } - return rc; } SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file *id, void *pBuf, int amt, i64 offset){ DO_OS_MALLOC_TEST(id); @@ -17336,8 +19597,8 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ #ifdef SQLITE_TEST if( op!=SQLITE_FCNTL_COMMIT_PHASETWO ){ /* 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 + ** is using a regular VFS, it is called after the corresponding + ** transaction has been committed. Injecting a fault at this point ** confuses the test scripts - the COMMIT comand returns SQLITE_NOMEM ** but the transaction is committed anyway. ** @@ -17406,10 +19667,10 @@ SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){ ** VFS methods. */ SQLITE_PRIVATE int sqlite3OsOpen( - sqlite3_vfs *pVfs, - const char *zPath, - sqlite3_file *pFile, - int flags, + sqlite3_vfs *pVfs, + const char *zPath, + sqlite3_file *pFile, + int flags, int *pFlagsOut ){ int rc; @@ -17428,18 +19689,18 @@ SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dir return pVfs->xDelete(pVfs, zPath, dirSync); } SQLITE_PRIVATE int sqlite3OsAccess( - sqlite3_vfs *pVfs, - const char *zPath, - int flags, + sqlite3_vfs *pVfs, + const char *zPath, + int flags, int *pResOut ){ DO_OS_MALLOC_TEST(0); return pVfs->xAccess(pVfs, zPath, flags, pResOut); } SQLITE_PRIVATE int sqlite3OsFullPathname( - sqlite3_vfs *pVfs, - const char *zPath, - int nPathOut, + sqlite3_vfs *pVfs, + const char *zPath, + int nPathOut, char *zPathOut ){ DO_OS_MALLOC_TEST(0); @@ -17466,6 +19727,9 @@ SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufO SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){ return pVfs->xSleep(pVfs, nMicro); } +SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs *pVfs){ + return pVfs->xGetLastError ? pVfs->xGetLastError(pVfs, 0, 0) : 0; +} SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ int rc; /* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64() @@ -17485,13 +19749,13 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p } SQLITE_PRIVATE int sqlite3OsOpenMalloc( - sqlite3_vfs *pVfs, - const char *zFile, - sqlite3_file **ppFile, + sqlite3_vfs *pVfs, + const char *zFile, + sqlite3_file **ppFile, int flags, int *pOutFlags ){ - int rc = SQLITE_NOMEM; + int rc; sqlite3_file *pFile; pFile = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile); if( pFile ){ @@ -17501,15 +19765,15 @@ SQLITE_PRIVATE int sqlite3OsOpenMalloc( }else{ *ppFile = pFile; } + }else{ + rc = SQLITE_NOMEM_BKPT; } return rc; } -SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *pFile){ - int rc = SQLITE_OK; +SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *pFile){ assert( pFile ); - rc = sqlite3OsClose(pFile); + sqlite3OsClose(pFile); sqlite3_free(pFile); - return rc; } /* @@ -17520,7 +19784,7 @@ SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *pFile){ */ SQLITE_PRIVATE int sqlite3OsInit(void){ void *p = sqlite3_malloc(10); - if( p==0 ) return SQLITE_NOMEM; + if( p==0 ) return SQLITE_NOMEM_BKPT; sqlite3_free(p); return sqlite3_os_init(); } @@ -17947,10 +20211,11 @@ static void sqlite3MemFree(void *pPrior){ */ static int sqlite3MemSize(void *pPrior){ #ifdef SQLITE_MALLOCSIZE - return pPrior ? (int)SQLITE_MALLOCSIZE(pPrior) : 0; + assert( pPrior!=0 ); + return (int)SQLITE_MALLOCSIZE(pPrior); #else sqlite3_int64 *p; - if( pPrior==0 ) return 0; + assert( pPrior!=0 ); p = (sqlite3_int64*)pPrior; p--; return (int)p[0]; @@ -19080,7 +21345,7 @@ static void memsys3FreeUnsafe(void *pOld){ */ static int memsys3Size(void *p){ Mem3Block *pBlock; - if( p==0 ) return 0; + assert( p!=0 ); pBlock = (Mem3Block*)p; assert( (pBlock[-1].u.hdr.size4x&1)!=0 ); return (pBlock[-1].u.hdr.size4x&~3)*2 - 4; @@ -19319,7 +21584,7 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){ ** ** This memory allocator uses the following algorithm: ** -** 1. All memory allocations sizes are rounded up to a power of 2. +** 1. All memory allocation sizes are rounded up to a power of 2. ** ** 2. If two adjacent free blocks are the halves of a larger block, ** then the two blocks are coalesced into the single larger block. @@ -19396,6 +21661,7 @@ static SQLITE_WSD struct Mem5Global { */ sqlite3_mutex *mutex; +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) /* ** Performance statistics */ @@ -19407,11 +21673,12 @@ static SQLITE_WSD struct Mem5Global { u32 maxOut; /* Maximum instantaneous currentOut */ u32 maxCount; /* Maximum instantaneous currentCount */ u32 maxRequest; /* Largest allocation (exclusive of internal frag) */ +#endif /* ** Lists of free blocks. aiFreelist[0] is a list of free blocks of ** size mem5.szAtom. aiFreelist[1] holds blocks of size szAtom*2. - ** and so forth. + ** aiFreelist[2] holds free blocks of size szAtom*4. And so forth. */ int aiFreelist[LOGMAX+1]; @@ -19477,9 +21744,7 @@ static void memsys5Link(int i, int iLogsize){ } /* -** 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. +** Obtain or release the mutex needed to access global data structures. */ static void memsys5Enter(void){ sqlite3_mutex_enter(mem5.mutex); @@ -19489,17 +21754,15 @@ static void memsys5Leave(void){ } /* -** Return the size of an outstanding allocation, in bytes. The -** size returned omits the 8-byte header overhead. This only -** works for chunks that are currently checked out. +** Return the size of an outstanding allocation, in bytes. +** This only works for chunks that are currently checked out. */ static int memsys5Size(void *p){ - int iSize = 0; - if( p ){ - int i = (int)(((u8 *)p-mem5.zPool)/mem5.szAtom); - assert( i>=0 && i=0 && i0 ); + /* No more than 1GiB per allocation */ + if( nByte > 0x40000000 ) return 0; + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) /* Keep track of the maximum allocation request. Even unfulfilled ** requests are counted */ if( (u32)nByte>mem5.maxRequest ){ mem5.maxRequest = nByte; } +#endif - /* Abort if the requested allocation size is larger than the largest - ** power of two that we can represent using 32-bit signed integers. - */ - if( nByte > 0x40000000 ){ - return 0; - } /* Round nByte up to the next valid power of two */ - for(iFullSz=mem5.szAtom, iLogsize=0; iFullSz0 ); assert( mem5.currentOut>=(size*mem5.szAtom) ); mem5.currentCount--; mem5.currentOut -= size*mem5.szAtom; assert( mem5.currentOut>0 || mem5.currentCount==0 ); assert( mem5.currentCount>0 || mem5.currentOut==0 ); +#endif mem5.aCtrl[iBlock] = CTRL_FREE | iLogsize; while( ALWAYS(iLogsize>iLogsize) & 1 ){ iBuddy = iBlock - size; + assert( iBuddy>=0 ); }else{ iBuddy = iBlock + size; + if( iBuddy>=mem5.nBlock ) break; } - assert( iBuddy>=0 ); - if( (iBuddy+(1<mem5.nBlock ) break; if( mem5.aCtrl[iBuddy]!=(CTRL_FREE | iLogsize) ) break; memsys5Unlink(iBuddy, iLogsize); iLogsize++; @@ -19693,13 +21960,11 @@ static void *memsys5Realloc(void *pPrior, int nBytes){ if( nBytes<=nOld ){ return pPrior; } - memsys5Enter(); - p = memsys5MallocUnsafe(nBytes); + p = memsys5Malloc(nBytes); if( p ){ memcpy(p, pPrior, nOld); - memsys5FreeUnsafe(pPrior); + memsys5Free(pPrior); } - memsys5Leave(); return p; } @@ -20313,7 +22578,9 @@ 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 {PTHREAD_MUTEX_INITIALIZER,0,0,(pthread_t)0,0} +#elif defined(SQLITE_ENABLE_API_ARMOR) +#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0 } #else #define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER } #endif @@ -20707,8 +22974,8 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ */ #ifdef SQLITE_PERFORMANCE_TRACE -/* -** hwtime.h contains inline assembler code for implementing +/* +** hwtime.h contains inline assembler code for implementing ** high-performance timing routines. */ /************** Include hwtime.h in the middle of os_common.h ****************/ @@ -20818,14 +23085,14 @@ static sqlite_uint64 g_elapsed; ** of code will give us the ability to simulate a disk I/O error. This ** is used for testing the I/O recovery logic. */ -#ifdef 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; +#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) #define SimulateIOError(CODE) \ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ @@ -20851,17 +23118,17 @@ static void local_ioerr(){ #define SimulateIOErrorBenign(X) #define SimulateIOError(A) #define SimulateDiskfullError(A) -#endif +#endif /* defined(SQLITE_TEST) */ /* ** When testing, keep a count of the number of open files. */ -#ifdef SQLITE_TEST -SQLITE_API int sqlite3_open_file_count = 0; +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; #define OpenCounter(X) sqlite3_open_file_count+=(X) #else #define OpenCounter(X) -#endif +#endif /* defined(SQLITE_TEST) */ #endif /* !defined(_OS_COMMON_H_) */ @@ -21475,9 +23742,7 @@ SQLITE_PRIVATE int sqlite3MallocInit(void){ sqlite3MemSetDefault(); } memset(&mem0, 0, sizeof(mem0)); - if( sqlite3GlobalConfig.bCoreMutex ){ - mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); - } + mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); if( sqlite3GlobalConfig.pScratch && sqlite3GlobalConfig.szScratch>=100 && sqlite3GlobalConfig.nScratch>0 ){ int i, n, sz; @@ -21568,7 +23833,7 @@ static int mallocWithAlarm(int n, void **pp){ void *p; assert( sqlite3_mutex_held(mem0.mutex) ); nFull = sqlite3GlobalConfig.m.xRoundup(n); - sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, n); + sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n); if( mem0.alarmThreshold>0 ){ sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); if( nUsed >= mem0.alarmThreshold - nFull ){ @@ -21660,7 +23925,7 @@ SQLITE_PRIVATE void *sqlite3ScratchMalloc(int n){ assert( n>0 ); sqlite3_mutex_enter(mem0.mutex); - sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n); + sqlite3StatusHighwater(SQLITE_STATUS_SCRATCH_SIZE, n); if( mem0.nScratchFree && sqlite3GlobalConfig.szScratch>=n ){ p = mem0.pScratchFree; mem0.pScratchFree = mem0.pScratchFree->pNext; @@ -21704,7 +23969,7 @@ SQLITE_PRIVATE void sqlite3ScratchFree(void *p){ scratchAllocOut--; #endif - if( p>=sqlite3GlobalConfig.pScratch && p=db->lookaside.pStart && plookaside.pEnd; + return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd); } #else #define isLookaside(A,B) 0 @@ -21755,6 +24020,7 @@ SQLITE_PRIVATE int sqlite3MallocSize(void *p){ return sqlite3GlobalConfig.m.xSize(p); } SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ + assert( p!=0 ); if( db==0 || !isLookaside(db,p) ){ #if SQLITE_DEBUG if( db==0 ){ @@ -21774,7 +24040,7 @@ SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ SQLITE_API sqlite3_uint64 SQLITE_STDCALL sqlite3_msize(void *p){ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - return (sqlite3_uint64)sqlite3GlobalConfig.m.xSize(p); + return p ? sqlite3GlobalConfig.m.xSize(p) : 0; } /* @@ -21862,7 +24128,7 @@ SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ pNew = pOld; }else if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); - sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); + sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); nDiff = nNew - nOld; if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= mem0.alarmThreshold-nDiff ){ @@ -21920,16 +24186,31 @@ SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){ ** the mallocFailed flag in the connection pointer. */ SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, u64 n){ - void *p = sqlite3DbMallocRaw(db, n); - if( p ){ - memset(p, 0, (size_t)n); - } + void *p; + testcase( db==0 ); + p = sqlite3DbMallocRaw(db, n); + if( p ) memset(p, 0, (size_t)n); + return p; +} + + +/* Finish the work of sqlite3DbMallocRawNN for the unusual and +** slower case when the allocation cannot be fulfilled using lookaside. +*/ +static SQLITE_NOINLINE void *dbMallocRawFinish(sqlite3 *db, u64 n){ + void *p; + assert( db!=0 ); + p = sqlite3Malloc(n); + if( !p ) sqlite3OomFault(db); + sqlite3MemdebugSetType(p, + (db->lookaside.bDisable==0) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP); return p; } /* -** Allocate and zero memory. If the allocation fails, make -** the mallocFailed flag in the connection pointer. +** Allocate memory, either lookaside (if possible) or heap. +** If the allocation fails, set the mallocFailed flag in +** the connection pointer. ** ** If db!=0 and db->mallocFailed is true (indicating a prior malloc ** failure on the same database connection) then always return 0. @@ -21944,64 +24225,73 @@ SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, u64 n){ ** ** 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 +** not a NULL pointer. */ SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, u64 n){ void *p; - assert( db==0 || sqlite3_mutex_held(db->mutex) ); - assert( db==0 || db->pnBytesFreed==0 ); + if( db ) return sqlite3DbMallocRawNN(db, n); + p = sqlite3Malloc(n); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + return p; +} +SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3 *db, u64 n){ #ifndef SQLITE_OMIT_LOOKASIDE - if( db ){ - LookasideSlot *pBuf; - if( db->mallocFailed ){ - return 0; - } - if( db->lookaside.bEnabled ){ - 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++; - db->lookaside.anStat[0]++; - if( db->lookaside.nOut>db->lookaside.mxOut ){ - db->lookaside.mxOut = db->lookaside.nOut; - } - return (void*)pBuf; + LookasideSlot *pBuf; + assert( db!=0 ); + assert( sqlite3_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++; + 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; } #else - if( db && db->mallocFailed ){ + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( db->pnBytesFreed==0 ); + if( db->mallocFailed ){ return 0; } #endif - p = sqlite3Malloc(n); - if( !p && db ){ - db->mallocFailed = 1; - } - sqlite3MemdebugSetType(p, - (db && db->lookaside.bEnabled) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP); - return p; + return dbMallocRawFinish(db, n); } +/* Forward declaration */ +static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *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){ + 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; + return dbReallocFinish(db, p, n); +} +static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){ void *pNew = 0; assert( db!=0 ); - assert( sqlite3_mutex_held(db->mutex) ); + assert( p!=0 ); if( db->mallocFailed==0 ){ - if( p==0 ){ - return sqlite3DbMallocRaw(db, n); - } if( isLookaside(db, p) ){ - if( n<=db->lookaside.sz ){ - return p; - } - pNew = sqlite3DbMallocRaw(db, n); + pNew = sqlite3DbMallocRawNN(db, n); if( pNew ){ memcpy(pNew, p, db->lookaside.sz); sqlite3DbFree(db, p); @@ -22012,10 +24302,10 @@ SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n){ sqlite3MemdebugSetType(p, MEMTYPE_HEAP); pNew = sqlite3_realloc64(p, n); if( !pNew ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } sqlite3MemdebugSetType(pNew, - (db->lookaside.bEnabled ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP)); + (db->lookaside.bDisable==0 ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP)); } } return pNew; @@ -22057,11 +24347,12 @@ SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){ } SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ char *zNew; + assert( db!=0 ); if( z==0 ){ return 0; } assert( (n&0x7fffffff)==n ); - zNew = sqlite3DbMallocRaw(db, n+1); + zNew = sqlite3DbMallocRawNN(db, n+1); if( zNew ){ memcpy(zNew, z, (size_t)n); zNew[n] = 0; @@ -22077,13 +24368,45 @@ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ *pz = sqlite3DbStrDup(db, zNew); } +/* +** Call this routine to record the fact that an OOM (out-of-memory) error +** has happened. This routine will set db->mallocFailed, and also +** temporarily disable the lookaside memory allocator and interrupt +** any running VDBEs. +*/ +SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){ + if( db->mallocFailed==0 && db->bBenignMalloc==0 ){ + db->mallocFailed = 1; + if( db->nVdbeExec>0 ){ + db->u1.isInterrupted = 1; + } + db->lookaside.bDisable++; + } +} + +/* +** This routine reactivates the memory allocator and clears the +** db->mallocFailed flag as necessary. +** +** The memory allocator is not restarted if there are running +** VDBEs. +*/ +SQLITE_PRIVATE void sqlite3OomClear(sqlite3 *db){ + if( db->mallocFailed && db->nVdbeExec==0 ){ + db->mallocFailed = 0; + db->u1.isInterrupted = 0; + assert( db->lookaside.bDisable>0 ); + db->lookaside.bDisable--; + } +} + /* ** Take actions at the end of an API call to indicate an OOM error */ static SQLITE_NOINLINE int apiOomError(sqlite3 *db){ - db->mallocFailed = 0; + sqlite3OomClear(db); sqlite3Error(db, SQLITE_NOMEM); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* @@ -22130,26 +24453,26 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ ** Conversion types fall into various categories as defined by the ** following enumeration. */ -#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */ -#define etFLOAT 2 /* Floating point. %f */ -#define etEXP 3 /* Exponentional notation. %e and %E */ -#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */ -#define etSIZE 5 /* Return number of characters processed so far. %n */ -#define etSTRING 6 /* Strings. %s */ -#define etDYNSTRING 7 /* Dynamically allocated strings. %z */ -#define etPERCENT 8 /* Percent symbol. %% */ -#define etCHARX 9 /* Characters. %c */ +#define etRADIX 0 /* Integer types. %d, %x, %o, and so forth */ +#define etFLOAT 1 /* Floating point. %f */ +#define etEXP 2 /* Exponentional notation. %e and %E */ +#define etGENERIC 3 /* Floating or exponential, depending on exponent. %g */ +#define etSIZE 4 /* Return number of characters processed so far. %n */ +#define etSTRING 5 /* Strings. %s */ +#define etDYNSTRING 6 /* Dynamically allocated strings. %z */ +#define etPERCENT 7 /* Percent symbol. %% */ +#define etCHARX 8 /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ -#define etSQLESCAPE 10 /* Strings with '\'' doubled. %q */ -#define etSQLESCAPE2 11 /* Strings with '\'' doubled and enclosed in '', +#define etSQLESCAPE 9 /* Strings with '\'' doubled. %q */ +#define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ -#define etTOKEN 12 /* a pointer to a Token structure */ -#define etSRCLIST 13 /* a pointer to a SrcList */ -#define etPOINTER 14 /* The %p conversion */ -#define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */ -#define etORDINAL 16 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ +#define etTOKEN 11 /* a pointer to a Token structure */ +#define etSRCLIST 12 /* a pointer to a SrcList */ +#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 etINVALID 0 /* Any unrecognized conversion type */ +#define etINVALID 16 /* Any unrecognized conversion type */ /* @@ -22286,7 +24609,6 @@ static char *getTextArg(PrintfArguments *p){ */ SQLITE_PRIVATE void sqlite3VXPrintf( StrAccum *pAccum, /* Accumulate results here */ - u32 bFlags, /* SQLITE_PRINTF_* flags */ const char *fmt, /* Format string */ va_list ap /* arguments */ ){ @@ -22305,7 +24627,7 @@ SQLITE_PRIVATE void sqlite3VXPrintf( etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ - etByte xtype = 0; /* Conversion paradigm */ + 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'. */ @@ -22326,11 +24648,11 @@ SQLITE_PRIVATE void sqlite3VXPrintf( char buf[etBUFSIZE]; /* Conversion buffer */ bufpt = 0; - if( bFlags ){ - if( (bArgList = (bFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ + if( pAccum->printfFlags ){ + if( (bArgList = (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ pArgList = va_arg(ap, PrintfArguments*); } - useIntern = bFlags & SQLITE_PRINTF_INTERNAL; + useIntern = pAccum->printfFlags & SQLITE_PRINTF_INTERNAL; }else{ bArgList = useIntern = 0; } @@ -22385,6 +24707,12 @@ SQLITE_PRIVATE void sqlite3VXPrintf( testcase( wx>0x7fffffff ); width = wx & 0x7fffffff; } + assert( width>=0 ); +#ifdef SQLITE_PRINTF_PRECISION_LIMIT + if( width>SQLITE_PRINTF_PRECISION_LIMIT ){ + width = SQLITE_PRINTF_PRECISION_LIMIT; + } +#endif /* Get the precision */ if( c=='.' ){ @@ -22411,6 +24739,14 @@ SQLITE_PRIVATE void sqlite3VXPrintf( }else{ precision = -1; } + assert( precision>=(-1) ); +#ifdef 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; @@ -22841,7 +25177,7 @@ SQLITE_PRIVATE void sqlite3VXPrintf( if( width>0 && flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); if( zExtra ){ - sqlite3_free(zExtra); + sqlite3DbFree(pAccum->db, zExtra); zExtra = 0; } }/* End for loop over the format string */ @@ -22867,8 +25203,9 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ setStrAccumError(p, STRACCUM_TOOBIG); return N; }else{ - char *zOld = (p->zText==p->zBase ? 0 : p->zText); + 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, @@ -22889,9 +25226,10 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ } if( zNew ){ assert( p->zText!=0 || p->nChar==0 ); - if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); + if( !isMalloced(p) && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; p->nAlloc = sqlite3DbMallocSize(p->db, zNew); + p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3StrAccumReset(p); setStrAccumError(p, STRACCUM_NOMEM); @@ -22909,6 +25247,7 @@ SQLITE_PRIVATE void sqlite3AppendChar(StrAccum *p, int N, char c){ if( p->nChar+(i64)N >= p->nAlloc && (N = sqlite3StrAccumEnlarge(p, N))<=0 ){ return; } + assert( (p->zText==p->zBase)==!isMalloced(p) ); while( (N--)>0 ) p->zText[p->nChar++] = c; } @@ -22926,6 +25265,7 @@ static void SQLITE_NOINLINE enlargeAndAppend(StrAccum *p, const char *z, int N){ memcpy(&p->zText[p->nChar], z, N); p->nChar += N; } + assert( (p->zText==0 || p->zText==p->zBase)==!isMalloced(p) ); } /* @@ -22961,11 +25301,13 @@ SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){ */ SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ if( p->zText ){ + assert( (p->zText==p->zBase)==!isMalloced(p) ); p->zText[p->nChar] = 0; - if( p->mxAlloc>0 && p->zText==p->zBase ){ + 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); } @@ -22978,8 +25320,10 @@ SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ ** Reset an StrAccum string. Reclaim all malloced memory. */ SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum *p){ - if( p->zText!=p->zBase ){ + assert( (p->zText==0 || p->zText==p->zBase)==!isMalloced(p) ); + if( isMalloced(p) ){ sqlite3DbFree(p->db, p->zText); + p->printfFlags &= ~SQLITE_PRINTF_MALLOCED; } p->zText = 0; } @@ -23005,6 +25349,7 @@ SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum *p, sqlite3 *db, char *zBase, i p->nAlloc = n; p->mxAlloc = mx; p->accError = 0; + p->printfFlags = 0; } /* @@ -23018,10 +25363,11 @@ SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list a assert( db!=0 ); sqlite3StrAccumInit(&acc, db, zBase, sizeof(zBase), db->aLimit[SQLITE_LIMIT_LENGTH]); - sqlite3VXPrintf(&acc, SQLITE_PRINTF_INTERNAL, zFormat, ap); + acc.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3VXPrintf(&acc, zFormat, ap); z = sqlite3StrAccumFinish(&acc); if( acc.accError==STRACCUM_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } return z; } @@ -23058,7 +25404,7 @@ SQLITE_API char *SQLITE_STDCALL sqlite3_vmprintf(const char *zFormat, va_list ap if( sqlite3_initialize() ) return 0; #endif sqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + sqlite3VXPrintf(&acc, zFormat, ap); z = sqlite3StrAccumFinish(&acc); return z; } @@ -23103,7 +25449,7 @@ SQLITE_API char *SQLITE_STDCALL sqlite3_vsnprintf(int n, char *zBuf, const char } #endif sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + sqlite3VXPrintf(&acc, zFormat, ap); return sqlite3StrAccumFinish(&acc); } SQLITE_API char *SQLITE_CDECL sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ @@ -23134,7 +25480,7 @@ static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + sqlite3VXPrintf(&acc, zFormat, ap); sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, sqlite3StrAccumFinish(&acc)); } @@ -23163,7 +25509,7 @@ SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){ char zBuf[500]; sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); va_start(ap,zFormat); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + sqlite3VXPrintf(&acc, zFormat, ap); va_end(ap); sqlite3StrAccumFinish(&acc); fprintf(stdout,"%s", zBuf); @@ -23176,10 +25522,10 @@ SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){ ** variable-argument wrapper around sqlite3VXPrintf(). The bFlags argument ** can contain the bit SQLITE_PRINTF_INTERNAL enable internal formats. */ -SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, u32 bFlags, const char *zFormat, ...){ +SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, const char *zFormat, ...){ va_list ap; va_start(ap,zFormat); - sqlite3VXPrintf(p, bFlags, zFormat, ap); + sqlite3VXPrintf(p, zFormat, ap); va_end(ap); } @@ -23250,7 +25596,7 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); } va_start(ap, zFormat); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + sqlite3VXPrintf(&acc, zFormat, ap); va_end(ap); if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1); sqlite3StrAccumFinish(&acc); @@ -23266,6 +25612,45 @@ static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){ sqlite3TreeViewLine(p, "%s", zLabel); } +/* +** Generate a human-readable description of a WITH clause. +*/ +SQLITE_PRIVATE void sqlite3TreeViewWith(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); + }else{ + sqlite3TreeViewLine(pView, "WITH (0x%p)", pWith); + } + if( pWith->nCte>0 ){ + pView = sqlite3TreeViewPush(pView, 1); + for(i=0; inCte; 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); + if( pCte->pCols && pCte->pCols->nExpr>0 ){ + char cSep = '('; + int j; + for(j=0; jpCols->nExpr; j++){ + sqlite3XPrintf(&x, "%c%s", cSep, pCte->pCols->a[j].zName); + cSep = ','; + } + sqlite3XPrintf(&x, ")"); + } + sqlite3XPrintf(&x, " AS"); + sqlite3StrAccumFinish(&x); + sqlite3TreeViewItem(pView, zLine, inCte-1); + sqlite3TreeViewSelect(pView, pCte->pSelect, 0); + sqlite3TreeViewPop(pView); + } + sqlite3TreeViewPop(pView); + } +} + /* ** Generate a human-readable description of a the Select object. @@ -23274,10 +25659,16 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m int n = 0; int cnt = 0; pView = sqlite3TreeViewPush(pView, moreToFollow); + if( p->pWith ){ + sqlite3TreeViewWith(pView, p->pWith, 1); + cnt = 1; + sqlite3TreeViewPush(pView, 1); + } do{ - sqlite3TreeViewLine(pView, "SELECT%s%s (0x%p) selFlags=0x%x", + 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 + ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), p, p->selFlags, + (int)p->nSelectRow ); if( cnt++ ) sqlite3TreeViewPop(pView); if( p->pPrior ){ @@ -23302,20 +25693,20 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m StrAccum x; char zLine[100]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); - sqlite3XPrintf(&x, 0, "{%d,*}", pItem->iCursor); + sqlite3XPrintf(&x, "{%d,*}", pItem->iCursor); if( pItem->zDatabase ){ - sqlite3XPrintf(&x, 0, " %s.%s", pItem->zDatabase, pItem->zName); + sqlite3XPrintf(&x, " %s.%s", pItem->zDatabase, pItem->zName); }else if( pItem->zName ){ - sqlite3XPrintf(&x, 0, " %s", pItem->zName); + sqlite3XPrintf(&x, " %s", pItem->zName); } if( pItem->pTab ){ - sqlite3XPrintf(&x, 0, " tabname=%Q", pItem->pTab->zName); + sqlite3XPrintf(&x, " tabname=%Q", pItem->pTab->zName); } if( pItem->zAlias ){ - sqlite3XPrintf(&x, 0, " (AS %s)", pItem->zAlias); + sqlite3XPrintf(&x, " (AS %s)", pItem->zAlias); } if( pItem->fg.jointype & JT_LEFT ){ - sqlite3XPrintf(&x, 0, " LEFT-JOIN"); + sqlite3XPrintf(&x, " LEFT-JOIN"); } sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, ipSrc->nSrc-1); @@ -23481,6 +25872,12 @@ 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_SPAN: { + sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } + case TK_COLLATE: { sqlite3TreeViewLine(pView, "COLLATE %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); @@ -23832,7 +26229,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( *ppThread = 0; p = sqlite3Malloc(sizeof(*p)); - if( p==0 ) return SQLITE_NOMEM; + if( p==0 ) return SQLITE_NOMEM_BKPT; memset(p, 0, sizeof(*p)); p->xTask = xTask; p->pIn = pIn; @@ -23858,7 +26255,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ int rc; assert( ppOut!=0 ); - if( NEVER(p==0) ) return SQLITE_NOMEM; + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; if( p->done ){ *ppOut = p->pOut; rc = SQLITE_OK; @@ -23923,7 +26320,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( assert( xTask!=0 ); *ppThread = 0; p = sqlite3Malloc(sizeof(*p)); - if( p==0 ) return SQLITE_NOMEM; + 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 @@ -23955,7 +26352,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ BOOL bRc; assert( ppOut!=0 ); - if( NEVER(p==0) ) return SQLITE_NOMEM; + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; if( p->xTask==0 ){ /* assert( p->id==GetCurrentThreadId() ); */ rc = WAIT_OBJECT_0; @@ -24003,7 +26400,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( assert( xTask!=0 ); *ppThread = 0; p = sqlite3Malloc(sizeof(*p)); - if( p==0 ) return SQLITE_NOMEM; + if( p==0 ) return SQLITE_NOMEM_BKPT; if( (SQLITE_PTR_TO_INT(p)/17)&1 ){ p->xTask = xTask; p->pIn = pIn; @@ -24019,7 +26416,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ assert( ppOut!=0 ); - if( NEVER(p==0) ) return SQLITE_NOMEM; + if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT; if( p->xTask ){ *ppOut = p->xTask(p->pIn); }else{ @@ -24030,7 +26427,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ #if defined(SQLITE_TEST) { void *pTstAlloc = sqlite3Malloc(10); - if (!pTstAlloc) return SQLITE_NOMEM; + if (!pTstAlloc) return SQLITE_NOMEM_BKPT; sqlite3_free(pTstAlloc); } #endif @@ -24083,13 +26480,13 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ /* #include */ /* #include "vdbeInt.h" */ -#ifndef SQLITE_AMALGAMATION +#if !defined(SQLITE_AMALGAMATION) && SQLITE_BYTEORDER==0 /* ** The following constant value is used by the SQLITE_BIGENDIAN and ** SQLITE_LITTLEENDIAN macros. */ SQLITE_PRIVATE const int sqlite3one = 1; -#endif /* SQLITE_AMALGAMATION */ +#endif /* SQLITE_AMALGAMATION && SQLITE_BYTEORDER==0 */ /* ** This lookup table is used to help decode the first byte of @@ -24277,7 +26674,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired rc = sqlite3VdbeMemMakeWriteable(pMem); if( rc!=SQLITE_OK ){ assert( rc==SQLITE_NOMEM ); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } zIn = (u8*)pMem->z; zTerm = &zIn[pMem->n&~1]; @@ -24319,7 +26716,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired zTerm = &zIn[pMem->n]; zOut = sqlite3DbMallocRaw(pMem->db, len); if( !zOut ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } z = zOut; @@ -24362,7 +26759,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired c = pMem->flags; sqlite3VdbeMemRelease(pMem); - pMem->flags = MEM_Str|MEM_Term|(c&MEM_AffMask); + pMem->flags = MEM_Str|MEM_Term|(c&(MEM_AffMask|MEM_Subtype)); pMem->enc = desiredEnc; pMem->z = (char*)zOut; pMem->zMalloc = pMem->z; @@ -24688,13 +27085,49 @@ SQLITE_PRIVATE int sqlite3Strlen30(const char *z){ return 0x3fffffff & (int)strlen(z); } +/* +** Return the declared type of a column. Or return zDflt if the column +** has no declared type. +** +** 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){ + if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt; + return pCol->zName + strlen(pCol->zName) + 1; +} + +/* +** Helper function for sqlite3Error() - called rarely. Broken out into +** a separate routine to avoid unnecessary register saves on entry to +** sqlite3Error(). +*/ +static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){ + if( db->pErr ) sqlite3ValueSetNull(db->pErr); + sqlite3SystemError(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 +** that would be appropriate. */ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ assert( db!=0 ); db->errCode = err_code; - if( db->pErr ) sqlite3ValueSetNull(db->pErr); + if( err_code || db->pErr ) sqlite3ErrorFinish(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){ + if( rc==SQLITE_IOERR_NOMEM ) return; + rc &= 0xff; + if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ + db->iSysErrno = sqlite3OsGetLastError(db->pVfs); + } } /* @@ -24721,6 +27154,7 @@ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){ assert( db!=0 ); db->errCode = err_code; + sqlite3SystemError(db, err_code); if( zFormat==0 ){ sqlite3Error(db, err_code); }else if( db->pErr || (db->pErr = sqlite3ValueNew(db))!=0 ){ @@ -24784,18 +27218,13 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ ** brackets from around identifiers. For example: "[a-b-c]" becomes ** "a-b-c". */ -SQLITE_PRIVATE int sqlite3Dequote(char *z){ +SQLITE_PRIVATE void sqlite3Dequote(char *z){ char quote; int i, j; - if( z==0 ) return -1; + if( z==0 ) return; quote = z[0]; - switch( quote ){ - case '\'': break; - case '"': break; - case '`': break; /* For MySQL compatibility */ - case '[': quote = ']'; break; /* For MS SqlServer compatibility */ - default: return -1; - } + if( !sqlite3Isquote(quote) ) return; + if( quote=='[' ) quote = ']'; for(i=1, j=0;; i++){ assert( z[i] ); if( z[i]==quote ){ @@ -24810,7 +27239,14 @@ SQLITE_PRIVATE int sqlite3Dequote(char *z){ } } z[j] = 0; - return j; +} + +/* +** Generate a Token object from a string +*/ +SQLITE_PRIVATE void sqlite3TokenInit(Token *p, char *z){ + p->z = z; + p->n = sqlite3Strlen30(z); } /* Convenient short-hand */ @@ -24827,16 +27263,25 @@ SQLITE_PRIVATE int sqlite3Dequote(char *z){ ** independence" that SQLite uses internally when comparing identifiers. */ SQLITE_API int SQLITE_STDCALL sqlite3_stricmp(const char *zLeft, const char *zRight){ - register unsigned char *a, *b; if( zLeft==0 ){ return zRight ? -1 : 0; }else if( zRight==0 ){ return 1; } + return sqlite3StrICmp(zLeft, zRight); +} +SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){ + unsigned char *a, *b; + int c; a = (unsigned char *)zLeft; b = (unsigned char *)zRight; - while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } - return UpperToLower[*a] - UpperToLower[*b]; + for(;;){ + c = (int)UpperToLower[*a] - (int)UpperToLower[*b]; + if( c || *a==0 ) break; + a++; + b++; + } + return c; } SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ register unsigned char *a, *b; @@ -24886,7 +27331,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en int eValid = 1; /* True exponent is either not used or is well-formed */ double result; int nDigits = 0; - int nonNum = 0; + int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); *pResult = 0.0; /* Default return value, in case of an error */ @@ -24899,7 +27344,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); for(i=3-enc; i=zEnd ) goto do_atof_calc; @@ -24947,7 +27390,12 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en if( *z=='e' || *z=='E' ){ z+=incr; eValid = 0; - if( z>=zEnd ) goto do_atof_calc; + + /* This branch is needed to avoid a (harmless) buffer overread. The + ** special comment alerts the mutation tester that the correct answer + ** is obtained even if the branch is omitted */ + if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ + /* get sign of exponent */ if( *z=='-' ){ esign = -1; @@ -24964,9 +27412,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en } /* skip trailing spaces */ - if( nDigits && eValid ){ - while( z0 ){ - while( s<(LARGEST_INT64/10) && e>0 ) e--,s*=10; - }else{ - while( !(s%10) && e>0 ) e--,s/=10; + /* Attempt to reduce exponent. + ** + ** Branches that are not required for the correct answer but which only + ** help to obtain the correct answer faster are marked with special + ** comments, as a hint to the mutation tester. + */ + while( e>0 ){ /*OPTIMIZATION-IF-TRUE*/ + if( esign>0 ){ + if( s>=(LARGEST_INT64/10) ) break; /*OPTIMIZATION-IF-FALSE*/ + s *= 10; + }else{ + if( s%10!=0 ) break; /*OPTIMIZATION-IF-FALSE*/ + s /= 10; + } + e--; } /* adjust the sign of significand */ s = sign<0 ? -s : s; - /* if exponent, scale significand as appropriate - ** and store in result. */ - if( e ){ + 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 && e<342 ){ - while( e%308 ) { scale *= 1.0e+1; e -= 1; } - if( esign<0 ){ - result = s / scale; - result /= 1.0e+308; - }else{ - result = s * scale; - result *= 1.0e+308; - } - }else if( e>=342 ){ - if( esign<0 ){ - result = 0.0*s; - }else{ - result = 1e308*1e308*s; /* Infinity */ + if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/ + if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/ + while( e%308 ) { scale *= 1.0e+1; e -= 1; } + if( esign<0 ){ + result = s / scale; + result /= 1.0e+308; + }else{ + result = s * scale; + result *= 1.0e+308; + } + }else{ assert( e>=342 ); + if( esign<0 ){ + result = 0.0*s; + }else{ + result = 1e308*1e308*s; /* Infinity */ + } } }else{ /* 1.0e+22 is the largest power of 10 than can be @@ -25025,8 +27481,6 @@ do_atof_calc: result = s * scale; } } - } else { - result = (double)s; } } @@ -25034,7 +27488,7 @@ do_atof_calc: *pResult = result; /* return true if number and no extra non-whitespace chracters after */ - return z>=zEnd && nDigits>0 && eValid && nonNum==0; + return z==zEnd && nDigits>0 && eValid && nonNum==0; #else return !sqlite3Atoi64(z, pResult, length, enc); #endif /* SQLITE_OMIT_FLOATING_POINT */ @@ -25096,7 +27550,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc int neg = 0; /* assume positive */ int i; int c = 0; - int nonNum = 0; + int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ const char *zStart; const char *zEnd = zNum + length; assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); @@ -25107,7 +27561,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); for(i=3-enc; i19*incr || nonNum ){ + if( &zNum[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; @@ -25176,7 +27634,6 @@ SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') - && sqlite3Isxdigit(z[2]) ){ u64 u = 0; int i, k; @@ -25423,7 +27880,8 @@ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ /* a: p0<<28 | p2<<14 | p4 (unmasked) */ if (!(a&0x80)) { - /* we can skip these cause they were (effectively) done above in calc'ing s */ + /* we can skip these cause they were (effectively) done above + ** while calculating s */ /* a &= (0x7f<<28)|(0x7f<<14)|(0x7f); */ /* b &= (0x7f<<14)|(0x7f); */ b = b<<7; @@ -25645,7 +28103,7 @@ SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){ */ SQLITE_PRIVATE int sqlite3VarintLen(u64 v){ int i; - for(i=1; (v >>= 7)!=0; i++){ assert( i<9 ); } + for(i=1; (v >>= 7)!=0; i++){ assert( i<10 ); } return i; } @@ -25676,10 +28134,12 @@ SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){ SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){ #if SQLITE_BYTEORDER==4321 memcpy(p,&v,4); -#elif SQLITE_BYTEORDER==1234 && defined(__GNUC__) && GCC_VERSION>=4003000 +#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ + && defined(__GNUC__) && GCC_VERSION>=4003000 u32 x = __builtin_bswap32(v); memcpy(p,&x,4); -#elif SQLITE_BYTEORDER==1234 && defined(_MSC_VER) && _MSC_VER>=1300 +#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ + && defined(_MSC_VER) && _MSC_VER>=1300 u32 x = _byteswap_ulong(v); memcpy(p,&x,4); #else @@ -25719,7 +28179,7 @@ SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ char *zBlob; int i; - zBlob = (char *)sqlite3DbMallocRaw(db, n/2 + 1); + zBlob = (char *)sqlite3DbMallocRawNN(db, n/2 + 1); n--; if( zBlob ){ for(i=0; i255 ){ y += 40; x >>= 4; } + while( x>255 ){ y += 40; x >>= 4; } /*OPTIMIZATION-IF-TRUE*/ while( x>15 ){ y += 10; x >>= 1; } } return a[x&7] + y - 10; @@ -25958,21 +28418,32 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){ } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ + defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \ + defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) /* ** Convert a LogEst into an integer. +** +** 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){ u64 n; - if( x<10 ) return 1; n = x%10; x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; - if( x>=3 ){ - return x>60 ? (u64)LARGEST_INT64 : (n+8)<<(x-3); - } - return (n+8)>>(3-x); +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ + 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 + ** possible to this routine is 310, resulting in a maximum x of 31 */ + assert( x<=60 ); +#endif + return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); } +#endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ /************** End of util.c ************************************************/ /************** Begin file hash.c ********************************************/ @@ -26033,7 +28504,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash *pH){ static unsigned int strHash(const char *z){ unsigned int h = 0; unsigned char c; - while( (c = (unsigned char)*z++)!=0 ){ + while( (c = (unsigned char)*z++)!=0 ){ /*OPTIMIZATION-IF-TRUE*/ h = (h<<3) ^ h ^ sqlite3UpperToLower[c]; } return h; @@ -26126,7 +28597,7 @@ static HashElem *findElementWithHash( int count; /* Number of elements left to test */ unsigned int h; /* The computed hash */ - if( pH->ht ){ + if( pH->ht ){ /*OPTIMIZATION-IF-TRUE*/ struct _ht *pEntry; h = strHash(pKey) % pH->htsize; pEntry = &pH->ht[h]; @@ -26247,175 +28718,178 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ /************** End of hash.c ************************************************/ /************** Begin file opcodes.c *****************************************/ /* Automatically generated. Do not edit */ -/* See the mkopcodec.awk script for details. */ -#if !defined(SQLITE_OMIT_EXPLAIN) || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) +/* See the tool/mkopcodec.tcl script for details. */ +#if !defined(SQLITE_OMIT_EXPLAIN) \ + || defined(VDBE_PROFILE) \ + || defined(SQLITE_DEBUG) #if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) || defined(SQLITE_DEBUG) # define OpHelp(X) "\0" X #else # define OpHelp(X) #endif SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ - static const char *const azName[] = { "?", - /* 1 */ "Savepoint" OpHelp(""), - /* 2 */ "AutoCommit" OpHelp(""), - /* 3 */ "Transaction" OpHelp(""), - /* 4 */ "SorterNext" OpHelp(""), - /* 5 */ "PrevIfOpen" OpHelp(""), - /* 6 */ "NextIfOpen" OpHelp(""), - /* 7 */ "Prev" OpHelp(""), - /* 8 */ "Next" OpHelp(""), - /* 9 */ "Checkpoint" OpHelp(""), - /* 10 */ "JournalMode" OpHelp(""), - /* 11 */ "Vacuum" OpHelp(""), - /* 12 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), - /* 13 */ "VUpdate" OpHelp("data=r[P3@P2]"), - /* 14 */ "Goto" OpHelp(""), - /* 15 */ "Gosub" OpHelp(""), - /* 16 */ "Return" OpHelp(""), - /* 17 */ "InitCoroutine" OpHelp(""), - /* 18 */ "EndCoroutine" OpHelp(""), - /* 19 */ "Not" OpHelp("r[P2]= !r[P1]"), - /* 20 */ "Yield" OpHelp(""), - /* 21 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 22 */ "Halt" OpHelp(""), - /* 23 */ "Integer" OpHelp("r[P2]=P1"), - /* 24 */ "Int64" OpHelp("r[P2]=P4"), - /* 25 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 26 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 27 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 28 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 29 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 30 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 31 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 32 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 33 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 34 */ "CollSeq" OpHelp(""), - /* 35 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), - /* 36 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), - /* 37 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 38 */ "MustBeInt" OpHelp(""), - /* 39 */ "RealAffinity" OpHelp(""), - /* 40 */ "Cast" OpHelp("affinity(r[P1])"), - /* 41 */ "Permutation" OpHelp(""), - /* 42 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 43 */ "Jump" OpHelp(""), - /* 44 */ "Once" OpHelp(""), - /* 45 */ "If" OpHelp(""), - /* 46 */ "IfNot" OpHelp(""), - /* 47 */ "Column" OpHelp("r[P3]=PX"), - /* 48 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 49 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 50 */ "Count" OpHelp("r[P2]=count()"), - /* 51 */ "ReadCookie" OpHelp(""), - /* 52 */ "SetCookie" OpHelp(""), - /* 53 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 54 */ "OpenRead" OpHelp("root=P2 iDb=P3"), - /* 55 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), - /* 56 */ "OpenAutoindex" OpHelp("nColumn=P2"), - /* 57 */ "OpenEphemeral" OpHelp("nColumn=P2"), - /* 58 */ "SorterOpen" OpHelp(""), - /* 59 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), - /* 60 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 61 */ "Close" OpHelp(""), - /* 62 */ "ColumnsUsed" OpHelp(""), - /* 63 */ "SeekLT" OpHelp("key=r[P3@P4]"), - /* 64 */ "SeekLE" OpHelp("key=r[P3@P4]"), - /* 65 */ "SeekGE" OpHelp("key=r[P3@P4]"), - /* 66 */ "SeekGT" OpHelp("key=r[P3@P4]"), - /* 67 */ "Seek" OpHelp("intkey=r[P2]"), - /* 68 */ "NoConflict" OpHelp("key=r[P3@P4]"), - /* 69 */ "NotFound" OpHelp("key=r[P3@P4]"), - /* 70 */ "Found" OpHelp("key=r[P3@P4]"), - /* 71 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), - /* 72 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), - /* 73 */ "NotExists" OpHelp("intkey=r[P3]"), - /* 74 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 75 */ "NewRowid" OpHelp("r[P2]=rowid"), - /* 76 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), - /* 77 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), - /* 78 */ "Ne" OpHelp("if r[P1]!=r[P3] goto P2"), - /* 79 */ "Eq" OpHelp("if r[P1]==r[P3] goto P2"), - /* 80 */ "Gt" OpHelp("if r[P1]>r[P3] goto P2"), - /* 81 */ "Le" OpHelp("if r[P1]<=r[P3] goto P2"), - /* 82 */ "Lt" OpHelp("if r[P1]=r[P3] goto P2"), - /* 84 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), - /* 85 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), - /* 86 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), - /* 87 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<>r[P1]"), - /* 89 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), - /* 90 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), - /* 91 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), - /* 92 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), - /* 93 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), - /* 94 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), - /* 95 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), - /* 96 */ "BitNot" OpHelp("r[P1]= ~r[P1]"), - /* 97 */ "String8" OpHelp("r[P2]='P4'"), - /* 98 */ "Delete" OpHelp(""), - /* 99 */ "ResetCount" OpHelp(""), - /* 100 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 101 */ "SorterData" OpHelp("r[P2]=data"), - /* 102 */ "RowKey" OpHelp("r[P2]=key"), - /* 103 */ "RowData" OpHelp("r[P2]=data"), - /* 104 */ "Rowid" OpHelp("r[P2]=rowid"), - /* 105 */ "NullRow" OpHelp(""), - /* 106 */ "Last" OpHelp(""), - /* 107 */ "SorterSort" OpHelp(""), - /* 108 */ "Sort" OpHelp(""), - /* 109 */ "Rewind" OpHelp(""), - /* 110 */ "SorterInsert" OpHelp(""), - /* 111 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 112 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 113 */ "IdxRowid" OpHelp("r[P2]=rowid"), - /* 114 */ "IdxLE" OpHelp("key=r[P3@P4]"), - /* 115 */ "IdxGT" OpHelp("key=r[P3@P4]"), - /* 116 */ "IdxLT" OpHelp("key=r[P3@P4]"), - /* 117 */ "IdxGE" OpHelp("key=r[P3@P4]"), - /* 118 */ "Destroy" OpHelp(""), - /* 119 */ "Clear" OpHelp(""), - /* 120 */ "ResetSorter" OpHelp(""), - /* 121 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), - /* 122 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), - /* 123 */ "ParseSchema" OpHelp(""), - /* 124 */ "LoadAnalysis" OpHelp(""), - /* 125 */ "DropTable" OpHelp(""), - /* 126 */ "DropIndex" OpHelp(""), - /* 127 */ "DropTrigger" OpHelp(""), - /* 128 */ "IntegrityCk" OpHelp(""), - /* 129 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), - /* 130 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), - /* 131 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), - /* 132 */ "Program" OpHelp(""), - /* 133 */ "Real" OpHelp("r[P2]=P4"), - /* 134 */ "Param" OpHelp(""), - /* 135 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 136 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), - /* 137 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), - /* 138 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), - /* 139 */ "SetIfNotPos" OpHelp("if r[P1]<=0 then r[P2]=P3"), - /* 140 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]-=P3, goto P2"), - /* 141 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 142 */ "JumpZeroIncr" OpHelp("if (r[P1]++)==0 ) goto P2"), - /* 143 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 144 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 145 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 146 */ "IncrVacuum" OpHelp(""), - /* 147 */ "Expire" OpHelp(""), - /* 148 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 149 */ "VBegin" OpHelp(""), - /* 150 */ "VCreate" OpHelp(""), - /* 151 */ "VDestroy" OpHelp(""), - /* 152 */ "VOpen" OpHelp(""), - /* 153 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 154 */ "VNext" OpHelp(""), - /* 155 */ "VRename" OpHelp(""), - /* 156 */ "Pagecount" OpHelp(""), - /* 157 */ "MaxPgcnt" OpHelp(""), - /* 158 */ "Init" OpHelp("Start at P2"), - /* 159 */ "Noop" OpHelp(""), - /* 160 */ "Explain" OpHelp(""), + 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(""), + /* 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 */ "NotExists" OpHelp("intkey=r[P3]"), + /* 33 */ "Last" OpHelp(""), + /* 34 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), + /* 35 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), + /* 36 */ "Ne" OpHelp("if r[P1]!=r[P3] goto P2"), + /* 37 */ "Eq" OpHelp("if r[P1]==r[P3] goto P2"), + /* 38 */ "Gt" OpHelp("if r[P1]>r[P3] goto P2"), + /* 39 */ "Le" OpHelp("if r[P1]<=r[P3] goto P2"), + /* 40 */ "Lt" OpHelp("if r[P1]=r[P3] goto P2"), + /* 42 */ "SorterSort" 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]"), + /* 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 */ "Sort" OpHelp(""), + /* 54 */ "BitNot" OpHelp("r[P1]= ~r[P1]"), + /* 55 */ "Rewind" OpHelp(""), + /* 56 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 57 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 58 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 59 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 60 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 61 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 62 */ "Program" OpHelp(""), + /* 63 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 64 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 65 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]-=P3, goto P2"), + /* 66 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 67 */ "IncrVacuum" OpHelp(""), + /* 68 */ "VNext" OpHelp(""), + /* 69 */ "Init" OpHelp("Start at P2"), + /* 70 */ "Return" OpHelp(""), + /* 71 */ "EndCoroutine" OpHelp(""), + /* 72 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 73 */ "Halt" OpHelp(""), + /* 74 */ "Integer" OpHelp("r[P2]=P1"), + /* 75 */ "Int64" OpHelp("r[P2]=P4"), + /* 76 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 77 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 78 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 79 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 80 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 81 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 82 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 83 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 84 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 85 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 86 */ "CollSeq" OpHelp(""), + /* 87 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), + /* 88 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), + /* 89 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 90 */ "RealAffinity" OpHelp(""), + /* 91 */ "Cast" OpHelp("affinity(r[P1])"), + /* 92 */ "Permutation" OpHelp(""), + /* 93 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 94 */ "Column" OpHelp("r[P3]=PX"), + /* 95 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 96 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 97 */ "String8" OpHelp("r[P2]='P4'"), + /* 98 */ "Count" OpHelp("r[P2]=count()"), + /* 99 */ "ReadCookie" OpHelp(""), + /* 100 */ "SetCookie" OpHelp(""), + /* 101 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 102 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 103 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 104 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 105 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 106 */ "SorterOpen" OpHelp(""), + /* 107 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 108 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 109 */ "Close" OpHelp(""), + /* 110 */ "ColumnsUsed" OpHelp(""), + /* 111 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 112 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 113 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 114 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), + /* 115 */ "Delete" OpHelp(""), + /* 116 */ "ResetCount" OpHelp(""), + /* 117 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 118 */ "SorterData" OpHelp("r[P2]=data"), + /* 119 */ "RowKey" OpHelp("r[P2]=key"), + /* 120 */ "RowData" OpHelp("r[P2]=data"), + /* 121 */ "Rowid" OpHelp("r[P2]=rowid"), + /* 122 */ "NullRow" OpHelp(""), + /* 123 */ "SorterInsert" OpHelp(""), + /* 124 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 125 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 126 */ "Seek" OpHelp("Move P3 to P1.rowid"), + /* 127 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 128 */ "Destroy" OpHelp(""), + /* 129 */ "Clear" OpHelp(""), + /* 130 */ "ResetSorter" OpHelp(""), + /* 131 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), + /* 132 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), + /* 133 */ "Real" OpHelp("r[P2]=P4"), + /* 134 */ "ParseSchema" OpHelp(""), + /* 135 */ "LoadAnalysis" OpHelp(""), + /* 136 */ "DropTable" OpHelp(""), + /* 137 */ "DropIndex" OpHelp(""), + /* 138 */ "DropTrigger" OpHelp(""), + /* 139 */ "IntegrityCk" OpHelp(""), + /* 140 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 141 */ "Param" OpHelp(""), + /* 142 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 143 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 144 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 145 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 146 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 147 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 148 */ "Expire" OpHelp(""), + /* 149 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 150 */ "VBegin" OpHelp(""), + /* 151 */ "VCreate" OpHelp(""), + /* 152 */ "VDestroy" OpHelp(""), + /* 153 */ "VOpen" OpHelp(""), + /* 154 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 155 */ "VRename" OpHelp(""), + /* 156 */ "Pagecount" OpHelp(""), + /* 157 */ "MaxPgcnt" OpHelp(""), + /* 158 */ "CursorHint" OpHelp(""), + /* 159 */ "Noop" OpHelp(""), + /* 160 */ "Explain" OpHelp(""), }; return azName[i]; } @@ -26496,6 +28970,19 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ # endif #endif +/* Use pread() and pwrite() if they are available */ +#if defined(__APPLE__) +# define HAVE_PREAD 1 +# define HAVE_PWRITE 1 +#endif +#if defined(HAVE_PREAD64) && defined(HAVE_PWRITE64) +# undef USE_PREAD +# define USE_PREAD64 1 +#elif defined(HAVE_PREAD) && defined(HAVE_PWRITE) +# undef USE_PREAD64 +# define USE_PREAD 1 +#endif + /* ** standard include files. */ @@ -26574,6 +29061,11 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ */ #define MAX_PATHNAME 512 +/* +** Maximum supported symbolic links +*/ +#define SQLITE_MAX_SYMLINKS 100 + /* Always cast the getpid() return type for compatibility with ** kernel modules in VxWorks. */ #define osGetpid(X) (pid_t)getpid() @@ -26683,8 +29175,6 @@ static pid_t randomnessPid = 0; #define UNIXFILE_DELETE 0x20 /* Delete on close */ #define UNIXFILE_URI 0x40 /* Filename might have query parameters */ #define UNIXFILE_NOLOCK 0x80 /* Do no file locking */ -#define UNIXFILE_WARNED 0x0100 /* verifyDbFile() warnings issued */ -#define UNIXFILE_BLOCK 0x0200 /* Next SHM lock might block */ /* ** Include code that is common to all os_*.c files @@ -26728,8 +29218,8 @@ static pid_t randomnessPid = 0; */ #ifdef SQLITE_PERFORMANCE_TRACE -/* -** hwtime.h contains inline assembler code for implementing +/* +** hwtime.h contains inline assembler code for implementing ** high-performance timing routines. */ /************** Include hwtime.h in the middle of os_common.h ****************/ @@ -26839,14 +29329,14 @@ static sqlite_uint64 g_elapsed; ** of code will give us the ability to simulate a disk I/O error. This ** is used for testing the I/O recovery logic. */ -#ifdef 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; +#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) #define SimulateIOError(CODE) \ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ @@ -26872,17 +29362,17 @@ static void local_ioerr(){ #define SimulateIOErrorBenign(X) #define SimulateIOError(A) #define SimulateDiskfullError(A) -#endif +#endif /* defined(SQLITE_TEST) */ /* ** When testing, keep a count of the number of open files. */ -#ifdef SQLITE_TEST -SQLITE_API int sqlite3_open_file_count = 0; +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; #define OpenCounter(X) sqlite3_open_file_count+=(X) #else #define OpenCounter(X) -#endif +#endif /* defined(SQLITE_TEST) */ #endif /* !defined(_OS_COMMON_H_) */ @@ -26947,19 +29437,6 @@ static int posixOpen(const char *zFile, int flags, int mode){ return open(zFile, flags, mode); } -/* -** On some systems, calls to fchown() will trigger a message in a security -** log if they come from non-root processes. So avoid calling fchown() if -** we are not running as root. -*/ -static int posixFchown(int fd, uid_t uid, gid_t gid){ -#if OS_VXWORKS - return 0; -#else - return geteuid() ? 0 : fchown(fd,uid,gid); -#endif -} - /* Forward reference */ static int openDirectory(const char*, int*); static int unixGetpagesize(void); @@ -27025,7 +29502,7 @@ static struct unix_syscall { #else { "pread64", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osPread64 ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[10].pCurrent) +#define osPread64 ((ssize_t(*)(int,void*,size_t,off64_t))aSyscall[10].pCurrent) { "write", (sqlite3_syscall_ptr)write, 0 }, #define osWrite ((ssize_t(*)(int,const void*,size_t))aSyscall[11].pCurrent) @@ -27043,10 +29520,10 @@ static struct unix_syscall { #else { "pwrite64", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off_t))\ +#define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ aSyscall[13].pCurrent) - { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, + { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, #define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE @@ -27068,29 +29545,74 @@ static struct unix_syscall { { "rmdir", (sqlite3_syscall_ptr)rmdir, 0 }, #define osRmdir ((int(*)(const char*))aSyscall[19].pCurrent) - { "fchown", (sqlite3_syscall_ptr)posixFchown, 0 }, +#if defined(HAVE_FCHOWN) + { "fchown", (sqlite3_syscall_ptr)fchown, 0 }, +#else + { "fchown", (sqlite3_syscall_ptr)0, 0 }, +#endif #define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent) + { "geteuid", (sqlite3_syscall_ptr)geteuid, 0 }, +#define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent) + #if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 - { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, -#define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[21].pCurrent) + { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, +#else + { "mmap", (sqlite3_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 }, -#define osMunmap ((void*(*)(void*,size_t))aSyscall[22].pCurrent) +#else + { "munmap", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osMunmap ((void*(*)(void*,size_t))aSyscall[23].pCurrent) -#if HAVE_MREMAP +#if HAVE_MREMAP && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "mremap", (sqlite3_syscall_ptr)mremap, 0 }, #else { "mremap", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osMremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[23].pCurrent) - { "getpagesize", (sqlite3_syscall_ptr)unixGetpagesize, 0 }, -#define osGetpagesize ((int(*)(void))aSyscall[24].pCurrent) +#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 }, +#else + { "getpagesize", (sqlite3_syscall_ptr)0, 0 }, #endif +#define osGetpagesize ((int(*)(void))aSyscall[25].pCurrent) + +#if defined(HAVE_READLINK) + { "readlink", (sqlite3_syscall_ptr)readlink, 0 }, +#else + { "readlink", (sqlite3_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 }, +#else + { "lstat", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[27].pCurrent) }; /* End of the overrideable system calls */ + +/* +** On some systems, calls to fchown() will trigger a message in a security +** log if they come from non-root processes. So avoid calling fchown() if +** we are not running as root. +*/ +static int robustFchown(int fd, uid_t uid, gid_t gid){ +#if defined(HAVE_FCHOWN) + return osGeteuid() ? 0 : osFchown(fd,uid,gid); +#else + return 0; +#endif +} + /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the ** "unix" VFSes. Return SQLITE_OK opon successfully updating the @@ -27375,23 +29897,12 @@ static int robust_ftruncate(int h, sqlite3_int64 sz){ ** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. */ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { + assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || + (sqliteIOErr == SQLITE_IOERR_UNLOCK) || + (sqliteIOErr == SQLITE_IOERR_RDLOCK) || + (sqliteIOErr == SQLITE_IOERR_CHECKRESERVEDLOCK) ); switch (posixError) { -#if 0 - /* At one point this code was not commented out. In theory, this branch - ** should never be hit, as this function should only be called after - ** a locking-related function (i.e. fcntl()) has returned non-zero with - ** the value of errno as the first argument. Since a system call has failed, - ** errno should be non-zero. - ** - ** Despite this, if errno really is zero, we still don't want to return - ** SQLITE_OK. The system call failed, and *some* SQLite error should be - ** propagated back to the caller. Commenting this branch out means errno==0 - ** will be handled by the "default:" case below. - */ - case 0: - return SQLITE_OK; -#endif - + case EACCES: case EAGAIN: case ETIMEDOUT: case EBUSY: @@ -27401,41 +29912,9 @@ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { * introspection, in which it actually means what it says */ return SQLITE_BUSY; - case EACCES: - /* EACCES is like EAGAIN during locking operations, but not any other time*/ - if( (sqliteIOErr == SQLITE_IOERR_LOCK) || - (sqliteIOErr == SQLITE_IOERR_UNLOCK) || - (sqliteIOErr == SQLITE_IOERR_RDLOCK) || - (sqliteIOErr == SQLITE_IOERR_CHECKRESERVEDLOCK) ){ - return SQLITE_BUSY; - } - /* else fall through */ case EPERM: return SQLITE_PERM; -#if EOPNOTSUPP!=ENOTSUP - case EOPNOTSUPP: - /* something went terribly awry, unless during file system support - * introspection, in which it actually means what it says */ -#endif -#ifdef ENOTSUP - case ENOTSUP: - /* invalid fd, unless during file system support introspection, in which - * it actually means what it says */ -#endif - case EIO: - case EBADF: - case EINVAL: - case ENOTCONN: - case ENODEV: - case ENXIO: - case ENOENT: -#ifdef ESTALE /* ESTALE is not defined on Interix systems */ - case ESTALE: -#endif - case ENOSYS: - /* these should force the client to close the file and reconnect */ - default: return sqliteIOErr; } @@ -27719,7 +30198,7 @@ static unixInodeInfo *inodeList = 0; /* ** -** This function - unixLogError_x(), is only ever called via the macro +** This function - unixLogErrorAtLine(), is only ever called via the macro ** unixLogError(). ** ** It is invoked after an error occurs in an OS function and errno has been @@ -27888,7 +30367,7 @@ static int findInodeInfo( rc = osFstat(fd, &statbuf); if( rc!=0 ){ storeLastErrno(pFile, errno); -#ifdef EOVERFLOW +#if defined(EOVERFLOW) && defined(SQLITE_DISABLE_LFS) if( pFile->lastErrno==EOVERFLOW ) return SQLITE_NOLFS; #endif return SQLITE_IOERR; @@ -27933,7 +30412,7 @@ static int findInodeInfo( if( pInode==0 ){ pInode = sqlite3_malloc64( sizeof(*pInode) ); if( pInode==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(pInode, 0, sizeof(*pInode)); memcpy(&pInode->fileId, &fileId, sizeof(fileId)); @@ -27975,30 +30454,25 @@ static int fileHasMoved(unixFile *pFile){ static void verifyDbFile(unixFile *pFile){ struct stat buf; int rc; - if( pFile->ctrlFlags & UNIXFILE_WARNED ){ - /* One or more of the following warnings have already been issued. Do not - ** repeat them so as not to clutter the error log */ - return; - } + + /* These verifications occurs for the main database only */ + if( pFile->ctrlFlags & UNIXFILE_NOLOCK ) return; + rc = osFstat(pFile->h, &buf); if( rc!=0 ){ sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath); - pFile->ctrlFlags |= UNIXFILE_WARNED; return; } - if( buf.st_nlink==0 && (pFile->ctrlFlags & UNIXFILE_DELETE)==0 ){ + if( buf.st_nlink==0 ){ sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath); - pFile->ctrlFlags |= UNIXFILE_WARNED; return; } if( buf.st_nlink>1 ){ sqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath); - pFile->ctrlFlags |= UNIXFILE_WARNED; return; } if( fileHasMoved(pFile) ){ sqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath); - pFile->ctrlFlags |= UNIXFILE_WARNED; return; } } @@ -28018,6 +30492,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); assert( pFile ); + assert( pFile->eFileLock<=SHARED_LOCK ); unixEnterMutex(); /* Because pFile->pInode is shared across threads */ /* Check if a thread in this process holds such a lock */ @@ -28074,9 +30549,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ unixInodeInfo *pInode = pFile->pInode; assert( unixMutexHeld() ); assert( pInode!=0 ); - if( ((pFile->ctrlFlags & UNIXFILE_EXCL)!=0 || pInode->bProcessLock) - && ((pFile->ctrlFlags & UNIXFILE_RDONLY)==0) - ){ + if( (pFile->ctrlFlags & (UNIXFILE_EXCL|UNIXFILE_RDONLY))==UNIXFILE_EXCL ){ if( pInode->bProcessLock==0 ){ struct flock lock; assert( pInode->nLock==0 ); @@ -28126,7 +30599,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** lock transitions in terms of the POSIX advisory shared and exclusive ** lock primitives (called read-locks and write-locks below, to avoid ** confusion with SQLite lock names). The algorithms are complicated - ** slightly in order to be compatible with windows systems simultaneously + ** slightly in order to be compatible with Windows95 systems simultaneously ** accessing the same database file, in case that is ever required. ** ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved @@ -28134,8 +30607,14 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** range', a range of 510 bytes at a well known offset. ** ** To obtain a SHARED lock, a read-lock is obtained on the 'pending - ** byte'. If this is successful, a random byte from the 'shared byte - ** range' is read-locked and the lock on the 'pending byte' released. + ** byte'. If this is successful, 'shared byte range' is read-locked + ** and the lock on the 'pending byte' released. (Legacy note: When + ** SQLite was first developed, Windows95 systems were still very common, + ** and Widnows95 lacks a shared-lock capability. So on Windows95, a + ** single randomly selected by from the 'shared byte range' is locked. + ** Windows95 is now pretty much extinct, but this work-around for the + ** lack of shared-locks on Windows95 lives on, for backwards + ** compatibility.) ** ** A process may only obtain a RESERVED lock after it has a SHARED lock. ** A RESERVED lock is implemented by grabbing a write-lock on the @@ -28154,11 +30633,6 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** range'. Since all other locks require a read-lock on one of the bytes ** within this range, this ensures that no other locks are held on the ** database. - ** - ** The reason a single byte cannot be used instead of the 'shared byte - ** range' is that some versions of windows do not support read-locks. By - ** locking a random byte from a range, concurrent SHARED locks may exist - ** even if the locking primitive used is always a write-lock. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; @@ -28428,9 +30902,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; rc = SQLITE_IOERR_UNLOCK; - if( IS_LOCK_ERROR(rc) ){ - storeLastErrno(pFile, tErrno); - } + storeLastErrno(pFile, tErrno); goto end_unlock; } lock.l_type = F_RDLCK; @@ -28452,9 +30924,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; rc = SQLITE_IOERR_UNLOCK; - if( IS_LOCK_ERROR(rc) ){ - storeLastErrno(pFile, tErrno); - } + storeLastErrno(pFile, tErrno); goto end_unlock; } }else @@ -28705,17 +31175,7 @@ static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) { SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); assert( pFile ); - - /* Check if a thread in this process holds such a lock */ - if( pFile->eFileLock>SHARED_LOCK ){ - /* Either this connection or some other connection in the same process - ** holds a lock on the file. No need to check further. */ - reserved = 1; - }else{ - /* The lock is held if and only if the lockfile exists */ - const char *zLockFile = (const char*)pFile->lockingContext; - reserved = osAccess(zLockFile, 0)==0; - } + reserved = osAccess((const char*)pFile->lockingContext, 0)==0; OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved)); *pResOut = reserved; return rc; @@ -28777,7 +31237,7 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) { rc = SQLITE_BUSY; } else { rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); - if( IS_LOCK_ERROR(rc) ){ + if( rc!=SQLITE_BUSY ){ storeLastErrno(pFile, tErrno); } } @@ -28824,14 +31284,12 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { /* To fully unlock the database, delete the lock file */ assert( eFileLock==NO_LOCK ); rc = osRmdir(zLockFile); - if( rc<0 && errno==ENOTDIR ) rc = osUnlink(zLockFile); if( rc<0 ){ int tErrno = errno; - rc = 0; - if( ENOENT != tErrno ){ + if( tErrno==ENOENT ){ + rc = SQLITE_OK; + }else{ rc = SQLITE_IOERR_UNLOCK; - } - if( IS_LOCK_ERROR(rc) ){ storeLastErrno(pFile, tErrno); } return rc; @@ -28844,14 +31302,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) { - int rc = SQLITE_OK; - if( id ){ - unixFile *pFile = (unixFile*)id; - dotlockUnlock(id, NO_LOCK); - sqlite3_free(pFile->lockingContext); - rc = closeUnixFile(id); - } - return rc; + unixFile *pFile = (unixFile*)id; + assert( id!=0 ); + dotlockUnlock(id, NO_LOCK); + sqlite3_free(pFile->lockingContext); + return closeUnixFile(id); } /****************** End of the dot-file lock implementation ******************* ******************************************************************************/ @@ -28917,10 +31372,8 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ int tErrno = errno; /* unlock failed with an error */ lrc = SQLITE_IOERR_UNLOCK; - if( IS_LOCK_ERROR(lrc) ){ - storeLastErrno(pFile, tErrno); - rc = lrc; - } + storeLastErrno(pFile, tErrno); + rc = lrc; } } else { int tErrno = errno; @@ -29053,12 +31506,9 @@ static int flockUnlock(sqlite3_file *id, int eFileLock) { ** Close a file. */ static int flockClose(sqlite3_file *id) { - int rc = SQLITE_OK; - if( id ){ - flockUnlock(id, NO_LOCK); - rc = closeUnixFile(id); - } - return rc; + assert( id!=0 ); + flockUnlock(id, NO_LOCK); + return closeUnixFile(id); } #endif /* SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORK */ @@ -29683,23 +32133,22 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { */ static int afpClose(sqlite3_file *id) { int rc = SQLITE_OK; - if( id ){ - unixFile *pFile = (unixFile*)id; - afpUnlock(id, NO_LOCK); - 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); - } - releaseInodeInfo(pFile); - sqlite3_free(pFile->lockingContext); - rc = closeUnixFile(id); - unixLeaveMutex(); + unixFile *pFile = (unixFile*)id; + assert( id!=0 ); + afpUnlock(id, NO_LOCK); + 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); } + releaseInodeInfo(pFile); + sqlite3_free(pFile->lockingContext); + rc = closeUnixFile(id); + unixLeaveMutex(); return rc; } @@ -29778,13 +32227,9 @@ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ SimulateIOError( got = -1 ); #else newOffset = lseek(id->h, offset, SEEK_SET); - SimulateIOError( newOffset-- ); - if( newOffset!=offset ){ - if( newOffset == -1 ){ - storeLastErrno((unixFile*)id, errno); - }else{ - storeLastErrno((unixFile*)id, 0); - } + SimulateIOError( newOffset = -1 ); + if( newOffset<0 ){ + storeLastErrno((unixFile*)id, errno); return -1; } got = osRead(id->h, pBuf, cnt); @@ -29883,6 +32328,7 @@ static int seekAndWriteFd( assert( nBuf==(nBuf&0x1ffff) ); assert( fd>2 ); + assert( piErrno!=0 ); nBuf &= 0x1ffff; TIMER_START; @@ -29893,11 +32339,10 @@ static int seekAndWriteFd( #else do{ i64 iSeek = lseek(fd, iOff, SEEK_SET); - SimulateIOError( iSeek-- ); - - if( iSeek!=iOff ){ - if( piErrno ) *piErrno = (iSeek==-1 ? errno : 0); - return -1; + SimulateIOError( iSeek = -1 ); + if( iSeek<0 ){ + rc = -1; + break; } rc = osWrite(fd, pBuf, nBuf); }while( rc<0 && errno==EINTR ); @@ -29906,7 +32351,7 @@ static int seekAndWriteFd( TIMER_END; OSTRACE(("WRITE %-3d %5d %7lld %llu\n", fd, rc, iOff, TIMER_ELAPSED)); - if( rc<0 && piErrno ) *piErrno = errno; + if( rc<0 ) *piErrno = errno; return rc; } @@ -29969,7 +32414,7 @@ static int unixWrite( } #endif -#if SQLITE_MAX_MMAP_SIZE>0 +#if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 /* Deal with as much of this write request as possible by transfering ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ @@ -30090,10 +32535,15 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ #endif /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a - ** no-op + ** no-op. But go ahead and call fstat() to validate the file + ** descriptor as we need a method to provoke a failure during + ** coverate testing. */ #ifdef SQLITE_NO_SYNC - rc = SQLITE_OK; + { + struct stat buf; + rc = osFstat(fd, &buf); + } #elif HAVE_FULLFSYNC if( fullSync ){ rc = osFcntl(fd, F_FULLFSYNC, 0); @@ -30159,16 +32609,20 @@ static int openDirectory(const char *zFilename, int *pFd){ char zDirname[MAX_PATHNAME+1]; sqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename); - for(ii=(int)strlen(zDirname); ii>1 && zDirname[ii]!='/'; ii--); + for(ii=(int)strlen(zDirname); ii>0 && zDirname[ii]!='/'; ii--); if( ii>0 ){ zDirname[ii] = '\0'; - fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0); - if( fd>=0 ){ - OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname)); - } + }else{ + if( zDirname[0]!='/' ) zDirname[0] = '.'; + zDirname[1] = 0; + } + fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0); + if( fd>=0 ){ + OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname)); } *pFd = fd; - return (fd>=0?SQLITE_OK:unixLogError(SQLITE_CANTOPEN_BKPT, "open", zDirname)); + if( fd>=0 ) return SQLITE_OK; + return unixLogError(SQLITE_CANTOPEN_BKPT, "openDirectory", zDirname); } /* @@ -30221,10 +32675,11 @@ static int unixSync(sqlite3_file *id, int flags){ OSTRACE(("DIRSYNC %s (have_fullfsync=%d fullsync=%d)\n", pFile->zPath, HAVE_FULLFSYNC, isFullsync)); rc = osOpenDirectory(pFile->zPath, &dirfd); - if( rc==SQLITE_OK && dirfd>=0 ){ + if( rc==SQLITE_OK ){ full_fsync(dirfd, 0, 0); robust_close(pFile, dirfd, __LINE__); - }else if( rc==SQLITE_CANTOPEN ){ + }else{ + assert( rc==SQLITE_CANTOPEN ); rc = SQLITE_OK; } pFile->ctrlFlags &= ~UNIXFILE_DIRSYNC; @@ -30356,18 +32811,14 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ int nWrite = 0; /* Number of bytes written by seekAndWrite */ i64 iWrite; /* Next offset to write to */ - iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1; + iWrite = (buf.st_size/nBlk)*nBlk + nBlk - 1; assert( iWrite>=buf.st_size ); - assert( (iWrite/nBlk)==((buf.st_size+nBlk-1)/nBlk) ); assert( ((iWrite+1)%nBlk)==0 ); - for(/*no-op*/; iWrite=nSize ) iWrite = nSize - 1; nWrite = seekAndWrite(pFile, iWrite, "", 1); if( nWrite!=1 ) return SQLITE_IOERR_WRITE; } - if( nWrite==0 || (nSize%nBlk) ){ - nWrite = seekAndWrite(pFile, nSize-1, "", 1); - if( nWrite!=1 ) return SQLITE_IOERR_WRITE; - } #endif } } @@ -30415,10 +32866,6 @@ static int unixGetTempname(int nBuf, char *zBuf); static int unixFileControl(sqlite3_file *id, int op, void *pArg){ unixFile *pFile = (unixFile*)id; switch( op ){ - case SQLITE_FCNTL_WAL_BLOCK: { - /* pFile->ctrlFlags |= UNIXFILE_BLOCK; // Deferred feature */ - return SQLITE_OK; - } case SQLITE_FCNTL_LOCKSTATE: { *(int*)pArg = pFile->eFileLock; return SQLITE_OK; @@ -30745,10 +33192,9 @@ static int unixShmSystemLock( assert( n==1 || lockType!=F_RDLCK ); /* Locks are within range */ - assert( n>=1 && n=1 && n<=SQLITE_SHM_NLOCK ); if( pShmNode->h>=0 ){ - int lkType; /* Initialize the locking parameters */ memset(&f, 0, sizeof(f)); f.l_type = lockType; @@ -30756,10 +33202,8 @@ static int unixShmSystemLock( f.l_start = ofst; f.l_len = n; - lkType = (pFile->ctrlFlags & UNIXFILE_BLOCK)!=0 ? F_SETLKW : F_SETLK; - rc = osFcntl(pShmNode->h, lkType, &f); + rc = osFcntl(pShmNode->h, F_SETLK, &f); rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY; - pFile->ctrlFlags &= ~UNIXFILE_BLOCK; } /* Update the global lock state and do debug tracing */ @@ -30826,7 +33270,7 @@ static int unixShmRegionPerMap(void){ static void unixShmPurge(unixFile *pFd){ unixShmNode *p = pFd->pInode->pShmNode; assert( unixMutexHeld() ); - if( p && p->nRef==0 ){ + if( p && ALWAYS(p->nRef==0) ){ int nShmPerMap = unixShmRegionPerMap(); int i; assert( p->pInode==pFd->pInode ); @@ -30893,7 +33337,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ /* Allocate space for the new unixShm object. */ p = sqlite3_malloc64( sizeof(*p) ); - if( p==0 ) return SQLITE_NOMEM; + if( p==0 ) return SQLITE_NOMEM_BKPT; memset(p, 0, sizeof(*p)); assert( pDbFd->pShm==0 ); @@ -30913,7 +33357,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ ** a new *-shm file is created, an attempt will be made to create it ** with the same permissions. */ - if( osFstat(pDbFd->h, &sStat) && pInode->bProcessLock==0 ){ + if( osFstat(pDbFd->h, &sStat) ){ rc = SQLITE_IOERR_FSTAT; goto shm_open_err; } @@ -30925,7 +33369,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ #endif pShmNode = sqlite3_malloc64( sizeof(*pShmNode) + nShmFilename ); if( pShmNode==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto shm_open_err; } memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename); @@ -30941,10 +33385,12 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ pShmNode->h = -1; pDbFd->pInode->pShmNode = pShmNode; pShmNode->pInode = pDbFd->pInode; - pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if( pShmNode->mutex==0 ){ - rc = SQLITE_NOMEM; - goto shm_open_err; + if( sqlite3GlobalConfig.bCoreMutex ){ + pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->mutex==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shm_open_err; + } } if( pInode->bProcessLock==0 ){ @@ -30963,7 +33409,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ ** is owned by the same user that owns the original database. Otherwise, ** the original owner will not be able to connect. */ - osFchown(pShmNode->h, sStat.st_uid, sStat.st_gid); + 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. @@ -31100,7 +33546,8 @@ static int unixShmMap( /* Write to the last byte of each newly allocated or extended page */ assert( (nByte % pgsz)==0 ); for(iPg=(sStat.st_size/pgsz); iPg<(nByte/pgsz); iPg++){ - if( seekAndWriteFd(pShmNode->h, iPg*pgsz + pgsz-1, "", 1, 0)!=1 ){ + int x = 0; + if( seekAndWriteFd(pShmNode->h, iPg*pgsz + pgsz-1, "", 1, &x)!=1 ){ const char *zFile = pShmNode->zFilename; rc = unixLogError(SQLITE_IOERR_SHMSIZE, "write", zFile); goto shmpage_out; @@ -31115,7 +33562,7 @@ static int unixShmMap( pShmNode->apRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ - rc = SQLITE_IOERR_NOMEM; + rc = SQLITE_IOERR_NOMEM_BKPT; goto shmpage_out; } pShmNode->apRegion = apNew; @@ -31135,7 +33582,7 @@ static int unixShmMap( }else{ pMem = sqlite3_malloc64(szRegion); if( pMem==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto shmpage_out; } memset(pMem, 0, szRegion); @@ -31394,7 +33841,9 @@ static void unixRemapfile( assert( pFd->mmapSizeActual>=pFd->mmapSize ); assert( MAP_FAILED!=0 ); +#ifdef SQLITE_MMAP_READWRITE if( (pFd->ctrlFlags & UNIXFILE_RDONLY)==0 ) flags |= PROT_WRITE; +#endif if( pOrig ){ #if HAVE_MREMAP @@ -31466,17 +33915,14 @@ static void unixRemapfile( ** recreated as a result of outstanding references) or an SQLite error ** code otherwise. */ -static int unixMapfile(unixFile *pFd, i64 nByte){ - i64 nMap = nByte; - int rc; - +static int unixMapfile(unixFile *pFd, i64 nMap){ assert( nMap>=0 || pFd->nFetchOut==0 ); + assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) ); if( pFd->nFetchOut>0 ) return SQLITE_OK; if( nMap<0 ){ struct stat statbuf; /* Low-level file information */ - rc = osFstat(pFd->h, &statbuf); - if( rc!=SQLITE_OK ){ + if( osFstat(pFd->h, &statbuf) ){ return SQLITE_IOERR_FSTAT; } nMap = statbuf.st_size; @@ -31485,12 +33931,9 @@ static int unixMapfile(unixFile *pFd, i64 nByte){ nMap = pFd->mmapSizeMax; } + assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) ); if( nMap!=pFd->mmapSize ){ - if( nMap>0 ){ - unixRemapfile(pFd, nMap); - }else{ - unixUnmapfile(pFd); - } + unixRemapfile(pFd, nMap); } return SQLITE_OK; @@ -31917,7 +34360,7 @@ static int fillInUnixFile( pNew->pId = vxworksFindFileId(zFilename); if( pNew->pId==0 ){ ctrlFlags |= UNIXFILE_NOLOCK; - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } #endif @@ -31973,7 +34416,7 @@ static int fillInUnixFile( afpLockingContext *pCtx; pNew->lockingContext = pCtx = sqlite3_malloc64( sizeof(*pCtx) ); if( pCtx==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ /* NB: zFilename exists and remains valid until the file is closed ** according to requirement F11141. So we do not need to make a @@ -32003,7 +34446,7 @@ static int fillInUnixFile( nFilename = (int)strlen(zFilename) + 6; zLockFile = (char *)sqlite3_malloc64(nFilename); if( zLockFile==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename); } @@ -32026,7 +34469,7 @@ static int fillInUnixFile( if( zSemName[n]=='/' ) zSemName[n] = '_'; pNew->pInode->pSem = sem_open(zSemName, O_CREAT, 0666, 1); if( pNew->pInode->pSem == SEM_FAILED ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; pNew->pInode->aSemName[0] = '\0'; } } @@ -32059,29 +34502,31 @@ static int fillInUnixFile( */ static const char *unixTempFileDir(void){ static const char *azDirs[] = { - 0, 0, 0, "/var/tmp", "/usr/tmp", "/tmp", - 0 /* List terminator */ + "." }; - unsigned int i; + unsigned int i = 0; struct stat buf; - const char *zDir = 0; + const char *zDir = sqlite3_temp_directory; - azDirs[0] = sqlite3_temp_directory; - if( !azDirs[1] ) azDirs[1] = getenv("SQLITE_TMPDIR"); - if( !azDirs[2] ) azDirs[2] = getenv("TMPDIR"); - for(i=0; i=sizeof(azDirs)/sizeof(azDirs[0]) ) break; + zDir = azDirs[i++]; } - return zDir; + return 0; } /* @@ -32090,38 +34535,26 @@ static const char *unixTempFileDir(void){ ** pVfs->mxPathname bytes. */ static int unixGetTempname(int nBuf, char *zBuf){ - static const unsigned char zChars[] = - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789"; - unsigned int i, j; const char *zDir; + int iLimit = 0; /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this ** function failing. */ + zBuf[0] = 0; SimulateIOError( return SQLITE_IOERR ); zDir = unixTempFileDir(); - if( zDir==0 ) zDir = "."; - - /* Check that the output buffer is large enough for the temporary file - ** name. If it is not, return SQLITE_ERROR. - */ - if( (strlen(zDir) + strlen(SQLITE_TEMP_FILE_PREFIX) + 18) >= (size_t)nBuf ){ - return SQLITE_ERROR; - } - + if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH; do{ - sqlite3_snprintf(nBuf-18, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX, zDir); - j = (int)strlen(zBuf); - sqlite3_randomness(15, &zBuf[j]); - for(i=0; i<15; i++, j++){ - zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; - } - zBuf[j] = 0; - zBuf[j+1] = 0; + u64 r; + sqlite3_randomness(sizeof(r), &r); + assert( nBuf>2 ); + zBuf[nBuf-2] = 0; + sqlite3_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 ); return SQLITE_OK; } @@ -32243,16 +34676,19 @@ static int findCreateFileMode( ** used by the test_multiplex.c module. */ nDb = sqlite3Strlen30(zPath) - 1; -#ifdef SQLITE_ENABLE_8_3_NAMES - while( nDb>0 && sqlite3Isalnum(zPath[nDb]) ) nDb--; - if( nDb==0 || zPath[nDb]!='-' ) return SQLITE_OK; -#else 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( zPath[nDb]!='\n' ); + 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. */ + if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK; +#endif nDb--; } -#endif memcpy(zDb, zPath, nDb); zDb[nDb] = '\0'; @@ -32380,7 +34816,7 @@ static int unixOpen( }else{ pUnused = sqlite3_malloc64(sizeof(*pUnused)); if( !pUnused ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } } p->pUnused = pUnused; @@ -32393,7 +34829,7 @@ static int unixOpen( }else if( !zName ){ /* If zName is NULL, the upper layer is requesting a temp file. */ assert(isDelete && !syncDir); - rc = unixGetTempname(MAX_PATHNAME+2, zTmpname); + rc = unixGetTempname(pVfs->mxPathname, zTmpname); if( rc!=SQLITE_OK ){ return rc; } @@ -32426,7 +34862,8 @@ static int unixOpen( } fd = robust_open(zName, openFlags, openMode); OSTRACE(("OPENX %-3d %s 0%o\n", fd, zName, openFlags)); - if( fd<0 && errno!=EISDIR && isReadWrite && !isExclusive ){ + 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); @@ -32445,7 +34882,7 @@ static int unixOpen( ** the same as the original database. */ if( flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL) ){ - osFchown(fd, uid, gid); + robustFchown(fd, uid, gid); } } assert( fd>=0 ); @@ -32465,7 +34902,7 @@ static int unixOpen( zPath = sqlite3_mprintf("%s", zName); if( zPath==0 ){ robust_close(p, fd, __LINE__); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } #else osUnlink(zName); @@ -32476,9 +34913,6 @@ static int unixOpen( p->openFlags = openFlags; } #endif - - noLock = eType!=SQLITE_OPEN_MAIN_DB; - #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE if( fstatfs(fd, &fsInfo) == -1 ){ @@ -32497,6 +34931,7 @@ static int unixOpen( /* Set up appropriate ctrlFlags */ if( isDelete ) ctrlFlags |= UNIXFILE_DELETE; if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY; + noLock = eType!=SQLITE_OPEN_MAIN_DB; if( noLock ) ctrlFlags |= UNIXFILE_NOLOCK; if( syncDir ) ctrlFlags |= UNIXFILE_DIRSYNC; if( flags & SQLITE_OPEN_URI ) ctrlFlags |= UNIXFILE_URI; @@ -32573,16 +35008,12 @@ static int unixDelete( int fd; rc = osOpenDirectory(zPath, &fd); if( rc==SQLITE_OK ){ -#if OS_VXWORKS - if( fsync(fd)==-1 ) -#else - if( fsync(fd) ) -#endif - { + if( full_fsync(fd,0,0) ){ rc = unixLogError(SQLITE_IOERR_DIR_FSYNC, "fsync", zPath); } robust_close(0, fd, __LINE__); - }else if( rc==SQLITE_CANTOPEN ){ + }else{ + assert( rc==SQLITE_CANTOPEN ); rc = SQLITE_OK; } } @@ -32606,33 +35037,49 @@ static int unixAccess( int flags, /* What do we want to learn about the zPath file? */ int *pResOut /* Write result boolean here */ ){ - int amode = 0; UNUSED_PARAMETER(NotUsed); SimulateIOError( return SQLITE_IOERR_ACCESS; ); - switch( flags ){ - case SQLITE_ACCESS_EXISTS: - amode = F_OK; - break; - case SQLITE_ACCESS_READWRITE: - amode = W_OK|R_OK; - break; - case SQLITE_ACCESS_READ: - amode = R_OK; - break; + assert( pResOut!=0 ); - default: - assert(!"Invalid flags argument"); - } - *pResOut = (osAccess(zPath, amode)==0); - if( flags==SQLITE_ACCESS_EXISTS && *pResOut ){ + /* The spec says there are three possible values for flags. But only + ** two of them are actually used */ + assert( flags==SQLITE_ACCESS_EXISTS || flags==SQLITE_ACCESS_READWRITE ); + + if( flags==SQLITE_ACCESS_EXISTS ){ struct stat buf; - if( 0==osStat(zPath, &buf) && buf.st_size==0 ){ - *pResOut = 0; - } + *pResOut = (0==osStat(zPath, &buf) && buf.st_size>0); + }else{ + *pResOut = osAccess(zPath, W_OK|R_OK)==0; } return SQLITE_OK; } +/* +** +*/ +static int mkFullPathname( + const char *zPath, /* Input path */ + char *zOut, /* Output buffer */ + int nOut /* Allocated size of buffer zOut */ +){ + int nPath = sqlite3Strlen30(zPath); + int iOff = 0; + if( zPath[0]!='/' ){ + if( osGetcwd(zOut, nOut-2)==0 ){ + return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + } + iOff = sqlite3Strlen30(zOut); + zOut[iOff++] = '/'; + } + if( (iOff+nPath+1)>nOut ){ + /* SQLite assumes that xFullPathname() nul-terminates the output buffer + ** even if it returns an error. */ + zOut[iOff] = '\0'; + return SQLITE_CANTOPEN_BKPT; + } + sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); + return SQLITE_OK; +} /* ** Turn a relative pathname into a full pathname. The relative path @@ -32649,6 +35096,17 @@ static int unixFullPathname( int nOut, /* Size of output buffer in bytes */ char *zOut /* Output buffer */ ){ +#if !defined(HAVE_READLINK) || !defined(HAVE_LSTAT) + return mkFullPathname(zPath, zOut, nOut); +#else + int rc = SQLITE_OK; + int nByte; + int nLink = 1; /* Number of symbolic links followed so far */ + const char *zIn = zPath; /* Input path for each iteration of loop */ + char *zDel = 0; + + assert( pVfs->mxPathname==MAX_PATHNAME ); + UNUSED_PARAMETER(pVfs); /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -32657,21 +35115,62 @@ static int unixFullPathname( */ SimulateIOError( return SQLITE_ERROR ); - assert( pVfs->mxPathname==MAX_PATHNAME ); - UNUSED_PARAMETER(pVfs); + do { - zOut[nOut-1] = '\0'; - if( zPath[0]=='/' ){ - sqlite3_snprintf(nOut, zOut, "%s", zPath); - }else{ - int nCwd; - if( osGetcwd(zOut, nOut-1)==0 ){ - return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + /* Call stat() on path zIn. Set bLink to true if the path is a symbolic + ** link, or false otherwise. */ + int bLink = 0; + struct stat buf; + if( osLstat(zIn, &buf)!=0 ){ + if( errno!=ENOENT ){ + rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); + } + }else{ + bLink = S_ISLNK(buf.st_mode); } - nCwd = (int)strlen(zOut); - sqlite3_snprintf(nOut-nCwd, &zOut[nCwd], "/%s", zPath); - } - return SQLITE_OK; + + if( bLink ){ + if( zDel==0 ){ + zDel = sqlite3_malloc(nOut); + if( zDel==0 ) rc = SQLITE_NOMEM_BKPT; + }else if( ++nLink>SQLITE_MAX_SYMLINKS ){ + rc = SQLITE_CANTOPEN_BKPT; + } + + if( rc==SQLITE_OK ){ + nByte = osReadlink(zIn, zDel, nOut-1); + if( nByte<0 ){ + rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); + }else{ + if( zDel[0]!='/' ){ + int n; + for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); + if( nByte+n+1>nOut ){ + rc = SQLITE_CANTOPEN_BKPT; + }else{ + memmove(&zDel[n], zDel, nByte+1); + memcpy(zDel, zIn, n); + nByte += n; + } + } + zDel[nByte] = '\0'; + } + } + + zIn = zDel; + } + + assert( rc!=SQLITE_OK || zIn!=zOut || zIn[0]=='/' ); + if( rc==SQLITE_OK && zIn!=zOut ){ + rc = mkFullPathname(zIn, zOut, nOut); + } + if( bLink==0 ) break; + zIn = zOut; + }while( rc==SQLITE_OK ); + + sqlite3_free(zDel); + return rc; +#endif /* HAVE_READLINK && HAVE_LSTAT */ } @@ -32840,11 +35339,8 @@ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000; #else struct timeval sNow; - if( gettimeofday(&sNow, 0)==0 ){ - *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; - }else{ - rc = SQLITE_ERROR; - } + (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; #endif #ifdef SQLITE_TEST @@ -32856,6 +35352,7 @@ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ return rc; } +#ifndef SQLITE_OMIT_DEPRECATED /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and @@ -32869,19 +35366,21 @@ static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){ *prNow = i/86400000.0; return rc; } +#else +# define unixCurrentTime 0 +#endif /* -** We added the xGetLastError() method with the intention of providing -** better low-level error messages when operating-system problems come up -** during SQLite operation. But so far, none of that has been implemented -** in the core. So this routine is never called. For now, it is merely -** a place-holder. +** The xGetLastError() method is designed to return a better +** low-level error message when operating-system problems come up +** during SQLite operation. Only the integer return code is currently +** used. */ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ UNUSED_PARAMETER(NotUsed); UNUSED_PARAMETER(NotUsed2); UNUSED_PARAMETER(NotUsed3); - return 0; + return errno; } @@ -33136,7 +35635,7 @@ static int proxyCreateLockPath(const char *lockPath){ } buf[i] = lockPath[i]; } - OSTRACE(("CREATELOCKPATH proxy lock path=%s pid=%d\n", lockPath, osGetpid(0))); + OSTRACE(("CREATELOCKPATH proxy lock path=%s pid=%d\n",lockPath,osGetpid(0))); return 0; } @@ -33172,7 +35671,7 @@ static int proxyCreateUnixFile( }else{ pUnused = sqlite3_malloc64(sizeof(*pUnused)); if( !pUnused ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } } if( fd<0 ){ @@ -33205,7 +35704,7 @@ static int proxyCreateUnixFile( pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew)); if( pNew==NULL ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto end_create_proxy; } memset(pNew, 0, sizeof(unixFile)); @@ -33548,7 +36047,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); - fsync(conchFile->h); + 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 */ @@ -33618,7 +36117,7 @@ static int proxyTakeConch(unixFile *pFile){ if( tempLockPath ){ pCtx->lockProxyPath = sqlite3DbStrDup(0, tempLockPath); if( !pCtx->lockProxyPath ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } } } @@ -33683,7 +36182,7 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ ** the name of the original database file. */ *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8); if( conchPath==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memcpy(conchPath, dbPath, len+1); @@ -33799,7 +36298,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { pCtx = sqlite3_malloc64( sizeof(*pCtx) ); if( pCtx==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(pCtx, 0, sizeof(*pCtx)); @@ -33835,7 +36334,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { if( rc==SQLITE_OK ){ pCtx->dbPath = sqlite3DbStrDup(0, dbPath); if( pCtx->dbPath==NULL ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } } if( rc==SQLITE_OK ){ @@ -34021,7 +36520,7 @@ static int proxyUnlock(sqlite3_file *id, int eFileLock) { ** Close a file that uses proxy locks. */ static int proxyClose(sqlite3_file *id) { - if( id ){ + if( ALWAYS(id) ){ unixFile *pFile = (unixFile*)id; proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; unixFile *lockProxy = pCtx->lockProxy; @@ -34165,7 +36664,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==25 ); + assert( ArraySize(aSyscall)==28 ); /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ @@ -34248,8 +36747,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void){ */ #ifdef SQLITE_PERFORMANCE_TRACE -/* -** hwtime.h contains inline assembler code for implementing +/* +** hwtime.h contains inline assembler code for implementing ** high-performance timing routines. */ /************** Include hwtime.h in the middle of os_common.h ****************/ @@ -34359,14 +36858,14 @@ static sqlite_uint64 g_elapsed; ** of code will give us the ability to simulate a disk I/O error. This ** is used for testing the I/O recovery logic. */ -#ifdef 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; +#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) #define SimulateIOError(CODE) \ if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ @@ -34392,17 +36891,17 @@ static void local_ioerr(){ #define SimulateIOErrorBenign(X) #define SimulateIOError(A) #define SimulateDiskfullError(A) -#endif +#endif /* defined(SQLITE_TEST) */ /* ** When testing, keep a count of the number of open files. */ -#ifdef SQLITE_TEST -SQLITE_API int sqlite3_open_file_count = 0; +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; #define OpenCounter(X) sqlite3_open_file_count+=(X) #else #define OpenCounter(X) -#endif +#endif /* defined(SQLITE_TEST) */ #endif /* !defined(_OS_COMMON_H_) */ @@ -34465,6 +36964,10 @@ SQLITE_API int sqlite3_open_file_count = 0; # define NTDDI_WINBLUE 0x06030000 #endif +#ifndef NTDDI_WINTHRESHOLD +# define NTDDI_WINTHRESHOLD 0x06040000 +#endif + /* ** Check to see if the GetVersionEx[AW] functions are deprecated on the ** target system. GetVersionEx was first deprecated in Win8.1. @@ -34477,6 +36980,19 @@ SQLITE_API int sqlite3_open_file_count = 0; # endif #endif +/* +** Check to see if the CreateFileMappingA function is supported on the +** target system. It is unavailable when using "mincore.lib" on Win10. +** When compiling for Windows 10, always assume "mincore.lib" is in use. +*/ +#ifndef SQLITE_WIN32_CREATEFILEMAPPINGA +# if defined(NTDDI_VERSION) && NTDDI_VERSION >= NTDDI_WINTHRESHOLD +# define SQLITE_WIN32_CREATEFILEMAPPINGA 0 +# else +# define SQLITE_WIN32_CREATEFILEMAPPINGA 1 +# endif +#endif + /* ** This constant should already be defined (in the "WinDef.h" SDK file). */ @@ -34716,11 +37232,23 @@ struct winFile { # define SQLITE_WIN32_HEAP_CREATE (TRUE) #endif +/* + * This is cache size used in the calculation of the initial size of the + * Win32-specific heap. It cannot be negative. + */ +#ifndef SQLITE_WIN32_CACHE_SIZE +# if SQLITE_DEFAULT_CACHE_SIZE>=0 +# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE) +# else +# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE)) +# endif +#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_DEFAULT_CACHE_SIZE) * \ +# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \ (SQLITE_DEFAULT_PAGE_SIZE) + 4194304) #endif @@ -34883,8 +37411,9 @@ static struct win_syscall { #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) -#if (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ + SQLITE_WIN32_CREATEFILEMAPPINGA { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, #else { "CreateFileMappingA", (SYSCALL)0, 0 }, @@ -35114,8 +37643,7 @@ static struct win_syscall { #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) -#if defined(SQLITE_WIN32_HAS_ANSI) && defined(SQLITE_WIN32_GETVERSIONEX) && \ - SQLITE_WIN32_GETVERSIONEX +#if defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_GETVERSIONEX { "GetVersionExA", (SYSCALL)GetVersionExA, 0 }, #else { "GetVersionExA", (SYSCALL)0, 0 }, @@ -35125,7 +37653,7 @@ static struct win_syscall { LPOSVERSIONINFOA))aSyscall[34].pCurrent) #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ - defined(SQLITE_WIN32_GETVERSIONEX) && SQLITE_WIN32_GETVERSIONEX + SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else { "GetVersionExW", (SYSCALL)0, 0 }, @@ -35594,7 +38122,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_win32_compact_heap(LPUINT pnLargest){ if( lastErrno==NO_ERROR ){ sqlite3_log(SQLITE_NOMEM, "failed to HeapCompact (no space), heap=%p", (void*)hHeap); - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ sqlite3_log(SQLITE_ERROR, "failed to HeapCompact (%lu), heap=%p", osGetLastError(), (void*)hHeap); @@ -35620,8 +38148,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_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 = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); ) - MUTEX_LOGIC( pMem = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM); ) + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); ) sqlite3_mutex_enter(pMaster); sqlite3_mutex_enter(pMem); winMemAssertMagic(); @@ -35666,6 +38194,12 @@ SQLITE_API void SQLITE_STDCALL sqlite3_win32_write_debug(const char *zBuf, int n int nMin = MIN(nBuf, (SQLITE_WIN32_DBG_BUF_SIZE - 1)); /* may be negative. */ if( nMin<-1 ) nMin = -1; /* all negative values become -1. */ assert( nMin==-1 || nMin==0 || nMin0 ){ memset(zDbgBuf, 0, SQLITE_WIN32_DBG_BUF_SIZE); @@ -35736,7 +38270,7 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ ** the LockFileEx() API. */ -#if !defined(SQLITE_WIN32_GETVERSIONEX) || !SQLITE_WIN32_GETVERSIONEX +#if !SQLITE_WIN32_GETVERSIONEX # define osIsNT() (1) #elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) @@ -35757,7 +38291,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_win32_is_nt(void){ ** kernel. */ return 1; -#elif defined(SQLITE_WIN32_GETVERSIONEX) && SQLITE_WIN32_GETVERSIONEX +#elif SQLITE_WIN32_GETVERSIONEX if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ #if defined(SQLITE_WIN32_HAS_ANSI) OSVERSIONINFOA sInfo; @@ -35914,7 +38448,7 @@ static int winMemInit(void *pAppData){ "failed to HeapCreate (%lu), flags=%u, initSize=%lu, maxSize=%lu", osGetLastError(), SQLITE_WIN32_HEAP_FLAGS, dwInitialSize, dwMaximumSize); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pWinMemData->bOwned = TRUE; assert( pWinMemData->bOwned ); @@ -35924,7 +38458,7 @@ static int winMemInit(void *pAppData){ if( !pWinMemData->hHeap ){ sqlite3_log(SQLITE_NOMEM, "failed to GetProcessHeap (%lu)", osGetLastError()); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pWinMemData->bOwned = FALSE; assert( !pWinMemData->bOwned ); @@ -35991,147 +38525,244 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){ #endif /* SQLITE_WIN32_MALLOC */ /* -** Convert a UTF-8 string to Microsoft Unicode (UTF-16?). +** Convert a UTF-8 string to Microsoft Unicode. ** -** Space to hold the returned string is obtained from malloc. +** Space to hold the returned string is obtained from sqlite3_malloc(). */ -static LPWSTR winUtf8ToUnicode(const char *zFilename){ +static LPWSTR winUtf8ToUnicode(const char *zText){ int nChar; - LPWSTR zWideFilename; + LPWSTR zWideText; - nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); + nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0); if( nChar==0 ){ return 0; } - zWideFilename = sqlite3MallocZero( nChar*sizeof(zWideFilename[0]) ); - if( zWideFilename==0 ){ + zWideText = sqlite3MallocZero( nChar*sizeof(WCHAR) ); + if( zWideText==0 ){ return 0; } - nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, + nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText, nChar); if( nChar==0 ){ - sqlite3_free(zWideFilename); - zWideFilename = 0; + sqlite3_free(zWideText); + zWideText = 0; } - return zWideFilename; + return zWideText; } /* -** Convert Microsoft Unicode to UTF-8. Space to hold the returned string is -** obtained from sqlite3_malloc(). +** Convert a Microsoft Unicode string to UTF-8. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). */ -static char *winUnicodeToUtf8(LPCWSTR zWideFilename){ +static char *winUnicodeToUtf8(LPCWSTR zWideText){ int nByte; - char *zFilename; + char *zText; - nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); + nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0); if( nByte == 0 ){ return 0; } - zFilename = sqlite3MallocZero( nByte ); - if( zFilename==0 ){ + zText = sqlite3MallocZero( nByte ); + if( zText==0 ){ return 0; } - nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, + nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte, 0, 0); if( nByte == 0 ){ - sqlite3_free(zFilename); - zFilename = 0; + sqlite3_free(zText); + zText = 0; } - return zFilename; + return zText; } /* -** Convert an ANSI string to Microsoft Unicode, based on the -** current codepage settings for file apis. +** 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 sqlite3_malloc(). */ -static LPWSTR winMbcsToUnicode(const char *zFilename){ +static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ int nByte; - LPWSTR zMbcsFilename; - int codepage = osAreFileApisANSI() ? CP_ACP : CP_OEMCP; + LPWSTR zMbcsText; + int codepage = useAnsi ? CP_ACP : CP_OEMCP; - nByte = osMultiByteToWideChar(codepage, 0, zFilename, -1, NULL, + nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, 0)*sizeof(WCHAR); if( nByte==0 ){ return 0; } - zMbcsFilename = sqlite3MallocZero( nByte*sizeof(zMbcsFilename[0]) ); - if( zMbcsFilename==0 ){ + zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) ); + if( zMbcsText==0 ){ return 0; } - nByte = osMultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, + nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, nByte); if( nByte==0 ){ - sqlite3_free(zMbcsFilename); - zMbcsFilename = 0; + sqlite3_free(zMbcsText); + zMbcsText = 0; } - return zMbcsFilename; + return zMbcsText; } /* -** Convert Microsoft Unicode to multi-byte character string, based on the -** user's ANSI codepage. +** 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 sqlite3_malloc(). */ -static char *winUnicodeToMbcs(LPCWSTR zWideFilename){ +static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){ int nByte; - char *zFilename; - int codepage = osAreFileApisANSI() ? CP_ACP : CP_OEMCP; + char *zText; + int codepage = useAnsi ? CP_ACP : CP_OEMCP; - nByte = osWideCharToMultiByte(codepage, 0, zWideFilename, -1, 0, 0, 0, 0); + nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, 0, 0, 0, 0); if( nByte == 0 ){ return 0; } - zFilename = sqlite3MallocZero( nByte ); - if( zFilename==0 ){ + zText = sqlite3MallocZero( nByte ); + if( zText==0 ){ return 0; } - nByte = osWideCharToMultiByte(codepage, 0, zWideFilename, -1, zFilename, + nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, zText, nByte, 0, 0); if( nByte == 0 ){ - sqlite3_free(zFilename); - zFilename = 0; + sqlite3_free(zText); + zText = 0; } - return zFilename; + return zText; } /* -** Convert multibyte character string to UTF-8. Space to hold the -** returned string is obtained from sqlite3_malloc(). +** Convert a multi-byte character string to UTF-8. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). */ -SQLITE_API char *SQLITE_STDCALL sqlite3_win32_mbcs_to_utf8(const char *zFilename){ - char *zFilenameUtf8; +static char *winMbcsToUtf8(const char *zText, int useAnsi){ + char *zTextUtf8; LPWSTR zTmpWide; - zTmpWide = winMbcsToUnicode(zFilename); + zTmpWide = winMbcsToUnicode(zText, useAnsi); if( zTmpWide==0 ){ return 0; } - zFilenameUtf8 = winUnicodeToUtf8(zTmpWide); + zTextUtf8 = winUnicodeToUtf8(zTmpWide); sqlite3_free(zTmpWide); - return zFilenameUtf8; + return zTextUtf8; } /* -** Convert UTF-8 to multibyte character string. Space to hold the -** returned string is obtained from sqlite3_malloc(). +** Convert a UTF-8 string to a multi-byte character string. +** +** Space to hold the returned string is obtained from sqlite3_malloc(). */ -SQLITE_API char *SQLITE_STDCALL sqlite3_win32_utf8_to_mbcs(const char *zFilename){ - char *zFilenameMbcs; +static char *winUtf8ToMbcs(const char *zText, int useAnsi){ + char *zTextMbcs; LPWSTR zTmpWide; - zTmpWide = winUtf8ToUnicode(zFilename); + zTmpWide = winUtf8ToUnicode(zText); if( zTmpWide==0 ){ return 0; } - zFilenameMbcs = winUnicodeToMbcs(zTmpWide); + zTextMbcs = winUnicodeToMbcs(zTmpWide, useAnsi); sqlite3_free(zTmpWide); - return zFilenameMbcs; + return zTextMbcs; +} + +/* +** This is a public wrapper for the winUtf8ToUnicode() function. +*/ +SQLITE_API LPWSTR SQLITE_STDCALL sqlite3_win32_utf8_to_unicode(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToUnicode(zText); +} + +/* +** This is a public wrapper for the winUnicodeToUtf8() function. +*/ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zWideText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUnicodeToUtf8(zWideText); +} + +/* +** This is a public wrapper for the winMbcsToUtf8() function. +*/ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_mbcs_to_utf8(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winMbcsToUtf8(zText, osAreFileApisANSI()); +} + +/* +** This is a public wrapper for the winMbcsToUtf8() function. +*/ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winMbcsToUtf8(zText, useAnsi); +} + +/* +** This is a public wrapper for the winUtf8ToMbcs() function. +*/ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_utf8_to_mbcs(const char *zText){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToMbcs(zText, osAreFileApisANSI()); +} + +/* +** This is a public wrapper for the winUtf8ToMbcs() function. +*/ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zText ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return winUtf8ToMbcs(zText, useAnsi); } /* @@ -36161,7 +38792,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_win32_set_directory(DWORD type, LPCWSTR zV if( zValue && zValue[0] ){ zValueUtf8 = winUnicodeToUtf8(zValue); if ( zValueUtf8==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } } sqlite3_free(*ppDirectory); @@ -36233,7 +38864,7 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ sqlite3BeginBenignMalloc(); - zOut = sqlite3_win32_mbcs_to_utf8(zTemp); + zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); sqlite3EndBenignMalloc(); /* free the system buffer allocated by FormatMessage */ osLocalFree(zTemp); @@ -36375,16 +39006,17 @@ static void winLogIoerr(int nRetry, int lineno){ } } -#if SQLITE_OS_WINCE -/************************************************************************* -** This section contains code for WinCE only. -*/ -#if !defined(SQLITE_MSVC_LOCALTIME_API) || !SQLITE_MSVC_LOCALTIME_API /* -** The MSVC CRT on Windows CE may not have a localtime() function. So -** create a substitute. +** This #if does not rely on the SQLITE_OS_WINCE define because the +** corresponding section in "date.c" cannot use it. */ -/* #include */ +#if !defined(SQLITE_OMIT_LOCALTIME) && defined(_WIN32_WCE) && \ + (!defined(SQLITE_MSVC_LOCALTIME_API) || !SQLITE_MSVC_LOCALTIME_API) +/* +** The MSVC CRT on Windows CE may not have a localtime() function. +** So define a substitute. +*/ +/* # include */ struct tm *__cdecl localtime(const time_t *t) { static struct tm y; @@ -36408,6 +39040,10 @@ struct tm *__cdecl localtime(const time_t *t) } #endif +#if SQLITE_OS_WINCE +/************************************************************************* +** This section contains code for WinCE only. +*/ #define HANDLE_TO_WINFILE(a) (winFile*)&((char*)a)[-(int)offsetof(winFile,h)] /* @@ -36438,7 +39074,7 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){ zName = winUtf8ToUnicode(zFilename); if( zName==0 ){ /* out of memory */ - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } /* Initialize the local lockdata */ @@ -36991,7 +39627,7 @@ static int winWrite( "offset=%lld, lock=%d\n", osGetCurrentProcessId(), pFile, pFile->h, pBuf, amt, offset, pFile->locktype)); -#if SQLITE_MAX_MMAP_SIZE>0 +#if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 /* Deal with as much of this write request as possible by transfering ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ @@ -37421,9 +40057,8 @@ static int winLock(sqlite3_file *id, int locktype){ ** the PENDING_LOCK byte is temporary. */ newLocktype = pFile->locktype; - if( (pFile->locktype==NO_LOCK) - || ( (locktype==EXCLUSIVE_LOCK) - && (pFile->locktype==RESERVED_LOCK)) + if( pFile->locktype==NO_LOCK + || (locktype==EXCLUSIVE_LOCK && pFile->locktype<=RESERVED_LOCK) ){ int cnt = 3; while( cnt-->0 && (res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, @@ -37539,7 +40174,7 @@ static int winCheckReservedLock(sqlite3_file *id, int *pResOut){ res = 1; OSTRACE(("TEST-WR-LOCK file=%p, result=%d (local)\n", pFile->h, res)); }else{ - res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS,RESERVED_BYTE, 0, 1, 0); + res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS,RESERVED_BYTE,0,1,0); if( res ){ winUnlockFile(&pFile->h, RESERVED_BYTE, 0, 1, 0); } @@ -37629,7 +40264,7 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } - case SQLITE_LAST_ERRNO: { + case SQLITE_FCNTL_LAST_ERRNO: { *(int*)pArg = (int)pFile->lastErrno; OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; @@ -37987,12 +40622,12 @@ static int winOpenSharedMemory(winFile *pDbFd){ ** allocate space for a new winShmNode and filename. */ p = sqlite3MallocZero( sizeof(*p) ); - if( p==0 ) return SQLITE_IOERR_NOMEM; + if( p==0 ) return SQLITE_IOERR_NOMEM_BKPT; nName = sqlite3Strlen30(pDbFd->zPath); pNew = sqlite3MallocZero( sizeof(*pShmNode) + nName + 17 ); if( pNew==0 ){ sqlite3_free(p); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } pNew->zFilename = (char*)&pNew[1]; sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); @@ -38017,10 +40652,12 @@ static int winOpenSharedMemory(winFile *pDbFd){ pShmNode->pNext = winShmNodeList; winShmNodeList = pShmNode; - pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if( pShmNode->mutex==0 ){ - rc = SQLITE_IOERR_NOMEM; - goto shm_open_err; + if( sqlite3GlobalConfig.bCoreMutex ){ + pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->mutex==0 ){ + rc = SQLITE_IOERR_NOMEM_BKPT; + goto shm_open_err; + } } rc = winOpen(pDbFd->pVfs, @@ -38324,7 +40961,7 @@ static int winShmMap( pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0]) ); if( !apNew ){ - rc = SQLITE_IOERR_NOMEM; + rc = SQLITE_IOERR_NOMEM_BKPT; goto shmpage_out; } pShmNode->aRegion = apNew; @@ -38341,7 +40978,7 @@ static int winShmMap( hMap = osCreateFileMappingW(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); -#elif defined(SQLITE_WIN32_HAS_ANSI) +#elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA hMap = osCreateFileMappingA(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); @@ -38485,17 +41122,19 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ DWORD flags = FILE_MAP_READ; winUnmapfile(pFd); +#ifdef SQLITE_MMAP_READWRITE if( (pFd->ctrlFlags & WINFILE_RDONLY)==0 ){ protect = PAGE_READWRITE; flags |= FILE_MAP_WRITE; } +#endif #if SQLITE_OS_WINRT pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); #elif defined(SQLITE_WIN32_HAS_WIDE) pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); -#elif defined(SQLITE_WIN32_HAS_ANSI) +#elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA pFd->hMap = osCreateFileMappingA(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); @@ -38676,7 +41315,7 @@ static char *winConvertToUtf8Filename(const void *zFilename){ } #ifdef SQLITE_WIN32_HAS_ANSI else{ - zConverted = sqlite3_win32_mbcs_to_utf8(zFilename); + zConverted = winMbcsToUtf8(zFilename, osAreFileApisANSI()); } #endif /* caller will handle out of memory */ @@ -38697,7 +41336,7 @@ static void *winConvertFromUtf8Filename(const char *zFilename){ } #ifdef SQLITE_WIN32_HAS_ANSI else{ - zConverted = sqlite3_win32_utf8_to_mbcs(zFilename); + zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); } #endif /* caller will handle out of memory */ @@ -38752,7 +41391,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ zBuf = sqlite3MallocZero( nBuf ); if( !zBuf ){ OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } /* Figure out the effective temporary directory. First, check if one @@ -38810,7 +41449,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ if( !zConverted ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( winIsDir(zConverted) ){ sqlite3_snprintf(nMax, zBuf, "%s", zDir); @@ -38823,7 +41462,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ if( !zConverted ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( cygwin_conv_path( osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir, @@ -38844,7 +41483,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ sqlite3_free(zConverted); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); sqlite3_free(zUtf8); @@ -38862,7 +41501,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ if( !zWidePath ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( osGetTempPathW(nMax, zWidePath)==0 ){ sqlite3_free(zWidePath); @@ -38880,7 +41519,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ sqlite3_free(zWidePath); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } } #ifdef SQLITE_WIN32_HAS_ANSI @@ -38890,7 +41529,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ if( !zMbcsPath ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( osGetTempPathA(nMax, zMbcsPath)==0 ){ sqlite3_free(zBuf); @@ -38898,14 +41537,14 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(), "winGetTempname3", 0); } - zUtf8 = sqlite3_win32_mbcs_to_utf8(zMbcsPath); + zUtf8 = winMbcsToUtf8(zMbcsPath, osAreFileApisANSI()); if( zUtf8 ){ sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); sqlite3_free(zUtf8); }else{ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } } #endif /* SQLITE_WIN32_HAS_ANSI */ @@ -39097,7 +41736,7 @@ static int winOpen( if( zConverted==0 ){ sqlite3_free(zTmpname); OSTRACE(("OPEN name=%s, rc=SQLITE_IOERR_NOMEM", zUtf8Name)); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( winIsDir(zConverted) ){ @@ -39297,7 +41936,7 @@ static int winDelete( zConverted = winConvertFromUtf8Filename(zFilename); if( zConverted==0 ){ OSTRACE(("DELETE name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( osIsNT() ){ do { @@ -39405,7 +42044,7 @@ static int winAccess( zConverted = winConvertFromUtf8Filename(zFilename); if( zConverted==0 ){ OSTRACE(("ACCESS name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( osIsNT() ){ int cnt = 0; @@ -39532,7 +42171,7 @@ static int winFullPathname( */ char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); if( !zOut ){ - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( cygwin_conv_path( (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | @@ -39544,7 +42183,7 @@ static int winFullPathname( char *zUtf8 = winConvertToUtf8Filename(zOut); if( !zUtf8 ){ sqlite3_free(zOut); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", sqlite3_data_directory, winGetDirSep(), zUtf8); @@ -39554,7 +42193,7 @@ static int winFullPathname( }else{ char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); if( !zOut ){ - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( cygwin_conv_path( (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), @@ -39566,7 +42205,7 @@ static int winFullPathname( char *zUtf8 = winConvertToUtf8Filename(zOut); if( !zUtf8 ){ sqlite3_free(zOut); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); sqlite3_free(zUtf8); @@ -39626,7 +42265,7 @@ static int winFullPathname( } zConverted = winConvertFromUtf8Filename(zRelative); if( zConverted==0 ){ - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } if( osIsNT() ){ LPWSTR zTemp; @@ -39640,7 +42279,7 @@ static int winFullPathname( zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); if( zTemp==0 ){ sqlite3_free(zConverted); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0); if( nByte==0 ){ @@ -39666,7 +42305,7 @@ static int winFullPathname( zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); if( zTemp==0 ){ sqlite3_free(zConverted); - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } nByte = osGetFullPathNameA((char*)zConverted, nByte, zTemp, 0); if( nByte==0 ){ @@ -39676,7 +42315,7 @@ static int winFullPathname( "winFullPathname4", zRelative); } sqlite3_free(zConverted); - zOut = sqlite3_win32_mbcs_to_utf8(zTemp); + zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); sqlite3_free(zTemp); } #endif @@ -39685,7 +42324,7 @@ static int winFullPathname( sqlite3_free(zOut); return SQLITE_OK; }else{ - return SQLITE_IOERR_NOMEM; + return SQLITE_IOERR_NOMEM_BKPT; } #endif } @@ -39760,65 +42399,85 @@ static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ #define winDlClose 0 #endif +/* State information for the randomness gatherer. */ +typedef struct EntropyGatherer EntropyGatherer; +struct EntropyGatherer { + unsigned char *a; /* Gather entropy into this buffer */ + int na; /* Size of a[] in bytes */ + int i; /* XOR next input into a[i] */ + int nXor; /* Number of XOR operations done */ +}; + +#if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) +/* Mix sz bytes of entropy into p. */ +static void xorMemory(EntropyGatherer *p, unsigned char *x, int sz){ + int j, k; + for(j=0, k=p->i; ja[k++] ^= x[j]; + if( k>=p->na ) k = 0; + } + p->i = k; + p->nXor += sz; +} +#endif /* !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) */ /* ** Write up to nBuf bytes of randomness into zBuf. */ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ - int n = 0; - UNUSED_PARAMETER(pVfs); #if defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) - n = nBuf; + UNUSED_PARAMETER(pVfs); memset(zBuf, 0, nBuf); + return nBuf; #else - if( sizeof(SYSTEMTIME)<=nBuf-n ){ + 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; + e.i = 0; + { SYSTEMTIME x; osGetSystemTime(&x); - memcpy(&zBuf[n], &x, sizeof(x)); - n += sizeof(x); + xorMemory(&e, (unsigned char*)&x, sizeof(SYSTEMTIME)); } - if( sizeof(DWORD)<=nBuf-n ){ + { DWORD pid = osGetCurrentProcessId(); - memcpy(&zBuf[n], &pid, sizeof(pid)); - n += sizeof(pid); + xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); } #if SQLITE_OS_WINRT - if( sizeof(ULONGLONG)<=nBuf-n ){ + { ULONGLONG cnt = osGetTickCount64(); - memcpy(&zBuf[n], &cnt, sizeof(cnt)); - n += sizeof(cnt); + xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); } #else - if( sizeof(DWORD)<=nBuf-n ){ + { DWORD cnt = osGetTickCount(); - memcpy(&zBuf[n], &cnt, sizeof(cnt)); - n += sizeof(cnt); + xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); } -#endif - if( sizeof(LARGE_INTEGER)<=nBuf-n ){ +#endif /* SQLITE_OS_WINRT */ + { LARGE_INTEGER i; osQueryPerformanceCounter(&i); - memcpy(&zBuf[n], &i, sizeof(i)); - n += sizeof(i); + xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); } #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID - if( sizeof(UUID)<=nBuf-n ){ + { UUID id; memset(&id, 0, sizeof(UUID)); osUuidCreate(&id); - memcpy(&zBuf[n], &id, sizeof(UUID)); - n += sizeof(UUID); - } - if( sizeof(UUID)<=nBuf-n ){ - UUID id; + xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); memset(&id, 0, sizeof(UUID)); osUuidCreateSequential(&id); - memcpy(&zBuf[n], &id, sizeof(UUID)); - n += sizeof(UUID); + xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); } -#endif -#endif /* defined(SQLITE_TEST) || defined(SQLITE_ZERO_PRNG_SEED) */ - return n; +#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ + return e.nXor>nBuf ? nBuf : e.nXor; +#endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ } @@ -39934,8 +42593,10 @@ static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ ** sqlite3_errmsg(), possibly making IO errors easier to debug. */ static int winGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ + DWORD e = osGetLastError(); UNUSED_PARAMETER(pVfs); - return winGetLastErrorMsg(osGetLastError(), nBuf, zBuf); + if( nBuf>0 ) winGetLastErrorMsg(e, nBuf, zBuf); + return e; } /* @@ -40073,7 +42734,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void){ /* Round the union size down to the nearest pointer boundary, since that's how ** it will be aligned within the Bitvec struct. */ -#define BITVEC_USIZE (((BITVEC_SZ-(3*sizeof(u32)))/sizeof(Bitvec*))*sizeof(Bitvec*)) +#define BITVEC_USIZE \ + (((BITVEC_SZ-(3*sizeof(u32)))/sizeof(Bitvec*))*sizeof(Bitvec*)) /* Type of the array "element" for the bitmap representation. ** Should be a power of 2, and ideally, evenly divide into BITVEC_USIZE. @@ -40208,7 +42870,7 @@ SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){ i = i%p->iDivisor; if( p->u.apSub[bin]==0 ){ p->u.apSub[bin] = sqlite3BitvecCreate( p->iDivisor ); - if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM; + if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM_BKPT; } p = p->u.apSub[bin]; } @@ -40243,7 +42905,7 @@ bitvec_set_rehash: int rc; u32 *aiValues = sqlite3StackAllocRaw(0, sizeof(p->u.aHash)); if( aiValues==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; }else{ memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); memset(p->u.apSub, 0, sizeof(p->u.apSub)); @@ -40459,13 +43121,36 @@ bitvec_end: /* #include "sqliteInt.h" */ /* -** A complete page cache is an instance of this structure. +** A complete page cache is an instance of this structure. Every +** entry in the cache holds a single page of the database file. The +** btree layer only operates on the cached copy of the database pages. +** +** A page cache entry is "clean" if it exactly matches what is currently +** on disk. A page is "dirty" if it has been modified and needs to be +** persisted to disk. +** +** pDirty, pDirtyTail, pSynced: +** All dirty pages are linked into the doubly linked list using +** PgHdr.pDirtyNext and pDirtyPrev. The list is maintained in LRU order +** such that p was added to the list more recently than p->pDirtyNext. +** PCache.pDirty points to the first (newest) element in the list and +** pDirtyTail to the last (oldest). +** +** 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 +** 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 +** pointers). */ struct PCache { PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */ PgHdr *pSynced; /* Last synced page in dirty page list */ int nRefSum; /* Sum of ref counts over all pages */ int szCache; /* Configured cache size */ + int szSpill; /* Size before spilling occurs */ int szPage; /* Size of every page in this cache */ int szExtra; /* Size of extra space for each page */ u8 bPurgeable; /* True if pages are on backing store */ @@ -40475,6 +43160,95 @@ struct PCache { sqlite3_pcache *pCache; /* Pluggable cache module */ }; +/********************************** Test and Debug Logic **********************/ +/* +** 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 +** 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;} + void pcacheDump(PCache *pCache){ + int N; + int i, j; + sqlite3_pcache_page *pLower; + PgHdr *pPg; + unsigned char *a; + + if( sqlite3PcacheTrace<2 ) return; + if( pCache->pCache==0 ) return; + N = sqlite3PcachePagecount(pCache); + if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump; + for(i=1; i<=N; i++){ + pLower = sqlite3GlobalConfig.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); + a = (unsigned char *)pLower->pBuf; + for(j=0; j<12; j++) printf("%02x", a[j]); + printf("\n"); + if( pPg->pPage==0 ){ + sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0); + } + } + } + #else +# define pcacheTrace(X) +# define pcacheDump(X) +#endif + +/* +** Check invariants on a PgHdr entry. Return true if everything is OK. +** Return false if any invariant is violated. +** +** This routine is for use inside of assert() statements only. For +** example: +** +** assert( sqlite3PcachePageSanity(pPg) ); +*/ +#if SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){ + PCache *pCache; + assert( pPg!=0 ); + assert( pPg->pgno>0 ); /* Page number is 1 or more */ + pCache = pPg->pCache; + assert( pCache!=0 ); /* Every page has an associated PCache */ + if( pPg->flags & PGHDR_CLEAN ){ + assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ + assert( pCache->pDirty!=pPg ); /* CLEAN pages not on dirty list */ + assert( pCache->pDirtyTail!=pPg ); + } + /* WRITEABLE pages must also be DIRTY */ + if( pPg->flags & PGHDR_WRITEABLE ){ + 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: + ** (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 + ** If NEED_SYNC had been cleared in step 2, then it would not be reset + ** in step 3, and page might be written into the database without first + ** syncing the rollback journal, which might cause corruption on a power + ** loss. + ** + ** Another example is when the database page size is smaller than the + ** disk sector size. When any page of a sector is journalled, all pages + ** in that sector are marked NEED_SYNC even if they are still CLEAN, just + ** in case they are later modified, since all pages in the same sector + ** must be journalled and synced before any of those pages can be safely + ** written. + */ + return 1; +} +#endif /* SQLITE_DEBUG */ + + /********************************** Linked List Management ********************/ /* Allowed values for second argument to pcacheManageDirtyList() */ @@ -40491,17 +43265,16 @@ struct PCache { static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ PCache *p = pPage->pCache; + pcacheTrace(("%p.DIRTYLIST.%s %d\n", p, + addRemove==1 ? "REMOVE" : addRemove==2 ? "ADD" : "FRONT", + pPage->pgno)); if( addRemove & PCACHE_DIRTYLIST_REMOVE ){ assert( pPage->pDirtyNext || pPage==p->pDirtyTail ); assert( pPage->pDirtyPrev || pPage==p->pDirty ); /* Update the PCache1.pSynced variable if necessary. */ if( p->pSynced==pPage ){ - PgHdr *pSynced = pPage->pDirtyPrev; - while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){ - pSynced = pSynced->pDirtyPrev; - } - p->pSynced = pSynced; + p->pSynced = pPage->pDirtyPrev; } if( pPage->pDirtyNext ){ @@ -40513,10 +43286,15 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ if( pPage->pDirtyPrev ){ 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 + ** searching for a dirty page to eject from the cache when it might + ** otherwise have to. */ assert( pPage==p->pDirty ); p->pDirty = pPage->pDirtyNext; - if( p->pDirty==0 && p->bPurgeable ){ - assert( p->eCreate==1 ); + assert( p->bPurgeable || p->eCreate==2 ); + if( p->pDirty==0 ){ /*OPTIMIZATION-IF-TRUE*/ + assert( p->bPurgeable==0 || p->eCreate==1 ); p->eCreate = 2; } } @@ -40538,10 +43316,19 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ } } p->pDirty = pPage; - if( !p->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){ + + /* 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 + ** 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*/ + ){ p->pSynced = pPage; } } + pcacheDump(p); } /* @@ -40550,15 +43337,15 @@ 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); + pcacheDump(p->pCache); } } /* -** Compute the number of pages of cache requested. p->szCache is the +** Compute the number of pages of cache requested. p->szCache is the ** cache size requested by the "PRAGMA cache_size" statement. -** -** */ static int numberOfCachePages(PCache *p){ if( p->szCache>=0 ){ @@ -40621,6 +43408,8 @@ SQLITE_PRIVATE int sqlite3PcacheOpen( p->xStress = xStress; p->pStress = pStress; p->szCache = 100; + p->szSpill = 1; + pcacheTrace(("%p.OPEN szPage %d bPurgeable %d\n",p,szPage,bPurgeable)); return sqlite3PcacheSetPageSize(p, szPage); } @@ -40636,13 +43425,14 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){ szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)), pCache->bPurgeable ); - if( pNew==0 ) return SQLITE_NOMEM; + if( pNew==0 ) return SQLITE_NOMEM_BKPT; sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache)); if( pCache->pCache ){ sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); } pCache->pCache = pNew; pCache->szPage = szPage; + pcacheTrace(("%p.PAGESIZE %d\n",pCache,szPage)); } return SQLITE_OK; } @@ -40677,11 +43467,13 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch( int createFlag /* If true, create page if it does not exist already */ ){ int eCreate; + sqlite3_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. ** 0 Do not allocate a new page. (createFlag==0) @@ -40694,12 +43486,15 @@ 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) ); - return sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); + pRes = sqlite3GlobalConfig.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 -** page because new clean pages are available for reuse and the cache +** 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 ** callback to spill dirty pages to the journal. It will then try to @@ -40716,36 +43511,43 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress( PgHdr *pPg; if( pCache->eCreate==2 ) return 0; - - /* 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 - ** unreferenced dirty page. - */ - for(pPg=pCache->pSynced; - pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); - pPg=pPg->pDirtyPrev - ); - pCache->pSynced = pPg; - if( !pPg ){ - for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev); - } - if( pPg ){ - int rc; + if( sqlite3PcachePagecount(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 + ** unreferenced dirty page. + ** + ** If the LRU page in the dirty list that has a clear PGHDR_NEED_SYNC + ** flag is currently referenced, then the following may leave pSynced + ** set incorrectly (pointing to other than the LRU page with NEED_SYNC + ** cleared). This is Ok, as pSynced is just an optimization. */ + for(pPg=pCache->pSynced; + pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); + pPg=pPg->pDirtyPrev + ); + pCache->pSynced = pPg; + if( !pPg ){ + for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev); + } + if( pPg ){ + int rc; #ifdef SQLITE_LOG_CACHE_SPILL - sqlite3_log(SQLITE_FULL, - "spill page %d making room for %d - cache used: %d/%d", - pPg->pgno, pgno, - sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache), + sqlite3_log(SQLITE_FULL, + "spill page %d making room for %d - cache used: %d/%d", + pPg->pgno, pgno, + sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache), numberOfCachePages(pCache)); #endif - rc = pCache->xStress(pCache->pStress, pPg); - if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ - return rc; + pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno)); + rc = pCache->xStress(pCache->pStress, pPg); + pcacheDump(pCache); + if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ + return rc; + } } } *ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2); - return *ppPage==0 ? SQLITE_NOMEM : SQLITE_OK; + return *ppPage==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; } /* @@ -40798,6 +43600,7 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish( } pCache->nRefSum++; pPgHdr->nRef++; + assert( sqlite3PcachePageSanity(pPgHdr) ); return pPgHdr; } @@ -40811,8 +43614,11 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ if( (--p->nRef)==0 ){ if( p->flags&PGHDR_CLEAN ){ pcacheUnpin(p); - }else if( p->pDirtyPrev!=0 ){ - /* Move the page to the head of the dirty list. */ + }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. */ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); } } @@ -40823,6 +43629,7 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ */ SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr *p){ assert(p->nRef>0); + assert( sqlite3PcachePageSanity(p) ); p->nRef++; p->pCache->nRefSum++; } @@ -40834,6 +43641,7 @@ SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr *p){ */ SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){ assert( p->nRef==1 ); + assert( sqlite3PcachePageSanity(p) ); if( p->flags&PGHDR_DIRTY ){ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE); } @@ -40847,13 +43655,16 @@ SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){ */ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){ assert( p->nRef>0 ); - if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ + assert( sqlite3PcachePageSanity(p) ); + if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/ p->flags &= ~PGHDR_DONT_WRITE; if( p->flags & PGHDR_CLEAN ){ p->flags ^= (PGHDR_DIRTY|PGHDR_CLEAN); + pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno)); assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY ); pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD); } + assert( sqlite3PcachePageSanity(p) ); } } @@ -40862,11 +43673,14 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){ ** make it so. */ SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr *p){ - if( (p->flags & PGHDR_DIRTY) ){ + 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); } @@ -40878,11 +43692,24 @@ SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr *p){ */ SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache *pCache){ PgHdr *p; + pcacheTrace(("%p.CLEAN-ALL\n",pCache)); while( (p = pCache->pDirty)!=0 ){ sqlite3PcacheMakeClean(p); } } +/* +** Clear the PGHDR_NEED_SYNC and PGHDR_WRITEABLE flag from all dirty pages. +*/ +SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache *pCache){ + PgHdr *p; + pcacheTrace(("%p.CLEAR-WRITEABLE\n",pCache)); + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + p->flags &= ~(PGHDR_NEED_SYNC|PGHDR_WRITEABLE); + } + pCache->pSynced = pCache->pDirtyTail; +} + /* ** Clear the PGHDR_NEED_SYNC flag from all dirty pages. */ @@ -40901,6 +43728,8 @@ SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ PCache *pCache = p->pCache; assert( p->nRef>0 ); assert( newPgno>0 ); + assert( sqlite3PcachePageSanity(p) ); + pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno)); sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno); p->pgno = newPgno; if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){ @@ -40921,6 +43750,7 @@ SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){ if( pCache->pCache ){ PgHdr *p; PgHdr *pNext; + pcacheTrace(("%p.TRUNCATE %d\n",pCache,pgno)); for(p=pCache->pDirty; p; p=pNext){ pNext = p->pDirtyNext; /* This routine never gets call with a positive pgno except right @@ -40928,7 +43758,7 @@ SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){ ** it must be that pgno==0. */ assert( p->pgno>0 ); - if( ALWAYS(p->pgno>pgno) ){ + if( p->pgno>pgno ){ assert( p->flags&PGHDR_DIRTY ); sqlite3PcacheMakeClean(p); } @@ -40951,6 +43781,7 @@ SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){ */ SQLITE_PRIVATE void sqlite3PcacheClose(PCache *pCache){ assert( pCache->pCache!=0 ); + pcacheTrace(("%p.CLOSE\n",pCache)); sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); } @@ -41086,6 +43917,25 @@ SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){ numberOfCachePages(pCache)); } +/* +** Set the suggested cache-spill value. Make no changes if if the +** 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){ + int res; + assert( p->pCache!=0 ); + if( mxPage ){ + if( mxPage<0 ){ + mxPage = (int)((-1024*(i64)mxPage)/(p->szPage+p->szExtra)); + } + p->szSpill = mxPage; + } + res = numberOfCachePages(p); + if( resszSpill ) res = p->szSpill; + return res; +} + /* ** Free up as much memory as possible from the page cache. */ @@ -41100,6 +43950,17 @@ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){ */ SQLITE_PRIVATE int sqlite3HeaderSizePcache(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){ + PgHdr *pDirty; + int nDirty = 0; + int nCache = numberOfCachePages(pCache); + for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext) nDirty++; + return nCache ? (int)(((i64)nDirty * 100) / nCache) : 0; +} #if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) /* @@ -41184,7 +44045,7 @@ 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, 0, 0, N). +** sqlite3_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. @@ -41439,7 +44300,7 @@ static void *pcache1Alloc(int nByte){ pcache1.nFreeSlot--; pcache1.bUnderPressure = pcache1.nFreeSlot=0 ); - sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte); sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1); } sqlite3_mutex_leave(pcache1.mutex); @@ -41453,7 +44314,7 @@ static void *pcache1Alloc(int nByte){ if( p ){ int sz = sqlite3MallocSize(p); sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte); sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); sqlite3_mutex_leave(pcache1.mutex); } @@ -41467,9 +44328,8 @@ static void *pcache1Alloc(int nByte){ ** Free an allocated buffer obtained from pcache1Alloc(). */ static void pcache1Free(void *p){ - int nFreed = 0; if( p==0 ) return; - if( p>=pcache1.pStart && pnFresh==0 ){ + if( p->nFresh==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* 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 = sqlite3DbMallocRaw(p->db, sizeof(*pNew)); + pNew = sqlite3DbMallocRawNN(p->db, sizeof(*pNew)); if( pNew==0 ){ return 0; } @@ -42570,7 +45436,9 @@ SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet *p, i64 rowid){ pEntry->pRight = 0; pLast = p->pLast; if( pLast ){ - if( (p->rsFlags & ROWSET_SORTED)!=0 && rowid<=pLast->v ){ + if( rowid<=pLast->v ){ /*OPTIMIZATION-IF-FALSE*/ + /* Avoid unnecessary sorts by preserving the ROWSET_SORTED flags + ** where possible */ p->rsFlags &= ~ROWSET_SORTED; } pLast->pRight = pEntry; @@ -42692,23 +45560,29 @@ static struct RowSetEntry *rowSetNDeepTree( ){ struct RowSetEntry *p; /* Root of the new tree */ struct RowSetEntry *pLeft; /* Left subtree */ - if( *ppList==0 ){ - return 0; + if( *ppList==0 ){ /*OPTIMIZATION-IF-TRUE*/ + /* Prevent unnecessary deep recursion when we run out of entries */ + return 0; } - if( iDepth==1 ){ + if( iDepth>1 ){ /*OPTIMIZATION-IF-TRUE*/ + /* This branch causes a *balanced* tree to be generated. A valid tree + ** is still generated without this branch, but the tree is wildly + ** unbalanced and inefficient. */ + pLeft = rowSetNDeepTree(ppList, iDepth-1); + p = *ppList; + if( p==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* It is safe to always return here, but the resulting tree + ** would be unbalanced */ + return pLeft; + } + p->pLeft = pLeft; + *ppList = p->pRight; + p->pRight = rowSetNDeepTree(ppList, iDepth-1); + }else{ p = *ppList; *ppList = p->pRight; p->pLeft = p->pRight = 0; - return p; } - pLeft = rowSetNDeepTree(ppList, iDepth-1); - p = *ppList; - if( p==0 ){ - return pLeft; - } - p->pLeft = pLeft; - *ppList = p->pRight; - p->pRight = rowSetNDeepTree(ppList, iDepth-1); return p; } @@ -42735,59 +45609,37 @@ static struct RowSetEntry *rowSetListToTree(struct RowSetEntry *pList){ return p; } -/* -** Take all the entries on p->pEntry and on the trees in p->pForest and -** sort them all together into one big ordered list on p->pEntry. -** -** This routine should only be called once in the life of a RowSet. -*/ -static void rowSetToList(RowSet *p){ - - /* This routine is called only once */ - assert( p!=0 && (p->rsFlags & ROWSET_NEXT)==0 ); - - if( (p->rsFlags & ROWSET_SORTED)==0 ){ - p->pEntry = rowSetEntrySort(p->pEntry); - } - - /* While this module could theoretically support it, sqlite3RowSetNext() - ** is never called after sqlite3RowSetText() for the same RowSet. So - ** there is never a forest to deal with. Should this change, simply - ** remove the assert() and the #if 0. */ - assert( p->pForest==0 ); -#if 0 - while( p->pForest ){ - struct RowSetEntry *pTree = p->pForest->pLeft; - if( pTree ){ - struct RowSetEntry *pHead, *pTail; - rowSetTreeToList(pTree, &pHead, &pTail); - p->pEntry = rowSetEntryMerge(p->pEntry, pHead); - } - p->pForest = p->pForest->pRight; - } -#endif - p->rsFlags |= ROWSET_NEXT; /* Verify this routine is never called again */ -} - /* ** Extract the smallest element from the RowSet. ** 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() -** routine may not be called again. +** routine may not be called again. +** +** This routine may not be called after sqlite3RowSetTest() 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){ assert( p!=0 ); + assert( p->pForest==0 ); /* Cannot be used with sqlite3RowSetText() */ /* Merge the forest into a single sorted list on first call */ - if( (p->rsFlags & ROWSET_NEXT)==0 ) rowSetToList(p); + if( (p->rsFlags & ROWSET_NEXT)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + if( (p->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + p->pEntry = rowSetEntrySort(p->pEntry); + } + p->rsFlags |= ROWSET_SORTED|ROWSET_NEXT; + } /* Return the next entry on the list */ if( p->pEntry ){ *pRowid = p->pEntry->v; p->pEntry = p->pEntry->pRight; - if( p->pEntry==0 ){ + if( p->pEntry==0 ){ /*OPTIMIZATION-IF-TRUE*/ + /* Free memory immediately, rather than waiting on sqlite3_finalize() */ sqlite3RowSetClear(p); } return 1; @@ -42810,13 +45662,15 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 /* This routine is never called after sqlite3RowSetNext() */ assert( pRowSet!=0 && (pRowSet->rsFlags & ROWSET_NEXT)==0 ); - /* Sort entries into the forest on the first test of a new batch + /* Sort entries into the forest on the first test of a new batch. + ** To save unnecessary work, only do this when the batch number changes. */ - if( iBatch!=pRowSet->iBatch ){ + if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/ p = pRowSet->pEntry; if( p ){ struct RowSetEntry **ppPrevTree = &pRowSet->pForest; - if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ + if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/ + /* Only sort the current set of entiries if they need it */ p = rowSetEntrySort(p); } for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ @@ -42936,6 +45790,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 # define sqlite3WalHeapMemory(z) 0 # define sqlite3WalFramesize(z) 0 # define sqlite3WalFindFrame(x,y,z) 0 +# define sqlite3WalFile(x) 0 #else #define WAL_SAVEPOINT_NDATA 4 @@ -43018,6 +45873,11 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op); */ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal); +#ifdef SQLITE_ENABLE_SNAPSHOT +SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot); +SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot); +#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). @@ -43025,6 +45885,9 @@ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal); SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal); #endif +/* Return the sqlite3_file object for the WAL file */ +SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal); + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* _WAL_H_ */ @@ -43437,6 +46300,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ */ #define MAX_SECTOR_SIZE 0x10000 + /* ** An instance of the following structure is allocated for each active ** savepoint and statement transaction in the system. All such structures @@ -43632,6 +46496,7 @@ struct Pager { 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 */ @@ -43879,6 +46744,7 @@ static int assert_pager_state(Pager *p){ ** state. */ if( MEMDB ){ + assert( !isOpen(p->fd) ); assert( p->noSync ); assert( p->journalMode==PAGER_JOURNALMODE_OFF || p->journalMode==PAGER_JOURNALMODE_MEMORY @@ -43965,7 +46831,7 @@ static int assert_pager_state(Pager *p){ ** back to OPEN state. */ assert( pPager->errCode!=SQLITE_OK ); - assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); + assert( sqlite3PcacheRefCount(pPager->pPCache)>0 || pPager->tempFile ); break; } @@ -44177,6 +47043,8 @@ static int jrnlBufferSize(Pager *pPager){ return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager); } +#else +# define jrnlBufferSize(x) 0 #endif /* @@ -44337,6 +47205,7 @@ 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) ); if( pPager->journalOff ){ const i64 iLimit = pPager->journalSizeLimit; /* Local cache of jsl */ @@ -44718,7 +47587,7 @@ static void releaseAllSavepoints(Pager *pPager){ for(ii=0; iinSavepoint; ii++){ sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint); } - if( !pPager->exclusiveMode || sqlite3IsMemJournal(pPager->sjfd) ){ + if( !pPager->exclusiveMode || sqlite3JournalIsInMemory(pPager->sjfd) ){ sqlite3OsClose(pPager->sjfd); } sqlite3_free(pPager->aSavepoint); @@ -44824,13 +47693,17 @@ static void pager_unlock(Pager *pPager){ ** it can safely move back to PAGER_OPEN state. This happens in both ** normal and exclusive-locking mode. */ + assert( pPager->errCode==SQLITE_OK || !MEMDB ); if( pPager->errCode ){ - assert( !MEMDB ); - pager_reset(pPager); - pPager->changeCountDone = pPager->tempFile; - pPager->eState = PAGER_OPEN; - pPager->errCode = SQLITE_OK; + if( pPager->tempFile==0 ){ + pager_reset(pPager); + pPager->changeCountDone = 0; + pPager->eState = PAGER_OPEN; + }else{ + pPager->eState = (isOpen(pPager->jfd) ? PAGER_OPEN : PAGER_READER); + } if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); + pPager->errCode = SQLITE_OK; } pPager->journalOff = 0; @@ -44874,6 +47747,29 @@ static int pager_error(Pager *pPager, int rc){ static int pager_truncate(Pager *pPager, Pgno nPage); +/* +** The write transaction open on pPager is being committed (bCommit==1) +** or rolled back (bCommit==0). +** +** Return TRUE if and only if all dirty pages should be flushed to disk. +** +** Rules: +** +** * For non-TEMP databases, always sync to disk. This is necessary +** for transactions to be durable. +** +** * Sync TEMP database only on a COMMIT (not a ROLLBACK) when the backing +** file has been created already (via a spill on pagerStress()) and +** when the number of dirty pages in memory exceeds 25% of the total +** cache size. +*/ +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); +} + /* ** This routine ends a transaction. A transaction is usually ended by ** either a COMMIT or a ROLLBACK operation. This routine may be called @@ -44956,8 +47852,8 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ assert( !pagerUseWal(pPager) ); /* Finalize the journal file. */ - if( sqlite3IsMemJournal(pPager->jfd) ){ - assert( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ); + if( sqlite3JournalIsInMemory(pPager->jfd) ){ + /* assert( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ); */ sqlite3OsClose(pPager->jfd); }else if( pPager->journalMode==PAGER_JOURNALMODE_TRUNCATE ){ if( pPager->journalOff==0 ){ @@ -44977,22 +47873,23 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) ){ - rc = zeroJournalHdr(pPager, hasMaster); + rc = zeroJournalHdr(pPager, hasMaster||pPager->tempFile); pPager->journalOff = 0; }else{ /* This branch may be executed with Pager.journalMode==MEMORY if ** a hot-journal was just rolled back. In this case the journal ** file should be closed and deleted. If this connection writes to - ** the database file, it will do so using an in-memory journal. + ** the database file, it will do so using an in-memory journal. */ - int bDelete = (!pPager->tempFile && sqlite3JournalExists(pPager->jfd)); + int bDelete = !pPager->tempFile; + assert( sqlite3JournalIsInMemory(pPager->jfd)==0 ); assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE || pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->journalMode==PAGER_JOURNALMODE_WAL ); sqlite3OsClose(pPager->jfd); if( bDelete ){ - rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0); + rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, pPager->extraSync); } } } @@ -45011,8 +47908,14 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; pPager->nRec = 0; - sqlite3PcacheCleanAll(pPager->pPCache); - sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize); + if( rc==SQLITE_OK ){ + if( pagerFlushOnCommit(pPager, bCommit) ){ + sqlite3PcacheCleanAll(pPager->pPCache); + }else{ + sqlite3PcacheClearWritable(pPager->pPCache); + } + sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize); + } if( pagerUseWal(pPager) ){ /* Drop the WAL write-lock, if any. Also, if the connection was in @@ -45296,7 +48199,7 @@ static int pager_playback_one_page( pPg = sqlite3PagerLookup(pPager, pgno); } assert( pPg || !MEMDB ); - assert( pPager->eState!=PAGER_OPEN || pPg==0 ); + assert( pPager->eState!=PAGER_OPEN || pPg==0 || pPager->tempFile ); PAGERTRACE(("PLAYBACK %d page %d hash(%08x) %s\n", PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, (u8*)aData), (isMainJrnl?"main-journal":"sub-journal") @@ -45318,9 +48221,9 @@ static int pager_playback_one_page( pPager->dbFileSize = pgno; } if( pPager->pBackup ){ - CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM); + CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); - CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM, aData); + CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData); } }else if( !isMainJrnl && pPg==0 ){ /* If this is a rollback of a savepoint and data was not written to @@ -45342,11 +48245,10 @@ static int pager_playback_one_page( assert( isSavepnt ); assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)==0 ); pPager->doNotSpill |= SPILLFLAG_ROLLBACK; - rc = sqlite3PagerAcquire(pPager, pgno, &pPg, 1); + rc = sqlite3PagerGet(pPager, pgno, &pPg, 1); assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)!=0 ); pPager->doNotSpill &= ~SPILLFLAG_ROLLBACK; if( rc!=SQLITE_OK ) return rc; - pPg->flags &= ~PGHDR_NEED_READ; sqlite3PcacheMakeDirty(pPg); } if( pPg ){ @@ -45360,29 +48262,10 @@ static int pager_playback_one_page( pData = pPg->pData; memcpy(pData, (u8*)aData, pPager->pageSize); pPager->xReiniter(pPg); - if( isMainJrnl && (!isSavepnt || *pOffset<=pPager->journalHdr) ){ - /* If the contents of this page were just restored from the main - ** journal file, then its content must be as they were when the - ** transaction was first opened. In this case we can mark the page - ** as clean, since there will be no need to write it out to the - ** database. - ** - ** There is one exception to this rule. If the page is being rolled - ** back as part of a savepoint (or statement) rollback from an - ** unsynced portion of the main journal file, then it is not safe - ** to mark the page as clean. This is because marking the page as - ** clean will clear the PGHDR_NEED_SYNC flag. Since the page is - ** already in the journal file (recorded in Pager.pInJournal) and - ** the PGHDR_NEED_SYNC flag is cleared, if the page is written to - ** again within this transaction, it will be marked as dirty but - ** the PGHDR_NEED_SYNC flag will not be set. It could then potentially - ** be written out into the database file before its journal file - ** segment is synced. If a crash occurs during or following this, - ** database corruption may ensue. - */ - assert( !pagerUseWal(pPager) ); - sqlite3PcacheMakeClean(pPg); - } + /* It used to be that sqlite3PcacheMakeClean(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 + ** has been removed. */ pager_set_pagehash(pPg); /* If this was page 1, then restore the value of Pager.dbFileVers. @@ -45392,7 +48275,7 @@ static int pager_playback_one_page( } /* Decode the page just read from disk */ - CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM); + CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT); sqlite3PcacheRelease(pPg); } return rc; @@ -45458,7 +48341,7 @@ static int pager_delmaster(Pager *pPager, const char *zMaster){ pMaster = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile * 2); pJournal = (sqlite3_file *)(((u8 *)pMaster) + pVfs->szOsFile); if( !pMaster ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ const int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MASTER_JOURNAL); rc = sqlite3OsOpen(pVfs, zMaster, pMaster, flags, 0); @@ -45475,7 +48358,7 @@ static int pager_delmaster(Pager *pPager, const char *zMaster){ nMasterPtr = pVfs->mxPathname+1; zMasterJournal = sqlite3Malloc(nMasterJournal + nMasterPtr + 1); if( !zMasterJournal ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto delmaster_out; } zMasterPtr = &zMasterJournal[nMasterJournal+1]; @@ -45723,7 +48606,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** TODO: Technically the following is an error because it assumes that ** buffer Pager.pTmpSpace is (mxPathname+1) bytes or larger. i.e. that ** (pPager->pageSize >= pPager->pVfs->mxPathname+1). Using os_unix.c, - ** mxPathname is 512, which is the same as the minimum allowable value + ** mxPathname is 512, which is the same as the minimum allowable value ** for pageSize. */ zMaster = pPager->pTmpSpace; @@ -45945,7 +48828,7 @@ static int readDbPage(PgHdr *pPg, u32 iFrame){ memcpy(&pPager->dbFileVers, dbFileVers, sizeof(pPager->dbFileVers)); } } - CODEC1(pPager, pPg->pData, pgno, 3, rc = SQLITE_NOMEM); + CODEC1(pPager, pPg->pData, pgno, 3, rc = SQLITE_NOMEM_BKPT); PAGER_INCR(sqlite3_pager_readdb_count); PAGER_INCR(pPager->nRead); @@ -46173,6 +49056,8 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ */ assert( pPager->eState==PAGER_OPEN ); assert( pPager->eLock>=SHARED_LOCK ); + assert( isOpen(pPager->fd) ); + assert( pPager->tempFile==0 ); nPage = sqlite3WalDbsize(pPager->pWal); /* If the number of pages in the database is not available from the @@ -46180,14 +49065,11 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ ** 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 ){ + if( nPage==0 && ALWAYS(isOpen(pPager->fd)) ){ i64 n = 0; /* Size of db file in bytes */ - assert( isOpen(pPager->fd) || pPager->tempFile ); - if( isOpen(pPager->fd) ){ - int rc = sqlite3OsFileSize(pPager->fd, &n); - if( rc!=SQLITE_OK ){ - return rc; - } + int rc = sqlite3OsFileSize(pPager->fd, &n); + if( rc!=SQLITE_OK ){ + return rc; } nPage = (Pgno)((n+pPager->pageSize-1) / pPager->pageSize); } @@ -46305,7 +49187,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ if( pSavepoint ){ pDone = sqlite3BitvecCreate(pSavepoint->nOrig); if( !pDone ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } } @@ -46401,12 +49283,21 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ } /* -** Change the maximum number of in-memory pages that are allowed. +** 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); } +/* +** 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); +} + /* ** Invoke SQLITE_FCNTL_MMAP_SIZE based on the current value of szMmap. */ @@ -46443,7 +49334,7 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){ ** The "level" in pgFlags & PAGER_SYNCHRONOUS_MASK sets the robustness ** of the database to damage due to OS crashes or power failures by ** changing the number of syncs()s when writing the journals. -** There are three levels: +** There are four levels: ** ** OFF sqlite3OsSync() is never called. This is the default ** for temporary and transient files. @@ -46463,6 +49354,10 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){ ** assurance that the journal will not be corrupted to the ** point of causing damage to the database during rollback. ** +** EXTRA This is like FULL except that is also syncs the directory +** that contains the rollback journal after the rollback +** journal is unlinked. +** ** The above is for a rollback-journal mode. For WAL mode, OFF continues ** to mean that no syncs ever occur. NORMAL means that the WAL is synced ** prior to the start of checkpoint and that the database file is synced @@ -46470,7 +49365,8 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){ ** was written back into the database. But no sync operations occur for ** an ordinary commit in NORMAL mode with WAL. FULL means that the WAL ** file is synced following each commit operation, in addition to the -** syncs associated with NORMAL. +** syncs associated with NORMAL. There is no difference between FULL +** and EXTRA for WAL mode. ** ** Do not confuse synchronous=FULL with SQLITE_SYNC_FULL. The ** SQLITE_SYNC_FULL macro means to use the MacOSX-style full-fsync @@ -46489,9 +49385,15 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags( unsigned pgFlags /* Various flags */ ){ unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; - assert( level>=1 && level<=3 ); - pPager->noSync = (level==1 || pPager->tempFile) ?1:0; - pPager->fullSync = (level==3 && !pPager->tempFile) ?1:0; + if( pPager->tempFile ){ + pPager->noSync = 1; + pPager->fullSync = 0; + pPager->extraSync = 0; + }else{ + pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; + pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; + pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; + } if( pPager->noSync ){ pPager->syncFlags = 0; pPager->ckptSyncFlags = 0; @@ -46653,7 +49555,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR } if( rc==SQLITE_OK ){ pNew = (char *)sqlite3PageMalloc(pageSize); - if( !pNew ) rc = SQLITE_NOMEM; + if( !pNew ) rc = SQLITE_NOMEM_BKPT; } if( rc==SQLITE_OK ){ @@ -46929,7 +49831,7 @@ static int pagerAcquireMapPage( *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra); if( p==0 ){ sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pData); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } p->pExtra = (void *)&p[1]; p->flags = PGHDR_MMAP; @@ -47243,8 +50145,9 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ /* This function is only called for rollback pagers in WRITER_DBMOD state. */ assert( !pagerUseWal(pPager) ); - assert( pPager->eState==PAGER_WRITER_DBMOD ); + assert( pPager->tempFile || pPager->eState==PAGER_WRITER_DBMOD ); assert( pPager->eLock==EXCLUSIVE_LOCK ); + assert( isOpen(pPager->fd) || pList->pDirty==0 ); /* If the file is a temp-file has not yet been opened, open it now. It ** is not possible for rc to be other than SQLITE_OK if this branch @@ -47287,7 +50190,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ if( pList->pgno==1 ) pager_write_changecounter(pList); /* Encode the database */ - CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM, pData); + CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM_BKPT, pData); /* Write out the page data. */ rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset); @@ -47332,11 +50235,14 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ static int openSubJournal(Pager *pPager){ int rc = SQLITE_OK; if( !isOpen(pPager->sjfd) ){ + const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE + | SQLITE_OPEN_DELETEONCLOSE; + int nStmtSpill = sqlite3Config.nStmtSpill; if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->subjInMemory ){ - sqlite3MemJournalOpen(pPager->sjfd); - }else{ - rc = pagerOpentemp(pPager, pPager->sjfd, SQLITE_OPEN_SUBJOURNAL); + nStmtSpill = -1; } + rc = sqlite3JournalOpen(pPager->pVfs, 0, pPager->sjfd, flags, nStmtSpill); } return rc; } @@ -47374,7 +50280,7 @@ static int subjournalPage(PgHdr *pPg){ i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize); char *pData2; - CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM, pData2); + CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno)); rc = write32bits(pPager->sjfd, offset, pPg->pgno); if( rc==SQLITE_OK ){ @@ -47482,6 +50388,25 @@ static int pagerStress(void *p, PgHdr *pPg){ return pager_error(pPager, rc); } +/* +** Flush all unreferenced dirty pages to disk. +*/ +SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){ + int rc = pPager->errCode; + if( !MEMDB ){ + PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); + assert( assert_pager_state(pPager) ); + while( rc==SQLITE_OK && pList ){ + PgHdr *pNext = pList->pDirty; + if( pList->nRef==0 ){ + rc = pagerStress((void*)pPager, pList); + } + pList = pNext; + } + } + + return rc; +} /* ** Allocate and initialize a new Pager object and put a pointer to it @@ -47538,18 +50463,8 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int nUri = 0; /* Number of bytes of URI args at *zUri */ /* Figure out how much space is required for each journal file-handle - ** (there are two of them, the main journal and the sub-journal). This - ** is the maximum space required for an in-memory journal file handle - ** and a regular journal file-handle. Note that a "regular journal-handle" - ** may be a wrapper capable of caching the first portion of the journal - ** file in memory to implement the atomic-write optimization (see - ** source file journal.c). - */ - if( sqlite3JournalSize(pVfs)>sqlite3MemJournalSize() ){ - journalFileSize = ROUND8(sqlite3JournalSize(pVfs)); - }else{ - journalFileSize = ROUND8(sqlite3MemJournalSize()); - } + ** (there are two of them, the main journal and the sub-journal). */ + journalFileSize = ROUND8(sqlite3JournalSize(pVfs)); /* Set the output variable to NULL in case an error occurs. */ *ppPager = 0; @@ -47559,7 +50474,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( memDb = 1; if( zFilename && zFilename[0] ){ zPathname = sqlite3DbStrDup(0, zFilename); - if( zPathname==0 ) return SQLITE_NOMEM; + if( zPathname==0 ) return SQLITE_NOMEM_BKPT; nPathname = sqlite3Strlen30(zPathname); zFilename = 0; } @@ -47575,7 +50490,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( nPathname = pVfs->mxPathname+1; zPathname = sqlite3DbMallocRaw(0, nPathname*2); if( zPathname==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); @@ -47628,7 +50543,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( assert( EIGHT_BYTE_ALIGNMENT(SQLITE_INT_TO_PTR(journalFileSize)) ); if( !pPtr ){ sqlite3DbFree(0, zPathname); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pPager = (Pager*)(pPtr); pPager->pPCache = (PCache*)(pPtr += ROUND8(sizeof(*pPager))); @@ -47777,11 +50692,13 @@ act_like_temp_file: pPager->noSync = pPager->tempFile; 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->syncFlags = SQLITE_SYNC_NORMAL; pPager->walSyncFlags = SQLITE_SYNC_NORMAL | WAL_SYNC_TRANSACTIONS; pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL; @@ -47898,6 +50815,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ if( rc==SQLITE_OK && !locked ){ Pgno nPage; /* Number of pages in database file */ + assert( pPager->tempFile==0 ); rc = pagerPagecount(pPager, &nPage); if( rc==SQLITE_OK ){ /* If the database is zero pages in size, that means that either (1) the @@ -47959,7 +50877,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 sqlite3PagerAcquire() until after this function +** It is illegal to call sqlite3PagerGet() 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. ** @@ -47990,17 +50908,17 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ /* 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. - */ + ** exclusive access mode. */ assert( sqlite3PcacheRefCount(pPager->pPCache)==0 ); assert( assert_pager_state(pPager) ); assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER ); - if( NEVER(MEMDB && pPager->errCode) ){ return pPager->errCode; } + assert( pPager->errCode==SQLITE_OK ); if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){ int bHotJournal = 1; /* True if there exists a hot journal-file */ assert( !MEMDB ); + assert( pPager->tempFile==0 || pPager->eLock==EXCLUSIVE_LOCK ); rc = pager_wait_on_lock(pPager, SHARED_LOCK); if( rc!=SQLITE_OK ){ @@ -48086,7 +51004,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ assert( rc==SQLITE_OK ); rc = pagerSyncHotJournal(pPager); if( rc==SQLITE_OK ){ - rc = pager_playback(pPager, 1); + rc = pager_playback(pPager, !pPager->tempFile); pPager->eState = PAGER_OPEN; } }else if( !pPager->exclusiveMode ){ @@ -48182,7 +51100,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ rc = pagerBeginReadTransaction(pPager); } - if( pPager->eState==PAGER_OPEN && rc==SQLITE_OK ){ + if( pPager->tempFile==0 && pPager->eState==PAGER_OPEN && rc==SQLITE_OK ){ rc = pagerPagecount(pPager, &pPager->dbSize); } @@ -48262,7 +51180,7 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** Since Lookup() never goes to disk, it never has to deal with locks ** or journal files. */ -SQLITE_PRIVATE int sqlite3PagerAcquire( +SQLITE_PRIVATE int sqlite3PagerGet( Pager *pPager, /* The pager open on the database file */ Pgno pgno, /* Page number to fetch */ DbPage **ppPage, /* Write a pointer to the page here */ @@ -48315,7 +51233,7 @@ SQLITE_PRIVATE int sqlite3PagerAcquire( ); if( rc==SQLITE_OK && pData ){ - if( pPager->eState>PAGER_READER ){ + if( pPager->eState>PAGER_READER || pPager->tempFile ){ pPg = sqlite3PagerLookup(pPager, pgno); } if( pPg==0 ){ @@ -48342,7 +51260,7 @@ SQLITE_PRIVATE int sqlite3PagerAcquire( if( rc!=SQLITE_OK ) goto pager_acquire_err; if( pBase==0 ){ pPg = *ppPage = 0; - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto pager_acquire_err; } } @@ -48382,7 +51300,8 @@ SQLITE_PRIVATE int sqlite3PagerAcquire( goto pager_acquire_err; } - if( MEMDB || pPager->dbSizefd) ){ + assert( !isOpen(pPager->fd) || !MEMDB ); + if( !isOpen(pPager->fd) || pPager->dbSizepPager->mxPgno ){ rc = SQLITE_FULL; goto pager_acquire_err; @@ -48516,7 +51435,7 @@ static int pager_open_journal(Pager *pPager){ if( !pagerUseWal(pPager) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize); if( pPager->pInJournal==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* Open the journal file if it is not already open. */ @@ -48524,24 +51443,24 @@ static int pager_open_journal(Pager *pPager){ if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){ sqlite3MemJournalOpen(pPager->jfd); }else{ - const int flags = /* VFS flags to open journal file */ - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE| - (pPager->tempFile ? - (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL): - (SQLITE_OPEN_MAIN_JOURNAL) - ); + int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + int nSpill; + if( pPager->tempFile ){ + flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL); + nSpill = sqlite3Config.nStmtSpill; + }else{ + flags |= SQLITE_OPEN_MAIN_JOURNAL; + nSpill = jrnlBufferSize(pPager); + } + /* Verify that the database still has the same name as it did when ** it was originally opened. */ rc = databaseIsUnmoved(pPager); if( rc==SQLITE_OK ){ -#ifdef SQLITE_ENABLE_ATOMIC_WRITE - rc = sqlite3JournalOpen( - pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager) + rc = sqlite3JournalOpen ( + pVfs, pPager->zJournal, pPager->jfd, flags, nSpill ); -#else - rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0); -#endif } } assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); @@ -48608,7 +51527,7 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory if( rc!=SQLITE_OK ){ return rc; } - sqlite3WalExclusiveMode(pPager->pWal, 1); + (void)sqlite3WalExclusiveMode(pPager->pWal, 1); } /* Grab the write lock on the log file. If successful, upgrade to @@ -48671,7 +51590,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) ); assert( pPager->journalHdr<=pPager->journalOff ); - CODEC2(pPager, pPg->pData, pPg->pgno, 7, return SQLITE_NOMEM, pData2); + CODEC2(pPager, pPg->pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); cksum = pager_cksum(pPager, (u8*)pData2); /* Even if an IO or diskfull error occurs while journalling the @@ -48848,7 +51767,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ PgHdr *pPage; if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ if( pg!=PAGER_MJ_PGNO(pPager) ){ - rc = sqlite3PagerGet(pPager, pg, &pPage); + rc = sqlite3PagerGet(pPager, pg, &pPage, 0); if( rc==SQLITE_OK ){ rc = pager_write(pPage); if( pPage->flags&PGHDR_NEED_SYNC ){ @@ -48905,12 +51824,14 @@ SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){ Pager *pPager = pPg->pPager; assert( (pPg->flags & PGHDR_MMAP)==0 ); assert( pPager->eState>=PAGER_WRITER_LOCKED ); - assert( pPager->eState!=PAGER_ERROR ); assert( assert_pager_state(pPager) ); - if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ + if( pPager->errCode ){ + return pPager->errCode; + }else if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg); return SQLITE_OK; }else if( pPager->sectorSize > (u32)pPager->pageSize ){ + assert( pPager->tempFile==0 ); return pagerWriteLargeSector(pPg); }else{ return pager_write(pPg); @@ -48941,14 +51862,21 @@ SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){ ** ** Tests show that this optimization can quadruple the speed of large ** DELETE operations. +** +** This optimization cannot be used with a temp-file, as the page may +** have been dirty at the start of the transaction. In that case, if +** memory pressure forces page pPg out of the cache, the data does need +** 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){ Pager *pPager = pPg->pPager; - if( (pPg->flags&PGHDR_DIRTY) && pPager->nSavepoint==0 ){ + if( !pPager->tempFile && (pPg->flags&PGHDR_DIRTY) && pPager->nSavepoint==0 ){ PAGERTRACE(("DONT_WRITE page %d of %d\n", pPg->pgno, PAGERID(pPager))); IOTRACE(("CLEAN %p %d\n", pPager, pPg->pgno)) pPg->flags |= PGHDR_DONT_WRITE; pPg->flags &= ~PGHDR_WRITEABLE; + testcase( pPg->flags & PGHDR_NEED_SYNC ); pager_set_pagehash(pPg); } } @@ -49007,7 +51935,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); + rc = sqlite3PagerGet(pPager, 1, &pPgHdr, 0); assert( pPgHdr==0 || rc==SQLITE_OK ); /* If page one was fetched successfully, and this function is not @@ -49027,7 +51955,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ if( DIRECT_MODE ){ const void *zBuf; assert( pPager->dbFileSize>0 ); - CODEC2(pPager, pPgHdr->pData, 1, 6, rc=SQLITE_NOMEM, zBuf); + CODEC2(pPager, pPgHdr->pData, 1, 6, rc=SQLITE_NOMEM_BKPT, zBuf); if( rc==SQLITE_OK ){ rc = sqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0); pPager->aStat[PAGER_STAT_WRITE]++; @@ -49085,14 +52013,17 @@ SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){ ** returned. */ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){ - int rc = SQLITE_OK; - assert( pPager->eState==PAGER_WRITER_CACHEMOD - || pPager->eState==PAGER_WRITER_DBMOD - || pPager->eState==PAGER_WRITER_LOCKED - ); + int rc = pPager->errCode; assert( assert_pager_state(pPager) ); - if( 0==pagerUseWal(pPager) ){ - rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + if( rc==SQLITE_OK ){ + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + || pPager->eState==PAGER_WRITER_LOCKED + ); + assert( assert_pager_state(pPager) ); + if( 0==pagerUseWal(pPager) ){ + rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + } } return rc; } @@ -49140,17 +52071,21 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( /* If a prior error occurred, report that error again. */ 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; + PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n", pPager->zFilename, zMaster, pPager->dbSize)); /* If no database changes have been made, return early. */ if( pPager->eStatetempFile ); + assert( isOpen(pPager->fd) || pPager->tempFile ); + if( 0==pagerFlushOnCommit(pPager, 1) ){ /* 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. - */ + ** backup in progress needs to be restarted. */ sqlite3BackupRestart(pPager->pBackup); }else{ if( pagerUseWal(pPager) ){ @@ -49159,7 +52094,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( if( pList==0 ){ /* Must have at least one page for the WAL commit flag. ** Ticket [2d1a5c67dfc2363e44f29d9bbd57f] 2011-05-18 */ - rc = sqlite3PagerGet(pPager, 1, &pPageOne); + rc = sqlite3PagerGet(pPager, 1, &pPageOne, 0); pList = pPageOne; pList->pDirty = 0; } @@ -49489,10 +52424,10 @@ SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, i } /* -** Return true if this is an in-memory pager. +** Return true if this is an in-memory or temp-file backed pager. */ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ - return MEMDB; + return pPager->tempFile; } /* @@ -49523,7 +52458,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ pPager->aSavepoint, sizeof(PagerSavepoint)*nSavepoint ); if( !aNew ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(&aNew[nCurrent], 0, (nSavepoint-nCurrent) * sizeof(PagerSavepoint)); pPager->aSavepoint = aNew; @@ -49539,7 +52474,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ aNew[ii].iSubRec = pPager->nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize); if( !aNew[ii].pInSavepoint ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } if( pagerUseWal(pPager) ){ sqlite3WalSavepoint(pPager->pWal, aNew[ii].aWalData); @@ -49617,7 +52552,7 @@ 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( sqlite3IsMemJournal(pPager->sjfd) ){ + if( sqlite3JournalIsInMemory(pPager->sjfd) ){ rc = sqlite3OsTruncate(pPager->sjfd, 0); assert( rc==SQLITE_OK ); } @@ -49656,7 +52591,7 @@ SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager *pPager, int nullIfMemDb){ /* ** Return the VFS structure for the pager. */ -SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){ +SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){ return pPager->pVfs; } @@ -49669,6 +52604,18 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){ return pPager->fd; } +/* +** 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){ +#if SQLITE_OMIT_WAL + return pPager->jfd; +#else + return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; +#endif +} + /* ** Return the full pathname of the journal file. */ @@ -49676,14 +52623,6 @@ SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){ return pPager->zJournal; } -/* -** Return true if fsync() calls are disabled for this pager. Return FALSE -** if fsync()s are executed normally. -*/ -SQLITE_PRIVATE int sqlite3PagerNosync(Pager *pPager){ - return pPager->noSync; -} - #ifdef SQLITE_HAS_CODEC /* ** Set or retrieve the codec for this pager @@ -49768,7 +52707,8 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i /* In order to be able to rollback, an in-memory database must journal ** the page we are moving from. */ - if( MEMDB ){ + assert( pPager->tempFile || !MEMDB ); + if( pPager->tempFile ){ rc = sqlite3PagerWrite(pPg); if( rc ) return rc; } @@ -49825,7 +52765,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i assert( !pPgOld || pPgOld->nRef==1 ); if( pPgOld ){ pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC); - if( MEMDB ){ + 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); @@ -49842,8 +52782,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i ** to exist, in case the transaction needs to roll back. Use pPgOld ** as the original page since it has already been allocated. */ - if( MEMDB ){ - assert( pPgOld ); + if( pPager->tempFile && pPgOld ){ sqlite3PcacheMove(pPgOld, origPgno); sqlite3PagerUnrefNotNull(pPgOld); } @@ -49864,7 +52803,7 @@ 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); + rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr, 0); if( rc!=SQLITE_OK ){ if( needSyncPgno<=pPager->dbOrigSize ){ assert( pPager->pTmpSpace!=0 ); @@ -50095,7 +53034,8 @@ 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){ - if( !MEMDB && pPager->tempFile==0 ) pager_reset(pPager); + assert( MEMDB==0 || pPager->tempFile ); + if( pPager->tempFile==0 ) pager_reset(pPager); } #endif @@ -50130,6 +53070,7 @@ SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager){ */ SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager){ const sqlite3_io_methods *pMethods = pPager->fd->pMethods; + if( pPager->noLock ) return 0; return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap); } @@ -50273,11 +53214,40 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){ pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; pagerFixMaplimit(pPager); + if( rc && !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK); } } 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){ + int rc = SQLITE_ERROR; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotGet(pPager->pWal, ppSnapshot); + } + return rc; +} + +/* +** If this is a WAL database, store a pointer to pSnapshot. Next time a +** 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){ + int rc = SQLITE_OK; + if( pPager->pWal ){ + sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ #endif /* !SQLITE_OMIT_WAL */ #ifdef SQLITE_ENABLE_ZIPVFS @@ -50573,7 +53543,8 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0; /* ** Indices of various locking bytes. WAL_NREADER is the number -** of available reader locks and should be at least 3. +** of available reader locks and should be at least 3. The default +** is SQLITE_SHM_NLOCK==8 and WAL_NREADER==5. */ #define WAL_WRITE_LOCK 0 #define WAL_ALL_BUT_WRITE 1 @@ -50593,7 +53564,10 @@ typedef struct WalCkptInfo WalCkptInfo; ** The following object holds a copy of the wal-index header content. ** ** The actual header in the wal-index consists of two copies of this -** object. +** object followed by one instance of the WalCkptInfo object. +** For all versions of SQLite through 3.10.0 and probably beyond, +** the locking bytes (WalCkptInfo.aLock) start at offset 120 and +** the total header size is 136 bytes. ** ** The szPage value can be any power of 2 between 512 and 32768, inclusive. ** Or it can be 1 to represent a 65536-byte page. The latter case was @@ -50626,6 +53600,16 @@ struct WalIndexHdr { ** However, a WAL_WRITE_LOCK thread can move the value of nBackfill from ** mxFrame back to zero when the WAL is reset. ** +** nBackfillAttempted is the largest value of nBackfill that a checkpoint +** has attempted to achieve. Normally nBackfill==nBackfillAtempted, however +** the nBackfillAttempted is set before any backfilling is done and the +** nBackfill is only set after all backfilling completes. So if a checkpoint +** crashes, nBackfillAttempted might be larger than nBackfill. The +** WalIndexHdr.mxFrame must never be less than nBackfillAttempted. +** +** The aLock[] field is a set of bytes used for locking. These bytes should +** never be read or written. +** ** There is one entry in aReadMark[] for each reader lock. If a reader ** holds read-lock K, then the value in aReadMark[K] is no greater than ** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff) @@ -50665,6 +53649,9 @@ struct WalIndexHdr { struct WalCkptInfo { u32 nBackfill; /* Number of WAL frames backfilled into DB */ u32 aReadMark[WAL_NREADER]; /* Reader marks */ + u8 aLock[SQLITE_SHM_NLOCK]; /* Reserved space for locks */ + u32 nBackfillAttempted; /* WAL frames perhaps written, or maybe not */ + u32 notUsed0; /* Available for future enhancements */ }; #define READMARK_NOT_USED 0xffffffff @@ -50674,9 +53661,8 @@ struct WalCkptInfo { ** only support mandatory file-locks, we do not read or write data ** from the region of the file on which locks are applied. */ -#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2 + sizeof(WalCkptInfo)) -#define WALINDEX_LOCK_RESERVED 16 -#define WALINDEX_HDR_SIZE (WALINDEX_LOCK_OFFSET+WALINDEX_LOCK_RESERVED) +#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2+offsetof(WalCkptInfo,aLock)) +#define WALINDEX_HDR_SIZE (sizeof(WalIndexHdr)*2+sizeof(WalCkptInfo)) /* Size of header before each frame in wal */ #define WAL_FRAME_HDRSIZE 24 @@ -50730,11 +53716,15 @@ struct Wal { u8 padToSectorBoundary; /* Pad transactions out to the next sector */ WalIndexHdr hdr; /* Wal-index header for current transaction */ u32 minFrame; /* Ignore wal frames before this one */ + u32 iReCksum; /* On commit, recalculate checksums from here */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ #ifdef SQLITE_DEBUG u8 lockError; /* True if a locking error has occurred */ #endif +#ifdef SQLITE_ENABLE_SNAPSHOT + WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */ +#endif }; /* @@ -50827,7 +53817,7 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ apNew = (volatile u32 **)sqlite3_realloc64((void *)pWal->apWiData, nByte); if( !apNew ){ *ppPage = 0; - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset((void*)&apNew[pWal->nWiData], 0, sizeof(u32*)*(iPage+1-pWal->nWiData)); @@ -50839,7 +53829,7 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ 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; + if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; }else{ rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] @@ -50980,14 +53970,18 @@ static void walEncodeFrame( assert( WAL_FRAME_HDRSIZE==24 ); sqlite3Put4byte(&aFrame[0], iPage); sqlite3Put4byte(&aFrame[4], nTruncate); - memcpy(&aFrame[8], pWal->hdr.aSalt, 8); + if( pWal->iReCksum==0 ){ + memcpy(&aFrame[8], pWal->hdr.aSalt, 8); - nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); - walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); - walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); + nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); + walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); + walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); - sqlite3Put4byte(&aFrame[16], aCksum[0]); - sqlite3Put4byte(&aFrame[20], aCksum[1]); + sqlite3Put4byte(&aFrame[16], aCksum[0]); + sqlite3Put4byte(&aFrame[20], aCksum[1]); + }else{ + memset(&aFrame[8], 0, 16); + } } /* @@ -51090,10 +54084,9 @@ static void walUnlockShared(Wal *pWal, int lockIdx){ 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 fBlock){ +static int walLockExclusive(Wal *pWal, int lockIdx, int n){ int rc; if( pWal->exclusiveMode ) return SQLITE_OK; - if( fBlock ) sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_WAL_BLOCK, 0); rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, @@ -51379,7 +54372,7 @@ static int walIndexRecover(Wal *pWal){ assert( pWal->writeLock ); iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; nLock = SQLITE_SHM_NLOCK - iLock; - rc = walLockExclusive(pWal, iLock, nLock, 0); + rc = walLockExclusive(pWal, iLock, nLock); if( rc ){ return rc; } @@ -51451,7 +54444,7 @@ static int walIndexRecover(Wal *pWal){ szFrame = szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame); if( !aFrame ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto recovery_error; } aData = &aFrame[WAL_FRAME_HDRSIZE]; @@ -51500,6 +54493,7 @@ finished: */ pInfo = walCkptInfo(pWal); pInfo->nBackfill = 0; + pInfo->nBackfillAttempted = pWal->hdr.mxFrame; pInfo->aReadMark[0] = 0; for(i=1; iaReadMark[i] = READMARK_NOT_USED; if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame; @@ -51571,7 +54565,11 @@ SQLITE_PRIVATE int sqlite3WalOpen( /* In the amalgamation, the os_unix.c and os_win.c source files come before ** this source file. Verify that the #defines of the locking byte offsets ** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value. + ** For that matter, if the lock offset ever changes from its initial design + ** value of 120, we need to know that so there is an assert() to check it. */ + assert( 120==WALINDEX_LOCK_OFFSET ); + assert( 136==WALINDEX_HDR_SIZE ); #ifdef WIN_SHM_BASE assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET ); #endif @@ -51584,7 +54582,7 @@ SQLITE_PRIVATE int sqlite3WalOpen( *ppWal = 0; pRet = (Wal*)sqlite3MallocZero(sizeof(Wal) + pVfs->szOsFile); if( !pRet ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pRet->pVfs = pVfs; @@ -51848,7 +54846,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){ + iLast*sizeof(ht_slot); p = (WalIterator *)sqlite3_malloc64(nByte); if( !p ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(p, 0, nByte); p->nSegment = nSegment; @@ -51860,7 +54858,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){ sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) ); if( !aTmp ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } for(i=0; rc==SQLITE_OK && ihdr.aSalt[1], &salt1, 4); walIndexWriteHdr(pWal); pInfo->nBackfill = 0; + pInfo->nBackfillAttempted = 0; pInfo->aReadMark[1] = 0; for(i=2; iaReadMark[i] = READMARK_NOT_USED; assert( pInfo->aReadMark[0]==0 ); @@ -52066,6 +55065,8 @@ static int walCheckpoint( 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); @@ -52358,7 +55359,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ walUnlockShared(pWal, WAL_WRITE_LOCK); rc = SQLITE_READONLY_RECOVERY; } - }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 1)) ){ + }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){ pWal->writeLock = 1; if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ badHdr = walIndexTryHdr(pWal, pChanged); @@ -52449,6 +55450,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ int mxI; /* Index of largest aReadMark[] value */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ + u32 mxFrame; /* Wal frame to lock to */ assert( pWal->readLock<0 ); /* Not currently locked */ @@ -52512,7 +55514,12 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } 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))) +#endif + ){ /* The WAL has been completely backfilled (or it is empty). ** and can be safely ignored. */ @@ -52550,85 +55557,88 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ */ mxReadMark = 0; mxI = 0; + mxFrame = pWal->hdr.mxFrame; +#ifdef SQLITE_ENABLE_SNAPSHOT + if( pWal->pSnapshot && pWal->pSnapshot->mxFramepSnapshot->mxFrame; + } +#endif for(i=1; iaReadMark[i]; - if( mxReadMark<=thisMark && thisMark<=pWal->hdr.mxFrame ){ + if( mxReadMark<=thisMark && thisMark<=mxFrame ){ assert( thisMark!=READMARK_NOT_USED ); mxReadMark = thisMark; mxI = i; } } - /* There was once an "if" here. The extra "{" is to preserve indentation. */ - { - if( (pWal->readOnly & WAL_SHM_RDONLY)==0 - && (mxReadMarkhdr.mxFrame || mxI==0) - ){ - for(i=1; iaReadMark[i] = pWal->hdr.mxFrame; - mxI = i; - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); - break; - }else if( rc!=SQLITE_BUSY ){ - return rc; - } + if( (pWal->readOnly & WAL_SHM_RDONLY)==0 + && (mxReadMarkaReadMark[i] = mxFrame; + mxI = i; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + break; + }else if( rc!=SQLITE_BUSY ){ + return rc; } } - if( mxI==0 ){ - assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); - return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; - } + } + if( mxI==0 ){ + assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; + } - rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); - if( rc ){ - return rc==SQLITE_BUSY ? WAL_RETRY : rc; - } - /* Now that the read-lock has been obtained, check that neither the - ** value in the aReadMark[] array or the contents of the wal-index - ** header have changed. - ** - ** It is necessary to check that the wal-index header did not change - ** between the time it was read and when the shared-lock was obtained - ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility - ** that the log file may have been wrapped by a writer, or that frames - ** that occur later in the log than pWal->hdr.mxFrame may have been - ** copied into the database by a checkpointer. If either of these things - ** happened, then reading the database with the current value of - ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry - ** instead. - ** - ** Before checking that the live wal-index header has not changed - ** since it was read, set Wal.minFrame to the first frame in the wal - ** file that has not yet been checkpointed. This client will not need - ** to read any frames earlier than minFrame from the wal file - they - ** can be safely read directly from the database file. - ** - ** Because a ShmBarrier() call is made between taking the copy of - ** nBackfill and checking that the wal-header in shared-memory still - ** matches the one cached in pWal->hdr, it is guaranteed that the - ** checkpointer that set nBackfill was not working with a wal-index - ** header newer than that cached in pWal->hdr. If it were, that could - ** cause a problem. The checkpointer could omit to checkpoint - ** a version of page X that lies before pWal->minFrame (call that version - ** A) on the basis that there is a newer version (version B) of the same - ** page later in the wal file. But if version B happens to like past - ** frame pWal->hdr.mxFrame - then the client would incorrectly assume - ** that it can read version A from the database file. However, since - ** 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; - walShmBarrier(pWal); - if( pInfo->aReadMark[mxI]!=mxReadMark - || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) - ){ - walUnlockShared(pWal, WAL_READ_LOCK(mxI)); - return WAL_RETRY; - }else{ - assert( mxReadMark<=pWal->hdr.mxFrame ); - pWal->readLock = (i16)mxI; - } + rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); + if( rc ){ + return rc==SQLITE_BUSY ? WAL_RETRY : rc; + } + /* Now that the read-lock has been obtained, check that neither the + ** value in the aReadMark[] array or the contents of the wal-index + ** header have changed. + ** + ** It is necessary to check that the wal-index header did not change + ** between the time it was read and when the shared-lock was obtained + ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility + ** that the log file may have been wrapped by a writer, or that frames + ** that occur later in the log than pWal->hdr.mxFrame may have been + ** copied into the database by a checkpointer. If either of these things + ** happened, then reading the database with the current value of + ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry + ** instead. + ** + ** Before checking that the live wal-index header has not changed + ** since it was read, set Wal.minFrame to the first frame in the wal + ** file that has not yet been checkpointed. This client will not need + ** to read any frames earlier than minFrame from the wal file - they + ** can be safely read directly from the database file. + ** + ** Because a ShmBarrier() call is made between taking the copy of + ** nBackfill and checking that the wal-header in shared-memory still + ** matches the one cached in pWal->hdr, it is guaranteed that the + ** checkpointer that set nBackfill was not working with a wal-index + ** header newer than that cached in pWal->hdr. If it were, that could + ** cause a problem. The checkpointer could omit to checkpoint + ** a version of page X that lies before pWal->minFrame (call that version + ** A) on the basis that there is a newer version (version B) of the same + ** page later in the wal file. But if version B happens to like past + ** frame pWal->hdr.mxFrame - then the client would incorrectly assume + ** that it can read version A from the database file. However, since + ** 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; + walShmBarrier(pWal); + if( pInfo->aReadMark[mxI]!=mxReadMark + || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) + ){ + walUnlockShared(pWal, WAL_READ_LOCK(mxI)); + return WAL_RETRY; + }else{ + assert( mxReadMark<=pWal->hdr.mxFrame ); + pWal->readLock = (i16)mxI; } return rc; } @@ -52651,6 +55661,14 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ +#ifdef SQLITE_ENABLE_SNAPSHOT + int bChanged = 0; + WalIndexHdr *pSnapshot = pWal->pSnapshot; + if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ + bChanged = 1; + } +#endif + do{ rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); }while( rc==WAL_RETRY ); @@ -52658,6 +55676,66 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ testcase( (rc&0xff)==SQLITE_IOERR ); testcase( rc==SQLITE_PROTOCOL ); testcase( rc==SQLITE_OK ); + +#ifdef SQLITE_ENABLE_SNAPSHOT + if( rc==SQLITE_OK ){ + if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ + /* At this point the client has a lock on an aReadMark[] slot holding + ** a value equal to or smaller than pSnapshot->mxFrame, but pWal->hdr + ** is populated with the wal-index header corresponding to the head + ** of the wal file. Verify that pSnapshot is still valid before + ** continuing. Reasons why pSnapshot might no longer be valid: + ** + ** (1) The WAL file has been reset since the snapshot was taken. + ** In this case, the salt will have changed. + ** + ** (2) A checkpoint as been attempted that wrote frames past + ** pSnapshot->mxFrame into the database file. Note that the + ** checkpoint need not have completed for this to cause problems. + */ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + + assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 ); + assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); + + /* It is possible that there is a checkpointer thread running + ** concurrent with this code. If this is the case, it may be that the + ** checkpointer has already determined that it will checkpoint + ** snapshot X, where X is later in the wal file than pSnapshot, but + ** 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. */ + 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 + ** with *pSnapshot and set *pChanged as appropriate for opening the + ** snapshot. */ + if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + && pSnapshot->mxFrame>=pInfo->nBackfillAttempted + ){ + assert( pWal->readLock>0 ); + memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); + *pChanged = bChanged; + }else{ + rc = SQLITE_BUSY_SNAPSHOT; + } + + /* Release the shared CKPT lock obtained above. */ + walUnlockShared(pWal, WAL_CKPT_LOCK); + } + + + if( rc!=SQLITE_OK ){ + sqlite3WalEndReadTransaction(pWal); + } + } + } +#endif return rc; } @@ -52830,6 +55908,7 @@ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ /* Cannot start a write transaction without first holding a read ** transaction. */ assert( pWal->readLock>=0 ); + assert( pWal->writeLock==0 && pWal->iReCksum==0 ); if( pWal->readOnly ){ return SQLITE_READONLY; @@ -52838,7 +55917,7 @@ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ /* Only one writer allowed at a time. Get the write lock. Return ** SQLITE_BUSY if unable. */ - rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0); + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); if( rc ){ return rc; } @@ -52865,6 +55944,7 @@ SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal){ if( pWal->writeLock ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; + pWal->iReCksum = 0; pWal->truncateOnCommit = 0; } return SQLITE_OK; @@ -52983,7 +56063,7 @@ static int walRestartLog(Wal *pWal){ if( pInfo->nBackfill>0 ){ u32 salt1; sqlite3_randomness(4, &salt1); - rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1, 0); + 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 ** readers are currently using the WAL), then the transactions @@ -53071,7 +56151,7 @@ static int walWriteOneFrame( 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; + if( (pData = sqlite3PagerCodec(pPage))==0 ) return SQLITE_NOMEM_BKPT; #else pData = pPage->pData; #endif @@ -53083,6 +56163,59 @@ static int walWriteOneFrame( return rc; } +/* +** This function is called as part of committing a transaction within which +** one or more frames have been overwritten. It updates the checksums for +** all frames written to the wal file by the current transaction starting +** with the earliest to have been overwritten. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int walRewriteChecksums(Wal *pWal, u32 iLast){ + const int szPage = pWal->szPage;/* Database page size */ + int rc = SQLITE_OK; /* Return code */ + u8 *aBuf; /* Buffer to load data from wal file into */ + u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-headers in */ + u32 iRead; /* Next frame to read from wal file */ + i64 iCksumOff; + + aBuf = sqlite3_malloc(szPage + WAL_FRAME_HDRSIZE); + if( aBuf==0 ) return SQLITE_NOMEM_BKPT; + + /* Find the checksum values to use as input for the recalculating the + ** first checksum. If the first frame is frame 1 (implying that the current + ** transaction restarted the wal file), these values must be read from the + ** wal-file header. Otherwise, read them from the frame header of the + ** previous frame. */ + assert( pWal->iReCksum>0 ); + if( pWal->iReCksum==1 ){ + iCksumOff = 24; + }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)]); + + 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); + if( rc==SQLITE_OK ){ + u32 iPgno, nDbSize; + iPgno = sqlite3Get4byte(aBuf); + nDbSize = sqlite3Get4byte(&aBuf[4]); + + walEncodeFrame(pWal, iPgno, nDbSize, &aBuf[WAL_FRAME_HDRSIZE], aFrame); + rc = sqlite3OsWrite(pWal->pWalFd, aFrame, sizeof(aFrame), iOff); + } + } + + sqlite3_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()). @@ -53103,6 +56236,8 @@ SQLITE_PRIVATE int sqlite3WalFrames( int szFrame; /* The size of a single frame */ i64 iOffset; /* Next byte to write in WAL file */ WalWriter w; /* The writer */ + u32 iFirst = 0; /* First frame that may be overwritten */ + WalIndexHdr *pLive; /* Pointer to shared header */ assert( pList ); assert( pWal->writeLock ); @@ -53118,6 +56253,11 @@ SQLITE_PRIVATE int sqlite3WalFrames( } #endif + pLive = (WalIndexHdr*)walIndexHdr(pWal); + if( memcmp(&pWal->hdr, (void *)pLive, sizeof(WalIndexHdr))!=0 ){ + iFirst = pLive->mxFrame+1; + } + /* See if it is possible to write these frames into the start of the ** log file, instead of appending to it at pWal->hdr.mxFrame. */ @@ -53182,6 +56322,33 @@ SQLITE_PRIVATE int sqlite3WalFrames( /* Write all frames into the log file exactly once */ for(p=pList; p; p=p->pDirty){ int nDbSize; /* 0 normally. Positive == commit flag */ + + /* Check if this page has already been written into the wal file by + ** the current transaction. If so, overwrite the existing frame and + ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that + ** 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); + assert( rc==SQLITE_OK || iWrite==0 ); + if( iWrite>=iFirst ){ + i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; + void *pData; + if( pWal->iReCksum==0 || iWriteiReCksum ){ + pWal->iReCksum = iWrite; + } +#if defined(SQLITE_HAS_CODEC) + if( (pData = sqlite3PagerCodec(p))==0 ) return SQLITE_NOMEM; +#else + pData = p->pData; +#endif + rc = sqlite3OsWrite(pWal->pWalFd, pData, szPage, iOff); + if( rc ) return rc; + p->flags &= ~PGHDR_WAL_APPEND; + continue; + } + } + iFrame++; assert( iOffset==walFrameOffset(iFrame, szPage) ); nDbSize = (isCommit && p->pDirty==0) ? nTruncate : 0; @@ -53189,6 +56356,13 @@ SQLITE_PRIVATE int sqlite3WalFrames( if( rc ) return rc; pLast = p; iOffset += szFrame; + p->flags |= PGHDR_WAL_APPEND; + } + + /* Recalculate checksums within the wal file if required. */ + if( isCommit && pWal->iReCksum ){ + rc = walRewriteChecksums(pWal, iFrame); + if( rc ) return rc; } /* If this is the end of a transaction, then we might need to pad @@ -53240,6 +56414,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( */ iFrame = pWal->hdr.mxFrame; for(p=pList; p && rc==SQLITE_OK; p=p->pDirty){ + if( (p->flags & PGHDR_WAL_APPEND)==0 ) continue; iFrame++; rc = walIndexAppend(pWal, iFrame, p->pgno); } @@ -53308,7 +56483,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive ** "checkpoint" lock on the database file. */ - rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1, 0); + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc ){ /* EVIDENCE-OF: R-10421-19736 If any other process is running a ** checkpoint operation at the same time, the lock cannot be obtained and @@ -53352,6 +56527,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( /* Copy data from the log to the database file. */ if( rc==SQLITE_OK ){ + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else{ @@ -53467,6 +56643,52 @@ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){ return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); } +#ifdef SQLITE_ENABLE_SNAPSHOT +/* Create a snapshot object. The content of a snapshot is opaque to +** every other subsystem, so the WAL module can put whatever it needs +** in the object. +*/ +SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ + int rc = SQLITE_OK; + WalIndexHdr *pRet; + + assert( pWal->readLock>=0 && pWal->writeLock==0 ); + + pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr)); + if( pRet==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr)); + *ppSnapshot = (sqlite3_snapshot*)pRet; + } + + return rc; +} + +/* Try to open on pSnapshot when the next read-transaction starts +*/ +SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){ + pWal->pSnapshot = (WalIndexHdr*)pSnapshot; +} + +/* +** 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 SQLITE_STDCALL sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ + WalIndexHdr *pHdr1 = (WalIndexHdr*)p1; + WalIndexHdr *pHdr2 = (WalIndexHdr*)p2; + + /* aSalt[0] is a copy of the value stored in the wal file header. It + ** is incremented each time the wal file is restarted. */ + if( pHdr1->aSalt[0]aSalt[0] ) return -1; + if( pHdr1->aSalt[0]>pHdr2->aSalt[0] ) return +1; + if( pHdr1->mxFramemxFrame ) return -1; + if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1; + return 0; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + #ifdef SQLITE_ENABLE_ZIPVFS /* ** If the argument is not NULL, it points to a Wal object that holds a @@ -53479,6 +56701,12 @@ SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal){ } #endif +/* Return the sqlite3_file object for the WAL file +*/ +SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ + return pWal->pWalFd; +} + #endif /* #ifndef SQLITE_OMIT_WAL */ /************** End of wal.c *************************************************/ @@ -53780,7 +57008,6 @@ struct MemPage { u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ - u8 noPayload; /* True if internal intKey page (thus w/o data) */ 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 */ @@ -53974,7 +57201,6 @@ struct CellInfo { u8 *pPayload; /* Pointer to the start of payload */ u32 nPayload; /* Bytes of payload */ u16 nLocal; /* Amount of payload held locally, not on overflow */ - u16 iOverflow; /* Offset to overflow page number. Zero if no overflow */ u16 nSize; /* Size of the cell content on the main b-tree page */ }; @@ -54021,7 +57247,7 @@ struct BtCursor { 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 sqlite3PagerAcquire() */ + 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 @@ -54369,21 +57595,6 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){ #endif -#ifndef SQLITE_OMIT_INCRBLOB -/* -** Enter and leave a mutex on a Btree given a cursor owned by that -** Btree. These entry points are used by incremental I/O and can be -** omitted if that module is not used. -*/ -SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor *pCur){ - sqlite3BtreeEnter(pCur->pBtree); -} -SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){ - sqlite3BtreeLeave(pCur->pBtree); -} -#endif /* SQLITE_OMIT_INCRBLOB */ - - /* ** Enter the mutex on every Btree associated with a database ** connection. This is needed (for example) prior to parsing @@ -54417,14 +57628,6 @@ SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){ } } -/* -** Return true if a particular Btree requires a lock. Return FALSE if -** no lock is ever required since it is not sharable. -*/ -SQLITE_PRIVATE int sqlite3BtreeSharable(Btree *p){ - return p->sharable; -} - #ifndef NDEBUG /* ** Return true if the current thread holds the database connection @@ -54498,6 +57701,25 @@ SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ } } #endif /* if SQLITE_THREADSAFE */ + +#ifndef SQLITE_OMIT_INCRBLOB +/* +** Enter a mutex on a Btree given a cursor owned by that Btree. +** +** These entry points are used by incremental I/O only. Enter() is required +** 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); +} +# if SQLITE_THREADSAFE +SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){ + sqlite3BtreeLeave(pCur->pBtree); +} +# endif +#endif /* ifndef SQLITE_OMIT_INCRBLOB */ + #endif /* ifndef SQLITE_OMIT_SHARED_CACHE */ /************** End of btmutex.c *********************************************/ @@ -54854,7 +58076,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ if( !pLock ){ pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock)); if( !pLock ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pLock->iTable = iTable; pLock->pBtree = p; @@ -54954,6 +58176,10 @@ static void releasePage(MemPage *pPage); /* Forward reference */ static int cursorHoldsMutex(BtCursor *p){ return sqlite3_mutex_held(p->pBt->mutex); } +static int cursorOwnsBtShared(BtCursor *p){ + assert( cursorHoldsMutex(p) ); + return (p->pBtree->db==p->pBt->db); +} #endif /* @@ -55053,7 +58279,7 @@ static int btreeSetHasContent(BtShared *pBt, Pgno pgno){ assert( pgno<=pBt->nPage ); pBt->pHasContent = sqlite3BitvecCreate(pBt->nPage); if( !pBt->pHasContent ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } } if( rc==SQLITE_OK && pgno<=sqlite3BitvecSize(pBt->pHasContent) ){ @@ -55132,7 +58358,7 @@ static int saveCursorKey(BtCursor *pCur){ sqlite3_free(pKey); } }else{ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } } assert( !pCur->curIntKey || !pCur->pKey ); @@ -55264,7 +58490,7 @@ static int btreeMoveto( pIdxKey = sqlite3VdbeAllocUnpackedRecord( pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree ); - if( pIdxKey==0 ) return SQLITE_NOMEM; + if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 ){ sqlite3DbFree(pCur->pKeyInfo->db, pFree); @@ -55290,7 +58516,7 @@ static int btreeMoveto( static int btreeRestoreCursorPosition(BtCursor *pCur){ int rc; int skipNext; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState>=CURSOR_REQUIRESEEK ); if( pCur->eState==CURSOR_FAULT ){ return pCur->skipNext; @@ -55362,6 +58588,26 @@ SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow) return SQLITE_OK; } +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* +** Provide hints to the cursor. The particular hint given (and the type +** 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, ...){ + /* Used only by system that substitute their own storage engine */ +} +#endif + +/* +** Provide flag hints to the cursor. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){ + assert( x==BTREE_SEEK_EQ || x==BTREE_BULKLOAD || x==0 ); + pCur->hints = x; +} + + #ifndef SQLITE_OMIT_AUTOVACUUM /* ** Given a page number of a regular database page, return the page @@ -55415,7 +58661,7 @@ 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); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); if( rc!=SQLITE_OK ){ *pRC = rc; return; @@ -55458,7 +58704,7 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){ assert( sqlite3_mutex_held(pBt->mutex) ); iPtrmap = PTRMAP_PAGENO(pBt, key); - rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); if( rc!=0 ){ return rc; } @@ -55535,8 +58781,7 @@ static SQLITE_NOINLINE void btreeParseCellAdjustSizeForOverflow( }else{ pInfo->nLocal = (u16)minLocal; } - pInfo->iOverflow = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell); - pInfo->nSize = pInfo->iOverflow + 4; + pInfo->nSize = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell) + 4; } /* @@ -55560,7 +58805,6 @@ static void btreeParseCellPtrNoPayload( ){ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->leaf==0 ); - assert( pPage->noPayload ); assert( pPage->childPtrSize==4 ); #ifndef SQLITE_DEBUG UNUSED_PARAMETER(pPage); @@ -55568,7 +58812,6 @@ static void btreeParseCellPtrNoPayload( pInfo->nSize = 4 + getVarint(&pCell[4], (u64*)&pInfo->nKey); pInfo->nPayload = 0; pInfo->nLocal = 0; - pInfo->iOverflow = 0; pInfo->pPayload = 0; return; } @@ -55583,8 +58826,6 @@ static void btreeParseCellPtr( assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->leaf==0 || pPage->leaf==1 ); - assert( pPage->intKeyLeaf || pPage->noPayload ); - assert( pPage->noPayload==0 ); assert( pPage->intKeyLeaf ); assert( pPage->childPtrSize==0 ); pIter = pCell; @@ -55638,7 +58879,6 @@ static void btreeParseCellPtr( pInfo->nSize = nPayload + (u16)(pIter - pCell); if( pInfo->nSize<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; - pInfo->iOverflow = 0; }else{ btreeParseCellAdjustSizeForOverflow(pPage, pCell, pInfo); } @@ -55654,7 +58894,6 @@ static void btreeParseCellPtrIndex( assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->leaf==0 || pPage->leaf==1 ); assert( pPage->intKeyLeaf==0 ); - assert( pPage->noPayload==0 ); pIter = pCell + pPage->childPtrSize; nPayload = *pIter; if( nPayload>=0x80 ){ @@ -55677,7 +58916,6 @@ static void btreeParseCellPtrIndex( pInfo->nSize = nPayload + (u16)(pIter - pCell); if( pInfo->nSize<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; - pInfo->iOverflow = 0; }else{ btreeParseCellAdjustSizeForOverflow(pPage, pCell, pInfo); } @@ -55716,7 +58954,6 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ pPage->xParseCell(pPage, pCell, &debuginfo); #endif - assert( pPage->noPayload==0 ); nSize = *pIter; if( nSize>=0x80 ){ pEnd = &pIter[8]; @@ -55793,8 +59030,8 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){ if( *pRC ) return; assert( pCell!=0 ); pPage->xParseCell(pPage, pCell, &info); - if( info.iOverflow ){ - Pgno ovfl = get4byte(&pCell[info.iOverflow]); + if( info.nLocalpBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, pRC); } } @@ -56165,35 +59402,32 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->xCellSize = cellSizePtr; pBt = pPage->pBt; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ - /* EVIDENCE-OF: R-03640-13415 A value of 5 means the page is an interior - ** table b-tree page. */ + /* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an + ** interior table b-tree page. */ assert( (PTF_LEAFDATA|PTF_INTKEY)==5 ); - /* EVIDENCE-OF: R-20501-61796 A value of 13 means the page is a leaf - ** table b-tree page. */ + /* EVIDENCE-OF: R-26900-09176 A value of 13 (0x0d) means the page is a + ** leaf table b-tree page. */ assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 ); pPage->intKey = 1; if( pPage->leaf ){ pPage->intKeyLeaf = 1; - pPage->noPayload = 0; pPage->xParseCell = btreeParseCellPtr; }else{ pPage->intKeyLeaf = 0; - pPage->noPayload = 1; pPage->xCellSize = cellSizePtrNoPayload; pPage->xParseCell = btreeParseCellPtrNoPayload; } pPage->maxLocal = pBt->maxLeaf; pPage->minLocal = pBt->minLeaf; }else if( flagByte==PTF_ZERODATA ){ - /* EVIDENCE-OF: R-27225-53936 A value of 2 means the page is an interior - ** index b-tree page. */ + /* EVIDENCE-OF: R-43316-37308 A value of 2 (0x02) means the page is an + ** interior index b-tree page. */ assert( (PTF_ZERODATA)==2 ); - /* EVIDENCE-OF: R-16571-11615 A value of 10 means the page is a leaf - ** index b-tree page. */ + /* EVIDENCE-OF: R-59615-42828 A value of 10 (0x0a) means the page is a + ** leaf index b-tree page. */ assert( (PTF_ZERODATA|PTF_LEAF)==10 ); pPage->intKey = 0; pPage->intKeyLeaf = 0; - pPage->noPayload = 0; pPage->xParseCell = btreeParseCellPtrIndex; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; @@ -56385,11 +59619,14 @@ static void zeroPage(MemPage *pPage, int flags){ */ static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){ MemPage *pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); - pPage->aData = sqlite3PagerGetData(pDbPage); - pPage->pDbPage = pDbPage; - pPage->pBt = pBt; - pPage->pgno = pgno; - pPage->hdrOffset = pgno==1 ? 100 : 0; + if( pgno!=pPage->pgno ){ + pPage->aData = sqlite3PagerGetData(pDbPage); + pPage->pDbPage = pDbPage; + pPage->pBt = pBt; + pPage->pgno = pgno; + pPage->hdrOffset = pgno==1 ? 100 : 0; + } + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); return pPage; } @@ -56415,7 +59652,7 @@ static int btreeGetPage( assert( flags==0 || flags==PAGER_GET_NOCONTENT || flags==PAGER_GET_READONLY ); assert( sqlite3_mutex_held(pBt->mutex) ); - rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); if( rc ) return rc; *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); return SQLITE_OK; @@ -56480,24 +59717,25 @@ static int getAndInitPage( rc = SQLITE_CORRUPT_BKPT; goto getAndInitPage_error; } - rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); if( rc ){ goto getAndInitPage_error; } - *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); + *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); if( (*ppPage)->isInit==0 ){ + btreePageFromDbPage(pDbPage, pgno, pBt); rc = btreeInitPage(*ppPage); if( rc!=SQLITE_OK ){ releasePage(*ppPage); goto getAndInitPage_error; } } + assert( (*ppPage)->pgno==pgno ); + assert( (*ppPage)->aData==sqlite3PagerGetData(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) - ){ + if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ rc = SQLITE_CORRUPT_BKPT; releasePage(*ppPage); goto getAndInitPage_error; @@ -56664,7 +59902,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( } p = sqlite3MallocZero(sizeof(Btree)); if( !p ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } p->inTrans = TRANS_NONE; p->db = db; @@ -56688,7 +59926,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( p->sharable = 1; if( !zFullPathname ){ sqlite3_free(p); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } if( isMemdb ){ memcpy(zFullPathname, zFilename, nFilename); @@ -56756,7 +59994,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( pBt = sqlite3MallocZero( sizeof(*pBt) ); if( pBt==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto btree_open_out; } rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, @@ -56825,8 +60063,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); if( pBt->mutex==0 ){ - rc = SQLITE_NOMEM; - db->mallocFailed = 0; + rc = SQLITE_NOMEM_BKPT; goto btree_open_out; } } @@ -56849,12 +60086,12 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( for(i=0; inDb; i++){ if( (pSib = db->aDb[i].pBt)!=0 && pSib->sharable ){ while( pSib->pPrev ){ pSib = pSib->pPrev; } - if( p->pBtpBt ){ + if( (uptr)p->pBt<(uptr)pSib->pBt ){ p->pNext = pSib; p->pPrev = 0; pSib->pPrev = p; }else{ - while( pSib->pNext && pSib->pNext->pBtpBt ){ + while( pSib->pNext && (uptr)pSib->pNext->pBt<(uptr)p->pBt ){ pSib = pSib->pNext; } p->pNext = pSib->pNext; @@ -57036,19 +60273,11 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ } /* -** Change the limit on the number of pages allowed in the cache. -** -** The maximum number of cache pages is set to the absolute -** value of mxPage. If mxPage is negative, the pager will -** operate asynchronously - it will not stop to do fsync()s -** to insure data is written to the disk surface before -** continuing. Transactions still work if synchronous is off, -** and the database cannot be corrupted if this program -** crashes. But if the operating system crashes or there is -** an abrupt power failure when synchronous is off, the database -** could be left in an inconsistent and unrecoverable state. -** Synchronous is on by default so database corruption is not -** normally a worry. +** Change the "soft" limit on the number of pages in the cache. +** Unused and unmodified pages will be recycled when the number of +** pages in the cache exceeds this soft limit. But the size of the +** 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){ BtShared *pBt = p->pBt; @@ -57059,6 +60288,26 @@ SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){ return SQLITE_OK; } +/* +** Change the "spill" limit on the number of pages in the cache. +** If the number of pages exceeds this limit during a write transaction, +** the pager might attempt to "spill" pages to the journal early in +** order to free up memory. +** +** The value returned is the current spill size. If zero is passed +** 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){ + BtShared *pBt = p->pBt; + int res; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + res = sqlite3PagerSetSpillsize(pBt->pPager, mxPage); + sqlite3BtreeLeave(p); + return res; +} + #if SQLITE_MAX_MMAP_SIZE>0 /* ** Change the limit on the amount of the database file that may be @@ -57096,21 +60345,6 @@ SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags( } #endif -/* -** Return TRUE if the given btree is set to safety level 1. In other -** words, return TRUE if no sync() occurs on the disk files. -*/ -SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree *p){ - BtShared *pBt = p->pBt; - int rc; - assert( sqlite3_mutex_held(p->db->mutex) ); - sqlite3BtreeEnter(p); - assert( pBt && pBt->pPager ); - rc = sqlite3PagerNosync(pBt->pPager); - sqlite3BtreeLeave(p); - return rc; -} - /* ** Change the default pages size and the number of reserved bytes per page. ** Or, if the page size has already been fixed, return SQLITE_READONLY @@ -57356,9 +60590,25 @@ static int lockBtree(BtShared *pBt){ rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen); if( rc!=SQLITE_OK ){ goto page1_init_failed; - }else if( isOpen==0 ){ - releasePage(pPage1); - return SQLITE_OK; + }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 + if( isOpen==0 ){ + releasePage(pPage1); + return SQLITE_OK; + } } rc = SQLITE_NOTADB; } @@ -57598,7 +60848,6 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ ** proceed. */ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ - sqlite3 *pBlock = 0; BtShared *pBt = p->pBt; int rc = SQLITE_OK; @@ -57621,27 +60870,30 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ } #ifndef SQLITE_OMIT_SHARED_CACHE - /* 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. - */ - if( (wrflag && pBt->inTransaction==TRANS_WRITE) - || (pBt->btsFlags & BTS_PENDING)!=0 - ){ - pBlock = pBt->pWriter->db; - }else if( wrflag>1 ){ - BtLock *pIter; - for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ - if( pIter->pBtree!=p ){ - pBlock = pIter->pBtree->db; - break; + { + sqlite3 *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. + */ + if( (wrflag && pBt->inTransaction==TRANS_WRITE) + || (pBt->btsFlags & BTS_PENDING)!=0 + ){ + pBlock = pBt->pWriter->db; + }else if( wrflag>1 ){ + BtLock *pIter; + for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ + if( pIter->pBtree!=p ){ + pBlock = pIter->pBtree->db; + break; + } } } - } - if( pBlock ){ - sqlite3ConnectionBlocked(p->db, pBlock); - rc = SQLITE_LOCKED_SHAREDCACHE; - goto trans_begun; + if( pBlock ){ + sqlite3ConnectionBlocked(p->db, pBlock); + rc = SQLITE_LOCKED_SHAREDCACHE; + goto trans_begun; + } } #endif @@ -57816,11 +61068,11 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( eType==PTRMAP_OVERFLOW1 ){ CellInfo info; pPage->xParseCell(pPage, pCell, &info); - if( info.iOverflow - && pCell+info.iOverflow+3<=pPage->aData+pPage->maskPage - && iFrom==get4byte(&pCell[info.iOverflow]) + if( info.nLocalaData+pPage->maskPage + && iFrom==get4byte(pCell+info.nSize-4) ){ - put4byte(&pCell[info.iOverflow], iTo); + put4byte(pCell+info.nSize-4, iTo); break; } }else{ @@ -58523,13 +61775,13 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ ** on the database already. If a write-cursor is requested, then ** the caller is assumed to have an open write transaction. ** -** If wrFlag==0, then the cursor can only be used for reading. -** If wrFlag==1, then the cursor can be used for reading or for -** writing if other conditions for writing are also met. These -** are the conditions that must be met in order for writing to -** be allowed: +** If the BTREE_WRCSR bit of wrFlag is clear, then the cursor can only +** be used for reading. If the BTREE_WRCSR bit is set, then the cursor +** can be used for reading or for writing if other conditions for writing +** are also met. These are the conditions that must be met in order +** for writing to be allowed: ** -** 1: The cursor must have been opened with wrFlag==1 +** 1: The cursor must have been opened with wrFlag containing BTREE_WRCSR ** ** 2: Other database connections that share the same pager cache ** but which are not in the READ_UNCOMMITTED state may not have @@ -58541,6 +61793,16 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ ** ** 4: There must be an active transaction. ** +** The BTREE_FORDELETE bit of wrFlag may optionally be set if BTREE_WRCSR +** is set. If FORDELETE is set, that is a hint to the implementation that +** this cursor will only be used to seek to and delete entries of an index +** as part of a larger DELETE statement. The FORDELETE hint is not used by +** this implementation. But in a hypothetical alternative storage engine +** in which index entries are automatically deleted when corresponding table +** rows are deleted, the FORDELETE flag is a hint that all SEEK and DELETE +** operations on this cursor can be no-ops and all READ operations can +** return a null row (2-bytes: 0x01 0x00). +** ** No checking is done to make sure that page iTable really is the ** root page of a b-tree. If it is not, then the cursor acquired ** will not work correctly. @@ -58559,13 +61821,16 @@ static int btreeCursor( BtCursor *pX; /* Looping over other all cursors */ assert( sqlite3BtreeHoldsMutex(p) ); - assert( wrFlag==0 || wrFlag==1 ); + assert( wrFlag==0 + || wrFlag==BTREE_WRCSR + || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) + ); /* 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+1) ); + assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) ); assert( wrFlag==0 || !hasReadConflicts(p, iTable) ); /* Assert that the caller has opened the required transaction. */ @@ -58576,7 +61841,7 @@ static int btreeCursor( if( wrFlag ){ allocateTempSpace(pBt); - if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM; + if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM_BKPT; } if( iTable==1 && btreePagecount(pBt)==0 ){ assert( wrFlag==0 ); @@ -58590,8 +61855,7 @@ static int btreeCursor( pCur->pKeyInfo = pKeyInfo; pCur->pBtree = p; pCur->pBt = pBt; - assert( wrFlag==0 || wrFlag==BTCF_WriteFlag ); - pCur->curFlags = wrFlag; + pCur->curFlags = wrFlag ? BTCF_WriteFlag : 0; pCur->curPagerFlags = wrFlag ? 0 : PAGER_GET_READONLY; /* If there are two or more cursors on the same btree, then all such ** cursors *must* have the BTCF_Multiple flag set. */ @@ -58756,7 +62020,7 @@ SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){ ** to return an integer result code for historical reasons. */ SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){ - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 ); assert( pCur->iPageinfo.nPayload ); - if( &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] ){ - /* Trying to read or write past the end of the data is an error */ + assert( aPayload > pPage->aData ); + if( (uptr)(aPayload - pPage->aData) > (pBt->usableSize - pCur->info.nLocal) ){ + /* Trying to read or write past the end of the data is an error. The + ** conditional above is really: + ** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] + ** but is recast into its current form to avoid integer overflow problems + */ return SQLITE_CORRUPT_BKPT; } @@ -58975,7 +62244,7 @@ static int accessPayload( pCur->aOverflow, nOvfl*2*sizeof(Pgno) ); if( aNew==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ pCur->nOvflAlloc = nOvfl*2; pCur->aOverflow = aNew; @@ -59003,7 +62272,9 @@ static int accessPayload( /* If required, populate the overflow page-list cache. */ if( (pCur->curFlags & BTCF_ValidOvfl)!=0 ){ - assert(!pCur->aOverflow[iIdx] || pCur->aOverflow[iIdx]==nextPage); + assert( pCur->aOverflow[iIdx]==0 + || pCur->aOverflow[iIdx]==nextPage + || CORRUPT_DB ); pCur->aOverflow[iIdx] = nextPage; } @@ -59073,7 +62344,7 @@ static int accessPayload( { DbPage *pDbPage; - rc = sqlite3PagerAcquire(pBt->pPager, nextPage, &pDbPage, + rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage, ((eOp&0x01)==0 ? PAGER_GET_READONLY : 0) ); if( rc==SQLITE_OK ){ @@ -59134,7 +62405,7 @@ SQLITE_PRIVATE int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *p } #endif - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); rc = restoreCursorPosition(pCur); if( rc==SQLITE_OK ){ assert( pCur->eState==CURSOR_VALID ); @@ -59172,7 +62443,7 @@ static const void *fetchPayload( assert( pCur!=0 && pCur->iPage>=0 && pCur->apPage[pCur->iPage]); assert( pCur->eState==CURSOR_VALID ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); assert( pCur->info.nSize>0 ); assert( pCur->info.pPayload>pCur->apPage[pCur->iPage]->aData || CORRUPT_DB ); @@ -59218,7 +62489,7 @@ SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor *pCur, u32 *pAmt){ static int moveToChild(BtCursor *pCur, u32 newPgno){ BtShared *pBt = pCur->pBt; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageiPage>=0 ); @@ -59264,7 +62535,7 @@ static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){ ** the largest cell index. */ static void moveToParent(BtCursor *pCur){ - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>0 ); assert( pCur->apPage[pCur->iPage] ); @@ -59304,7 +62575,7 @@ static int moveToRoot(BtCursor *pCur){ MemPage *pRoot; int rc = SQLITE_OK; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( CURSOR_INVALID < CURSOR_REQUIRESEEK ); assert( CURSOR_VALID < CURSOR_REQUIRESEEK ); assert( CURSOR_FAULT > CURSOR_REQUIRESEEK ); @@ -59383,7 +62654,7 @@ static int moveToLeftmost(BtCursor *pCur){ int rc = SQLITE_OK; MemPage *pPage; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); while( rc==SQLITE_OK && !(pPage = pCur->apPage[pCur->iPage])->leaf ){ assert( pCur->aiIdx[pCur->iPage]nCell ); @@ -59408,7 +62679,7 @@ static int moveToRightmost(BtCursor *pCur){ int rc = SQLITE_OK; MemPage *pPage = 0; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); while( !(pPage = pCur->apPage[pCur->iPage])->leaf ){ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); @@ -59429,7 +62700,7 @@ static int moveToRightmost(BtCursor *pCur){ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ int rc; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); rc = moveToRoot(pCur); if( rc==SQLITE_OK ){ @@ -59452,7 +62723,7 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ int rc; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); /* If the cursor already points to the last entry, this is a no-op. */ @@ -59517,6 +62788,8 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ ** *pRes>0 The cursor is left pointing at an entry that ** is larger than intKey/pIdxKey. ** +** 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( BtCursor *pCur, /* The cursor to be moved */ @@ -59528,7 +62801,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( int rc; RecordCompare xRecordCompare; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( pRes ); assert( (pIdxKey==0)==(pCur->pKeyInfo==0) ); @@ -59676,7 +62949,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( } pCellKey = sqlite3Malloc( nCell+18 ); if( pCellKey==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto moveto_finish; } pCur->aiIdx[pCur->iPage] = (u16)idx; @@ -59776,7 +63049,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ int idx; MemPage *pPage; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); assert( *pRes==0 ); if( pCur->eState!=CURSOR_VALID ){ @@ -59840,7 +63113,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ } SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ MemPage *pPage; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pRes!=0 ); assert( *pRes==0 || *pRes==1 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); @@ -59885,7 +63158,7 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ int rc; MemPage *pPage; - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pRes!=0 ); assert( *pRes==0 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); @@ -59941,7 +63214,7 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ return rc; } SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){ - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pRes!=0 ); assert( *pRes==0 || *pRes==1 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); @@ -60456,13 +63729,13 @@ static int clearCell( assert( sqlite3_mutex_held(pPage->pBt->mutex) ); pPage->xParseCell(pPage, pCell, &info); *pnSize = info.nSize; - if( info.iOverflow==0 ){ + if( info.nLocal==info.nPayload ){ return SQLITE_OK; /* No overflow pages. Return without doing anything */ } - if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){ + if( pCell+info.nSize-1 > pPage->aData+pPage->maskPage ){ return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ } - ovflPgno = get4byte(&pCell[info.iOverflow]); + ovflPgno = get4byte(pCell + info.nSize - 4); assert( pBt->usableSize > 4 ); ovflPageSize = pBt->usableSize - 4; nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; @@ -60607,11 +63880,10 @@ static int fillInCell( { CellInfo info; pPage->xParseCell(pPage, pCell, &info); - assert( nHeader=(int)(info.pPayload - pCell) ); + assert( nHeader==(int)(info.pPayload - pCell) ); assert( info.nKey==nKey ); assert( *pnSize == info.nSize ); assert( spaceLeft == info.nLocal ); - assert( pPrior == &pCell[info.iOverflow] ); } #endif @@ -60932,7 +64204,7 @@ static int rebuildPage( pData = pEnd; for(i=0; iaData && pCellapCell[i] will never overlap on a well-formed @@ -61043,7 +64315,7 @@ static int pageFreeArray( for(i=iFirst; iapCell[i]; - if( pCell>=pStart && pCellapCell[i+iNew]; int iOff = get2byteAligned(&pPg->aCellIdx[i*2]); - if( pCell>=aData && pCell<&aData[pPg->pBt->usableSize] ){ + if( SQLITE_WITHIN(pCell, aData, &aData[pPg->pBt->usableSize]) ){ pCell = &pTmp[pCell - aData]; } assert( 0==memcmp(pCell, &aData[iOff], @@ -61321,8 +64593,8 @@ static int ptrmapCheckPages(MemPage **apPage, int nPage){ z = findCell(pPage, j); pPage->xParseCell(pPage, z, &info); - if( info.iOverflow ){ - Pgno ovfl = get4byte(&z[info.iOverflow]); + if( info.nLocalpgno && e==PTRMAP_OVERFLOW1 ); } @@ -61440,9 +64712,6 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** If aOvflSpace is set to a null pointer, this function returns ** SQLITE_NOMEM. */ -#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) -#pragma optimize("", off) -#endif static int balance_nonroot( MemPage *pParent, /* Parent page of siblings being balanced */ int iParentIdx, /* Index of "the page" in pParent */ @@ -61499,7 +64768,7 @@ static int balance_nonroot( assert( pParent->nOverflow==0 || pParent->aiOvfl[0]==iParentIdx ); if( !aOvflSpace ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* Find the sibling pages to balance. Also locate the cells in pParent @@ -61599,7 +64868,7 @@ static int balance_nonroot( assert( szScratch<=6*(int)pBt->pageSize ); b.apCell = sqlite3ScratchMalloc( szScratch ); if( b.apCell==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto balance_cleanup; } b.szCell = (u16*)&b.apCell[nMaxCells]; @@ -61658,9 +64927,8 @@ static int balance_nonroot( ** long be able to find the cells if a pointer to each cell is not saved ** first. */ - memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*limit); + memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow)); if( pOld->nOverflow>0 ){ - memset(&b.szCell[b.nCell+limit], 0, sizeof(b.szCell[0])*pOld->nOverflow); limit = pOld->aiOvfl[0]; for(j=0; j=nNew || pNew->pgno!=aPgno[iOld] - || pCell=&aOld[usableSize] + || !SQLITE_WITHIN(pCell,aOld,&aOld[usableSize]) ){ if( !leafCorrection ){ ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc); @@ -62036,9 +65303,9 @@ static int balance_nonroot( ** any cell). But it is important to pass the correct size to ** insertCell(), so reparse the cell now. ** - ** Note that this can never happen in an SQLite data file, as all - ** cells are at least 4 bytes. It only happens in b-trees used - ** to evaluate "IN (SELECT ...)" and similar clauses. + ** This can only happen for b-trees used to evaluate "IN (SELECT ...)" + ** and WITHOUT ROWID tables with exactly one column which is the + ** primary key. */ if( b.szCell[j]==4 ){ assert(leafCorrection==4); @@ -62188,9 +65455,6 @@ balance_cleanup: return rc; } -#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) -#pragma optimize("", on) -#endif /* @@ -62275,8 +65539,8 @@ static int balance(BtCursor *pCur){ u8 aBalanceQuickSpace[13]; u8 *pFree = 0; - TESTONLY( int balance_quick_called = 0 ); - TESTONLY( int balance_deeper_called = 0 ); + VVA_ONLY( int balance_quick_called = 0 ); + VVA_ONLY( int balance_deeper_called = 0 ); do { int iPage = pCur->iPage; @@ -62289,7 +65553,8 @@ static int balance(BtCursor *pCur){ ** and copy the current contents of the root-page to it. The ** next iteration of the do-loop will balance the child page. */ - assert( (balance_deeper_called++)==0 ); + assert( balance_deeper_called==0 ); + VVA_ONLY( balance_deeper_called++ ); rc = balance_deeper(pPage, &pCur->apPage[1]); if( rc==SQLITE_OK ){ pCur->iPage = 1; @@ -62328,7 +65593,8 @@ static int balance(BtCursor *pCur){ ** function. If this were not verified, a subtle bug involving reuse ** of the aBalanceQuickSpace[] might sneak in. */ - assert( (balance_quick_called++)==0 ); + assert( balance_quick_called==0 ); + VVA_ONLY( balance_quick_called++ ); rc = balance_quick(pParent, pPage, aBalanceQuickSpace); }else #endif @@ -62429,7 +65695,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( return pCur->skipNext; } - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( (pCur->curFlags & BTCF_WriteFlag)!=0 && pBt->inTransaction==TRANS_WRITE && (pBt->btsFlags & BTS_READ_ONLY)==0 ); @@ -62559,13 +65825,21 @@ end_insert: /* ** Delete the entry that the cursor is pointing to. ** -** If the second parameter is zero, then the cursor is left pointing at an -** arbitrary location after the delete. If it is non-zero, then the cursor -** is left in a state such that the next call to BtreeNext() or BtreePrev() -** moves it to the same row as it would if the call to BtreeDelete() had -** been omitted. +** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then +** the cursor is left pointing at an arbitrary location after the delete. +** But if that bit is set, then the cursor is left in a state such that +** the next call to BtreeNext() or BtreePrev() moves it to the same row +** as it would have been on if the call to BtreeDelete() had been omitted. +** +** The BTREE_AUXDELETE bit of flags indicates that is one of several deletes +** associated with a single table entry and its indexes. Only one of those +** deletes is considered the "primary" delete. The primary delete occurs +** on a cursor that is not a BTREE_FORDELETE cursor. All but one delete +** operation on non-FORDELETE cursors is tagged with the AUXDELETE flag. +** 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, int bPreserve){ +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ Btree *p = pCur->pBtree; BtShared *pBt = p->pBt; int rc; /* Return code */ @@ -62575,8 +65849,9 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, int bPreserve){ int iCellDepth; /* Depth of node containing pCell */ u16 szCell; /* Size of the cell being deleted */ int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */ + u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */ - assert( cursorHoldsMutex(pCur) ); + assert( cursorOwnsBtShared(pCur) ); assert( pBt->inTransaction==TRANS_WRITE ); assert( (pBt->btsFlags & BTS_READ_ONLY)==0 ); assert( pCur->curFlags & BTCF_WriteFlag ); @@ -62584,12 +65859,35 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, int bPreserve){ assert( !hasReadConflicts(p, pCur->pgnoRoot) ); assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); assert( pCur->eState==CURSOR_VALID ); + assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); iCellDepth = pCur->iPage; iCellIdx = pCur->aiIdx[iCellDepth]; pPage = pCur->apPage[iCellDepth]; pCell = findCell(pPage, iCellIdx); + /* If the bPreserve flag is set to true, then the cursor position must + ** be preserved following this delete operation. If the current delete + ** will cause a b-tree rebalance, then this is done by saving the cursor + ** key and leaving the cursor in CURSOR_REQUIRESEEK state before + ** returning. + ** + ** Or, if the current delete will not cause a rebalance, then the cursor + ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately + ** before or after the deleted entry. In this case set bSkipnext to true. */ + if( bPreserve ){ + if( !pPage->leaf + || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3) + ){ + /* A b-tree rebalance will be required after deleting this entry. + ** Save the cursor key. */ + rc = saveCursorKey(pCur); + if( rc ) return rc; + }else{ + bSkipnext = 1; + } + } + /* If the page containing the entry to delete is not a leaf page, move ** the cursor to the largest entry in the tree that is smaller than ** the entry being deleted. This cell will replace the cell being deleted @@ -62616,28 +65914,6 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, int bPreserve){ invalidateIncrblobCursors(p, pCur->info.nKey, 0); } - /* If the bPreserve flag is set to true, then the cursor position must - ** be preserved following this delete operation. If the current delete - ** will cause a b-tree rebalance, then this is done by saving the cursor - ** key and leaving the cursor in CURSOR_REQUIRESEEK state before - ** returning. - ** - ** Or, if the current delete will not cause a rebalance, then the cursor - ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately - ** before or after the deleted entry. In this case set bSkipnext to true. */ - if( bPreserve ){ - if( !pPage->leaf - || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3) - ){ - /* A b-tree rebalance will be required after deleting this entry. - ** Save the cursor key. */ - rc = saveCursorKey(pCur); - if( rc ) return rc; - }else{ - bSkipnext = 1; - } - } - /* 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. */ @@ -62695,8 +65971,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, int bPreserve){ if( rc==SQLITE_OK ){ if( bSkipnext ){ - assert( bPreserve && pCur->iPage==iCellDepth ); - assert( pPage==pCur->apPage[pCur->iPage] ); + assert( bPreserve && (pCur->iPage==iCellDepth || CORRUPT_DB) ); + assert( pPage==pCur->apPage[pCur->iPage] || CORRUPT_DB ); assert( (pPage->nCell>0 || CORRUPT_DB) && iCellIdx<=pPage->nCell ); pCur->eState = CURSOR_SKIPNEXT; if( iCellIdx>=pPage->nCell ){ @@ -63017,6 +66293,14 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ 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) ){ + return SQLITE_CORRUPT_BKPT; + } + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); if( rc ) return rc; rc = sqlite3BtreeClearTable(p, iTable, 0); @@ -63027,76 +66311,67 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ *piMoved = 0; - if( iTable>1 ){ #ifdef SQLITE_OMIT_AUTOVACUUM - freePage(pPage, &rc); - releasePage(pPage); + freePage(pPage, &rc); + releasePage(pPage); #else - if( pBt->autoVacuum ){ - Pgno maxRootPgno; - sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &maxRootPgno); + if( pBt->autoVacuum ){ + Pgno maxRootPgno; + sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &maxRootPgno); - if( iTable==maxRootPgno ){ - /* If the table being dropped is the table with the largest root-page - ** number in the database, put the root page on the free list. - */ - freePage(pPage, &rc); - releasePage(pPage); - if( rc!=SQLITE_OK ){ - return rc; - } - }else{ - /* The table being dropped does not have the largest root-page - ** number in the database. So move the page that does into the - ** gap left by the deleted root-page. - */ - MemPage *pMove; - releasePage(pPage); - rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - rc = relocatePage(pBt, pMove, PTRMAP_ROOTPAGE, 0, iTable, 0); - releasePage(pMove); - if( rc!=SQLITE_OK ){ - return rc; - } - pMove = 0; - rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); - freePage(pMove, &rc); - releasePage(pMove); - if( rc!=SQLITE_OK ){ - return rc; - } - *piMoved = maxRootPgno; - } - - /* Set the new 'max-root-page' value in the database header. This - ** is the old value less one, less one more if that happens to - ** be a root-page number, less one again if that is the - ** PENDING_BYTE_PAGE. + if( iTable==maxRootPgno ){ + /* If the table being dropped is the table with the largest root-page + ** number in the database, put the root page on the free list. */ - maxRootPgno--; - while( maxRootPgno==PENDING_BYTE_PAGE(pBt) - || PTRMAP_ISPAGE(pBt, maxRootPgno) ){ - maxRootPgno--; - } - assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) ); - - rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno); - }else{ freePage(pPage, &rc); releasePage(pPage); + if( rc!=SQLITE_OK ){ + return rc; + } + }else{ + /* The table being dropped does not have the largest root-page + ** number in the database. So move the page that does into the + ** gap left by the deleted root-page. + */ + MemPage *pMove; + releasePage(pPage); + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = relocatePage(pBt, pMove, PTRMAP_ROOTPAGE, 0, iTable, 0); + releasePage(pMove); + if( rc!=SQLITE_OK ){ + return rc; + } + pMove = 0; + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + freePage(pMove, &rc); + releasePage(pMove); + if( rc!=SQLITE_OK ){ + return rc; + } + *piMoved = maxRootPgno; } -#endif - }else{ - /* If sqlite3BtreeDropTable was called on page 1. - ** This really never should happen except in a corrupt - ** database. + + /* Set the new 'max-root-page' value in the database header. This + ** is the old value less one, less one more if that happens to + ** be a root-page number, less one again if that is the + ** PENDING_BYTE_PAGE. */ - zeroPage(pPage, PTF_INTKEY|PTF_LEAF ); + maxRootPgno--; + while( maxRootPgno==PENDING_BYTE_PAGE(pBt) + || PTRMAP_ISPAGE(pBt, maxRootPgno) ){ + maxRootPgno--; + } + assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) ); + + rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno); + }else{ + freePage(pPage, &rc); releasePage(pPage); } +#endif return rc; } SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ @@ -63283,9 +66558,9 @@ static void checkAppendMsg( sqlite3StrAccumAppend(&pCheck->errMsg, "\n", 1); } if( pCheck->zPfx ){ - sqlite3XPrintf(&pCheck->errMsg, 0, pCheck->zPfx, pCheck->v1, pCheck->v2); + sqlite3XPrintf(&pCheck->errMsg, pCheck->zPfx, pCheck->v1, pCheck->v2); } - sqlite3VXPrintf(&pCheck->errMsg, 1, zFormat, ap); + sqlite3VXPrintf(&pCheck->errMsg, zFormat, ap); va_end(ap); if( pCheck->errMsg.accError==STRACCUM_NOMEM ){ pCheck->mallocFailed = 1; @@ -63389,7 +66664,7 @@ static void checkList( break; } if( checkRef(pCheck, iPage) ) break; - if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage) ){ + if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){ checkAppendMsg(pCheck, "failed to get page %d", iPage); break; } @@ -63635,9 +66910,9 @@ static int checkTreePage( if( info.nPayload>info.nLocal ){ int nPage; /* Number of pages on the overflow chain */ Pgno pgnoOvfl; /* First page of the overflow chain */ - assert( pc + info.iOverflow <= usableSize ); + assert( pc + info.nSize - 4 <= usableSize ); nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4); - pgnoOvfl = get4byte(&pCell[info.iOverflow]); + pgnoOvfl = get4byte(&pCell[info.nSize - 4]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage); @@ -63786,7 +67061,8 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck( sqlite3BtreeEnter(p); assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); - assert( (nRef = sqlite3PagerRefcount(pBt->pPager))>=0 ); + VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) ); + assert( nRef>=0 ); sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = btreePagecount(sCheck.pBt); @@ -63799,6 +67075,7 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck( sCheck.aPgRef = 0; sCheck.heap = 0; sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); + sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; if( sCheck.nPage==0 ){ goto integrity_ck_cleanup; } @@ -64038,7 +67315,7 @@ SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){ */ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ int rc; - assert( cursorHoldsMutex(pCsr) ); + assert( cursorOwnsBtShared(pCsr) ); assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) ); assert( pCsr->curFlags & BTCF_Incrblob ); @@ -64126,15 +67403,6 @@ SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){ return rc; } -/* -** set the mask of hint flags for cursor pCsr. -*/ -SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *pCsr, unsigned int mask){ - assert( mask==BTREE_BULKLOAD || mask==BTREE_SEEK_EQ || mask==0 ); - pCsr->hints = mask; -} - -#ifdef SQLITE_DEBUG /* ** Return true if the cursor has a hint specified. This routine is ** only used from within assert() statements @@ -64142,7 +67410,6 @@ SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *pCsr, unsigned int mask){ SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){ return (pCsr->hints & mask)!=0; } -#endif /* ** Return true if the given Btree is read-only. @@ -64156,6 +67423,15 @@ SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){ */ SQLITE_PRIVATE int sqlite3HeaderSizeBtree(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){ + return p->sharable; +} +#endif + /************** End of btree.c ***********************************************/ /************** Begin file backup.c ******************************************/ /* @@ -64248,7 +67524,7 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ pParse = sqlite3StackAllocZero(pErrorDb, sizeof(*pParse)); if( pParse==0 ){ sqlite3ErrorWithMsg(pErrorDb, SQLITE_NOMEM, "out of memory"); - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ pParse->db = pDb; if( sqlite3OpenTempDatabase(pParse) ){ @@ -64342,7 +67618,7 @@ SQLITE_API sqlite3_backup *SQLITE_STDCALL sqlite3_backup_init( ** sqlite3_backup_finish(). */ p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); if( !p ){ - sqlite3Error(pDestDb, SQLITE_NOMEM); + sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT); } } @@ -64453,7 +67729,7 @@ 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)) + if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg, 0)) && SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg)) ){ const u8 *zIn = &zSrcData[iOff%nSrcPgsz]; @@ -64579,8 +67855,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage){ const Pgno iSrcPg = p->iNext; /* Source page number */ if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){ DbPage *pSrcPg; /* Source page object */ - rc = sqlite3PagerAcquire(pSrcPager, iSrcPg, &pSrcPg, - PAGER_GET_READONLY); + rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY); if( rc==SQLITE_OK ){ rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg), 0); sqlite3PagerUnref(pSrcPg); @@ -64680,7 +67955,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage){ 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); + rc = sqlite3PagerGet(pDestPager, iPg, &pPg, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite(pPg); sqlite3PagerUnref(pPg); @@ -64700,7 +67975,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage){ ){ PgHdr *pSrcPg = 0; const Pgno iSrcPg = (Pgno)((iOff/pgszSrc)+1); - rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg); + rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg, 0); if( rc==SQLITE_OK ){ u8 *zData = sqlite3PagerGetData(pSrcPg); rc = sqlite3OsWrite(pFile, zData, pgszSrc, iOff); @@ -64742,7 +68017,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage){ } if( rc==SQLITE_IOERR_NOMEM ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } p->rc = rc; } @@ -65077,6 +68352,7 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){ assert( sqlite3VdbeCheckMemInvariants(pMem) ); assert( (pMem->flags&MEM_RowSet)==0 ); + testcase( pMem->db==0 ); /* If the bPreserve flag is set to true, then the memory cell must already ** contain a valid string or blob value. */ @@ -65098,7 +68374,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre sqlite3VdbeMemSetNull(pMem); pMem->z = 0; pMem->szMalloc = 0; - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; }else{ pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); } @@ -65156,7 +68432,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ f = pMem->flags; if( (f&(MEM_Str|MEM_Blob)) && (pMem->szMalloc==0 || pMem->z!=pMem->zMalloc) ){ if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; @@ -65188,7 +68464,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ nByte = 1; } if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(&pMem->z[pMem->n], 0, pMem->u.nZero); @@ -65205,7 +68481,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ */ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; @@ -65254,7 +68530,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* For a Real or Integer, use sqlite3_snprintf() to produce the UTF-8 @@ -65680,7 +68956,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetRowSet(Mem *pMem){ assert( db!=0 ); assert( (pMem->flags & MEM_RowSet)==0 ); sqlite3VdbeMemRelease(pMem); - pMem->zMalloc = sqlite3DbMallocRaw(db, 64); + pMem->zMalloc = sqlite3DbMallocRawNN(db, 64); if( db->mallocFailed ){ pMem->flags = MEM_Null; pMem->szMalloc = 0; @@ -65721,7 +68997,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){ SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){ int i; Mem *pX; - for(i=1, pX=&pVdbe->aMem[1]; i<=pVdbe->nMem; i++, pX++){ + for(i=0, pX=pVdbe->aMem; inMem; i++, pX++){ if( pX->pScopyFrom==pMem ){ pX->flags |= MEM_Undefined; pX->pScopyFrom = 0; @@ -65762,10 +69038,6 @@ SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int sr SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){ int rc = SQLITE_OK; - /* The pFrom==0 case in the following assert() is when an sqlite3_value - ** from sqlite3_value_dup() is used as the argument - ** to sqlite3_result_value(). */ - assert( pTo->db==pFrom->db || pFrom->db==0 ); assert( (pFrom->flags & MEM_RowSet)==0 ); if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo); memcpy(pTo, pFrom, MEMCELLSIZE); @@ -65865,7 +69137,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( testcase( nAlloc==31 ); testcase( nAlloc==32 ); if( sqlite3VdbeMemClearAndResize(pMem, MAX(nAlloc,32)) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memcpy(pMem->z, z, nAlloc); }else if( xDel==SQLITE_DYNAMIC ){ @@ -65885,7 +69157,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( #ifndef SQLITE_OMIT_UTF16 if( pMem->enc!=SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } #endif @@ -66146,7 +69418,6 @@ static int valueFromFunction( FuncDef *pFunc = 0; /* Function definition */ sqlite3_value *pVal = 0; /* New value */ int rc = SQLITE_OK; /* Return code */ - int nName; /* Size of function name in bytes */ ExprList *pList = 0; /* Function arguments */ int i; /* Iterator variable */ @@ -66154,8 +69425,7 @@ static int valueFromFunction( assert( (p->flags & EP_TokenOnly)==0 ); pList = p->x.pList; if( pList ) nVal = pList->nExpr; - nName = sqlite3Strlen30(p->u.zToken); - pFunc = sqlite3FindFunction(db, p->u.zToken, nName, nVal, enc, 0); + pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) @@ -66166,7 +69436,7 @@ static int valueFromFunction( if( pList ){ apVal = (sqlite3_value**)sqlite3DbMallocZero(db, sizeof(apVal[0]) * nVal); if( apVal==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto value_from_function_out; } for(i=0; ixFunc(&ctx, nVal, apVal); + pFunc->xSFunc(&ctx, nVal, apVal); if( ctx.isError ){ rc = ctx.isError; sqlite3ErrorMsg(pCtx->pParse, "%s", sqlite3_value_text(pVal)); @@ -66247,7 +69517,7 @@ static int valueFromExpr( *ppVal = 0; return SQLITE_OK; } - while( (op = pExpr->op)==TK_UPLUS ) pExpr = pExpr->pLeft; + while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; /* Compressed expressions only appear when parsing the DEFAULT clause @@ -66342,7 +69612,7 @@ static int valueFromExpr( return rc; no_mem: - db->mallocFailed = 1; + sqlite3OomFault(db); sqlite3DbFree(db, zVal); assert( *ppVal==0 ); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -66350,7 +69620,7 @@ no_mem: #else assert( pCtx==0 ); sqlite3ValueFree(pVal); #endif - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* @@ -66388,21 +69658,20 @@ static void recordFunc( sqlite3_value **argv ){ const int file_format = 1; - int iSerial; /* Serial type */ + u32 iSerial; /* Serial type */ int nSerial; /* Bytes of space for iSerial as varint */ - int nVal; /* Bytes of space required for argv[0] */ + u32 nVal; /* Bytes of space required for argv[0] */ int nRet; sqlite3 *db; u8 *aRet; UNUSED_PARAMETER( argc ); - iSerial = sqlite3VdbeSerialType(argv[0], file_format); + iSerial = sqlite3VdbeSerialType(argv[0], file_format, &nVal); nSerial = sqlite3VarintLen(iSerial); - nVal = sqlite3VdbeSerialTypeLen(iSerial); db = sqlite3_context_db_handle(context); nRet = 1 + nSerial + nVal; - aRet = sqlite3DbMallocRaw(db, nRet); + aRet = sqlite3DbMallocRawNN(db, nRet); if( aRet==0 ){ sqlite3_result_error_nomem(context); }else{ @@ -66418,15 +69687,10 @@ static void recordFunc( ** Register built-in functions used to help read ANALYZE data. */ SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void){ - static SQLITE_WSD FuncDef aAnalyzeTableFuncs[] = { + static FuncDef aAnalyzeTableFuncs[] = { FUNCTION(sqlite_record, 1, 0, 0, recordFunc), }; - int i; - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); - FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aAnalyzeTableFuncs); - for(i=0; inRec ) return SQLITE_CORRUPT_BKPT; if( pMem==0 ){ pMem = *ppVal = sqlite3ValueNew(db); - if( pMem==0 ) return SQLITE_NOMEM; + if( pMem==0 ) return SQLITE_NOMEM_BKPT; } sqlite3VdbeSerialGet(&a[iField-szField], t, pMem); pMem->enc = ENC(db); @@ -66718,6 +69982,7 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ assert( pParse->aLabel==0 ); assert( pParse->nLabel==0 ); assert( pParse->nOpAlloc==0 ); + assert( pParse->szOpAlloc==0 ); return p; } @@ -66760,6 +70025,7 @@ SQLITE_API const char *SQLITE_STDCALL sqlite3_sql(sqlite3_stmt *pStmt){ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ Vdbe tmp, *pTmp; char *zTmp; + assert( pA->db==pB->db ); tmp = *pA; *pA = *pB; *pB = tmp; @@ -66807,10 +70073,11 @@ static int growOpArray(Vdbe *v, int nOp){ assert( nNew>=(p->nOpAlloc+nOp) ); pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); if( pNew ){ - p->nOpAlloc = sqlite3DbMallocSize(p->db, pNew)/sizeof(Op); + p->szOpAlloc = sqlite3DbMallocSize(p->db, pNew); + p->nOpAlloc = p->szOpAlloc/sizeof(Op); v->aOp = pNew; } - return (pNew ? SQLITE_OK : SQLITE_NOMEM); + return (pNew ? SQLITE_OK : SQLITE_NOMEM_BKPT); } #ifdef SQLITE_DEBUG @@ -66840,17 +70107,21 @@ static void test_addop_breakpoint(void){ ** the sqlite3VdbeChangeP4() 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 ); + if( growOpArray(p, 1) ) return 1; + assert( p->pParse->nOpAlloc>p->nOp ); + return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +} SQLITE_PRIVATE int sqlite3VdbeAddOp3(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 ); + assert( op>=0 && op<0xff ); if( p->pParse->nOpAlloc<=i ){ - if( growOpArray(p, 1) ){ - return 1; - } + return growOp3(p, op, p1, p2, p3); } p->nOp++; pOp = &p->aOp[i]; @@ -66927,8 +70198,7 @@ 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*); - int addr = sqlite3VdbeAddOp2(p, z==0 ? OP_Null : OP_String8, 0, iDest++); - if( z ) sqlite3VdbeChangeP4(p, addr, z, 0); + sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest++, 0, z, 0); }else{ assert( c=='i' ); sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest++); @@ -66967,7 +70237,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8( const u8 *zP4, /* The P4 operand */ int p4type /* P4 operand type */ ){ - char *p4copy = sqlite3DbMallocRaw(sqlite3VdbeDb(p), 8); + char *p4copy = sqlite3DbMallocRawNN(sqlite3VdbeDb(p), 8); if( p4copy ) memcpy(p4copy, zP4, 8); return sqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type); } @@ -66982,8 +70252,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8( */ SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){ int j; - int addr = sqlite3VdbeAddOp3(p, OP_ParseSchema, iDb, 0, 0); - sqlite3VdbeChangeP4(p, addr, zWhere, P4_DYNAMIC); + sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j); } @@ -67003,6 +70272,21 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( return addr; } +/* Insert the end of a co-routine +*/ +SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){ + sqlite3VdbeAddOp1(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 + ** might expect their registers to be preserved across an OP_Yield, and + ** that could cause problems if two or more co-routines are using the same + ** temporary register. + */ + v->pParse->nTempReg = 0; + v->pParse->nRangeReg = 0; +} + /* ** Create a new symbolic label for an instruction that has yet to be ** coded. The symbolic label is really just a negative number. The @@ -67028,7 +70312,7 @@ SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *v){ if( p->aLabel ){ p->aLabel[i] = -1; } - return -1-i; + return ADDR(i); } /* @@ -67038,7 +70322,7 @@ SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *v){ */ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ Parse *p = v->pParse; - int j = -1-x; + int j = ADDR(x); assert( v->magic==VDBE_MAGIC_INIT ); assert( jnLabel ); assert( j>=0 ); @@ -67055,6 +70339,13 @@ SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){ p->runOnlyOnce = 1; } +/* +** Mark the VDBE as one that can only be run multiple times. +*/ +SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){ + p->runOnlyOnce = 0; +} + #ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */ /* @@ -67201,73 +70492,84 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ ** (4) Initialize the p4.xAdvance pointer on opcodes that use it. ** ** (5) Reclaim the memory allocated for storing labels. +** +** This routine will only function correctly if the mkopcodeh.tcl generator +** script numbers the opcodes correctly. Changes to this routine must be +** coordinated with changes to mkopcodeh.tcl. */ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ - int i; int nMaxArgs = *pMaxFuncArgs; Op *pOp; Parse *pParse = p->pParse; int *aLabel = pParse->aLabel; p->readOnly = 1; p->bIsReader = 0; - for(pOp=p->aOp, i=p->nOp-1; i>=0; i--, pOp++){ - u8 opcode = pOp->opcode; + pOp = &p->aOp[p->nOp-1]; + while(1){ - /* NOTE: Be sure to update mkopcodeh.awk when adding or removing - ** cases from this switch! */ - switch( opcode ){ - case OP_Transaction: { - if( pOp->p2!=0 ) p->readOnly = 0; - /* fall thru */ - } - case OP_AutoCommit: - case OP_Savepoint: { - p->bIsReader = 1; - break; - } + /* Only JUMP opcodes and the short list of special opcodes in the switch + ** below need to be considered. The mkopcodeh.tcl generator script groups + ** all these opcodes together near the front of the opcode list. Skip + ** any opcode that does not need processing by virtual of the fact that + ** it is larger than SQLITE_MX_JUMP_OPCODE, as a performance optimization. + */ + if( pOp->opcode<=SQLITE_MX_JUMP_OPCODE ){ + /* NOTE: Be sure to update mkopcodeh.tcl when adding or removing + ** cases from this switch! */ + switch( pOp->opcode ){ + case OP_Transaction: { + if( pOp->p2!=0 ) p->readOnly = 0; + /* fall thru */ + } + case OP_AutoCommit: + case OP_Savepoint: { + p->bIsReader = 1; + break; + } #ifndef SQLITE_OMIT_WAL - case OP_Checkpoint: + case OP_Checkpoint: #endif - case OP_Vacuum: - case OP_JournalMode: { - p->readOnly = 0; - p->bIsReader = 1; - break; - } + case OP_Vacuum: + case OP_JournalMode: { + p->readOnly = 0; + p->bIsReader = 1; + break; + } #ifndef SQLITE_OMIT_VIRTUALTABLE - case OP_VUpdate: { - if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; - break; - } - case OP_VFilter: { - int n; - assert( p->nOp - i >= 3 ); - assert( pOp[-1].opcode==OP_Integer ); - n = pOp[-1].p1; - if( n>nMaxArgs ) nMaxArgs = n; - break; - } + case OP_VUpdate: { + if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; + break; + } + case OP_VFilter: { + int n; + assert( (pOp - p->aOp) >= 3 ); + assert( pOp[-1].opcode==OP_Integer ); + n = pOp[-1].p1; + if( n>nMaxArgs ) nMaxArgs = n; + break; + } #endif - case OP_Next: - case OP_NextIfOpen: - case OP_SorterNext: { - pOp->p4.xAdvance = sqlite3BtreeNext; - pOp->p4type = P4_ADVANCE; - break; + 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; + break; + } } - case OP_Prev: - case OP_PrevIfOpen: { - pOp->p4.xAdvance = sqlite3BtreePrevious; - pOp->p4type = P4_ADVANCE; - break; + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 && pOp->p2<0 ){ + assert( ADDR(pOp->p2)nLabel ); + pOp->p2 = aLabel[ADDR(pOp->p2)]; } } - - pOp->opflags = sqlite3OpcodeProperty[opcode]; - if( (pOp->opflags & OPFLG_JUMP)!=0 && pOp->p2<0 ){ - assert( -1-pOp->p2nLabel ); - pOp->p2 = aLabel[-1-pOp->p2]; - } + if( pOp==p->aOp ) break; + pOp--; } sqlite3DbFree(p->db, pParse->aLabel); pParse->aLabel = 0; @@ -67284,6 +70586,20 @@ SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){ return p->nOp; } +/* +** 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 +** fail due to a OOM fault and hence that the return value from +** sqlite3VdbeAddOpList() will always be non-NULL. +*/ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){ + assert( p->nOp + N <= p->pParse->nOpAlloc ); +} +#endif + /* ** This function returns a pointer to the array of opcodes associated with ** the Vdbe passed as the first argument. It is the callers responsibility @@ -67309,28 +70625,33 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg) } /* -** Add a whole list of operations to the operation stack. Return the -** address of the first operation added. +** Add a whole list of operations to the operation stack. Return a +** pointer to the first operation inserted. +** +** Non-zero P2 arguments to jump instructions are automatically adjusted +** so that the jump target is relative to the first operation inserted. */ -SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, int iLineno){ - int addr, i; - VdbeOp *pOut; +SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList( + Vdbe *p, /* Add opcodes to the prepared statement */ + int nOp, /* Number of opcodes to add */ + VdbeOpList const *aOp, /* The opcodes to be added */ + int iLineno /* Source-file line number of first opcode */ +){ + int i; + VdbeOp *pOut, *pFirst; assert( nOp>0 ); assert( p->magic==VDBE_MAGIC_INIT ); if( p->nOp + nOp > p->pParse->nOpAlloc && growOpArray(p, nOp) ){ return 0; } - addr = p->nOp; - pOut = &p->aOp[addr]; + pFirst = pOut = &p->aOp[p->nOp]; for(i=0; ip2; pOut->opcode = aOp->opcode; pOut->p1 = aOp->p1; - if( p2<0 ){ - assert( sqlite3OpcodeProperty[pOut->opcode] & OPFLG_JUMP ); - pOut->p2 = addr + ADDR(p2); - }else{ - pOut->p2 = p2; + pOut->p2 = aOp->p2; + assert( aOp->p2>=0 ); + if( (sqlite3OpcodeProperty[aOp->opcode] & OPFLG_JUMP)!=0 && aOp->p2>0 ){ + pOut->p2 += p->nOp; } pOut->p3 = aOp->p3; pOut->p4type = P4_NOTUSED; @@ -67346,12 +70667,12 @@ SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, #endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ - sqlite3VdbePrintOp(0, i+addr, &p->aOp[i+addr]); + sqlite3VdbePrintOp(0, i+p->nOp, &p->aOp[i+p->nOp]); } #endif } p->nOp += nOp; - return addr; + return pFirst; } #if defined(SQLITE_ENABLE_STMT_SCANSTATUS) @@ -67399,7 +70720,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){ sqlite3VdbeGetOp(p,addr)->p3 = val; } SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ - sqlite3VdbeGetOp(p,-1)->p5 = p5; + if( !p->db->mallocFailed ) p->aOp[p->nOp-1].p5 = p5; } /* @@ -67428,46 +70749,50 @@ static void vdbeFreeOpArray(sqlite3 *, Op *, int); ** Delete a P4 value if necessary. */ static void freeP4(sqlite3 *db, int p4type, void *p4){ - if( p4 ){ - assert( db ); - switch( p4type ){ - case P4_FUNCCTX: { - freeEphemeralFunction(db, ((sqlite3_context*)p4)->pFunc); - /* Fall through into the next case */ - } - case P4_REAL: - case P4_INT64: - case P4_DYNAMIC: - case P4_INTARRAY: { - sqlite3DbFree(db, p4); - break; - } - case P4_KEYINFO: { - if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4); - break; - } - 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); - }else{ - Mem *p = (Mem*)p4; - if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); - sqlite3DbFree(db, p); - } - break; - } - case P4_VTAB : { - if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4); - break; + assert( db ); + switch( p4type ){ + case P4_FUNCCTX: { + freeEphemeralFunction(db, ((sqlite3_context*)p4)->pFunc); + /* Fall through into the next case */ + } + case P4_REAL: + case P4_INT64: + case P4_DYNAMIC: + case P4_INTARRAY: { + sqlite3DbFree(db, p4); + break; + } + case P4_KEYINFO: { + if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4); + break; + } +#ifdef SQLITE_ENABLE_CURSOR_HINTS + case P4_EXPR: { + sqlite3ExprDelete(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); + }else{ + Mem *p = (Mem*)p4; + if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); + sqlite3DbFree(db, p); } + break; + } + case P4_VTAB : { + if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4); + break; } } } @@ -67481,7 +70806,7 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ if( aOp ){ Op *pOp; for(pOp=aOp; pOp<&aOp[nOp]; pOp++){ - freeP4(db, pOp->p4type, pOp->p4.p); + if( pOp->p4type ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); #endif @@ -67503,15 +70828,16 @@ SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){ /* ** Change the opcode at addr into OP_Noop */ -SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ - if( addrnOp ){ - VdbeOp *pOp = &p->aOp[addr]; - sqlite3 *db = p->db; - freeP4(db, pOp->p4type, pOp->p4.p); - memset(pOp, 0, sizeof(pOp[0])); - pOp->opcode = OP_Noop; - if( addr==p->nOp-1 ) p->nOp--; - } +SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ + VdbeOp *pOp; + if( p->db->mallocFailed ) return 0; + assert( addr>=0 && addrnOp ); + pOp = &p->aOp[addr]; + freeP4(p->db, pOp->p4type, pOp->p4.p); + pOp->p4type = P4_NOTUSED; + pOp->p4.z = 0; + pOp->opcode = OP_Noop; + return 1; } /* @@ -67520,8 +70846,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ */ SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ if( (p->nOp-1)>(p->pParse->iFixedOp) && p->aOp[p->nOp-1].opcode==op ){ - sqlite3VdbeChangeToNoop(p, p->nOp-1); - return 1; + return sqlite3VdbeChangeToNoop(p, p->nOp-1); }else{ return 0; } @@ -67544,16 +70869,34 @@ SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ ** ** If addr<0 then change P4 on the most recently inserted instruction. */ +static void SQLITE_NOINLINE vdbeChangeP4Full( + Vdbe *p, + Op *pOp, + const char *zP4, + int n +){ + if( pOp->p4type ){ + freeP4(p->db, pOp->p4type, pOp->p4.p); + pOp->p4type = 0; + pOp->p4.p = 0; + } + if( n<0 ){ + sqlite3VdbeChangeP4(p, (int)(pOp - p->aOp), zP4, n); + }else{ + if( n==0 ) n = sqlite3Strlen30(zP4); + pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n); + pOp->p4type = P4_DYNAMIC; + } +} SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ Op *pOp; sqlite3 *db; assert( p!=0 ); db = p->db; assert( p->magic==VDBE_MAGIC_INIT ); - if( p->aOp==0 || db->mallocFailed ){ - if( n!=P4_VTAB ){ - freeP4(db, n, (void*)*(char**)&zP4); - } + assert( p->aOp!=0 || db->mallocFailed ); + if( db->mallocFailed ){ + if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); return; } assert( p->nOp>0 ); @@ -67562,34 +70905,20 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int addr = p->nOp - 1; } pOp = &p->aOp[addr]; - assert( pOp->p4type==P4_NOTUSED - || pOp->p4type==P4_INT32 - || pOp->p4type==P4_KEYINFO ); - freeP4(db, pOp->p4type, pOp->p4.p); - pOp->p4.p = 0; + if( n>=0 || pOp->p4type ){ + vdbeChangeP4Full(p, pOp, zP4, n); + return; + } if( n==P4_INT32 ){ /* Note: this cast is safe, because the origin data point was an int ** that was cast to a (const char *). */ pOp->p4.i = SQLITE_PTR_TO_INT(zP4); pOp->p4type = P4_INT32; - }else if( zP4==0 ){ - pOp->p4.p = 0; - pOp->p4type = P4_NOTUSED; - }else if( n==P4_KEYINFO ){ - pOp->p4.p = (void*)zP4; - pOp->p4type = P4_KEYINFO; - }else if( n==P4_VTAB ){ - pOp->p4.p = (void*)zP4; - pOp->p4type = P4_VTAB; - sqlite3VtabLock((VTable *)zP4); - assert( ((VTable *)zP4)->db==p->db ); - }else if( n<0 ){ + }else if( zP4!=0 ){ + assert( n<0 ); pOp->p4.p = (void*)zP4; pOp->p4type = (signed char)n; - }else{ - if( n==0 ) n = sqlite3Strlen30(zP4); - pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n); - pOp->p4type = P4_DYNAMIC; + if( n==P4_VTAB ) sqlite3VtabLock((VTable*)zP4); } } @@ -67768,75 +71097,138 @@ static int displayComment( } #endif /* SQLITE_DEBUG */ +#if VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) +/* +** Translate the P4.pExpr value for an OP_CursorHint opcode into text +** that can be displayed in the P4 column of EXPLAIN output. +*/ +static void displayP4Expr(StrAccum *p, Expr *pExpr){ + const char *zOp = 0; + switch( pExpr->op ){ + case TK_STRING: + sqlite3XPrintf(p, "%Q", pExpr->u.zToken); + break; + case TK_INTEGER: + sqlite3XPrintf(p, "%d", pExpr->u.iValue); + break; + case TK_NULL: + sqlite3XPrintf(p, "NULL"); + break; + case TK_REGISTER: { + sqlite3XPrintf(p, "r[%d]", pExpr->iTable); + break; + } + case TK_COLUMN: { + if( pExpr->iColumn<0 ){ + sqlite3XPrintf(p, "rowid"); + }else{ + sqlite3XPrintf(p, "c%d", (int)pExpr->iColumn); + } + break; + } + case TK_LT: zOp = "LT"; break; + case TK_LE: zOp = "LE"; break; + case TK_GT: zOp = "GT"; break; + case TK_GE: zOp = "GE"; break; + case TK_NE: zOp = "NE"; break; + case TK_EQ: zOp = "EQ"; break; + case TK_IS: zOp = "IS"; break; + case TK_ISNOT: zOp = "ISNOT"; break; + case TK_AND: zOp = "AND"; break; + case TK_OR: zOp = "OR"; break; + case TK_PLUS: zOp = "ADD"; break; + case TK_STAR: zOp = "MUL"; break; + case TK_MINUS: zOp = "SUB"; break; + case TK_REM: zOp = "REM"; break; + case TK_BITAND: zOp = "BITAND"; break; + case TK_BITOR: zOp = "BITOR"; break; + case TK_SLASH: zOp = "DIV"; break; + case TK_LSHIFT: zOp = "LSHIFT"; break; + case TK_RSHIFT: zOp = "RSHIFT"; break; + case TK_CONCAT: zOp = "CONCAT"; break; + case TK_UMINUS: zOp = "MINUS"; break; + case TK_UPLUS: zOp = "PLUS"; break; + case TK_BITNOT: zOp = "BITNOT"; break; + case TK_NOT: zOp = "NOT"; break; + case TK_ISNULL: zOp = "ISNULL"; break; + case TK_NOTNULL: zOp = "NOTNULL"; break; -#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \ - || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) + default: + sqlite3XPrintf(p, "%s", "expr"); + break; + } + + if( zOp ){ + sqlite3XPrintf(p, "%s(", zOp); + displayP4Expr(p, pExpr->pLeft); + if( pExpr->pRight ){ + sqlite3StrAccumAppend(p, ",", 1); + displayP4Expr(p, pExpr->pRight); + } + sqlite3StrAccumAppend(p, ")", 1); + } +} +#endif /* VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) */ + + +#if VDBE_DISPLAY_P4 /* ** Compute a string that describes the P4 parameter for an opcode. ** Use zTemp for any required temporary buffer space. */ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ char *zP4 = zTemp; + StrAccum x; assert( nTemp>=20 ); + sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0); switch( pOp->p4type ){ case P4_KEYINFO: { - int i, j; + int j; KeyInfo *pKeyInfo = pOp->p4.pKeyInfo; assert( pKeyInfo->aSortOrder!=0 ); - sqlite3_snprintf(nTemp, zTemp, "k(%d", pKeyInfo->nField); - i = sqlite3Strlen30(zTemp); + sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField); for(j=0; jnField; j++){ CollSeq *pColl = pKeyInfo->aColl[j]; - const char *zColl = pColl ? pColl->zName : "nil"; - int n = sqlite3Strlen30(zColl); - if( n==6 && memcmp(zColl,"BINARY",6)==0 ){ - zColl = "B"; - n = 1; - } - if( i+n>nTemp-7 ){ - memcpy(&zTemp[i],",...",4); - i += 4; - break; - } - zTemp[i++] = ','; - if( pKeyInfo->aSortOrder[j] ){ - zTemp[i++] = '-'; - } - memcpy(&zTemp[i], zColl, n+1); - i += n; + const char *zColl = pColl ? pColl->zName : ""; + if( strcmp(zColl, "BINARY")==0 ) zColl = "B"; + sqlite3XPrintf(&x, ",%s%s", pKeyInfo->aSortOrder[j] ? "-" : "", zColl); } - zTemp[i++] = ')'; - zTemp[i] = 0; - assert( ip4.pExpr); + break; + } +#endif case P4_COLLSEQ: { CollSeq *pColl = pOp->p4.pColl; - sqlite3_snprintf(nTemp, zTemp, "(%.20s)", pColl->zName); + sqlite3XPrintf(&x, "(%.20s)", pColl->zName); break; } case P4_FUNCDEF: { FuncDef *pDef = pOp->p4.pFunc; - sqlite3_snprintf(nTemp, zTemp, "%s(%d)", pDef->zName, pDef->nArg); + sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); break; } #ifdef SQLITE_DEBUG case P4_FUNCCTX: { FuncDef *pDef = pOp->p4.pCtx->pFunc; - sqlite3_snprintf(nTemp, zTemp, "%s(%d)", pDef->zName, pDef->nArg); + sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); break; } #endif case P4_INT64: { - sqlite3_snprintf(nTemp, zTemp, "%lld", *pOp->p4.pI64); + sqlite3XPrintf(&x, "%lld", *pOp->p4.pI64); break; } case P4_INT32: { - sqlite3_snprintf(nTemp, zTemp, "%d", pOp->p4.i); + sqlite3XPrintf(&x, "%d", pOp->p4.i); break; } case P4_REAL: { - sqlite3_snprintf(nTemp, zTemp, "%.16g", *pOp->p4.pReal); + sqlite3XPrintf(&x, "%.16g", *pOp->p4.pReal); break; } case P4_MEM: { @@ -67844,11 +71236,11 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ if( pMem->flags & MEM_Str ){ zP4 = pMem->z; }else if( pMem->flags & MEM_Int ){ - sqlite3_snprintf(nTemp, zTemp, "%lld", pMem->u.i); + sqlite3XPrintf(&x, "%lld", pMem->u.i); }else if( pMem->flags & MEM_Real ){ - sqlite3_snprintf(nTemp, zTemp, "%.16g", pMem->u.r); + sqlite3XPrintf(&x, "%.16g", pMem->u.r); }else if( pMem->flags & MEM_Null ){ - sqlite3_snprintf(nTemp, zTemp, "NULL"); + zP4 = "NULL"; }else{ assert( pMem->flags & MEM_Blob ); zP4 = "(blob)"; @@ -67858,22 +71250,34 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ #ifndef SQLITE_OMIT_VIRTUALTABLE case P4_VTAB: { sqlite3_vtab *pVtab = pOp->p4.pVtab->pVtab; - sqlite3_snprintf(nTemp, zTemp, "vtab:%p", pVtab); + sqlite3XPrintf(&x, "vtab:%p", pVtab); break; } #endif case P4_INTARRAY: { - sqlite3_snprintf(nTemp, zTemp, "intarray"); + int i; + 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; ip4.pTab->zName); + break; + } default: { zP4 = pOp->p4.z; if( zP4==0 ){ @@ -67882,10 +71286,11 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ } } } + sqlite3StrAccumFinish(&x); assert( zP4!=0 ); return zP4; } -#endif +#endif /* VDBE_DISPLAY_P4 */ /* ** Declare to the Vdbe that the BTree object at db->aDb[i] is used. @@ -67904,7 +71309,7 @@ SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){ } } -#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 +#if !defined(SQLITE_OMIT_SHARED_CACHE) /* ** If SQLite is compiled to support shared-cache mode and to be threadsafe, ** this routine obtains the mutex associated with each BtShared structure @@ -68001,7 +71406,6 @@ static void releaseMemArray(Mem *p, int N){ if( p && N ){ Mem *pEnd = &p[N]; sqlite3 *db = p->db; - u8 malloc_failed = db->mallocFailed; if( db->pnBytesFreed ){ do{ if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); @@ -68037,7 +71441,6 @@ static void releaseMemArray(Mem *p, int N){ p->flags = MEM_Undefined; }while( (++p)mallocFailed = malloc_failed; } } @@ -68053,6 +71456,7 @@ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){ sqlite3VdbeFreeCursor(p->v, apCsr[i]); } releaseMemArray(aMem, p->nChildMem); + sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0); sqlite3DbFree(p->v->db, p); } @@ -68095,10 +71499,10 @@ SQLITE_PRIVATE int sqlite3VdbeList( releaseMemArray(pMem, 8); p->pResultSet = 0; - if( p->rc==SQLITE_NOMEM ){ + if( p->rc==SQLITE_NOMEM_BKPT ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ - db->mallocFailed = 1; + sqlite3OomFault(db); return SQLITE_ERROR; } @@ -68199,12 +71603,12 @@ SQLITE_PRIVATE int sqlite3VdbeList( pMem->u.i = pOp->p3; /* P3 */ pMem++; - if( sqlite3VdbeMemClearAndResize(pMem, 32) ){ /* P4 */ + if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */ assert( p->db->mallocFailed ); return SQLITE_ERROR; } pMem->flags = MEM_Str|MEM_Term; - zP4 = displayP4(pOp, pMem->z, 32); + zP4 = displayP4(pOp, pMem->z, pMem->szMalloc); if( zP4!=pMem->z ){ sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0); }else{ @@ -68296,43 +71700,46 @@ SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){ } #endif /* !SQLITE_OMIT_TRACE && SQLITE_ENABLE_IOTRACE */ -/* -** Allocate space from a fixed size buffer and return a pointer to -** that space. If insufficient space is available, return NULL. +/* An instance of this object describes bulk memory available for use +** by subcomponents of a prepared statement. Space is allocated out +** 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 */ +}; + +/* Try to allocate nByte bytes of 8-byte aligned bulk memory for pBuf +** from the ReusableSpace object. Return a pointer to the allocated +** memory on success. If insufficient memory is available in the +** ReusableSpace object, increase the ReusableSpace.nNeeded +** value by the amount needed and return NULL. ** -** The pBuf parameter is the initial value of a pointer which will -** receive the new memory. pBuf is normally NULL. If pBuf is not -** NULL, it means that memory space has already been allocated and that -** this routine should not allocate any new memory. When pBuf is not -** NULL simply return pBuf. Only allocate new memory space when pBuf -** is NULL. +** If pBuf is not initially NULL, that means that the memory has already +** been allocated by a prior call to this routine, so just return a copy +** of pBuf and leave ReusableSpace unchanged. ** -** nByte is the number of bytes of space needed. -** -** *ppFrom points to available space and pEnd points to the end of the -** available space. When space is allocated, *ppFrom is advanced past -** the end of the allocated space. -** -** *pnByte is a counter of the number of bytes of space that have failed -** to allocate. If there is insufficient space in *ppFrom to satisfy the -** request, then increment *pnByte by the amount of the request. +** This allocator is employed to repurpose unused slots at the end of the +** opcode array of prepared state for other memory needs of the prepared +** statement. */ static void *allocSpace( - void *pBuf, /* Where return pointer will be stored */ - int nByte, /* Number of bytes to allocate */ - u8 **ppFrom, /* IN/OUT: Allocate from *ppFrom */ - u8 *pEnd, /* Pointer to 1 byte past the end of *ppFrom buffer */ - int *pnByte /* If allocation cannot be made, increment *pnByte */ + struct ReusableSpace *p, /* Bulk memory available for allocation */ + void *pBuf, /* Pointer to a prior allocation */ + int nByte /* Bytes of memory needed */ ){ - assert( EIGHT_BYTE_ALIGNMENT(*ppFrom) ); - if( pBuf ) return pBuf; - nByte = ROUND8(nByte); - if( &(*ppFrom)[nByte] <= pEnd ){ - pBuf = (void*)*ppFrom; - *ppFrom += nByte; - }else{ - *pnByte += nByte; + assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) ); + if( pBuf==0 ){ + nByte = ROUND8(nByte); + if( nByte <= p->nFree ){ + p->nFree -= nByte; + pBuf = &p->pSpace[p->nFree]; + }else{ + p->nNeeded += nByte; + } } + assert( EIGHT_BYTE_ALIGNMENT(pBuf) ); return pBuf; } @@ -68355,14 +71762,13 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ p->magic = VDBE_MAGIC_RUN; #ifdef SQLITE_DEBUG - for(i=1; inMem; i++){ + for(i=0; inMem; i++){ assert( p->aMem[i].db==p->db ); } #endif p->pc = -1; p->rc = SQLITE_OK; p->errorAction = OE_Abort; - p->magic = VDBE_MAGIC_RUN; p->nChange = 0; p->cacheCtr = 1; p->minWriteFileFormat = 255; @@ -68405,9 +71811,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( int nArg; /* Number of arguments in subprograms */ int nOnce; /* Number of OP_Once instructions */ int n; /* Loop counter */ - u8 *zCsr; /* Memory available for allocation */ - u8 *zEnd; /* First byte past allocated memory */ - int nByte; /* How much extra memory is needed */ + struct ReusableSpace x; /* Reusable bulk memory */ assert( p!=0 ); assert( p->nOp>0 ); @@ -68423,61 +71827,59 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( nOnce = pParse->nOnce; if( nOnce==0 ) nOnce = 1; /* Ensure at least one byte in p->aOnceFlag[] */ - /* For each cursor required, also allocate a memory cell. Memory - ** cells (nMem+1-nCursor)..nMem, inclusive, will never be used by - ** the vdbe program. Instead they are used to allocate space for - ** VdbeCursor/BtCursor structures. The blob of memory associated with - ** cursor 0 is stored in memory cell nMem. Memory cell (nMem-1) - ** stores the blob of memory associated with cursor 1, etc. - ** + /* Each cursor uses a memory cell. The first cursor (cursor 0) can + ** use aMem[0] which is not otherwise used by the VDBE program. Allocate + ** space at the end of aMem[] for cursors 1 and greater. ** See also: allocateCursor(). */ nMem += nCursor; + if( nCursor==0 && nMem>0 ) nMem++; /* Space for aMem[0] even if not used */ - /* Allocate space for memory registers, SQL variables, VDBE cursors and - ** an array to marshal SQL function arguments in. + /* Figure out how much reusable memory is available at the end of the + ** opcode array. This extra memory will be reallocated for other elements + ** of the prepared statement. */ - zCsr = (u8*)&p->aOp[p->nOp]; /* Memory avaliable for allocation */ - zEnd = (u8*)&p->aOp[pParse->nOpAlloc]; /* First byte past end of zCsr[] */ + n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ + x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */ + assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); + x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ + assert( x.nFree>=0 ); + if( x.nFree>0 ){ + memset(x.pSpace, 0, x.nFree); + assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); + } resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain && nMem<10 ){ nMem = 10; } - memset(zCsr, 0, zEnd-zCsr); - zCsr += (zCsr - (u8*)0)&7; - assert( EIGHT_BYTE_ALIGNMENT(zCsr) ); p->expired = 0; - /* Memory for registers, parameters, cursor, etc, is allocated in two - ** passes. On the first pass, we try to reuse unused space at the + /* Memory for registers, parameters, cursor, etc, is allocated in one or two + ** passes. On the first pass, we try to reuse unused memory at the ** end of the opcode array. If we are unable to satisfy all memory ** requirements by reusing the opcode array tail, then the second - ** pass will fill in the rest using a fresh allocation. + ** pass will fill in the remainder using a fresh memory allocation. ** ** This two-pass approach that reuses as much memory as possible from - ** the leftover space at the end of the opcode array can significantly + ** the leftover memory at the end of the opcode array. This can significantly ** reduce the amount of memory held by a prepared statement. */ do { - nByte = 0; - p->aMem = allocSpace(p->aMem, nMem*sizeof(Mem), &zCsr, zEnd, &nByte); - p->aVar = allocSpace(p->aVar, nVar*sizeof(Mem), &zCsr, zEnd, &nByte); - p->apArg = allocSpace(p->apArg, nArg*sizeof(Mem*), &zCsr, zEnd, &nByte); - p->azVar = allocSpace(p->azVar, nVar*sizeof(char*), &zCsr, zEnd, &nByte); - p->apCsr = allocSpace(p->apCsr, nCursor*sizeof(VdbeCursor*), - &zCsr, zEnd, &nByte); - p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, &zCsr, zEnd, &nByte); + 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*)); + p->aOnceFlag = allocSpace(&x, p->aOnceFlag, nOnce); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), &zCsr, zEnd, &nByte); + p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64)); #endif - if( nByte ){ - p->pFree = sqlite3DbMallocZero(db, nByte); - } - zCsr = p->pFree; - zEnd = &zCsr[nByte]; - }while( nByte && !db->mallocFailed ); + if( x.nNeeded==0 ) break; + x.pSpace = p->pFree = sqlite3DbMallocZero(db, x.nNeeded); + x.nFree = x.nNeeded; + }while( !db->mallocFailed ); p->nCursor = nCursor; p->nOnceFlag = nOnce; @@ -68488,15 +71890,13 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->aVar[n].db = db; } } - if( p->azVar && pParse->nzVar>0 ){ - p->nzVar = pParse->nzVar; - memcpy(p->azVar, pParse->azVar, p->nzVar*sizeof(p->azVar[0])); - memset(pParse->azVar, 0, pParse->nzVar*sizeof(pParse->azVar[0])); - } + p->nzVar = pParse->nzVar; + p->azVar = pParse->azVar; + pParse->nzVar = 0; + pParse->azVar = 0; if( p->aMem ){ - p->aMem--; /* aMem[] goes from 1..nMem */ - p->nMem = nMem; /* not from 0..nMem-1 */ - for(n=1; n<=nMem; n++){ + p->nMem = nMem; + for(n=0; naMem[n].flags = MEM_Undefined; p->aMem[n].db = db; } @@ -68513,23 +71913,34 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx==0 ){ return; } - sqlite3VdbeSorterClose(p->db, pCx); - if( pCx->pBt ){ - sqlite3BtreeClose(pCx->pBt); - /* The pCx->pCursor will be close automatically, if it exists, by - ** the call above. */ - }else if( pCx->pCursor ){ - sqlite3BtreeCloseCursor(pCx->pCursor); - } + assert( pCx->pBt==0 || pCx->eCurType==CURTYPE_BTREE ); + switch( pCx->eCurType ){ + case CURTYPE_SORTER: { + sqlite3VdbeSorterClose(p->db, pCx); + break; + } + case CURTYPE_BTREE: { + if( pCx->pBt ){ + sqlite3BtreeClose(pCx->pBt); + /* The pCx->pCursor will be close automatically, if it exists, by + ** the call above. */ + }else{ + assert( pCx->uc.pCursor!=0 ); + sqlite3BtreeCloseCursor(pCx->uc.pCursor); + } + break; + } #ifndef SQLITE_OMIT_VIRTUALTABLE - else if( pCx->pVtabCursor ){ - sqlite3_vtab_cursor *pVtabCursor = pCx->pVtabCursor; - const sqlite3_module *pModule = pVtabCursor->pVtab->pModule; - assert( pVtabCursor->pVtab->nRef>0 ); - pVtabCursor->pVtab->nRef--; - pModule->xClose(pVtabCursor); - } + case CURTYPE_VTAB: { + sqlite3_vtab_cursor *pVCur = pCx->uc.pVCur; + const sqlite3_module *pModule = pVCur->pVtab->pModule; + assert( pVCur->pVtab->nRef>0 ); + pVCur->pVtab->nRef--; + pModule->xClose(pVCur); + break; + } #endif + } } /* @@ -68570,6 +71981,9 @@ 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); + v->pAuxData = pFrame->pAuxData; + pFrame->pAuxData = 0; return pFrame->pc; } @@ -68592,7 +72006,7 @@ static void closeAllCursors(Vdbe *p){ assert( p->nFrame==0 ); closeCursorsInFrame(p); if( p->aMem ){ - releaseMemArray(&p->aMem[1], p->nMem); + releaseMemArray(p->aMem, p->nMem); } while( p->pDelFrame ){ VdbeFrame *pDel = p->pDelFrame; @@ -68601,7 +72015,7 @@ static void closeAllCursors(Vdbe *p){ } /* Delete any auxdata allocations made by the VM */ - if( p->pAuxData ) sqlite3VdbeDeleteAuxData(p, -1, 0); + if( p->pAuxData ) sqlite3VdbeDeleteAuxData(p->db, &p->pAuxData, -1, 0); assert( p->pAuxData==0 ); } @@ -68617,7 +72031,7 @@ static void Cleanup(Vdbe *p){ int i; if( p->apCsr ) for(i=0; inCursor; i++) assert( p->apCsr[i]==0 ); if( p->aMem ){ - for(i=1; i<=p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); + for(i=0; inMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); } #endif @@ -68673,7 +72087,7 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( assert( vardb->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); pColName = &(p->aColName[idx+var*p->nResColumn]); @@ -68690,7 +72104,9 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( */ static int vdbeCommit(sqlite3 *db, Vdbe *p){ int i; - int nTrans = 0; /* Number of databases with an active write-transaction */ + int nTrans = 0; /* Number of databases with an active write-transaction + ** that are candidates for a two-phase commit using a + ** master-journal */ int rc = SQLITE_OK; int needXcommit = 0; @@ -68718,10 +72134,28 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( sqlite3BtreeIsInTrans(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 */ + static const u8 aMJNeeded[] = { + /* DELETE */ 1, + /* PERSIST */ 1, + /* OFF */ 0, + /* TRUNCATE */ 1, + /* MEMORY */ 0, + /* WAL */ 0 + }; + Pager *pPager; /* Pager associated with pBt */ needXcommit = 1; - if( i!=1 ) nTrans++; sqlite3BtreeEnter(pBt); - rc = sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt)); + pPager = sqlite3BtreePager(pBt); + if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF + && aMJNeeded[sqlite3PagerGetJournalMode(pPager)] + ){ + assert( i!=1 ); + nTrans++; + } + rc = sqlite3PagerExclusiveLock(pPager); sqlite3BtreeLeave(pBt); } } @@ -68779,7 +72213,6 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ #ifndef SQLITE_OMIT_DISKIO else{ sqlite3_vfs *pVfs = db->pVfs; - int needSync = 0; char *zMaster = 0; /* File-name for the master journal */ char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt); sqlite3_file *pMaster = 0; @@ -68791,7 +72224,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ /* Select a master journal file name */ nMainFile = sqlite3Strlen30(zMainFile); zMaster = sqlite3MPrintf(db, "%s-mjXXXXXX9XXz", zMainFile); - if( zMaster==0 ) return SQLITE_NOMEM; + if( zMaster==0 ) return SQLITE_NOMEM_BKPT; do { u32 iRandom; if( retryCount ){ @@ -68839,9 +72272,6 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ continue; /* Ignore TEMP and :memory: databases */ } assert( zFile[0]!=0 ); - if( !needSync && !sqlite3BtreeSyncDisabled(pBt) ){ - needSync = 1; - } rc = sqlite3OsWrite(pMaster, zFile, sqlite3Strlen30(zFile)+1, offset); offset += sqlite3Strlen30(zFile)+1; if( rc!=SQLITE_OK ){ @@ -68856,8 +72286,7 @@ 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( needSync - && 0==(sqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL) + if( 0==(sqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL) && SQLITE_OK!=(rc = sqlite3OsSync(pMaster, SQLITE_SYNC_NORMAL)) ){ sqlite3OsCloseFree(pMaster); @@ -68893,7 +72322,7 @@ 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, needSync); + rc = sqlite3OsDelete(pVfs, zMaster, 1); sqlite3DbFree(db, zMaster); zMaster = 0; if( rc ){ @@ -69080,8 +72509,8 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ - if( p->db->mallocFailed ){ - p->rc = SQLITE_NOMEM; + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM_BKPT; } if( p->aOnceFlag ) memset(p->aOnceFlag, 0, p->nOnceFlag); closeAllCursors(p); @@ -69241,8 +72670,8 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ } p->magic = VDBE_MAGIC_HALT; checkActiveVdbeCnt(db); - if( p->db->mallocFailed ){ - p->rc = SQLITE_NOMEM; + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM_BKPT; } /* If the auto-commit flag is set to true, then any locks that were held @@ -69278,12 +72707,12 @@ SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ sqlite3 *db = p->db; int rc = p->rc; if( p->zErrMsg ){ - u8 mallocFailed = db->mallocFailed; + db->bBenignMalloc++; sqlite3BeginBenignMalloc(); if( db->pErr==0 ) db->pErr = sqlite3ValueNew(db); sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); sqlite3EndBenignMalloc(); - db->mallocFailed = mallocFailed; + db->bBenignMalloc--; db->errCode = rc; }else{ sqlite3Error(db, rc); @@ -69429,8 +72858,7 @@ 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(Vdbe *pVdbe, int iOp, int mask){ - AuxData **pp = &pVdbe->pAuxData; +SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){ while( *pp ){ AuxData *pAux = *pp; if( (iOp<0) @@ -69441,7 +72869,7 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(Vdbe *pVdbe, int iOp, int mask){ pAux->xDelete(pAux->pAux); } *pp = pAux->pNext; - sqlite3DbFree(pVdbe->db, pAux); + sqlite3DbFree(db, pAux); }else{ pp= &pAux->pNext; } @@ -69468,6 +72896,7 @@ SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ sqlite3DbFree(db, pSub); } for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]); + sqlite3DbFree(db, p->azVar); vdbeFreeOpArray(db, p->aOp, p->nOp); sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); @@ -69516,7 +72945,8 @@ static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ #endif assert( p->deferredMoveto ); assert( p->isTable ); - rc = sqlite3BtreeMovetoUnpacked(p->pCursor, 0, p->movetoTarget, 0, &res); + assert( p->eCurType==CURTYPE_BTREE ); + rc = sqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res); if( rc ) return rc; if( res!=0 ) return SQLITE_CORRUPT_BKPT; #ifdef SQLITE_TEST @@ -69536,9 +72966,10 @@ static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ */ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ int isDifferentRow, rc; - assert( p->pCursor!=0 ); - assert( sqlite3BtreeCursorHasMoved(p->pCursor) ); - rc = sqlite3BtreeCursorRestore(p->pCursor, &isDifferentRow); + assert( p->eCurType==CURTYPE_BTREE ); + assert( p->uc.pCursor!=0 ); + assert( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ); + rc = sqlite3BtreeCursorRestore(p->uc.pCursor, &isDifferentRow); p->cacheStatus = CACHE_STALE; if( isDifferentRow ) p->nullRow = 1; return rc; @@ -69549,7 +72980,8 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ ** if need be. Return any I/O error from the restore operation. */ SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){ - if( sqlite3BtreeCursorHasMoved(p->pCursor) ){ + assert( p->eCurType==CURTYPE_BTREE ); + if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ return handleMovedCursor(p); } return SQLITE_OK; @@ -69568,12 +73000,21 @@ 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 *p){ - if( p->deferredMoveto ){ - return handleDeferredMoveto(p); - } - if( p->pCursor && sqlite3BtreeCursorHasMoved(p->pCursor) ){ - return handleMovedCursor(p); +SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(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); + } } return SQLITE_OK; } @@ -69623,11 +73064,13 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor *p){ /* ** Return the serial-type for the value stored in pMem. */ -SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){ +SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ int flags = pMem->flags; u32 n; + assert( pLen!=0 ); if( flags&MEM_Null ){ + *pLen = 0; return 0; } if( flags&MEM_Int ){ @@ -69641,15 +73084,23 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){ u = i; } if( u<=127 ){ - return ((i&1)==i && file_format>=4) ? 8+(u32)u : 1; + if( (i&1)==i && file_format>=4 ){ + *pLen = 0; + return 8+(u32)u; + }else{ + *pLen = 1; + return 1; + } } - if( u<=32767 ) return 2; - if( u<=8388607 ) return 3; - if( u<=2147483647 ) return 4; - if( u<=MAX_6BYTE ) return 5; + if( u<=32767 ){ *pLen = 2; return 2; } + if( u<=8388607 ){ *pLen = 3; return 3; } + if( u<=2147483647 ){ *pLen = 4; return 4; } + if( u<=MAX_6BYTE ){ *pLen = 6; return 5; } + *pLen = 8; return 6; } if( flags&MEM_Real ){ + *pLen = 8; return 7; } assert( pMem->db->mallocFailed || flags&(MEM_Str|MEM_Blob) ); @@ -69658,26 +73109,46 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){ if( flags & MEM_Zero ){ n += pMem->u.nZero; } + *pLen = n; return ((n*2) + 12 + ((flags&MEM_Str)!=0)); } /* -** The sizes for serial types less than 12 +** The sizes for serial types less than 128 */ static const u8 sqlite3SmallTypeSizes[] = { - 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, 0, 0 + /* 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, +/* 20 */ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, +/* 30 */ 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, +/* 40 */ 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, +/* 50 */ 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, +/* 60 */ 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, +/* 70 */ 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, +/* 80 */ 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, +/* 90 */ 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, +/* 100 */ 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, +/* 110 */ 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, +/* 120 */ 54, 54, 55, 55, 56, 56, 57, 57 }; /* ** Return the length of the data corresponding to the supplied serial-type. */ SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32 serial_type){ - if( serial_type>=12 ){ + 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]; } } +SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ + assert( serial_type<128 ); + return sqlite3SmallTypeSizes[serial_type]; +} /* ** If we are on an architecture with mixed-endian floating @@ -69773,7 +73244,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) == (int)sqlite3VdbeSerialTypeLen(serial_type) ); len = pMem->n; - memcpy(buf, pMem->z, len); + if( len>0 ) memcpy(buf, pMem->z, len); return len; } @@ -69876,6 +73347,10 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_INT(buf); +#ifdef __HP_cc + /* Work around a sign-extension bug in the HP compiler for HP/UX */ + if( buf[0]&0x80 ) pMem->u.i |= 0xffffffff80000000LL; +#endif pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 4; @@ -69993,6 +73468,7 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( pMem->db = pKeyInfo->db; /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ pMem->szMalloc = 0; + pMem->z = 0; d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); pMem++; if( (++u)>=p->nField ) break; @@ -70173,9 +73649,9 @@ static int vdbeCompareMemString( 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); - if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM; return rc; } } @@ -70191,6 +73667,34 @@ static SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){ return pB1->n - pB2->n; } +/* +** Do a comparison between a 64-bit signed integer and a 64-bit floating-point +** 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){ + if( sizeof(LONGDOUBLE_TYPE)>8 ){ + LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; + if( xr ) return +1; + return 0; + }else{ + i64 y; + double s; + if( r<-9223372036854775808.0 ) return +1; + if( r>9223372036854775807.0 ) return -1; + y = (i64)r; + if( iy ){ + if( y==SMALLEST_INT64 && r>0.0 ) return -1; + return +1; + } + s = (double)i; + if( sr ) return +1; + return 0; + } +} /* ** Compare the values contained by the two memory cells, returning @@ -70217,34 +73721,34 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C return (f2&MEM_Null) - (f1&MEM_Null); } - /* If one value is a number and the other is not, the number is less. - ** If both are numbers, compare as reals if one is a real, or as integers - ** if both values are integers. + /* At least one of the two values is a number */ if( combined_flags&(MEM_Int|MEM_Real) ){ - double r1, r2; if( (f1 & f2 & MEM_Int)!=0 ){ if( pMem1->u.i < pMem2->u.i ) return -1; - if( pMem1->u.i > pMem2->u.i ) return 1; + if( pMem1->u.i > pMem2->u.i ) return +1; return 0; } + if( (f1 & f2 & MEM_Real)!=0 ){ + if( pMem1->u.r < pMem2->u.r ) return -1; + if( pMem1->u.r > pMem2->u.r ) return +1; + return 0; + } + if( (f1&MEM_Int)!=0 ){ + if( (f2&MEM_Real)!=0 ){ + return sqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r); + }else{ + return -1; + } + } if( (f1&MEM_Real)!=0 ){ - r1 = pMem1->u.r; - }else if( (f1&MEM_Int)!=0 ){ - r1 = (double)pMem1->u.i; - }else{ - return 1; + if( (f2&MEM_Int)!=0 ){ + return -sqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r); + }else{ + return -1; + } } - if( (f2&MEM_Real)!=0 ){ - r2 = pMem2->u.r; - }else if( (f2&MEM_Int)!=0 ){ - r2 = (double)pMem2->u.i; - }else{ - return -1; - } - if( r1r2 ) return 1; - return 0; + return +1; } /* If one value is a string and the other is a blob, the string is less. @@ -70258,7 +73762,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C return -1; } - assert( pMem1->enc==pMem2->enc ); + assert( pMem1->enc==pMem2->enc || pMem1->db->mallocFailed ); assert( pMem1->enc==SQLITE_UTF8 || pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); @@ -70395,13 +73899,8 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ - double rhs = (double)pRhs->u.i; sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); - if( mem1.u.rrhs ){ - rc = +1; - } + rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); }else{ i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); i64 rhs = pRhs->u.i; @@ -70425,18 +73924,15 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else{ - double rhs = pRhs->u.r; - double lhs; sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); if( serial_type==7 ){ - lhs = mem1.u.r; + if( mem1.u.ru.r ){ + rc = -1; + }else if( mem1.u.r>pRhs->u.r ){ + rc = +1; + } }else{ - lhs = (double)mem1.u.i; - } - if( lhsrhs ){ - rc = +1; + rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); } } } @@ -70526,6 +74022,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) || pKeyInfo->db->mallocFailed ); + pPKey2->eqSeen = 1; return pPKey2->default_rc; } SQLITE_PRIVATE int sqlite3VdbeRecordCompare( @@ -70625,6 +74122,7 @@ static int vdbeRecordCompareInt( /* The first fields of the two keys are equal and there are no trailing ** fields. Return pPKey2->default_rc in this case. */ res = pPKey2->default_rc; + pPKey2->eqSeen = 1; } assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) ); @@ -70645,6 +74143,7 @@ static int vdbeRecordCompareString( int serial_type; int res; + assert( pPKey2->aMem[0].flags & MEM_Str ); vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); getVarint32(&aKey1[1], serial_type); if( serial_type<12 ){ @@ -70671,6 +74170,7 @@ static int vdbeRecordCompareString( res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); }else{ res = pPKey2->default_rc; + pPKey2->eqSeen = 1; } }else if( res>0 ){ res = pPKey2->r2; @@ -70828,9 +74328,11 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( ){ i64 nCellKey = 0; int rc; - BtCursor *pCur = pC->pCursor; + BtCursor *pCur; Mem m; + assert( pC->eCurType==CURTYPE_BTREE ); + pCur = pC->uc.pCursor; assert( sqlite3BtreeCursorIsValid(pCur) ); VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ @@ -70841,7 +74343,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( return SQLITE_CORRUPT_BKPT; } sqlite3VdbeMemInit(&m, db, 0); - rc = sqlite3VdbeMemFromBtree(pC->pCursor, 0, (u32)nCellKey, 1, &m); + rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, 1, &m); if( rc ){ return rc; } @@ -70937,14 +74439,100 @@ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ ** in memory obtained from sqlite3DbMalloc). */ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ - sqlite3 *db = p->db; - sqlite3DbFree(db, p->zErrMsg); - p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg); - sqlite3_free(pVtab->zErrMsg); - pVtab->zErrMsg = 0; + if( pVtab->zErrMsg ){ + sqlite3 *db = p->db; + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg); + sqlite3_free(pVtab->zErrMsg); + pVtab->zErrMsg = 0; + } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + +/* +** 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(). +** +** This function is used to free UnpackedRecord structures allocated by +** the vdbeUnpackRecord() function found in vdbeapi.c. +*/ +static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){ + if( p ){ + int i; + for(i=0; inField; i++){ + Mem *pMem = &p->aMem[i]; + if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); + } + sqlite3DbFree(db, p); + } +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** 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(), +** the required value will be read from the row the cursor points to. +*/ +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( + Vdbe *v, /* Vdbe pre-update hook is invoked by */ + VdbeCursor *pCsr, /* Cursor to grab old.* values from */ + int op, /* SQLITE_INSERT, UPDATE or DELETE */ + const char *zDb, /* Database name */ + Table *pTab, /* Modified table */ + i64 iKey1, /* Initial key value */ + int iReg /* Register for new.* record */ +){ + sqlite3 *db = v->db; + i64 iKey2; + PreUpdate preupdate; + const char *zTbl = pTab->zName; + static const u8 fakeSortOrder = 0; + + assert( db->pPreUpdate==0 ); + memset(&preupdate, 0, sizeof(PreUpdate)); + if( op==SQLITE_UPDATE ){ + iKey2 = v->aMem[iReg].u.i; + }else{ + iKey2 = iKey1; + } + + assert( pCsr->nField==pTab->nCol + || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) + ); + + preupdate.v = v; + preupdate.pCsr = pCsr; + preupdate.op = op; + preupdate.iNewReg = iReg; + preupdate.keyinfo.db = db; + preupdate.keyinfo.enc = ENC(db); + preupdate.keyinfo.nField = pTab->nCol; + preupdate.keyinfo.aSortOrder = (u8*)&fakeSortOrder; + preupdate.iKey1 = iKey1; + preupdate.iKey2 = iKey2; + preupdate.iPKey = pTab->iPKey; + + 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); + if( preupdate.aNew ){ + int i; + for(i=0; inField; i++){ + sqlite3VdbeMemRelease(&preupdate.aNew[i]); + } + sqlite3DbFree(db, preupdate.aNew); + } +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + /************** End of vdbeaux.c *********************************************/ /************** Begin file vdbeapi.c *****************************************/ /* @@ -71137,7 +74725,8 @@ SQLITE_API sqlite_int64 SQLITE_STDCALL sqlite3_value_int64(sqlite3_value *pVal){ return sqlite3VdbeIntValue((Mem*)pVal); } SQLITE_API unsigned int SQLITE_STDCALL sqlite3_value_subtype(sqlite3_value *pVal){ - return ((Mem*)pVal)->eSubtype; + Mem *pMem = (Mem*)pVal; + return ((pMem->flags & MEM_Subtype) ? pMem->eSubtype : 0); } SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_value_text(sqlite3_value *pVal){ return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8); @@ -71318,8 +74907,10 @@ SQLITE_API void SQLITE_STDCALL sqlite3_result_null(sqlite3_context *pCtx){ sqlite3VdbeMemSetNull(pCtx->pOut); } SQLITE_API void SQLITE_STDCALL sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - pCtx->pOut->eSubtype = eSubtype & 0xff; + Mem *pOut = pCtx->pOut; + assert( sqlite3_mutex_held(pOut->db->mutex) ); + pOut->eSubtype = eSubtype & 0xff; + pOut->flags |= MEM_Subtype; } SQLITE_API void SQLITE_STDCALL sqlite3_result_text( sqlite3_context *pCtx, @@ -71417,9 +75008,9 @@ SQLITE_API void SQLITE_STDCALL sqlite3_result_error_toobig(sqlite3_context *pCtx SQLITE_API void SQLITE_STDCALL sqlite3_result_error_nomem(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); - pCtx->isError = SQLITE_NOMEM; + pCtx->isError = SQLITE_NOMEM_BKPT; pCtx->fErrorOrAux = 1; - pCtx->pOut->db->mallocFailed = 1; + sqlite3OomFault(pCtx->pOut->db); } /* @@ -71493,7 +75084,7 @@ static int sqlite3Step(Vdbe *p){ db = p->db; if( db->mallocFailed ){ p->rc = SQLITE_NOMEM; - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } if( p->pc<=0 && p->expired ){ @@ -71556,7 +75147,7 @@ static int sqlite3Step(Vdbe *p){ db->errCode = rc; if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){ - p->rc = SQLITE_NOMEM; + p->rc = SQLITE_NOMEM_BKPT; } end_of_step: /* At this point local variable rc holds the value that should be @@ -71623,7 +75214,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt *pStmt){ v->rc = rc2; } else { v->zErrMsg = 0; - v->rc = rc = SQLITE_NOMEM; + v->rc = rc = SQLITE_NOMEM_BKPT; } } rc = sqlite3ApiExit(db, rc); @@ -71728,7 +75319,7 @@ static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){ ** same context that was returned on prior calls. */ SQLITE_API void *SQLITE_STDCALL sqlite3_aggregate_context(sqlite3_context *p, int nByte){ - assert( p && p->pFunc && p->pFunc->xStep ); + assert( p && p->pFunc && p->pFunc->xFinalize ); assert( sqlite3_mutex_held(p->pOut->db->mutex) ); testcase( nByte<0 ); if( (p->pMem->flags & MEM_Agg)==0 ){ @@ -71819,7 +75410,7 @@ failed: ** context. */ SQLITE_API int SQLITE_STDCALL sqlite3_aggregate_count(sqlite3_context *p){ - assert( p && p->pMem && p->pFunc && p->pFunc->xStep ); + assert( p && p->pMem && p->pFunc && p->pFunc->xFinalize ); return p->pMem->n; } #endif @@ -72047,7 +75638,7 @@ static const void *columnName( ** is the case, clear the mallocFailed flag and return NULL. */ if( db->mallocFailed ){ - db->mallocFailed = 0; + sqlite3OomClear(db); ret = 0; } sqlite3_mutex_leave(db->mutex); @@ -72247,6 +75838,9 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob( int nData, void (*xDel)(void*) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( nData<0 ) return SQLITE_MISUSE_BKPT; +#endif return bindText(pStmt, i, zData, nData, xDel, 0); } SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob64( @@ -72547,6 +76141,187 @@ SQLITE_API int SQLITE_STDCALL sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, i return (int)v; } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Allocate and populate an UnpackedRecord structure based on the serialized +** record in nKey/pKey. Return a pointer to the new UnpackedRecord structure +** if successful, or a NULL pointer if an OOM error is encountered. +*/ +static UnpackedRecord *vdbeUnpackRecord( + KeyInfo *pKeyInfo, + int nKey, + const void *pKey +){ + char *dummy; /* Dummy argument for AllocUnpackedRecord() */ + UnpackedRecord *pRet; /* Return value */ + + pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo, 0, 0, &dummy); + if( pRet ){ + memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nField+1)); + sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); + } + return pRet; +} + +/* +** 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 SQLITE_STDCALL sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ + PreUpdate *p = db->pPreUpdate; + int rc = SQLITE_OK; + + /* Test that this call is being made from within an SQLITE_DELETE or + ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ + if( !p || p->op==SQLITE_INSERT ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_old_out; + } + if( iIdx>=p->pCsr->nField || iIdx<0 ){ + rc = SQLITE_RANGE; + goto preupdate_old_out; + } + + /* If the old.* record has not yet been loaded into memory, do so now. */ + if( p->pUnpacked==0 ){ + u32 nRec; + u8 *aRec; + + rc = sqlite3BtreeDataSize(p->pCsr->uc.pCursor, &nRec); + if( rc!=SQLITE_OK ) goto preupdate_old_out; + aRec = sqlite3DbMallocRaw(db, nRec); + if( !aRec ) goto preupdate_old_out; + rc = sqlite3BtreeData(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); + goto preupdate_old_out; + } + p->aRecord = aRec; + } + + if( iIdx>=p->pUnpacked->nField ){ + *ppValue = (sqlite3_value *)columnNullValue(); + }else{ + *ppValue = &p->pUnpacked->aMem[iIdx]; + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(*ppValue, p->iKey1); + } + } + + preupdate_old_out: + sqlite3Error(db, rc); + return sqlite3ApiExit(db, rc); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** 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 SQLITE_STDCALL sqlite3_preupdate_count(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->keyinfo.nField : 0); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. It returns zero if the change that caused the callback was made +** immediately by a user SQL statement. Or, if the change was made by a +** trigger program, it returns the number of trigger programs currently +** on the stack (1 for a top-level trigger, 2 for a trigger fired by a +** top-level trigger etc.). +** +** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL +** or SET DEFAULT action is considered a trigger. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_preupdate_depth(sqlite3 *db){ + PreUpdate *p = db->pPreUpdate; + return (p ? p->v->nFrame : 0); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** 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 SQLITE_STDCALL sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ + PreUpdate *p = db->pPreUpdate; + int rc = SQLITE_OK; + Mem *pMem; + + if( !p || p->op==SQLITE_DELETE ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_new_out; + } + if( iIdx>=p->pCsr->nField || iIdx<0 ){ + rc = SQLITE_RANGE; + goto preupdate_new_out; + } + + if( p->op==SQLITE_INSERT ){ + /* For an INSERT, memory cell p->iNewReg contains the serialized record + ** that is being inserted. Deserialize it. */ + UnpackedRecord *pUnpack = p->pNewUnpacked; + if( !pUnpack ){ + Mem *pData = &p->v->aMem[p->iNewReg]; + rc = sqlite3VdbeMemExpandBlob(pData); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z); + if( !pUnpack ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + p->pNewUnpacked = pUnpack; + } + if( iIdx>=pUnpack->nField ){ + pMem = (sqlite3_value *)columnNullValue(); + }else{ + pMem = &pUnpack->aMem[iIdx]; + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + } + } + }else{ + /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required + ** value. Make a copy of the cell contents and return a pointer to it. + ** It is not safe to return a pointer to the memory cell itself as the + ** caller may modify the value text encoding. + */ + assert( p->op==SQLITE_UPDATE ); + if( !p->aNew ){ + p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField); + if( !p->aNew ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + } + assert( iIdx>=0 && iIdxpCsr->nField ); + pMem = &p->aNew[iIdx]; + if( pMem->flags==0 ){ + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else{ + rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + } + } + } + *ppValue = pMem; + + preupdate_new_out: + sqlite3Error(db, rc); + return sqlite3ApiExit(db, rc); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Return status data for a single loop within query pStmt. @@ -72748,9 +76523,9 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( if( pVar->flags & MEM_Null ){ sqlite3StrAccumAppend(&out, "NULL", 4); }else if( pVar->flags & MEM_Int ){ - sqlite3XPrintf(&out, 0, "%lld", pVar->u.i); + sqlite3XPrintf(&out, "%lld", pVar->u.i); }else if( pVar->flags & MEM_Real ){ - sqlite3XPrintf(&out, 0, "%!.15g", pVar->u.r); + sqlite3XPrintf(&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 @@ -72771,17 +76546,17 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( while( nOutn && (pVar->z[nOut]&0xc0)==0x80 ){ nOut++; } } #endif - sqlite3XPrintf(&out, 0, "'%.*q'", nOut, pVar->z); + sqlite3XPrintf(&out, "'%.*q'", nOut, pVar->z); #ifdef SQLITE_TRACE_SIZE_LIMIT if( nOutn ){ - sqlite3XPrintf(&out, 0, "/*+%d bytes*/", pVar->n-nOut); + sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut); } #endif #ifndef SQLITE_OMIT_UTF16 if( enc!=SQLITE_UTF8 ) sqlite3VdbeMemRelease(&utf8); #endif }else if( pVar->flags & MEM_Zero ){ - sqlite3XPrintf(&out, 0, "zeroblob(%d)", pVar->u.nZero); + sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero); }else{ int nOut; /* Number of bytes of the blob to include in output */ assert( pVar->flags & MEM_Blob ); @@ -72791,12 +76566,12 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( if( nOut>SQLITE_TRACE_SIZE_LIMIT ) nOut = SQLITE_TRACE_SIZE_LIMIT; #endif for(i=0; iz[i]&0xff); + sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff); } sqlite3StrAccumAppend(&out, "'", 1); #ifdef SQLITE_TRACE_SIZE_LIMIT if( nOutn ){ - sqlite3XPrintf(&out, 0, "/*+%d bytes*/", pVar->n-nOut); + sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut); } #endif } @@ -72897,6 +76672,16 @@ static void updateMaxBlobsize(Mem *p){ } #endif +/* +** This macro evaluates to true if either the update hook or the preupdate +** hook are enabled for database connect DB. +*/ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +# define HAS_UPDATE_HOOK(DB) ((DB)->xPreUpdateCallback||(DB)->xUpdateCallback) +#else +# define HAS_UPDATE_HOOK(DB) ((DB)->xUpdateCallback) +#endif + /* ** The next global variable is incremented each time the OP_Found opcode ** is executed. This is used to test whether or not the foreign key @@ -72976,7 +76761,7 @@ SQLITE_API int sqlite3_found_count = 0; && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;} /* Return true if the cursor was opened using the OP_OpenSorter opcode. */ -#define isSorter(x) ((x)->pSorter!=0) +#define isSorter(x) ((x)->eCurType==CURTYPE_SORTER) /* ** Allocate VdbeCursor number iCur. Return a pointer to it. Return NULL @@ -72987,7 +76772,7 @@ static VdbeCursor *allocateCursor( int iCur, /* Index of the new VdbeCursor */ int nField, /* Number of fields in the table or index */ int iDb, /* Database the cursor belongs to, or -1 */ - int isBtreeCursor /* True for B-Tree. False for pseudo-table or vtab */ + u8 eCurType /* Type of the new cursor */ ){ /* Find the memory cell that will be used to store the blob of memory ** required for this VdbeCursor structure. It is convenient to use a @@ -73003,33 +76788,34 @@ static VdbeCursor *allocateCursor( ** be freed lazily via the sqlite3_release_memory() API. This ** minimizes the number of malloc calls made by the system. ** - ** Memory cells for cursors are allocated at the top of the address - ** space. Memory cell (p->nMem) corresponds to cursor 0. Space for - ** cursor 1 is managed by memory cell (p->nMem-1), etc. + ** The memory cell for cursor 0 is aMem[0]. The rest are allocated from + ** the top of the register space. Cursor 1 is at Mem[p->nMem-1]. + ** Cursor 2 is at Mem[p->nMem-2]. And so forth. */ - Mem *pMem = &p->aMem[p->nMem-iCur]; + Mem *pMem = iCur>0 ? &p->aMem[p->nMem-iCur] : p->aMem; int nByte; VdbeCursor *pCx = 0; nByte = ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + - (isBtreeCursor?sqlite3BtreeCursorSize():0); + (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); - assert( iCurnCursor ); - if( p->apCsr[iCur] ){ + assert( iCur>=0 && iCurnCursor ); + if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; memset(pCx, 0, sizeof(VdbeCursor)); + pCx->eCurType = eCurType; pCx->iDb = iDb; pCx->nField = nField; pCx->aOffset = &pCx->aType[nField]; - if( isBtreeCursor ){ - pCx->pCursor = (BtCursor*) + if( eCurType==CURTYPE_BTREE ){ + pCx->uc.pCursor = (BtCursor*) &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; - sqlite3BtreeCursorZero(pCx->pCursor); + sqlite3BtreeCursorZero(pCx->uc.pCursor); } } return pCx; @@ -73092,7 +76878,7 @@ static void applyAffinity( if( affinity>=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL || affinity==SQLITE_AFF_NUMERIC ); - if( (pRec->flags & MEM_Int)==0 ){ + if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ if( (pRec->flags & MEM_Real)==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); }else{ @@ -73102,10 +76888,13 @@ static void applyAffinity( }else if( affinity==SQLITE_AFF_TEXT ){ /* Only attempt the conversion to TEXT if there is an integer or real ** representation (blob and NULL do not get converted) but no string - ** representation. - */ - if( 0==(pRec->flags&MEM_Str) && (pRec->flags&(MEM_Real|MEM_Int)) ){ - sqlite3VdbeMemStringify(pRec, enc, 1); + ** representation. It would be harmless to repeat the conversion if + ** 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); + } } pRec->flags &= ~(MEM_Real|MEM_Int); } @@ -73281,6 +77070,7 @@ static void memTracePrint(Mem *p){ sqlite3VdbeMemPrettyPrint(p, zBuf); printf(" %s", zBuf); } + if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype); } static void registerTrace(int iReg, Mem *p){ printf("REG[%d] = ", iReg); @@ -73418,16 +77208,24 @@ static int checkSavepointCount(sqlite3 *db){ /* ** Return the register of pOp->p2 after first preparing it to be ** overwritten with an integer value. -*/ +*/ +static SQLITE_NOINLINE Mem *out2PrereleaseWithClear(Mem *pOut){ + sqlite3VdbeMemSetNull(pOut); + pOut->flags = MEM_Int; + return pOut; +} static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ Mem *pOut; assert( pOp->p2>0 ); - assert( pOp->p2<=(p->nMem-p->nCursor) ); + assert( pOp->p2<=(p->nMem+1 - p->nCursor) ); pOut = &p->aMem[pOp->p2]; memAboutToChange(p, pOut); - if( VdbeMemDynamic(pOut) ) sqlite3VdbeMemSetNull(pOut); - pOut->flags = MEM_Int; - return pOut; + if( VdbeMemDynamic(pOut) ){ /*OPTIMIZATION-IF-FALSE*/ + return out2PrereleaseWithClear(pOut); + }else{ + pOut->flags = MEM_Int; + return pOut; + } } @@ -73442,6 +77240,9 @@ SQLITE_PRIVATE int sqlite3VdbeExec( Op *pOp = aOp; /* Current operation */ #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) Op *pOrigOp; /* Value of pOp at the top of the loop */ +#endif +#ifdef SQLITE_DEBUG + int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ #endif int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ @@ -73514,9 +77315,12 @@ SQLITE_PRIVATE int sqlite3VdbeExec( } sqlite3EndBenignMalloc(); #endif - for(pOp=&aOp[p->pc]; rc==SQLITE_OK; pOp++){ + for(pOp=&aOp[p->pc]; 1; pOp++){ + /* Errors are detected by individual opcodes, with an immediate + ** jumps to abort_due_to_error. */ + assert( rc==SQLITE_OK ); + assert( pOp>=aOp && pOp<&aOp[p->nOp]); - if( db->mallocFailed ) goto no_mem; #ifdef VDBE_PROFILE start = sqlite3Hwtime(); #endif @@ -73548,37 +77352,39 @@ SQLITE_PRIVATE int sqlite3VdbeExec( /* Sanity checking on other operands */ #ifdef SQLITE_DEBUG - assert( pOp->opflags==sqlite3OpcodeProperty[pOp->opcode] ); - if( (pOp->opflags & OPFLG_IN1)!=0 ){ - assert( pOp->p1>0 ); - assert( pOp->p1<=(p->nMem-p->nCursor) ); - assert( memIsValid(&aMem[pOp->p1]) ); - assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p1]) ); - REGISTER_TRACE(pOp->p1, &aMem[pOp->p1]); - } - if( (pOp->opflags & OPFLG_IN2)!=0 ){ - assert( pOp->p2>0 ); - assert( pOp->p2<=(p->nMem-p->nCursor) ); - assert( memIsValid(&aMem[pOp->p2]) ); - assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p2]) ); - REGISTER_TRACE(pOp->p2, &aMem[pOp->p2]); - } - if( (pOp->opflags & OPFLG_IN3)!=0 ){ - assert( pOp->p3>0 ); - assert( pOp->p3<=(p->nMem-p->nCursor) ); - assert( memIsValid(&aMem[pOp->p3]) ); - assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p3]) ); - REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]); - } - if( (pOp->opflags & OPFLG_OUT2)!=0 ){ - assert( pOp->p2>0 ); - assert( pOp->p2<=(p->nMem-p->nCursor) ); - memAboutToChange(p, &aMem[pOp->p2]); - } - if( (pOp->opflags & OPFLG_OUT3)!=0 ){ - assert( pOp->p3>0 ); - assert( pOp->p3<=(p->nMem-p->nCursor) ); - memAboutToChange(p, &aMem[pOp->p3]); + { + u8 opProperty = sqlite3OpcodeProperty[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]) ); + 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]) ); + 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]) ); + REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]); + } + if( (opProperty & OPFLG_OUT2)!=0 ){ + assert( pOp->p2>0 ); + assert( pOp->p2<=(p->nMem+1 - p->nCursor) ); + memAboutToChange(p, &aMem[pOp->p2]); + } + if( (opProperty & OPFLG_OUT3)!=0 ){ + assert( pOp->p3>0 ); + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); + memAboutToChange(p, &aMem[pOp->p3]); + } } #endif #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) @@ -73662,7 +77468,7 @@ check_for_interrupt: nProgressLimit = nVmStep + db->nProgressOps - (nVmStep%db->nProgressOps); if( db->xProgress(db->pProgressArg) ){ rc = SQLITE_INTERRUPT; - goto vdbe_error_halt; + goto abort_due_to_error; } } #endif @@ -73676,7 +77482,7 @@ check_for_interrupt: ** and then jump to address P2. */ case OP_Gosub: { /* jump */ - assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); pIn1 = &aMem[pOp->p1]; assert( VdbeMemDynamic(pIn1)==0 ); memAboutToChange(p, pIn1); @@ -73716,7 +77522,7 @@ case OP_Return: { /* in1 */ ** See also: EndCoroutine */ case OP_InitCoroutine: { /* jump */ - assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); assert( pOp->p2>=0 && pOp->p2nOp ); assert( pOp->p3>=0 && pOp->p3nOp ); pOut = &aMem[pOp->p1]; @@ -73818,8 +77624,6 @@ case OP_HaltIfNull: { /* in3 */ ** is the same as executing Halt. */ case OP_Halt: { - const char *zType; - const char *zLogFmt; VdbeFrame *pFrame; int pcx; @@ -73848,34 +77652,28 @@ case OP_Halt: { p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; p->pc = pcx; + assert( pOp->p5>=0 && pOp->p5<=4 ); if( p->rc ){ if( pOp->p5 ){ static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", "FOREIGN KEY" }; - assert( pOp->p5>=1 && pOp->p5<=4 ); testcase( pOp->p5==1 ); testcase( pOp->p5==2 ); testcase( pOp->p5==3 ); testcase( pOp->p5==4 ); - zType = azType[pOp->p5-1]; + sqlite3VdbeError(p, "%s constraint failed", azType[pOp->p5-1]); + if( pOp->p4.z ){ + p->zErrMsg = sqlite3MPrintf(db, "%z: %s", p->zErrMsg, pOp->p4.z); + } }else{ - zType = 0; - } - assert( zType!=0 || pOp->p4.z!=0 ); - zLogFmt = "abort at %d in [%s]: %s"; - if( zType && pOp->p4.z ){ - sqlite3VdbeError(p, "%s constraint failed: %s", zType, pOp->p4.z); - }else if( pOp->p4.z ){ sqlite3VdbeError(p, "%s", pOp->p4.z); - }else{ - sqlite3VdbeError(p, "%s constraint failed", zType); } - sqlite3_log(pOp->p1, zLogFmt, pcx, p->zSql, p->zErrMsg); + sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); if( rc==SQLITE_BUSY ){ - p->rc = rc = SQLITE_BUSY; + p->rc = SQLITE_BUSY; }else{ assert( rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT ); assert( rc==SQLITE_OK || db->nDeferredCons>0 || db->nDeferredImmCons>0 ); @@ -73941,7 +77739,7 @@ case OP_String8: { /* same as TK_STRING, out2 */ #ifndef SQLITE_OMIT_UTF16 if( encoding!=SQLITE_UTF8 ){ rc = sqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC); - if( rc==SQLITE_TOOBIG ) goto too_big; + assert( rc==SQLITE_OK || rc==SQLITE_TOOBIG ); if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem; assert( pOut->szMalloc>0 && pOut->zMalloc==pOut->z ); assert( VdbeMemDynamic(pOut)==0 ); @@ -73954,10 +77752,12 @@ case OP_String8: { /* same as TK_STRING, out2 */ 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; } + assert( rc==SQLITE_OK ); /* Fall through to the next case, OP_String */ } @@ -73966,10 +77766,12 @@ case OP_String8: { /* same as TK_STRING, out2 */ ** ** The string value P4 of length P1 (bytes) is stored in register P2. ** -** If P5!=0 and the content of register P3 is greater than zero, then +** If P3 is not zero and the content of register P3 is equal to P5, then ** the datatype of the register P2 is converted to BLOB. The content is ** the same sequence of bytes, it is merely interpreted as a BLOB instead -** of a string, as if it had been CAST. +** of a string, as if it had been CAST. In other words: +** +** if( P3!=0 and reg[P3]==P5 ) reg[P2] := CAST(reg[P2] as BLOB) */ case OP_String: { /* out2 */ assert( pOp->p4.z!=0 ); @@ -73979,13 +77781,14 @@ case OP_String: { /* out2 */ pOut->n = pOp->p1; pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); - if( pOp->p5 ){ - assert( pOp->p3>0 ); - assert( pOp->p3<=(p->nMem-p->nCursor) ); +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( pOp->p3>0 ){ + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); pIn3 = &aMem[pOp->p3]; assert( pIn3->flags & MEM_Int ); - if( pIn3->u.i ) pOut->flags = MEM_Blob|MEM_Static|MEM_Term; + if( pIn3->u.i==pOp->p5 ) pOut->flags = MEM_Blob|MEM_Static|MEM_Term; } +#endif break; } @@ -74006,7 +77809,7 @@ case OP_Null: { /* out2 */ u16 nullFlag; pOut = out2Prerelease(p, pOp); cnt = pOp->p3-pOp->p2; - assert( pOp->p3<=(p->nMem-p->nCursor) ); + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; while( cnt>0 ){ pOut++; @@ -74027,7 +77830,7 @@ case OP_Null: { /* out2 */ ** previously copied using OP_SCopy, the copies will continue to be valid. */ case OP_SoftNull: { - assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); pOut = &aMem[pOp->p1]; pOut->flags = (pOut->flags|MEM_Null)&~MEM_Undefined; break; @@ -74094,8 +77897,8 @@ case OP_Move: { pIn1 = &aMem[p1]; pOut = &aMem[p2]; do{ - assert( pOut<=&aMem[(p->nMem-p->nCursor)] ); - assert( pIn1<=&aMem[(p->nMem-p->nCursor)] ); + assert( pOut<=&aMem[(p->nMem+1 - p->nCursor)] ); + assert( pIn1<=&aMem[(p->nMem+1 - p->nCursor)] ); assert( memIsValid(pIn1) ); memAboutToChange(p, pOut); sqlite3VdbeMemMove(pOut, pIn1); @@ -74165,6 +77968,22 @@ case OP_SCopy: { /* out2 */ break; } +/* Opcode: IntCopy P1 P2 * * * +** Synopsis: r[P2]=r[P1] +** +** Transfer the integer value held in register P1 into register P2. +** +** This is an optimized version of SCopy that works only for integer +** values. +*/ +case OP_IntCopy: { /* out2 */ + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Int)!=0 ); + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetInt64(pOut, pIn1->u.i); + break; +} + /* Opcode: ResultRow P1 P2 * * * ** Synopsis: output=r[P1@P2] ** @@ -74179,7 +77998,7 @@ case OP_ResultRow: { int i; assert( p->nResColumn==pOp->p2 ); assert( pOp->p1>0 ); - assert( pOp->p1+pOp->p2<=(p->nMem-p->nCursor)+1 ); + assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK /* Run the progress counter just before returning. @@ -74189,7 +78008,7 @@ case OP_ResultRow: { && db->xProgress(db->pProgressArg)!=0 ){ rc = SQLITE_INTERRUPT; - goto vdbe_error_halt; + goto abort_due_to_error; } #endif @@ -74199,7 +78018,7 @@ case OP_ResultRow: { if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){ assert( db->flags&SQLITE_CountRows ); assert( p->usesStmtJournal ); - break; + goto abort_due_to_error; } /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then @@ -74219,9 +78038,7 @@ case OP_ResultRow: { */ assert( p->iStatement==0 || db->flags&SQLITE_CountRows ); rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE); - if( NEVER(rc!=SQLITE_OK) ){ - break; - } + assert( rc==SQLITE_OK ); /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; @@ -74493,10 +78310,10 @@ case OP_Function0: { assert( pOp->p4type==P4_FUNCDEF ); n = pOp->p5; - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem-p->nCursor)+1) ); + 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->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRaw(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); + pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); if( pCtx==0 ) goto no_mem; pCtx->pOut = 0; pCtx->pFunc = pOp->p4.pFunc; @@ -74535,8 +78352,8 @@ case OP_Function: { MemSetTypeFlag(pCtx->pOut, MEM_Null); pCtx->fErrorOrAux = 0; db->lastRowid = lastRowid; - (*pCtx->pFunc->xFunc)(pCtx, pCtx->argc, pCtx->argv); /* IMP: R-24505-23230 */ - lastRowid = db->lastRowid; /* Remember rowid changes made by xFunc */ + (*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 ){ @@ -74544,7 +78361,8 @@ case OP_Function: { sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut)); rc = pCtx->isError; } - sqlite3VdbeDeleteAuxData(p, pCtx->iOp, pOp->p1); + sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); + if( rc ) goto abort_due_to_error; } /* Copy the result of the function into register P3 */ @@ -74728,6 +78546,7 @@ case OP_Cast: { /* in1 */ rc = ExpandBlob(pIn1); sqlite3VdbeMemCast(pIn1, pOp->p2, encoding); UPDATE_MAX_BLOBSIZE(pIn1); + if( rc ) goto abort_due_to_error; break; } #endif /* SQLITE_OMIT_CAST */ @@ -74854,6 +78673,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Null); REGISTER_TRACE(pOp->p2, pOut); }else{ @@ -74868,21 +78688,23 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ /* Neither operand is NULL. Do a comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ - if( (pIn1->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ - applyNumericAffinity(pIn1,0); - } - if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ - applyNumericAffinity(pIn3,0); + if( (flags1 | flags3)&MEM_Str ){ + if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn1,0); + } + if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn3,0); + } } }else if( affinity==SQLITE_AFF_TEXT ){ - if( (pIn1->flags & MEM_Str)==0 && (pIn1->flags & (MEM_Int|MEM_Real))!=0 ){ + if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); } - if( (pIn3->flags & MEM_Str)==0 && (pIn3->flags & (MEM_Int|MEM_Real))!=0 ){ + if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn3->flags & MEM_Int ); testcase( pIn3->flags & MEM_Real ); sqlite3VdbeMemStringify(pIn3, encoding, 1); @@ -74891,15 +78713,14 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } } assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); - if( pIn1->flags & MEM_Zero ){ + if( flags1 & MEM_Zero ){ sqlite3VdbeMemExpandBlob(pIn1); flags1 &= ~MEM_Zero; } - if( pIn3->flags & MEM_Zero ){ + if( flags3 & MEM_Zero ){ sqlite3VdbeMemExpandBlob(pIn3); flags3 &= ~MEM_Zero; } - if( db->mallocFailed ) goto no_mem; res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } switch( pOp->opcode ){ @@ -74940,11 +78761,14 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** The permutation is only valid until the next OP_Compare that has ** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should ** occur immediately prior to the OP_Compare. +** +** The first integer in the P4 integer array is the length of the array +** and does not become part of the permutation. */ case OP_Permutation: { assert( pOp->p4type==P4_INTARRAY ); assert( pOp->p4.ai ); - aPermute = pOp->p4.ai; + aPermute = pOp->p4.ai + 1; break; } @@ -74989,11 +78813,11 @@ case OP_Compare: { if( aPermute ){ int k, mx = 0; for(k=0; kmx ) mx = aPermute[k]; - assert( p1>0 && p1+mx<=(p->nMem-p->nCursor)+1 ); - assert( p2>0 && p2+mx<=(p->nMem-p->nCursor)+1 ); + assert( p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1 ); + assert( p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1 ); }else{ - assert( p1>0 && p1+n<=(p->nMem-p->nCursor)+1 ); - assert( p2>0 && p2+n<=(p->nMem-p->nCursor)+1 ); + assert( p1>0 && p1+n<=(p->nMem+1 - p->nCursor)+1 ); + assert( p2>0 && p2+n<=(p->nMem+1 - p->nCursor)+1 ); } #endif /* SQLITE_DEBUG */ for(i=0; iflags value */ Mem *pReg; /* PseudoTable input register */ + pC = p->apCsr[pOp->p1]; p2 = pOp->p2; - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); + + /* If the cursor cache is stale, bring it up-to-date */ + rc = sqlite3VdbeCursorMoveto(&pC, &p2); + + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; memAboutToChange(p, pDest); assert( pOp->p1>=0 && pOp->p1nCursor ); - pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( p2nField ); aOffset = pC->aOffset; -#ifndef SQLITE_OMIT_VIRTUALTABLE - assert( pC->pVtabCursor==0 ); /* OP_Column never called on virtual table */ -#endif - pCrsr = pC->pCursor; - assert( pCrsr!=0 || pC->pseudoTableReg>0 ); /* pCrsr NULL on PseudoTables */ - assert( pCrsr!=0 || pC->nullRow ); /* pC->nullRow on PseudoTables */ + assert( pC->eCurType!=CURTYPE_VTAB ); + assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); + assert( pC->eCurType!=CURTYPE_SORTER ); + pCrsr = pC->uc.pCursor; - /* If the cursor cache is stale, bring it up-to-date */ - rc = sqlite3VdbeCursorMoveto(pC); if( rc ) goto abort_due_to_error; if( pC->cacheStatus!=p->cacheCtr ){ if( pC->nullRow ){ - if( pCrsr==0 ){ - assert( pC->pseudoTableReg>0 ); - pReg = &aMem[pC->pseudoTableReg]; + if( pC->eCurType==CURTYPE_PSEUDO ){ + assert( pC->uc.pseudoTableReg>0 ); + pReg = &aMem[pC->uc.pseudoTableReg]; assert( pReg->flags & MEM_Blob ); assert( memIsValid(pReg) ); pC->payloadSize = pC->szRow = avail = pReg->n; @@ -75283,6 +79106,7 @@ case OP_Column: { goto op_column_out; } }else{ + assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); if( pC->isTable==0 ){ assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -75303,31 +79127,17 @@ case OP_Column: { 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] ){ + goto too_big; }else{ pC->szRow = avail; } - if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } } pC->cacheStatus = p->cacheCtr; pC->iHdrOffset = getVarint32(pC->aRow, offset); pC->nHdrParsed = 0; aOffset[0] = offset; - /* Make sure a corrupt database has not given us an oversize header. - ** Do this now to avoid an oversize memory allocation. - ** - ** Type entries can be between 1 and 5 bytes each. But 4 and 5 byte - ** types use so much data space that there can only be 4096 and 32 of - ** them, respectively. So the maximum header length results from a - ** 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 op_column_error; - } if( availaRow does not have to hold the entire row, but it does at least @@ -75336,6 +79146,20 @@ case OP_Column: { ** dynamically allocated. */ pC->aRow = 0; pC->szRow = 0; + + /* Make sure a corrupt database has not given us an oversize header. + ** Do this now to avoid an oversize memory allocation. + ** + ** Type entries can be between 1 and 5 bytes each. But 4 and 5 byte + ** types use so much data space that there can only be 4096 and 32 of + ** them, respectively. So the maximum header length results from a + ** 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; + } } /* The following goto is an optimization. It can be omitted and @@ -75358,11 +79182,8 @@ 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); - if( rc!=SQLITE_OK ){ - goto op_column_error; - } + rc = sqlite3VdbeMemFromBtree(pCrsr, 0, aOffset[0], !pC->isTable, &sMem); + if( rc!=SQLITE_OK ) goto abort_due_to_error; zData = (u8*)sMem.z; }else{ zData = pC->aRow; @@ -75370,48 +79191,40 @@ case OP_Column: { /* Fill in pC->aType[i] and aOffset[i] values through the p2-th field. */ i = pC->nHdrParsed; - offset = aOffset[i]; + offset64 = aOffset[i]; zHdr = zData + pC->iHdrOffset; zEndHdr = zData + aOffset[0]; assert( i<=p2 && zHdraType[i] = t; - szField = sqlite3VdbeSerialTypeLen(t); - offset += szField; - if( offsetaType[i++] = t; + aOffset[i] = (u32)(offset64 & 0xffffffff); }while( i<=p2 && zHdrnHdrParsed = i; pC->iHdrOffset = (u32)(zHdr - zData); - if( pC->aRow==0 ){ - sqlite3VdbeMemRelease(&sMem); - sMem.flags = MEM_Null; - } /* The record is corrupt if any of the following are true: ** (1) the bytes of the header extend past the declared header size - ** (zHdr>zEndHdr) ** (2) the entire header was used but not all data was used - ** (zHdr==zEndHdr && offset!=pC->payloadSize) ** (3) the end of the data extends beyond the end of the record. - ** (offset > pC->payloadSize) */ - if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset!=pC->payloadSize)) - || (offset > pC->payloadSize) + if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset64!=pC->payloadSize)) + || (offset64 > pC->payloadSize) ){ + if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem); rc = SQLITE_CORRUPT_BKPT; - goto op_column_error; + goto abort_due_to_error; } + if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem); + + }else{ + t = 0; } /* If after trying to extract new entries from the header, nHdrParsed is @@ -75426,6 +79239,8 @@ case OP_Column: { } goto op_column_out; } + }else{ + t = pC->aType[p2]; } /* Extract the content for the p2+1-th column. Control can only @@ -75436,11 +79251,32 @@ case OP_Column: { assert( rc==SQLITE_OK ); assert( sqlite3VdbeCheckMemInvariants(pDest) ); if( VdbeMemDynamic(pDest) ) sqlite3VdbeMemSetNull(pDest); - t = pC->aType[p2]; + assert( t==pC->aType[p2] ); + pDest->enc = encoding; if( pC->szRow>=aOffset[p2+1] ){ /* This is the common case where the desired content fits on the original ** page - where the content is not on an overflow page */ - sqlite3VdbeSerialGet(pC->aRow+aOffset[p2], t, pDest); + zData = pC->aRow + aOffset[p2]; + if( t<12 ){ + sqlite3VdbeSerialGet(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(). + */ + static const u16 aFlag[] = { MEM_Blob, MEM_Str|MEM_Term }; + pDest->n = len = (t-12)/2; + if( pDest->szMalloc < len+2 ){ + pDest->flags = MEM_Null; + if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem; + }else{ + pDest->z = pDest->zMalloc; + } + memcpy(pDest->z, zData, len); + pDest->z[len] = 0; + pDest->z[len+1] = 0; + pDest->flags = aFlag[t&1]; + } }else{ /* This branch happens only when content is on overflow pages */ if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 @@ -75452,39 +79288,19 @@ case OP_Column: { ** 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. NULL will work for the value for strings - ** and blobs and whatever is in the payloadSize64 variable - ** will work for everything else. */ - sqlite3VdbeSerialGet(t<=13 ? (u8*)&payloadSize64 : 0, t, pDest); + ** content from disk. */ + static u8 aZero[8]; /* This is the bogus content */ + sqlite3VdbeSerialGet(aZero, t, pDest); }else{ rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable, pDest); - if( rc!=SQLITE_OK ){ - goto op_column_error; - } + if( rc!=SQLITE_OK ) goto abort_due_to_error; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); pDest->flags &= ~MEM_Ephem; } } - pDest->enc = encoding; op_column_out: - /* If the column value is an ephemeral string, go ahead and persist - ** that string in case the cursor moves before the column value is - ** used. The following code does the equivalent of Deephemeralize() - ** but does it faster. */ - if( (pDest->flags & MEM_Ephem)!=0 && pDest->z ){ - fx = pDest->flags & (MEM_Str|MEM_Blob); - assert( fx!=0 ); - zData = (const u8*)pDest->z; - len = pDest->n; - if( sqlite3VdbeMemClearAndResize(pDest, len+2) ) goto no_mem; - memcpy(pDest->z, zData, len); - pDest->z[len] = 0; - pDest->z[len+1] = 0; - pDest->flags = fx|MEM_Term; - } -op_column_error: UPDATE_MAX_BLOBSIZE(pDest); REGISTER_TRACE(pOp->p3, pDest); break; @@ -75508,7 +79324,7 @@ case OP_Affinity: { assert( zAffinity[pOp->p2]==0 ); pIn1 = &aMem[pOp->p1]; while( (cAff = *(zAffinity++))!=0 ){ - assert( pIn1 <= &p->aMem[(p->nMem-p->nCursor)] ); + assert( pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)] ); assert( memIsValid(pIn1) ); applyAffinity(pIn1, cAff, encoding); pIn1++; @@ -75548,7 +79364,7 @@ case OP_MakeRecord: { int file_format; /* File format to use for encoding */ int i; /* Space used in zNewRecord[] header */ int j; /* Space used in zNewRecord[] content */ - int len; /* Length of a field */ + u32 len; /* Length of a field */ /* Assuming the record contains N fields, the record format looks ** like this: @@ -75570,7 +79386,7 @@ case OP_MakeRecord: { nZero = 0; /* Number of zero bytes at the end of the record */ nField = pOp->p1; zAffinity = pOp->p4.z; - assert( nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem-p->nCursor)+1 ); + assert( nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1 ); pData0 = &aMem[nField]; nField = pOp->p2; pLast = &pData0[nField-1]; @@ -75598,8 +79414,7 @@ case OP_MakeRecord: { pRec = pLast; do{ assert( memIsValid(pRec) ); - pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format); - len = sqlite3VdbeSerialTypeLen(serial_type); + pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format, &len); if( pRec->flags & MEM_Zero ){ if( nData ){ if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; @@ -75612,7 +79427,9 @@ case OP_MakeRecord: { testcase( serial_type==127 ); testcase( serial_type==128 ); nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type); - }while( (--pRec)>=pData0 ); + if( pRec==pData0 ) break; + pRec--; + }while(1); /* EVIDENCE-OF: R-22564-11647 The header begins with a single varint ** which determines the total number of bytes in the header. The varint @@ -75661,7 +79478,7 @@ case OP_MakeRecord: { assert( i==nHdr ); assert( j==nByte ); - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pOut->n = (int)nByte; pOut->flags = MEM_Blob; if( nZero ){ @@ -75685,10 +79502,12 @@ case OP_Count: { /* out2 */ i64 nEntry; BtCursor *pCrsr; - pCrsr = p->apCsr[pOp->p1]->pCursor; + assert( p->apCsr[pOp->p1]->eCurType==CURTYPE_BTREE ); + pCrsr = p->apCsr[pOp->p1]->uc.pCursor; assert( pCrsr ); nEntry = 0; /* Not needed. Only used to silence a warning. */ rc = sqlite3BtreeCount(pCrsr, &nEntry); + if( rc ) goto abort_due_to_error; pOut = out2Prerelease(p, pOp); pOut->u.i = nEntry; break; @@ -75745,7 +79564,7 @@ case OP_Savepoint: { #endif /* Create a new savepoint structure. */ - pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1); + pNew = sqlite3DbMallocRawNN(db, sizeof(Savepoint)+nName+1); if( pNew ){ pNew->zName = (char *)&pNew[1]; memcpy(pNew->zName, zName, nName+1); @@ -75758,7 +79577,7 @@ case OP_Savepoint: { }else{ db->nSavepoint++; } - + /* Link the new savepoint into the database handle's list. */ pNew->pNext = db->pSavepoint; db->pSavepoint = pNew; @@ -75866,6 +79685,7 @@ case OP_Savepoint: { } } } + if( rc ) goto abort_due_to_error; break; } @@ -75882,28 +79702,27 @@ case OP_Savepoint: { case OP_AutoCommit: { int desiredAutoCommit; int iRollback; - int turnOnAC; desiredAutoCommit = pOp->p1; iRollback = pOp->p2; - turnOnAC = desiredAutoCommit && !db->autoCommit; assert( desiredAutoCommit==1 || desiredAutoCommit==0 ); assert( desiredAutoCommit==1 || iRollback==0 ); assert( db->nVdbeActive>0 ); /* At least this one VM is active */ assert( p->bIsReader ); - if( turnOnAC && !iRollback && 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 - " - "SQL statements in progress"); - rc = SQLITE_BUSY; - }else if( desiredAutoCommit!=db->autoCommit ){ + if( desiredAutoCommit!=db->autoCommit ){ if( iRollback ){ assert( desiredAutoCommit==1 ); sqlite3RollbackAll(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 - " + "SQL statements in progress"); + rc = SQLITE_BUSY; + goto abort_due_to_error; }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ goto vdbe_return; }else{ @@ -75930,6 +79749,7 @@ case OP_AutoCommit: { "cannot commit - no transaction is active")); rc = SQLITE_ERROR; + goto abort_due_to_error; } break; } @@ -76052,6 +79872,7 @@ case OP_Transaction: { p->expired = 1; rc = SQLITE_SCHEMA; } + if( rc ) goto abort_due_to_error; break; } @@ -76088,15 +79909,15 @@ case OP_ReadCookie: { /* out2 */ /* Opcode: SetCookie P1 P2 P3 * * ** -** Write the content of register P3 (interpreted as an integer) -** into cookie number P2 of database P1. P2==1 is the schema version. -** P2==2 is the database format. P2==3 is the recommended pager cache +** Write the integer value P3 into cookie number P2 of database P1. +** P2==1 is the schema version. P2==2 is the database format. +** P2==3 is the recommended pager cache ** size, and so forth. P1==0 is the main database file and P1==1 is the ** database file used to store temporary tables. ** ** A transaction must be started before executing this opcode. */ -case OP_SetCookie: { /* in3 */ +case OP_SetCookie: { Db *pDb; assert( pOp->p2p1>=0 && pOp->p1nDb ); @@ -76105,17 +79926,15 @@ case OP_SetCookie: { /* in3 */ pDb = &db->aDb[pOp->p1]; assert( pDb->pBt!=0 ); assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); - pIn3 = &aMem[pOp->p3]; - sqlite3VdbeMemIntegerify(pIn3); /* See note about index shifting on OP_ReadCookie */ - rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i); + rc = sqlite3BtreeUpdateMeta(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 = (int)pIn3->u.i; + pDb->pSchema->schema_cookie = pOp->p3; db->flags |= SQLITE_InternChanges; }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ - pDb->pSchema->file_format = (u8)pIn3->u.i; + pDb->pSchema->file_format = pOp->p3; } if( pOp->p1==1 ){ /* Invalidate all prepared statements whenever the TEMP database @@ -76123,6 +79942,7 @@ case OP_SetCookie: { /* in3 */ sqlite3ExpirePreparedStatements(db); p->expired = 0; } + if( rc ) goto abort_due_to_error; break; } @@ -76213,7 +80033,6 @@ case OP_ReopenIdx: { case OP_OpenRead: case OP_OpenWrite: - assert( (pOp->p5&(OPFLAG_P2ISREG|OPFLAG_BULKCSR|OPFLAG_SEEKEQ))==pOp->p5 ); assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); assert( p->bIsReader ); assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx @@ -76221,7 +80040,7 @@ case OP_OpenWrite: if( p->expired ){ rc = SQLITE_ABORT_ROLLBACK; - break; + goto abort_due_to_error; } nField = 0; @@ -76234,7 +80053,8 @@ case OP_OpenWrite: pX = pDb->pBt; assert( pX!=0 ); if( pOp->opcode==OP_OpenWrite ){ - wrFlag = 1; + assert( OPFLAG_FORDELETE==BTREE_FORDELETE ); + wrFlag = BTREE_WRCSR | (pOp->p5 & OPFLAG_FORDELETE); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->file_format < p->minWriteFileFormat ){ p->minWriteFileFormat = pDb->pSchema->file_format; @@ -76244,7 +80064,7 @@ case OP_OpenWrite: } if( pOp->p5 & OPFLAG_P2ISREG ){ assert( p2>0 ); - assert( p2<=(p->nMem-p->nCursor) ); + assert( p2<=(p->nMem+1 - p->nCursor) ); pIn2 = &aMem[p2]; assert( memIsValid(pIn2) ); assert( (pIn2->flags & MEM_Int)!=0 ); @@ -76254,10 +80074,7 @@ case OP_OpenWrite: ** 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. */ - if( NEVER(p2<2) ) { - rc = SQLITE_CORRUPT_BKPT; - goto abort_due_to_error; - } + assert( p2>=2 ); } if( pOp->p4type==P4_KEYINFO ){ pKeyInfo = pOp->p4.pKeyInfo; @@ -76270,12 +80087,15 @@ case OP_OpenWrite: assert( pOp->p1>=0 ); assert( nField>=0 ); testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ - pCur = allocateCursor(p, pOp->p1, nField, iDb, 1); + pCur = allocateCursor(p, pOp->p1, nField, iDb, CURTYPE_BTREE); if( pCur==0 ) goto no_mem; pCur->nullRow = 1; pCur->isOrdered = 1; pCur->pgnoRoot = p2; - rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->pCursor); +#ifdef SQLITE_DEBUG + pCur->wrFlag = wrFlag; +#endif + rc = sqlite3BtreeCursor(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 @@ -76286,8 +80106,13 @@ case OP_OpenWrite: open_cursor_set_hints: assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ ); - sqlite3BtreeCursorHints(pCur->pCursor, - (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); + testcase( pOp->p5 & OPFLAG_BULKCSR ); +#ifdef SQLITE_ENABLE_CURSOR_HINTS + testcase( pOp->p2 & OPFLAG_SEEKEQ ); +#endif + sqlite3BtreeCursorHintFlags(pCur->uc.pCursor, + (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); + if( rc ) goto abort_due_to_error; break; } @@ -76330,7 +80155,7 @@ case OP_OpenEphemeral: { SQLITE_OPEN_TRANSIENT_DB; assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); + pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; @@ -76354,14 +80179,17 @@ case OP_OpenEphemeral: { assert( pKeyInfo->db==db ); assert( pKeyInfo->enc==ENC(db) ); pCx->pKeyInfo = pKeyInfo; - rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, pKeyInfo, pCx->pCursor); + rc = sqlite3BtreeCursor(pCx->pBt, pgno, BTREE_WRCSR, + pKeyInfo, pCx->uc.pCursor); } pCx->isTable = 0; }else{ - rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, 1, 0, pCx->pCursor); + rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, BTREE_WRCSR, + 0, pCx->uc.pCursor); pCx->isTable = 1; } } + if( rc ) goto abort_due_to_error; pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); break; } @@ -76381,12 +80209,13 @@ case OP_SorterOpen: { assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); + pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_SORTER); if( pCx==0 ) goto no_mem; pCx->pKeyInfo = pOp->p4.pKeyInfo; assert( pCx->pKeyInfo->db==db ); assert( pCx->pKeyInfo->enc==ENC(db) ); rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx); + if( rc ) goto abort_due_to_error; break; } @@ -76401,7 +80230,7 @@ case OP_SequenceTest: { VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; - assert( pC->pSorter ); + assert( isSorter(pC) ); if( (pC->seqCount++)==0 ){ goto jump_to_p2; } @@ -76429,10 +80258,10 @@ case OP_OpenPseudo: { assert( pOp->p1>=0 ); assert( pOp->p3>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, 0); + pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, CURTYPE_PSEUDO); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; - pCx->pseudoTableReg = pOp->p2; + pCx->uc.pseudoTableReg = pOp->p2; pCx->isTable = 1; assert( pOp->p5==0 ); break; @@ -76464,7 +80293,7 @@ case OP_Close: { case OP_ColumnsUsed: { VdbeCursor *pC; pC = p->apCsr[pOp->p1]; - assert( pC->pCursor ); + assert( pC->eCurType==CURTYPE_BTREE ); pC->maskUsed = *(u64*)pOp->p4.pI64; break; } @@ -76482,6 +80311,13 @@ case OP_ColumnsUsed: { ** is greater than or equal to the key value. If there are no records ** greater than or equal to the key and P2 is not zero, then jump to P2. ** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will always land on a record that equally equals the key, or +** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this +** opcode must be followed by an IdxLE opcode with the same arguments. +** The IdxLE opcode will be skipped if this opcode succeeds, but the +** IdxLE opcode will be used on subsequent loop iterations. +** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. @@ -76540,51 +80376,48 @@ case OP_ColumnsUsed: { ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. ** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will always land on a record that equally equals the key, or +** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this +** opcode must be followed by an IdxGE opcode with the same arguments. +** The IdxGE opcode will be skipped if this opcode succeeds, but the +** IdxGE opcode will be used on subsequent loop iterations. +** ** 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 */ - int res; - int oc; - VdbeCursor *pC; - UnpackedRecord r; - int nField; - i64 iKey; /* The rowid we are to seek to */ + int res; /* Comparison result */ + int oc; /* Opcode */ + VdbeCursor *pC; /* The cursor to seek */ + UnpackedRecord r; /* The key to seek for */ + int nField; /* Number of columns or fields in the key */ + i64 iKey; /* The rowid we are to seek to */ + int eqOnly; /* Only interested in == results */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p2!=0 ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->pseudoTableReg==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); assert( OP_SeekLE == OP_SeekLT+1 ); assert( OP_SeekGE == OP_SeekLT+2 ); assert( OP_SeekGT == OP_SeekLT+3 ); assert( pC->isOrdered ); - assert( pC->pCursor!=0 ); + assert( pC->uc.pCursor!=0 ); oc = pOp->opcode; + eqOnly = 0; pC->nullRow = 0; #ifdef SQLITE_DEBUG pC->seekOp = pOp->opcode; #endif - /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and - ** OP_SeekLE opcodes are allowed, and these must be immediately followed - ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key. - */ -#ifdef SQLITE_DEBUG - if( sqlite3BtreeCursorHasHint(pC->pCursor, BTREE_SEEK_EQ) ){ - assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); - assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); - assert( pOp[1].p1==pOp[0].p1 ); - assert( pOp[1].p2==pOp[0].p2 ); - assert( pOp[1].p3==pOp[0].p3 ); - assert( pOp[1].p4.i==pOp[0].p4.i ); - } -#endif - if( pC->isTable ){ + /* The BTREE_SEEK_EQ flag is only set on index cursors */ + assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 ); + /* 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. */ @@ -76627,12 +80460,26 @@ case OP_SeekGT: { /* jump, in3 */ if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; } } - rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)iKey, 0, &res); + rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res); pC->movetoTarget = iKey; /* Used by OP_Delete */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; } }else{ + /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and + ** 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) ){ + eqOnly = 1; + assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + assert( pOp[1].p1==pOp[0].p1 ); + assert( pOp[1].p2==pOp[0].p2 ); + assert( pOp[1].p3==pOp[0].p3 ); + assert( pOp[1].p4.i==pOp[0].p4.i ); + } + nField = pOp->p4.i; assert( pOp->p4type==P4_INT32 ); assert( nField>0 ); @@ -76657,10 +80504,15 @@ case OP_SeekGT: { /* jump, in3 */ { int i; for(i=0; ipCursor, &r, 0, 0, &res); + r.eqSeen = 0; + rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, &r, 0, 0, &res); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } + if( eqOnly && r.eqSeen==0 ){ + assert( res!=0 ); + goto seek_not_found; + } } pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -76670,7 +80522,7 @@ case OP_SeekGT: { /* jump, in3 */ if( oc>=OP_SeekGE ){ assert( oc==OP_SeekGE || oc==OP_SeekGT ); if( res<0 || (res==0 && oc==OP_SeekGT) ){ res = 0; - rc = sqlite3BtreeNext(pC->pCursor, &res); + rc = sqlite3BtreeNext(pC->uc.pCursor, &res); if( rc!=SQLITE_OK ) goto abort_due_to_error; }else{ res = 0; @@ -76679,47 +80531,26 @@ 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->pCursor, &res); + rc = sqlite3BtreePrevious(pC->uc.pCursor, &res); if( rc!=SQLITE_OK ) 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->pCursor); + res = sqlite3BtreeEof(pC->uc.pCursor); } } +seek_not_found: assert( pOp->p2>0 ); VdbeBranchTaken(res!=0,2); if( res ){ goto jump_to_p2; + }else if( eqOnly ){ + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + pOp++; /* Skip the OP_IdxLt or OP_IdxGT that follows */ } break; } - -/* Opcode: Seek P1 P2 * * * -** Synopsis: intkey=r[P2] -** -** P1 is an open table cursor and P2 is a rowid integer. Arrange -** for P1 to move so that it points to the rowid given by P2. -** -** This is actually a deferred seek. Nothing actually happens until -** the cursor is used to read a record. That way, if no reads -** occur, no unnecessary I/O happens. -*/ -case OP_Seek: { /* in2 */ - VdbeCursor *pC; - - assert( pOp->p1>=0 && pOp->p1nCursor ); - pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); - assert( pC->pCursor!=0 ); - assert( pC->isTable ); - pC->nullRow = 0; - pIn2 = &aMem[pOp->p2]; - pC->movetoTarget = sqlite3VdbeIntValue(pIn2); - pC->deferredMoveto = 1; - break; -} /* Opcode: Found P1 P2 P3 P4 * @@ -76806,7 +80637,8 @@ case OP_Found: { /* jump, in3 */ pC->seekOp = pOp->opcode; #endif pIn3 = &aMem[pOp->p3]; - assert( pC->pCursor!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); pFree = 0; if( pOp->p4.i>0 ){ @@ -76843,10 +80675,10 @@ case OP_Found: { /* jump, in3 */ } } } - rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, pIdxKey, 0, 0, &res); + rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); sqlite3DbFree(db, pFree); if( rc!=SQLITE_OK ){ - break; + goto abort_due_to_error; } pC->seekResult = res; alreadyExists = (res==0); @@ -76897,8 +80729,8 @@ case OP_NotExists: { /* jump, in3 */ pC->seekOp = 0; #endif assert( pC->isTable ); - assert( pC->pseudoTableReg==0 ); - pCrsr = pC->pCursor; + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); res = 0; iKey = pIn3->u.i; @@ -76918,6 +80750,7 @@ case OP_NotExists: { /* jump, in3 */ goto jump_to_p2; } } + if( rc ) goto abort_due_to_error; break; } @@ -76932,6 +80765,7 @@ case OP_NotExists: { /* jump, in3 */ case OP_Sequence: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( p->apCsr[pOp->p1]!=0 ); + assert( p->apCsr[pOp->p1]->eCurType!=CURTYPE_VTAB ); pOut = out2Prerelease(p, pOp); pOut->u.i = p->apCsr[pOp->p1]->seqCount++; break; @@ -76967,7 +80801,8 @@ case OP_NewRowid: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->pCursor!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); { /* The next rowid or record number (different terms for the same ** thing) is obtained in a two-step algorithm. @@ -76995,15 +80830,15 @@ case OP_NewRowid: { /* out2 */ #endif if( !pC->useRandomRowid ){ - rc = sqlite3BtreeLast(pC->pCursor, &res); + rc = sqlite3BtreeLast(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->pCursor) ); - rc = sqlite3BtreeKeySize(pC->pCursor, &v); + assert( sqlite3BtreeCursorIsValid(pC->uc.pCursor) ); + rc = sqlite3BtreeKeySize(pC->uc.pCursor, &v); assert( rc==SQLITE_OK ); /* Cannot fail following BtreeLast() */ if( v>=MAX_ROWID ){ pC->useRandomRowid = 1; @@ -77024,7 +80859,7 @@ case OP_NewRowid: { /* out2 */ pMem = &pFrame->aMem[pOp->p3]; }else{ /* Assert that P3 is a valid memory cell. */ - assert( pOp->p3<=(p->nMem-p->nCursor) ); + assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); pMem = &aMem[pOp->p3]; memAboutToChange(p, pMem); } @@ -77054,11 +80889,12 @@ case OP_NewRowid: { /* out2 */ do{ sqlite3_randomness(sizeof(v), &v); v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ - }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v, + }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v, 0, &res))==SQLITE_OK) && (res==0) && (++cnt<100)); - if( rc==SQLITE_OK && res==0 ){ + if( rc ) goto abort_due_to_error; + if( res==0 ){ rc = SQLITE_FULL; /* IMP: R-38219-53002 */ goto abort_due_to_error; } @@ -77098,9 +80934,9 @@ case OP_NewRowid: { /* out2 */ ** is part of an INSERT operation. The difference is only important to ** the update hook. ** -** Parameter P4 may point to a string containing the table-name, or -** may be NULL. If it is not NULL, then the update-hook -** (sqlite3.xUpdateCallback) is invoked following a successful insert. +** Parameter P4 may point to a Table structure, or may be NULL. If it is +** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked +** following a successful insert. ** ** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically ** allocated, then ownership of P2 is transferred to the pseudo-cursor @@ -77126,17 +80962,19 @@ case OP_InsertInt: { int nZero; /* Number of zero-bytes to append */ int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ const char *zDb; /* database name - used by the update hook */ - const char *zTbl; /* Table name - used by the opdate hook */ + Table *pTab; /* Table structure - used by update and pre-update hooks */ int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */ + op = 0; pData = &aMem[pOp->p2]; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( memIsValid(pData) ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->pCursor!=0 ); - assert( pC->pseudoTableReg==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); assert( pC->isTable ); + assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC ); REGISTER_TRACE(pOp->p2, pData); if( pOp->opcode==OP_Insert ){ @@ -77150,6 +80988,28 @@ case OP_InsertInt: { iKey = pOp->p3; } + if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ + assert( pC->isTable ); + assert( pC->iDb>=0 ); + zDb = db->aDb[pC->iDb].zName; + pTab = pOp->p4.pTab; + assert( HasRowid(pTab) ); + op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); + }else{ + pTab = 0; /* Not needed. Silence a comiler warning. */ + 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, iKey, pOp->p2); + } +#endif + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = iKey; if( pData->flags & MEM_Null ){ @@ -77164,7 +81024,7 @@ case OP_InsertInt: { }else{ nZero = 0; } - rc = sqlite3BtreeInsert(pC->pCursor, 0, iKey, + rc = sqlite3BtreeInsert(pC->uc.pCursor, 0, iKey, pData->z, pData->n, nZero, (pOp->p5 & OPFLAG_APPEND)!=0, seekResult ); @@ -77172,74 +81032,137 @@ case OP_InsertInt: { pC->cacheStatus = CACHE_STALE; /* Invoke the update-hook if required. */ - if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){ - zDb = db->aDb[pC->iDb].zName; - zTbl = pOp->p4.z; - op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); - assert( pC->isTable ); - db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey); - assert( pC->iDb>=0 ); + if( rc ) goto abort_due_to_error; + if( db->xUpdateCallback && op ){ + db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, iKey); } break; } -/* Opcode: Delete P1 P2 * P4 P5 +/* Opcode: Delete P1 P2 P3 P4 P5 ** ** Delete the record at which the P1 cursor is currently pointing. ** -** If the P5 parameter is non-zero, the cursor will be left pointing at -** either the next or the previous record in the table. If it is left -** pointing at the next record, then the next Next instruction will be a -** no-op. As a result, in this case it is OK to delete a record from within a -** Next loop. If P5 is zero, then the cursor is left in an undefined state. +** If the OPFLAG_SAVEPOSITION bit of the P5 parameter is set, then +** the cursor will be left pointing at either the next or the previous +** record in the table. If it is left pointing at the next record, then +** the next Next instruction will be a no-op. As a result, in this case +** it is ok to delete a record from within a Next loop. If +** OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be +** left in an undefined state. ** -** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is -** incremented (otherwise not). +** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this +** delete one of several associated with deleting a table row and all its +** associated index entries. Exactly one of those deletes is the "primary" +** delete. The others are all on OPFLAG_FORDELETE cursors or else are +** marked with the AUXDELETE flag. +** +** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row +** change count is incremented (otherwise not). ** ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL, then it is the name of the table that P1 is -** pointing to. The update hook will be invoked, if it exists. -** If P4 is not NULL then the P1 cursor must have been positioned -** using OP_NotFound prior to invoking this opcode. +** If P4 is not NULL then it points to a Table struture. In this case either +** the update or pre-update hook, or both, may be invoked. The P1 cursor must +** have been positioned using OP_NotFound prior to invoking this opcode in +** this case. Specifically, if one is configured, the pre-update hook is +** invoked if P4 is not NULL. The update-hook is invoked if one is configured, +** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. +** +** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address +** of the memory cell that contains the value that the rowid of the row will +** be set to by the update. */ case OP_Delete: { VdbeCursor *pC; - u8 hasUpdateCallback; + const char *zDb; + Table *pTab; + int opflags; + opflags = pOp->p2; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */ + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); assert( pC->deferredMoveto==0 ); - hasUpdateCallback = db->xUpdateCallback && pOp->p4.z && pC->isTable; - if( pOp->p5 && hasUpdateCallback ){ - sqlite3BtreeKeySize(pC->pCursor, &pC->movetoTarget); - } - #ifdef SQLITE_DEBUG - /* 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 */ - if( pOp->p4.z && pC->isTable && pOp->p5==0 ){ + if( pOp->p4type==P4_TABLE && HasRowid(pOp->p4.pTab) && pOp->p5==0 ){ + /* 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 = 0; - sqlite3BtreeKeySize(pC->pCursor, &iKey); - assert( pC->movetoTarget==iKey ); + sqlite3BtreeKeySize(pC->uc.pCursor, &iKey); + assert( pC->movetoTarget==iKey ); } #endif + + /* If the update-hook or pre-update-hook will be invoked, set zDb to + ** the name of the db to pass as to it. Also set local pTab to a copy + ** of p4.pTab. Finally, if p5 is true, indicating that this cursor was + ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set + ** VdbeCursor.movetoTarget to the current rowid. */ + if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ + assert( pC->iDb>=0 ); + assert( pOp->p4.pTab!=0 ); + zDb = db->aDb[pC->iDb].zName; + pTab = pOp->p4.pTab; + if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){ + sqlite3BtreeKeySize(pC->uc.pCursor, &pC->movetoTarget); + } + }else{ + zDb = 0; /* Not needed. Silence a compiler warning. */ + pTab = 0; /* Not needed. Silence a compiler warning. */ + } + +#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, + (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, + zDb, pTab, pC->movetoTarget, + pOp->p3 + ); + } + if( opflags & OPFLAG_ISNOOP ) break; +#endif - rc = sqlite3BtreeDelete(pC->pCursor, pOp->p5); + /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ + assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 ); + assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION ); + assert( OPFLAG_AUXDELETE==BTREE_AUXDELETE ); + +#ifdef SQLITE_DEBUG + if( p->pFrame==0 ){ + if( pC->isEphemeral==0 + && (pOp->p5 & OPFLAG_AUXDELETE)==0 + && (pC->wrFlag & OPFLAG_FORDELETE)==0 + ){ + nExtraDelete++; + } + if( pOp->p2 & OPFLAG_NCHANGE ){ + nExtraDelete--; + } + } +#endif + + rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + if( rc ) goto abort_due_to_error; /* Invoke the update-hook if required. */ - if( rc==SQLITE_OK && hasUpdateCallback ){ - db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, - db->aDb[pC->iDb].zName, pOp->p4.z, pC->movetoTarget); - assert( pC->iDb>=0 ); + if( opflags & OPFLAG_NCHANGE ){ + p->nChange++; + if( db->xUpdateCallback && HasRowid(pTab) ){ + db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName, + pC->movetoTarget); + assert( pC->iDb>=0 ); + } } - if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++; + break; } /* Opcode: ResetCount * * * * * @@ -77283,6 +81206,7 @@ case OP_SorterCompare: { res = 0; rc = sqlite3VdbeSorterCompare(pC, pIn3, nKeyCol, &res); VdbeBranchTaken(res!=0,2); + if( rc ) goto abort_due_to_error; if( res ) goto jump_to_p2; break; }; @@ -77308,6 +81232,7 @@ case OP_SorterData: { rc = sqlite3VdbeSorterRowkey(pC, pOut); assert( rc!=SQLITE_OK || (pOut->flags & MEM_Blob) ); assert( pOp->p1>=0 && pOp->p1nCursor ); + if( rc ) goto abort_due_to_error; p->apCsr[pOp->p3]->cacheStatus = CACHE_STALE; break; } @@ -77347,14 +81272,14 @@ case OP_RowData: { /* Note that RowKey and RowData are really exactly the same instruction */ assert( pOp->p1>=0 && pOp->p1nCursor ); 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!=0 ); assert( pC->nullRow==0 ); - assert( pC->pseudoTableReg==0 ); - assert( pC->pCursor!=0 ); - pCrsr = pC->pCursor; + assert( pC->uc.pCursor!=0 ); + pCrsr = pC->uc.pCursor; /* The OP_RowKey and OP_RowData opcodes always follow OP_NotExists or ** OP_Rewind/Op_Next with no intervening instructions that might invalidate @@ -77396,6 +81321,7 @@ case OP_RowData: { }else{ rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z); } + if( rc ) goto abort_due_to_error; pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ UPDATE_MAX_BLOBSIZE(pOut); REGISTER_TRACE(pOp->p2, pOut); @@ -77422,29 +81348,32 @@ case OP_Rowid: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->pseudoTableReg==0 || pC->nullRow ); + assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); if( pC->nullRow ){ pOut->flags = MEM_Null; break; }else if( pC->deferredMoveto ){ v = pC->movetoTarget; #ifndef SQLITE_OMIT_VIRTUALTABLE - }else if( pC->pVtabCursor ){ - pVtab = pC->pVtabCursor->pVtab; + }else if( pC->eCurType==CURTYPE_VTAB ){ + assert( pC->uc.pVCur!=0 ); + pVtab = pC->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xRowid ); - rc = pModule->xRowid(pC->pVtabCursor, &v); + rc = pModule->xRowid(pC->uc.pVCur, &v); sqlite3VtabImportErrmsg(p, pVtab); + if( rc ) goto abort_due_to_error; #endif /* SQLITE_OMIT_VIRTUALTABLE */ }else{ - assert( pC->pCursor!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); rc = sqlite3VdbeCursorRestore(pC); if( rc ) goto abort_due_to_error; if( pC->nullRow ){ pOut->flags = MEM_Null; break; } - rc = sqlite3BtreeKeySize(pC->pCursor, &v); + rc = sqlite3BtreeKeySize(pC->uc.pCursor, &v); assert( rc==SQLITE_OK ); /* Always so because of CursorRestore() above */ } pOut->u.i = v; @@ -77465,8 +81394,9 @@ case OP_NullRow: { assert( pC!=0 ); pC->nullRow = 1; pC->cacheStatus = CACHE_STALE; - if( pC->pCursor ){ - sqlite3BtreeClearCursor(pC->pCursor); + if( pC->eCurType==CURTYPE_BTREE ){ + assert( pC->uc.pCursor!=0 ); + sqlite3BtreeClearCursor(pC->uc.pCursor); } break; } @@ -77491,7 +81421,8 @@ case OP_Last: { /* jump */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - pCrsr = pC->pCursor; + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; res = 0; assert( pCrsr!=0 ); rc = sqlite3BtreeLast(pCrsr, &res); @@ -77502,6 +81433,7 @@ case OP_Last: { /* jump */ #ifdef SQLITE_DEBUG pC->seekOp = OP_Last; #endif + if( rc ) goto abort_due_to_error; if( pOp->p2>0 ){ VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; @@ -77559,12 +81491,14 @@ case OP_Rewind: { /* jump */ if( isSorter(pC) ){ rc = sqlite3VdbeSorterRewind(pC, &res); }else{ - pCrsr = pC->pCursor; + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; assert( pCrsr ); rc = sqlite3BtreeFirst(pCrsr, &res); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; } + if( rc ) goto abort_due_to_error; pC->nullRow = (u8)res; assert( pOp->p2>0 && pOp->p2nOp ); VdbeBranchTaken(res!=0,2); @@ -77656,7 +81590,7 @@ case OP_Next: /* jump */ res = pOp->p3; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); - assert( pC->pCursor ); + 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 ); @@ -77673,10 +81607,11 @@ case OP_Next: /* jump */ || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE || pC->seekOp==OP_Last ); - rc = pOp->p4.xAdvance(pC->pCursor, &res); + rc = pOp->p4.xAdvance(pC->uc.pCursor, &res); next_tail: pC->cacheStatus = CACHE_STALE; VdbeBranchTaken(res==0,2); + if( rc ) goto abort_due_to_error; if( res==0 ){ pC->nullRow = 0; p->aCounter[pOp->p5]++; @@ -77724,22 +81659,22 @@ case OP_IdxInsert: { /* in2 */ pIn2 = &aMem[pOp->p2]; assert( pIn2->flags & MEM_Blob ); if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - assert( pC->pCursor!=0 ); + assert( pC->eCurType==CURTYPE_BTREE || pOp->opcode==OP_SorterInsert ); assert( pC->isTable==0 ); rc = ExpandBlob(pIn2); - if( rc==SQLITE_OK ){ - if( pOp->opcode==OP_SorterInsert ){ - rc = sqlite3VdbeSorterWrite(pC, pIn2); - }else{ - nKey = pIn2->n; - zKey = pIn2->z; - rc = sqlite3BtreeInsert(pC->pCursor, zKey, nKey, "", 0, 0, pOp->p3, - ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) - ); - assert( pC->deferredMoveto==0 ); - pC->cacheStatus = CACHE_STALE; - } + if( rc ) goto abort_due_to_error; + if( pOp->opcode==OP_SorterInsert ){ + rc = sqlite3VdbeSorterWrite(pC, pIn2); + }else{ + nKey = pIn2->n; + zKey = pIn2->z; + rc = sqlite3BtreeInsert(pC->uc.pCursor, zKey, nKey, "", 0, 0, pOp->p3, + ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) + ); + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; } + if( rc) goto abort_due_to_error; break; } @@ -77757,29 +81692,48 @@ case OP_IdxDelete: { UnpackedRecord r; assert( pOp->p3>0 ); - assert( pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem-p->nCursor)+1 ); + assert( pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem+1 - p->nCursor)+1 ); assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - pCrsr = pC->pCursor; + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); assert( pOp->p5==0 ); r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p3; r.default_rc = 0; r.aMem = &aMem[pOp->p2]; -#ifdef SQLITE_DEBUG - { int i; for(i=0; ideferredMoveto==0 ); pC->cacheStatus = CACHE_STALE; break; } +/* Opcode: Seek P1 * P3 P4 * +** Synopsis: Move P3 to P1.rowid +** +** 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 +** to the row that corresponds to the current row of P1. +** +** This is a deferred seek. Nothing actually happens until +** the cursor is used to read a record. That way, if no reads +** occur, no unnecessary I/O happens. +** +** P4 may be an array of integers (type P4_INTARRAY) containing +** one entry for each column in the P3 table. If array entry a(i) +** is non-zero, then reading column a(i)-1 from cursor P3 is +** equivalent to performing the deferred seek and then reading column i +** from P1. This information is stored in P3 and used to redirect +** reads against P3 over to P1, thus possibly avoiding the need to +** seek and read cursor P3. +*/ /* Opcode: IdxRowid P1 P2 * * * ** Synopsis: r[P2]=rowid ** @@ -77789,36 +81743,57 @@ case OP_IdxDelete: { ** ** See also: Rowid, MakeRecord. */ +case OP_Seek: case OP_IdxRowid: { /* out2 */ - BtCursor *pCrsr; - VdbeCursor *pC; - i64 rowid; + VdbeCursor *pC; /* The P1 index cursor */ + VdbeCursor *pTabCur; /* The P2 table cursor (OP_Seek only) */ + i64 rowid; /* Rowid that P1 current points to */ - pOut = out2Prerelease(p, pOp); assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - pCrsr = pC->pCursor; - assert( pCrsr!=0 ); - pOut->flags = MEM_Null; + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); assert( pC->deferredMoveto==0 ); + 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); /* sqlite3VbeCursorRestore() can only fail if the record has been deleted - ** out from under the cursor. That will never happend for an IdxRowid - ** opcode, hence the NEVER() arround the check of the return value. - */ - rc = sqlite3VdbeCursorRestore(pC); + ** 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, pCrsr, &rowid); + rc = sqlite3VdbeIdxRowid(db, pC->uc.pCursor, &rowid); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - pOut->u.i = rowid; - pOut->flags = MEM_Int; + if( pOp->opcode==OP_Seek ){ + assert( pOp->p3>=0 && pOp->p3nCursor ); + pTabCur = p->apCsr[pOp->p3]; + assert( pTabCur!=0 ); + assert( pTabCur->eCurType==CURTYPE_BTREE ); + assert( pTabCur->uc.pCursor!=0 ); + assert( pTabCur->isTable ); + pTabCur->nullRow = 0; + pTabCur->movetoTarget = rowid; + pTabCur->deferredMoveto = 1; + assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); + pTabCur->aAltMap = pOp->p4.ai; + pTabCur->pAltCursor = pC; + }else{ + pOut = out2Prerelease(p, pOp); + pOut->u.i = rowid; + pOut->flags = MEM_Int; + } + }else{ + assert( pOp->opcode==OP_IdxRowid ); + sqlite3VdbeMemSetNull(&aMem[pOp->p2]); } break; } @@ -77879,7 +81854,8 @@ case OP_IdxGE: { /* jump */ pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->isOrdered ); - assert( pC->pCursor!=0); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0); assert( pC->deferredMoveto==0 ); assert( pOp->p5==0 || pOp->p5==1 ); assert( pOp->p4type==P4_INT32 ); @@ -77907,6 +81883,7 @@ case OP_IdxGE: { /* jump */ res++; } VdbeBranchTaken(res>0,2); + if( rc ) goto abort_due_to_error; if( res>0 ) goto jump_to_p2; break; } @@ -77936,11 +81913,13 @@ case OP_Destroy: { /* out2 */ int iDb; assert( p->readOnly==0 ); + assert( pOp->p1>1 ); pOut = out2Prerelease(p, pOp); pOut->flags = MEM_Null; if( db->nVdbeRead > db->nVDestroy+1 ){ rc = SQLITE_LOCKED; p->errorAction = OE_Abort; + goto abort_due_to_error; }else{ iDb = pOp->p3; assert( DbMaskTest(p->btreeMask, iDb) ); @@ -77948,8 +81927,9 @@ case OP_Destroy: { /* out2 */ rc = sqlite3BtreeDropTable(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( rc==SQLITE_OK && iMoved!=0 ){ + if( iMoved!=0 ){ sqlite3RootPageMoved(db, iDb, iMoved, pOp->p1); /* All OP_Destroy operations occur on the same btree */ assert( resetSchemaOnFault==0 || resetSchemaOnFault==iDb+1 ); @@ -77995,6 +81975,7 @@ case OP_Clear: { aMem[pOp->p3].u.i += nChange; } } + if( rc ) goto abort_due_to_error; break; } @@ -78012,11 +81993,13 @@ case OP_ResetSorter: { assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - if( pC->pSorter ){ - sqlite3VdbeSorterReset(db, pC->pSorter); + if( isSorter(pC) ){ + sqlite3VdbeSorterReset(db, pC->uc.pSorter); }else{ + assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->isEphemeral ); - rc = sqlite3BtreeClearTableOfCursor(pC->pCursor); + rc = sqlite3BtreeClearTableOfCursor(pC->uc.pCursor); + if( rc ) goto abort_due_to_error; } break; } @@ -78065,6 +82048,7 @@ case OP_CreateTable: { /* out2 */ flags = BTREE_BLOBKEY; } rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags); + if( rc ) goto abort_due_to_error; pOut->u.i = pgno; break; } @@ -78105,7 +82089,7 @@ case OP_ParseSchema: { "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid", db->aDb[iDb].zName, zMaster, pOp->p4.z); if( zSql==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ assert( db->init.busy==0 ); db->init.busy = 1; @@ -78117,9 +82101,12 @@ case OP_ParseSchema: { db->init.busy = 0; } } - if( rc ) sqlite3ResetAllSchemasOfConnection(db); - if( rc==SQLITE_NOMEM ){ - goto no_mem; + if( rc ){ + sqlite3ResetAllSchemasOfConnection(db); + if( rc==SQLITE_NOMEM ){ + goto no_mem; + } + goto abort_due_to_error; } break; } @@ -78134,6 +82121,7 @@ case OP_ParseSchema: { case OP_LoadAnalysis: { assert( pOp->p1>=0 && pOp->p1nDb ); rc = sqlite3AnalysisLoad(db, pOp->p1); + if( rc ) goto abort_due_to_error; break; } #endif /* !defined(SQLITE_OMIT_ANALYZE) */ @@ -78179,7 +82167,7 @@ case OP_DropTrigger: { #ifndef SQLITE_OMIT_INTEGRITY_CHECK -/* Opcode: IntegrityCk P1 P2 P3 * P5 +/* Opcode: IntegrityCk P1 P2 P3 P4 P5 ** ** Do an analysis of the currently open database. Store in ** register P1 the text of an error message describing any problems. @@ -78190,9 +82178,8 @@ case OP_DropTrigger: { ** In other words, the analysis stops as soon as reg(P1) errors are ** seen. Reg(P1) is updated with the number of errors remaining. ** -** The root page numbers of all tables in the database are integer -** stored in reg(P1), reg(P1+1), reg(P1+2), .... There are P2 tables -** total. +** The root page numbers of all tables in the database are integers +** stored in P4_INTARRAY argument. ** ** If P5 is not zero, the check is done on the auxiliary database ** file, not the main database file. @@ -78202,30 +82189,24 @@ case OP_DropTrigger: { case OP_IntegrityCk: { int nRoot; /* Number of tables to check. (Number of root pages.) */ int *aRoot; /* Array of rootpage numbers for tables to be checked */ - int j; /* Loop counter */ int nErr; /* Number of errors reported */ char *z; /* Text of the error report */ Mem *pnErr; /* Register keeping track of errors remaining */ assert( p->bIsReader ); nRoot = pOp->p2; + aRoot = pOp->p4.ai; assert( nRoot>0 ); - aRoot = sqlite3DbMallocRaw(db, sizeof(int)*(nRoot+1) ); - if( aRoot==0 ) goto no_mem; - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); + assert( aRoot[nRoot]==0 ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pnErr = &aMem[pOp->p3]; assert( (pnErr->flags & MEM_Int)!=0 ); assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 ); pIn1 = &aMem[pOp->p1]; - for(j=0; jp5nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot, (int)pnErr->u.i, &nErr); - sqlite3DbFree(db, aRoot); pnErr->u.i -= nErr; sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ @@ -78393,7 +82374,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"); - break; + goto abort_due_to_error; } /* Register pRt is used to store the memory required to save the state @@ -78407,6 +82388,8 @@ case OP_Program: { /* jump */ ** variable nMem (and later, VdbeFrame.nChildMem) to this value. */ nMem = pProgram->nMem + pProgram->nCsr; + assert( nMem>0 ); + if( pProgram->nCsr==0 ) nMem++; nByte = ROUND8(sizeof(VdbeFrame)) + nMem * sizeof(Mem) + pProgram->nCsr * sizeof(VdbeCursor *) @@ -78443,7 +82426,8 @@ case OP_Program: { /* jump */ } }else{ pFrame = pRt->u.pFrame; - assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem ); + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem + || (pProgram->nCsr==0 && pProgram->nMem+1==pFrame->nChildMem) ); assert( pProgram->nCsr==pFrame->nChildCsr ); assert( (int)(pOp - aOp)==pFrame->pc ); } @@ -78453,12 +82437,15 @@ case OP_Program: { /* jump */ pFrame->lastRowid = lastRowid; pFrame->nChange = p->nChange; pFrame->nDbChange = p->db->nChange; + assert( pFrame->pAuxData==0 ); + pFrame->pAuxData = p->pAuxData; + p->pAuxData = 0; p->nChange = 0; p->pFrame = pFrame; - p->aMem = aMem = &VdbeFrameMem(pFrame)[-1]; + p->aMem = aMem = VdbeFrameMem(pFrame); p->nMem = pFrame->nChildMem; p->nCursor = (u16)pFrame->nChildCsr; - p->apCsr = (VdbeCursor **)&aMem[p->nMem+1]; + p->apCsr = (VdbeCursor **)&aMem[p->nMem]; p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor]; @@ -78592,20 +82579,31 @@ case OP_IfPos: { /* jump, in1 */ break; } -/* Opcode: SetIfNotPos P1 P2 P3 * * -** Synopsis: if r[P1]<=0 then r[P2]=P3 +/* Opcode: OffsetLimit P1 P2 P3 * * +** Synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) ** -** Register P1 must contain an integer. -** If the value of register P1 is not positive (if it is less than 1) then -** set the value of register P2 to be the integer P3. +** This opcode performs a commonly used computation associated with +** LIMIT and OFFSET process. r[P1] holds the limit counter. r[P3] +** holds the offset counter. The opcode computes the combined value +** of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2] +** value computed is the total number of rows that will need to be +** visited in order to complete the query. +** +** If r[P3] is zero or negative, that means there is no OFFSET +** and r[P2] is set to be the value of the LIMIT, r[P1]. +** +** if r[P1] is zero or negative, that means there is no LIMIT +** and r[P2] is set to -1. +** +** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. */ -case OP_SetIfNotPos: { /* in1, in2 */ +case OP_OffsetLimit: { /* in1, out2, in3 */ pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags&MEM_Int ); - if( pIn1->u.i<=0 ){ - pOut = out2Prerelease(p, pOp); - pOut->u.i = pOp->p3; - } + 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); break; } @@ -78644,21 +82642,6 @@ case OP_DecrJumpZero: { /* jump, in1 */ } -/* Opcode: JumpZeroIncr P1 P2 * * * -** Synopsis: if (r[P1]++)==0 ) goto P2 -** -** The register P1 must contain an integer. If register P1 is initially -** zero, then jump to P2. Increment register P1 regardless of whether or -** not the jump is taken. -*/ -case OP_JumpZeroIncr: { /* jump, in1 */ - pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags&MEM_Int ); - VdbeBranchTaken(pIn1->u.i==0, 2); - if( (pIn1->u.i++)==0 ) goto jump_to_p2; - break; -} - /* Opcode: AggStep0 * P2 P3 P4 P5 ** Synopsis: accum=r[P3] step(r[P2@P5]) ** @@ -78693,10 +82676,10 @@ case OP_AggStep0: { assert( pOp->p4type==P4_FUNCDEF ); n = pOp->p5; - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem-p->nCursor)+1) ); + 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->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRaw(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); + pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); if( pCtx==0 ) goto no_mem; pCtx->pMem = 0; pCtx->pFunc = pOp->p4.pFunc; @@ -78739,13 +82722,14 @@ case OP_AggStep: { pCtx->pOut = &t; pCtx->fErrorOrAux = 0; pCtx->skipFlag = 0; - (pCtx->pFunc->xStep)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ + (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)); rc = pCtx->isError; } sqlite3VdbeMemRelease(&t); + if( rc ) goto abort_due_to_error; }else{ assert( t.flags==MEM_Null ); } @@ -78772,12 +82756,13 @@ case OP_AggStep: { */ case OP_AggFinal: { Mem *pMem; - assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); pMem = &aMem[pOp->p1]; assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); if( rc ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem)); + goto abort_due_to_error; } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); @@ -78813,7 +82798,8 @@ case OP_Checkpoint: { || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE ); rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); - if( rc==SQLITE_BUSY ){ + if( rc ){ + if( rc!=SQLITE_BUSY ) goto abort_due_to_error; rc = SQLITE_OK; aRes[0] = 1; } @@ -78886,7 +82872,7 @@ case OP_JournalMode: { /* out2 */ "cannot change %s wal mode from within a transaction", (eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of") ); - break; + goto abort_due_to_error; }else{ if( eOld==PAGER_JOURNALMODE_WAL ){ @@ -78916,9 +82902,7 @@ case OP_JournalMode: { /* out2 */ } #endif /* ifndef SQLITE_OMIT_WAL */ - if( rc ){ - eNew = eOld; - } + if( rc ) eNew = eOld; eNew = sqlite3PagerSetJournalMode(pPager, eNew); pOut->flags = MEM_Str|MEM_Static|MEM_Term; @@ -78926,6 +82910,7 @@ case OP_JournalMode: { /* out2 */ pOut->n = sqlite3Strlen30(pOut->z); pOut->enc = SQLITE_UTF8; sqlite3VdbeChangeEncoding(pOut, encoding); + if( rc ) goto abort_due_to_error; break; }; #endif /* SQLITE_OMIT_PRAGMA */ @@ -78940,6 +82925,7 @@ case OP_JournalMode: { /* out2 */ case OP_Vacuum: { assert( p->readOnly==0 ); rc = sqlite3RunVacuum(&p->zErrMsg, db); + if( rc ) goto abort_due_to_error; break; } #endif @@ -78960,7 +82946,8 @@ case OP_IncrVacuum: { /* jump */ pBt = db->aDb[pOp->p1].pBt; rc = sqlite3BtreeIncrVacuum(pBt); VdbeBranchTaken(rc==SQLITE_DONE,2); - if( rc==SQLITE_DONE ){ + if( rc ){ + if( rc!=SQLITE_DONE ) goto abort_due_to_error; rc = SQLITE_OK; goto jump_to_p2; } @@ -79011,9 +82998,12 @@ case OP_TableLock: { assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); - if( (rc&0xFF)==SQLITE_LOCKED ){ - const char *z = pOp->p4.z; - sqlite3VdbeError(p, "database table is locked: %s", z); + if( rc ){ + if( (rc&0xFF)==SQLITE_LOCKED ){ + const char *z = pOp->p4.z; + sqlite3VdbeError(p, "database table is locked: %s", z); + } + goto abort_due_to_error; } } break; @@ -79035,6 +83025,7 @@ case OP_VBegin: { pVTab = pOp->p4.pVtab; rc = sqlite3VtabBegin(db, pVTab); if( pVTab ) sqlite3VtabImportErrmsg(p, pVTab->pVtab); + if( rc ) goto abort_due_to_error; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -79063,6 +83054,7 @@ case OP_VCreate: { rc = sqlite3VtabCallCreate(db, pOp->p1, zTab, &p->zErrMsg); } sqlite3VdbeMemRelease(&sMem); + if( rc ) goto abort_due_to_error; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -79077,6 +83069,7 @@ case OP_VDestroy: { db->nVDestroy++; rc = sqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z); db->nVDestroy--; + if( rc ) goto abort_due_to_error; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -79090,35 +83083,35 @@ case OP_VDestroy: { */ case OP_VOpen: { VdbeCursor *pCur; - sqlite3_vtab_cursor *pVtabCursor; + sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; const sqlite3_module *pModule; assert( p->bIsReader ); pCur = 0; - pVtabCursor = 0; + pVCur = 0; pVtab = pOp->p4.pVtab->pVtab; if( pVtab==0 || NEVER(pVtab->pModule==0) ){ rc = SQLITE_LOCKED; - break; + goto abort_due_to_error; } pModule = pVtab->pModule; - rc = pModule->xOpen(pVtab, &pVtabCursor); + rc = pModule->xOpen(pVtab, &pVCur); sqlite3VtabImportErrmsg(p, pVtab); - if( SQLITE_OK==rc ){ - /* Initialize sqlite3_vtab_cursor base class */ - pVtabCursor->pVtab = pVtab; + if( rc ) goto abort_due_to_error; - /* Initialize vdbe cursor object */ - pCur = allocateCursor(p, pOp->p1, 0, -1, 0); - if( pCur ){ - pCur->pVtabCursor = pVtabCursor; - pVtab->nRef++; - }else{ - assert( db->mallocFailed ); - pModule->xClose(pVtabCursor); - goto no_mem; - } + /* Initialize sqlite3_vtab_cursor base class */ + pVCur->pVtab = pVtab; + + /* Initialize vdbe cursor object */ + pCur = allocateCursor(p, pOp->p1, 0, -1, CURTYPE_VTAB); + if( pCur ){ + pCur->uc.pVCur = pVCur; + pVtab->nRef++; + }else{ + assert( db->mallocFailed ); + pModule->xClose(pVCur); + goto no_mem; } break; } @@ -79150,7 +83143,7 @@ case OP_VFilter: { /* jump */ const sqlite3_module *pModule; Mem *pQuery; Mem *pArgc; - sqlite3_vtab_cursor *pVtabCursor; + sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; VdbeCursor *pCur; int res; @@ -79162,9 +83155,9 @@ case OP_VFilter: { /* jump */ pCur = p->apCsr[pOp->p1]; assert( memIsValid(pQuery) ); REGISTER_TRACE(pOp->p3, pQuery); - assert( pCur->pVtabCursor ); - pVtabCursor = pCur->pVtabCursor; - pVtab = pVtabCursor->pVtab; + assert( pCur->eCurType==CURTYPE_VTAB ); + pVCur = pCur->uc.pVCur; + pVtab = pVCur->pVtab; pModule = pVtab->pModule; /* Grab the index number and argc parameters */ @@ -79178,11 +83171,10 @@ case OP_VFilter: { /* jump */ for(i = 0; ixFilter(pVtabCursor, iQuery, pOp->p4.z, nArg, apArg); + rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg); sqlite3VtabImportErrmsg(p, pVtab); - if( rc==SQLITE_OK ){ - res = pModule->xEof(pVtabCursor); - } + if( rc ) goto abort_due_to_error; + res = pModule->xEof(pVCur); pCur->nullRow = 0; VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; @@ -79205,21 +83197,21 @@ case OP_VColumn: { sqlite3_context sContext; VdbeCursor *pCur = p->apCsr[pOp->p1]; - assert( pCur->pVtabCursor ); - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); + assert( pCur->eCurType==CURTYPE_VTAB ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; memAboutToChange(p, pDest); if( pCur->nullRow ){ sqlite3VdbeMemSetNull(pDest); break; } - pVtab = pCur->pVtabCursor->pVtab; + pVtab = pCur->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xColumn ); memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; MemSetTypeFlag(pDest, MEM_Null); - rc = pModule->xColumn(pCur->pVtabCursor, &sContext, pOp->p2); + rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2); sqlite3VtabImportErrmsg(p, pVtab); if( sContext.isError ){ rc = sContext.isError; @@ -79231,6 +83223,7 @@ case OP_VColumn: { if( sqlite3VdbeMemTooBig(pDest) ){ goto too_big; } + if( rc ) goto abort_due_to_error; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -79250,11 +83243,11 @@ case OP_VNext: { /* jump */ res = 0; pCur = p->apCsr[pOp->p1]; - assert( pCur->pVtabCursor ); + assert( pCur->eCurType==CURTYPE_VTAB ); if( pCur->nullRow ){ break; } - pVtab = pCur->pVtabCursor->pVtab; + pVtab = pCur->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xNext ); @@ -79264,11 +83257,10 @@ case OP_VNext: { /* jump */ ** data is available) and the error code returned when xColumn or ** some other method is next invoked on the save virtual table cursor. */ - rc = pModule->xNext(pCur->pVtabCursor); + rc = pModule->xNext(pCur->uc.pVCur); sqlite3VtabImportErrmsg(p, pVtab); - if( rc==SQLITE_OK ){ - res = pModule->xEof(pCur->pVtabCursor); - } + if( rc ) goto abort_due_to_error; + res = pModule->xEof(pCur->uc.pVCur); VdbeBranchTaken(!res,2); if( !res ){ /* If there is data, jump to P2 */ @@ -79300,11 +83292,11 @@ case OP_VRename: { testcase( pName->enc==SQLITE_UTF16BE ); testcase( pName->enc==SQLITE_UTF16LE ); rc = sqlite3VdbeChangeEncoding(pName, SQLITE_UTF8); - if( rc==SQLITE_OK ){ - rc = pVtab->pModule->xRename(pVtab, pName->z); - sqlite3VtabImportErrmsg(p, pVtab); - p->expired = 0; - } + if( rc ) goto abort_due_to_error; + rc = pVtab->pModule->xRename(pVtab, pName->z); + sqlite3VtabImportErrmsg(p, pVtab); + p->expired = 0; + if( rc ) goto abort_due_to_error; break; } #endif @@ -79353,7 +83345,7 @@ case OP_VUpdate: { pVtab = pOp->p4.pVtab->pVtab; if( pVtab==0 || NEVER(pVtab->pModule==0) ){ rc = SQLITE_LOCKED; - break; + goto abort_due_to_error; } pModule = pVtab->pModule; nArg = pOp->p2; @@ -79385,6 +83377,7 @@ case OP_VUpdate: { }else{ p->nChange++; } + if( rc ) goto abort_due_to_error; } break; } @@ -79476,6 +83469,28 @@ case OP_Init: { /* jump */ break; } +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* Opcode: CursorHint P1 * * P4 * +** +** Provide a hint to cursor P1 that it only needs to return rows that +** satisfy the Expr in P4. TK_REGISTER terms in the P4 expression refer +** to values currently held in registers. TK_COLUMN terms in the P4 +** expression refer to columns in the b-tree to which cursor P1 is pointing. +*/ +case OP_CursorHint: { + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p4type==P4_EXPR ); + pC = p->apCsr[pOp->p1]; + if( pC ){ + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE, + pOp->p4.pExpr, aMem); + } + break; +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ /* Opcode: Noop * * * * * ** @@ -79519,11 +83534,12 @@ default: { /* This is really OP_Noop and OP_Explain */ #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeTrace ){ + u8 opProperty = sqlite3OpcodeProperty[pOrigOp->opcode]; if( rc!=0 ) printf("rc=%d\n",rc); - if( pOrigOp->opflags & (OPFLG_OUT2) ){ + if( opProperty & (OPFLG_OUT2) ){ registerTrace(pOrigOp->p2, &aMem[pOrigOp->p2]); } - if( pOrigOp->opflags & OPFLG_OUT3 ){ + if( opProperty & OPFLG_OUT3 ){ registerTrace(pOrigOp->p3, &aMem[pOrigOp->p3]); } } @@ -79534,14 +83550,19 @@ default: { /* This is really OP_Noop and OP_Explain */ /* If we reach this point, it means that execution is finished with ** an error of some kind. */ -vdbe_error_halt: +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)); + } p->rc = rc; + sqlite3SystemError(db, rc); testcase( sqlite3GlobalConfig.xLog!=0 ); sqlite3_log(rc, "statement aborts at %d: [%s] %s", (int)(pOp - aOp), p->zSql, p->zErrMsg); sqlite3VdbeHalt(p); - if( rc==SQLITE_IOERR_NOMEM ) db->mallocFailed = 1; + if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ sqlite3ResetOneSchema(db, resetSchemaOnFault-1); @@ -79555,6 +83576,9 @@ vdbe_return: testcase( nVmStep>0 ); p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; sqlite3VdbeLeave(p); + assert( rc!=SQLITE_OK || nExtraDelete==0 + || sqlite3_strlike("DELETE%",p->zSql,0)!=0 + ); return rc; /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH @@ -79563,36 +83587,25 @@ vdbe_return: too_big: sqlite3VdbeError(p, "string or blob too big"); rc = SQLITE_TOOBIG; - goto vdbe_error_halt; + goto abort_due_to_error; /* Jump to here if a malloc() fails. */ no_mem: - db->mallocFailed = 1; + sqlite3OomFault(db); sqlite3VdbeError(p, "out of memory"); - rc = SQLITE_NOMEM; - goto vdbe_error_halt; - - /* Jump to here for any other kind of fatal error. The "rc" variable - ** should hold the error number. - */ -abort_due_to_error: - assert( p->zErrMsg==0 ); - if( db->mallocFailed ) rc = SQLITE_NOMEM; - if( rc!=SQLITE_IOERR_NOMEM ){ - sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); - } - goto vdbe_error_halt; + rc = SQLITE_NOMEM_BKPT; + goto abort_due_to_error; /* Jump to here if the sqlite3_interrupt() API sets the interrupt ** flag. */ abort_due_to_interrupt: assert( db->u1.isInterrupted ); - rc = SQLITE_INTERRUPT; + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; p->rc = rc; sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); - goto vdbe_error_halt; + goto abort_due_to_error; } @@ -79630,6 +83643,8 @@ struct Incrblob { BtCursor *pCsr; /* Cursor pointing at blob row */ sqlite3_stmt *pStmt; /* Statement holding cursor open */ sqlite3 *db; /* The associated database */ + char *zDb; /* Database name */ + Table *pTab; /* Table object */ }; @@ -79676,7 +83691,7 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ }else{ p->iOffset = pC->aType[p->iCol + pC->nField]; p->nByte = sqlite3VdbeSerialTypeLen(type); - p->pCsr = pC->pCursor; + p->pCsr = pC->uc.pCursor; sqlite3BtreeIncrblobCursor(p->pCsr); } } @@ -79715,38 +83730,6 @@ SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( ){ int nAttempt = 0; int iCol; /* Index of zColumn in row-record */ - - /* This VDBE program seeks a btree cursor to the identified - ** db/table/row entry. The reason for using a vdbe program instead - ** of writing code to use the b-tree layer directly is that the - ** vdbe program will take advantage of the various transaction, - ** locking and error handling infrastructure built into the vdbe. - ** - ** After seeking the cursor, the vdbe executes an OP_ResultRow. - ** Code external to the Vdbe then "borrows" the b-tree cursor and - ** uses it to implement the blob_read(), blob_write() and - ** blob_bytes() functions. - ** - ** The sqlite3_blob_close() function finalizes the vdbe program, - ** which closes the b-tree cursor and (possibly) commits the - ** transaction. - */ - static const int iLn = VDBE_OFFSET_LINENO(4); - static const VdbeOpList openBlob[] = { - /* {OP_Transaction, 0, 0, 0}, // 0: Inserted separately */ - {OP_TableLock, 0, 0, 0}, /* 1: Acquire a read or write lock */ - /* One of the following two instructions is replaced by an OP_Noop. */ - {OP_OpenRead, 0, 0, 0}, /* 2: Open cursor 0 for reading */ - {OP_OpenWrite, 0, 0, 0}, /* 3: Open cursor 0 for read/write */ - {OP_Variable, 1, 1, 1}, /* 4: Push the rowid to the stack */ - {OP_NotExists, 0, 10, 1}, /* 5: Seek the cursor */ - {OP_Column, 0, 0, 1}, /* 6 */ - {OP_ResultRow, 1, 0, 0}, /* 7 */ - {OP_Goto, 0, 4, 0}, /* 8 */ - {OP_Close, 0, 0, 0}, /* 9 */ - {OP_Halt, 0, 0, 0}, /* 10 */ - }; - int rc = SQLITE_OK; char *zErr = 0; Table *pTab; @@ -79805,6 +83788,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( sqlite3BtreeLeaveAll(db); goto blob_open_out; } + pBlob->pTab = pTab; + pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zName; /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { @@ -79865,45 +83850,78 @@ SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(pParse); assert( pBlob->pStmt || db->mallocFailed ); if( pBlob->pStmt ){ + + /* This VDBE program seeks a btree cursor to the identified + ** db/table/row entry. The reason for using a vdbe program instead + ** of writing code to use the b-tree layer directly is that the + ** vdbe program will take advantage of the various transaction, + ** locking and error handling infrastructure built into the vdbe. + ** + ** After seeking the cursor, the vdbe executes an OP_ResultRow. + ** Code external to the Vdbe then "borrows" the b-tree cursor and + ** uses it to implement the blob_read(), blob_write() and + ** blob_bytes() functions. + ** + ** The sqlite3_blob_close() function finalizes the vdbe program, + ** which closes the b-tree cursor and (possibly) commits the + ** transaction. + */ + static const int iLn = VDBE_OFFSET_LINENO(2); + 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 */ + }; Vdbe *v = (Vdbe *)pBlob->pStmt; int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - + VdbeOp *aOp; sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, flags, pTab->pSchema->schema_cookie, pTab->pSchema->iGeneration); sqlite3VdbeChangeP5(v, 1); - sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn); + aOp = sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn); /* Make sure a mutex is held on the table to be accessed */ sqlite3VdbeUsesBtree(v, iDb); - /* Configure the OP_TableLock instruction */ + if( db->mallocFailed==0 ){ + assert( aOp!=0 ); + /* Configure the OP_TableLock instruction */ #ifdef SQLITE_OMIT_SHARED_CACHE - sqlite3VdbeChangeToNoop(v, 1); + aOp[0].opcode = OP_Noop; #else - sqlite3VdbeChangeP1(v, 1, iDb); - sqlite3VdbeChangeP2(v, 1, pTab->tnum); - sqlite3VdbeChangeP3(v, 1, flags); - sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT); + aOp[0].p1 = iDb; + aOp[0].p2 = pTab->tnum; + aOp[0].p3 = flags; + sqlite3VdbeChangeP4(v, 1, 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. */ - sqlite3VdbeChangeToNoop(v, 3 - flags); - sqlite3VdbeChangeP2(v, 2 + flags, pTab->tnum); - sqlite3VdbeChangeP3(v, 2 + flags, iDb); + /* Remove either the OP_OpenWrite or OpenRead. Set the P2 + ** parameter of the other to pTab->tnum. */ + if( flags ) aOp[1].opcode = OP_OpenWrite; + aOp[1].p2 = pTab->tnum; + aOp[1].p3 = iDb; + + /* Configure the number of columns. Configure the cursor to + ** think that the table has one more column than it really + ** does. An OP_Column to retrieve this imaginary column will + ** always return an SQL NULL. This is useful because it means + ** we can invoke OP_Column to fill in the vdbe cursors type + ** and offset cache without causing any IO. + */ + aOp[1].p4type = P4_INT32; + aOp[1].p4.i = pTab->nCol+1; + aOp[4].p2 = pTab->nCol; - /* Configure the number of columns. Configure the cursor to - ** think that the table has one more column than it really - ** does. An OP_Column to retrieve this imaginary column will - ** always return an SQL NULL. This is useful because it means - ** we can invoke OP_Column to fill in the vdbe cursors type - ** and offset cache without causing any IO. - */ - sqlite3VdbeChangeP4(v, 2+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32); - sqlite3VdbeChangeP2(v, 6, pTab->nCol); - if( !db->mallocFailed ){ pParse->nVar = 1; pParse->nMem = 1; pParse->nTab = 1; @@ -79993,6 +84011,30 @@ static int blobReadWrite( */ assert( db == v->db ); sqlite3BtreeEnterCursor(p->pCsr); + +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( xCall==sqlite3BtreePutData && db->xPreUpdateCallback ){ + /* If a pre-update hook is registered and this is a write cursor, + ** invoke it here. + ** + ** TODO: The preupdate-hook is passed SQLITE_DELETE, even though this + ** operation should really be an SQLITE_UPDATE. This is probably + ** incorrect, but is convenient because at this point the new.* values + ** are not easily obtainable. And for the sessions module, an + ** SQLITE_UPDATE where the PK columns do not change is handled in the + ** same way as an SQLITE_DELETE (the SQLITE_DELETE code is actually + ** slightly more efficient). Since you cannot write to a PK column + ** using the incremental-blob API, this works. For the sessions module + ** anyhow. + */ + sqlite3_int64 iKey; + sqlite3BtreeKeySize(p->pCsr, &iKey); + sqlite3VdbePreUpdateHook( + 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); if( rc==SQLITE_ABORT ){ @@ -80619,7 +84661,7 @@ static int vdbePmaReadBlob( int nNew = MAX(128, p->nAlloc*2); while( nByte>nNew ) nNew = nNew*2; aNew = sqlite3Realloc(p->aAlloc, nNew); - if( !aNew ) return SQLITE_NOMEM; + if( !aNew ) return SQLITE_NOMEM_BKPT; p->nAlloc = nNew; p->aAlloc = aNew; } @@ -80731,7 +84773,7 @@ static int vdbePmaReaderSeek( int iBuf = pReadr->iReadOff % pgsz; if( pReadr->aBuffer==0 ){ pReadr->aBuffer = (u8*)sqlite3Malloc(pgsz); - if( pReadr->aBuffer==0 ) rc = SQLITE_NOMEM; + if( pReadr->aBuffer==0 ) rc = SQLITE_NOMEM_BKPT; pReadr->nBuffer = pgsz; } if( rc==SQLITE_OK && iBuf ){ @@ -80816,7 +84858,7 @@ static int vdbePmaReaderInit( rc = vdbePmaReaderSeek(pTask, pReadr, pFile, iStart); if( rc==SQLITE_OK ){ - u64 nByte; /* Size of PMA in bytes */ + u64 nByte = 0; /* Size of PMA in bytes */ rc = vdbePmaReadVarint(pReadr, &nByte); pReadr->iEof = pReadr->iReadOff + nByte; *pnByte += nByte; @@ -81010,7 +85052,6 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( ){ int pgsz; /* Page size of main database */ int i; /* Used to iterate through aTask[] */ - int mxCache; /* Cache size */ VdbeSorter *pSorter; /* The new sorter */ KeyInfo *pKeyInfo; /* Copy of pCsr->pKeyInfo with db==0 */ int szKeyInfo; /* Size of pCsr->pKeyInfo in bytes */ @@ -81040,13 +85081,14 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( #endif assert( pCsr->pKeyInfo && pCsr->pBt==0 ); + assert( pCsr->eCurType==CURTYPE_SORTER ); szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*); sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); - pCsr->pSorter = pSorter; + pCsr->uc.pSorter = pSorter; if( pSorter==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz); memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo); @@ -81057,7 +85099,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt); pSorter->nTask = nWorker + 1; - pSorter->iPrev = nWorker-1; + pSorter->iPrev = (u8)(nWorker - 1); pSorter->bUseThreads = (pSorter->nTask>1); pSorter->db = db; for(i=0; inTask; i++){ @@ -81066,11 +85108,20 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } if( !sqlite3TempInMemory(db) ){ + i64 mxCache; /* Cache size in bytes*/ u32 szPma = sqlite3GlobalConfig.szPma; pSorter->mnPmaSize = szPma * pgsz; + mxCache = db->aDb[0].pSchema->cache_size; - if( mxCache<(int)szPma ) mxCache = (int)szPma; - pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ); + if( mxCache<0 ){ + /* A negative cache-size value C indicates that the cache is abs(C) + ** KiB in size. */ + mxCache = mxCache * -1024; + }else{ + mxCache = mxCache * pgsz; + } + 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 @@ -81080,7 +85131,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( assert( pSorter->iMemory==0 ); pSorter->nMemory = pgsz; pSorter->list.aMemory = (u8*)sqlite3Malloc(pgsz); - if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM; + if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM_BKPT; } } @@ -81328,12 +85379,14 @@ SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){ ** Free any cursor components allocated by sqlite3VdbeSorterXXX routines. */ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ - VdbeSorter *pSorter = pCsr->pSorter; + VdbeSorter *pSorter; + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; if( pSorter ){ sqlite3VdbeSorterReset(db, pSorter); sqlite3_free(pSorter->list.aMemory); sqlite3DbFree(db, pSorter); - pCsr->pSorter = 0; + pCsr->uc.pSorter = 0; } } @@ -81400,7 +85453,7 @@ static int vdbeSortAllocUnpacked(SortSubtask *pTask){ pTask->pSorter->pKeyInfo, 0, 0, &pFree ); assert( pTask->pUnpacked==(UnpackedRecord*)pFree ); - if( pFree==0 ) return SQLITE_NOMEM; + if( pFree==0 ) return SQLITE_NOMEM_BKPT; pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField; pTask->pUnpacked->errCode = 0; } @@ -81475,7 +85528,7 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ aSlot = (SorterRecord **)sqlite3MallocZero(64 * sizeof(SorterRecord *)); if( !aSlot ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } while( p ){ @@ -81525,7 +85578,7 @@ static void vdbePmaWriterInit( memset(p, 0, sizeof(PmaWriter)); p->aBuffer = (u8*)sqlite3Malloc(nBuf); if( !p->aBuffer ){ - p->eFWErr = SQLITE_NOMEM; + p->eFWErr = SQLITE_NOMEM_BKPT; }else{ p->iBufEnd = p->iBufStart = (iStart % nBuf); p->iWriteOff = iStart - p->iBufStart; @@ -81813,7 +85866,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ pSorter->nMemory = sqlite3MallocSize(aMem); }else if( pSorter->list.aMemory ){ pSorter->list.aMemory = sqlite3Malloc(pSorter->nMemory); - if( !pSorter->list.aMemory ) return SQLITE_NOMEM; + if( !pSorter->list.aMemory ) return SQLITE_NOMEM_BKPT; } rc = vdbeSorterCreateThread(pTask, vdbeSorterFlushThread, pCtx); @@ -81831,15 +85884,16 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite( const VdbeCursor *pCsr, /* Sorter cursor */ Mem *pVal /* Memory cell containing record */ ){ - VdbeSorter *pSorter = pCsr->pSorter; + VdbeSorter *pSorter; int rc = SQLITE_OK; /* Return Code */ SorterRecord *pNew; /* New list element */ - int bFlush; /* True to flush contents of memory to PMA */ int nReq; /* Bytes of memory required */ int nPMA; /* Bytes of PMA space required */ int t; /* serial type of first record field */ + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; getVarint32((const u8*)&pVal->z[1], t); if( t>0 && t<10 && t!=7 ){ pSorter->typeMask &= SORTER_TYPE_INTEGER; @@ -81896,27 +85950,28 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite( if( nMin>pSorter->nMemory ){ u8 *aNew; + int iListOff = (u8*)pSorter->list.pList - pSorter->list.aMemory; int nNew = pSorter->nMemory * 2; while( nNew < nMin ) nNew = nNew*2; if( nNew > pSorter->mxPmaSize ) nNew = pSorter->mxPmaSize; if( nNew < nMin ) nNew = nMin; aNew = sqlite3Realloc(pSorter->list.aMemory, nNew); - if( !aNew ) return SQLITE_NOMEM; - pSorter->list.pList = (SorterRecord*)( - aNew + ((u8*)pSorter->list.pList - pSorter->list.aMemory) - ); + if( !aNew ) return SQLITE_NOMEM_BKPT; + pSorter->list.pList = (SorterRecord*)&aNew[iListOff]; pSorter->list.aMemory = aNew; pSorter->nMemory = nNew; } pNew = (SorterRecord*)&pSorter->list.aMemory[pSorter->iMemory]; pSorter->iMemory += ROUND8(nReq); - pNew->u.iNext = (int)((u8*)(pSorter->list.pList) - pSorter->list.aMemory); + if( pSorter->list.pList ){ + pNew->u.iNext = (int)((u8*)(pSorter->list.pList) - pSorter->list.aMemory); + } }else{ pNew = (SorterRecord *)sqlite3Malloc(nReq); if( pNew==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pNew->u.pNext = pSorter->list.pList; } @@ -82063,7 +86118,7 @@ static int vdbeIncrMergerNew( pTask->file2.iEof += pIncr->mxSz; }else{ vdbeMergeEngineFree(pMerger); - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } return rc; } @@ -82368,10 +86423,10 @@ static int vdbeMergeEngineLevel0( int rc = SQLITE_OK; *ppOut = pNew = vdbeMergeEngineNew(nPMA); - if( pNew==0 ) rc = SQLITE_NOMEM; + if( pNew==0 ) rc = SQLITE_NOMEM_BKPT; for(i=0; iaReadr[i]; rc = vdbePmaReaderInit(pTask, &pTask->file, iOff, pReadr, &nDummy); iOff = pReadr->iEof; @@ -82439,7 +86494,7 @@ static int vdbeSorterAddToTree( if( pReadr->pIncr==0 ){ MergeEngine *pNew = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); if( pNew==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ rc = vdbeIncrMergerNew(pTask, pNew, &pReadr->pIncr); } @@ -82484,7 +86539,7 @@ static int vdbeSorterMergeTreeBuild( assert( pSorter->bUseThreads || pSorter->nTask==1 ); if( pSorter->nTask>1 ){ pMain = vdbeMergeEngineNew(pSorter->nTask); - if( pMain==0 ) rc = SQLITE_NOMEM; + if( pMain==0 ) rc = SQLITE_NOMEM_BKPT; } #endif @@ -82502,7 +86557,7 @@ static int vdbeSorterMergeTreeBuild( int i; int iSeq = 0; pRoot = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); - if( pRoot==0 ) rc = SQLITE_NOMEM; + if( pRoot==0 ) rc = SQLITE_NOMEM_BKPT; for(i=0; inPMA && rc==SQLITE_OK; i += SORTER_MAX_MERGE_COUNT){ MergeEngine *pMerger = 0; /* New level-0 PMA merger */ int nReader; /* Number of level-0 PMAs to merge */ @@ -82573,7 +86628,7 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ if( rc==SQLITE_OK ){ pReadr = (PmaReader*)sqlite3DbMallocZero(db, sizeof(PmaReader)); pSorter->pReader = pReadr; - if( pReadr==0 ) rc = SQLITE_NOMEM; + if( pReadr==0 ) rc = SQLITE_NOMEM_BKPT; } if( rc==SQLITE_OK ){ rc = vdbeIncrMergerNew(pLast, pMain, &pReadr->pIncr); @@ -82631,9 +86686,11 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ ** in sorted order. */ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ - VdbeSorter *pSorter = pCsr->pSorter; + VdbeSorter *pSorter; int rc = SQLITE_OK; /* Return code */ + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; assert( pSorter ); /* If no data has been written to disk, then do not do so now. Instead, @@ -82677,9 +86734,11 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ ** Advance to the next element in the sorter. */ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){ - VdbeSorter *pSorter = pCsr->pSorter; + VdbeSorter *pSorter; int rc; /* Return code */ + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; assert( pSorter->bUsePMA || (pSorter->pReader==0 && pSorter->pMerger==0) ); if( pSorter->bUsePMA ){ assert( pSorter->pReader==0 || pSorter->pMerger==0 ); @@ -82739,12 +86798,14 @@ static void *vdbeSorterRowkey( ** Copy the current sorter key into the memory cell pOut. */ SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){ - VdbeSorter *pSorter = pCsr->pSorter; + 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) ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pOut->n = nKey; MemSetTypeFlag(pOut, MEM_Blob); @@ -82775,17 +86836,21 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( int nKeyCol, /* Compare this many columns */ int *pRes /* OUT: Result of comparison */ ){ - VdbeSorter *pSorter = pCsr->pSorter; - UnpackedRecord *r2 = pSorter->pUnpacked; - KeyInfo *pKeyInfo = pCsr->pKeyInfo; + VdbeSorter *pSorter; + UnpackedRecord *r2; + KeyInfo *pKeyInfo; int i; void *pKey; int nKey; /* Sorter key to compare pVal with */ + assert( pCsr->eCurType==CURTYPE_SORTER ); + pSorter = pCsr->uc.pSorter; + r2 = pSorter->pUnpacked; + pKeyInfo = pCsr->pKeyInfo; if( r2==0 ){ char *p; r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p); assert( pSorter->pUnpacked==(UnpackedRecord*)p ); - if( r2==0 ) return SQLITE_NOMEM; + if( r2==0 ) return SQLITE_NOMEM_BKPT; r2->nField = nKeyCol; } assert( r2->nField==nKeyCol ); @@ -82804,265 +86869,6 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( } /************** End of vdbesort.c ********************************************/ -/************** Begin file journal.c *****************************************/ -/* -** 2007 August 22 -** -** 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 a special kind of sqlite3_file object used -** by SQLite to create journal files if the atomic-write optimization -** is enabled. -** -** The distinctive characteristic of this sqlite3_file is that the -** actual on disk file is created lazily. When the file is created, -** the caller specifies a buffer size for an in-memory buffer to -** be used to service read() and write() requests. The actual file -** on disk is not created or populated until either: -** -** 1) The in-memory representation grows too large for the allocated -** buffer, or -** 2) The sqlite3JournalCreate() function is called. -*/ -#ifdef SQLITE_ENABLE_ATOMIC_WRITE -/* #include "sqliteInt.h" */ - - -/* -** A JournalFile object is a subclass of sqlite3_file used by -** as an open file handle for journal files. -*/ -struct JournalFile { - sqlite3_io_methods *pMethod; /* I/O methods on journal files */ - int nBuf; /* Size of zBuf[] in bytes */ - char *zBuf; /* Space to buffer journal writes */ - int iSize; /* Amount of zBuf[] currently used */ - int flags; /* xOpen flags */ - sqlite3_vfs *pVfs; /* The "real" underlying VFS */ - sqlite3_file *pReal; /* The "real" underlying file descriptor */ - const char *zJournal; /* Name of the journal file */ -}; -typedef struct JournalFile JournalFile; - -/* -** If it does not already exists, create and populate the on-disk file -** for JournalFile p. -*/ -static int createFile(JournalFile *p){ - int rc = SQLITE_OK; - if( !p->pReal ){ - sqlite3_file *pReal = (sqlite3_file *)&p[1]; - rc = sqlite3OsOpen(p->pVfs, p->zJournal, pReal, p->flags, 0); - if( rc==SQLITE_OK ){ - p->pReal = pReal; - if( p->iSize>0 ){ - assert(p->iSize<=p->nBuf); - rc = sqlite3OsWrite(p->pReal, p->zBuf, p->iSize, 0); - } - if( rc!=SQLITE_OK ){ - /* If an error occurred while writing to the file, close it 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); - p->pReal = 0; - } - } - } - return rc; -} - -/* -** Close the file. -*/ -static int jrnlClose(sqlite3_file *pJfd){ - JournalFile *p = (JournalFile *)pJfd; - if( p->pReal ){ - sqlite3OsClose(p->pReal); - } - sqlite3_free(p->zBuf); - return SQLITE_OK; -} - -/* -** Read data from the file. -*/ -static int jrnlRead( - sqlite3_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 */ -){ - int rc = SQLITE_OK; - JournalFile *p = (JournalFile *)pJfd; - if( p->pReal ){ - rc = sqlite3OsRead(p->pReal, zBuf, iAmt, iOfst); - }else if( (iAmt+iOfst)>p->iSize ){ - rc = SQLITE_IOERR_SHORT_READ; - }else{ - memcpy(zBuf, &p->zBuf[iOfst], iAmt); - } - return rc; -} - -/* -** Write data to the file. -*/ -static int jrnlWrite( - sqlite3_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 */ -){ - int rc = SQLITE_OK; - JournalFile *p = (JournalFile *)pJfd; - if( !p->pReal && (iOfst+iAmt)>p->nBuf ){ - rc = createFile(p); - } - if( rc==SQLITE_OK ){ - if( p->pReal ){ - rc = sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst); - }else{ - memcpy(&p->zBuf[iOfst], zBuf, iAmt); - if( p->iSize<(iOfst+iAmt) ){ - p->iSize = (iOfst+iAmt); - } - } - } - return rc; -} - -/* -** Truncate the file. -*/ -static int jrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ - int rc = SQLITE_OK; - JournalFile *p = (JournalFile *)pJfd; - if( p->pReal ){ - rc = sqlite3OsTruncate(p->pReal, size); - }else if( sizeiSize ){ - p->iSize = size; - } - return rc; -} - -/* -** Sync the file. -*/ -static int jrnlSync(sqlite3_file *pJfd, int flags){ - int rc; - JournalFile *p = (JournalFile *)pJfd; - if( p->pReal ){ - rc = sqlite3OsSync(p->pReal, flags); - }else{ - rc = SQLITE_OK; - } - return rc; -} - -/* -** Query the size of the file in bytes. -*/ -static int jrnlFileSize(sqlite3_file *pJfd, sqlite_int64 *pSize){ - int rc = SQLITE_OK; - JournalFile *p = (JournalFile *)pJfd; - if( p->pReal ){ - rc = sqlite3OsFileSize(p->pReal, pSize); - }else{ - *pSize = (sqlite_int64) p->iSize; - } - return rc; -} - -/* -** Table of methods for JournalFile sqlite3_file object. -*/ -static struct sqlite3_io_methods JournalFileMethods = { - 1, /* iVersion */ - jrnlClose, /* xClose */ - jrnlRead, /* xRead */ - jrnlWrite, /* xWrite */ - jrnlTruncate, /* xTruncate */ - jrnlSync, /* xSync */ - jrnlFileSize, /* xFileSize */ - 0, /* xLock */ - 0, /* xUnlock */ - 0, /* xCheckReservedLock */ - 0, /* xFileControl */ - 0, /* xSectorSize */ - 0, /* xDeviceCharacteristics */ - 0, /* xShmMap */ - 0, /* xShmLock */ - 0, /* xShmBarrier */ - 0 /* xShmUnmap */ -}; - -/* -** Open a journal file. -*/ -SQLITE_PRIVATE int sqlite3JournalOpen( - sqlite3_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 */ - int flags, /* Opening flags */ - int nBuf /* Bytes buffered before opening the file */ -){ - JournalFile *p = (JournalFile *)pJfd; - memset(p, 0, sqlite3JournalSize(pVfs)); - if( nBuf>0 ){ - p->zBuf = sqlite3MallocZero(nBuf); - if( !p->zBuf ){ - return SQLITE_NOMEM; - } - }else{ - return sqlite3OsOpen(pVfs, zName, pJfd, flags, 0); - } - p->pMethod = &JournalFileMethods; - p->nBuf = nBuf; - p->flags = flags; - p->zJournal = zName; - p->pVfs = pVfs; - return SQLITE_OK; -} - -/* -** If the argument p points to a JournalFile structure, and the underlying -** file has not yet been created, create it now. -*/ -SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *p){ - if( p->pMethods!=&JournalFileMethods ){ - return SQLITE_OK; - } - return createFile((JournalFile *)p); -} - -/* -** The file-handle passed as the only argument is guaranteed to be an open -** file. It may or may not be of class JournalFile. If the file is a -** JournalFile, and the underlying file on disk has not yet been opened, -** return 0. Otherwise, return 1. -*/ -SQLITE_PRIVATE int sqlite3JournalExists(sqlite3_file *p){ - return (p->pMethods!=&JournalFileMethods || ((JournalFile *)p)->pReal!=0); -} - -/* -** 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){ - return (pVfs->szOsFile+sizeof(JournalFile)); -} -#endif - -/************** End of journal.c *********************************************/ /************** Begin file memjournal.c **************************************/ /* ** 2008 October 7 @@ -83079,6 +86885,15 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){ ** This file contains code use to implement an in-memory rollback journal. ** The in-memory rollback journal is used to journal transactions for ** ":memory:" databases and when the journal_mode=MEMORY pragma is used. +** +** Update: The in-memory journal is also used to temporarily cache +** smaller journals that are not critical for power-loss recovery. +** For example, statement journals that are not too big will be held +** entirely in memory, thus reducing the number of file I/O calls, and +** more importantly, reducing temporary file creation events. If these +** journals become too large for memory, they are spilled to disk. But +** in the common case, they are usually small and no file I/O needs to +** occur. */ /* #include "sqliteInt.h" */ @@ -83087,24 +86902,28 @@ typedef struct MemJournal MemJournal; typedef struct FilePoint FilePoint; typedef struct FileChunk FileChunk; -/* Space to hold the rollback journal is allocated in increments of -** this many bytes. -** -** The size chosen is a little less than a power of two. That way, -** the FileChunk object will have a size that almost exactly fills -** a power-of-two allocation. This minimizes wasted space in power-of-two -** memory allocators. -*/ -#define JOURNAL_CHUNKSIZE ((int)(1024-sizeof(FileChunk*))) - /* ** The rollback journal is composed of a linked list of these structures. +** +** The zChunk array is always at least 8 bytes in size - usually much more. +** Its actual size is stored in the MemJournal.nChunkSize variable. */ struct FileChunk { FileChunk *pNext; /* Next chunk in the journal */ - u8 zChunk[JOURNAL_CHUNKSIZE]; /* Content of this chunk */ + u8 zChunk[8]; /* Content of this chunk */ }; +/* +** By default, allocate this many bytes of memory for each FileChunk object. +*/ +#define MEMJOURNAL_DFLT_FILECHUNKSIZE 1024 + +/* +** For chunk size nChunkSize, return the number of bytes that should +** be allocated for each FileChunk structure. +*/ +#define fileChunkSize(nChunkSize) (sizeof(FileChunk) + ((nChunkSize)-8)) + /* ** An instance of this object serves as a cursor into the rollback journal. ** The cursor can be either for reading or writing. @@ -83115,14 +86934,22 @@ struct FilePoint { }; /* -** This subclass is a subclass of sqlite3_file. Each open memory-journal +** This structure is a subclass of sqlite3_file. Each open memory-journal ** is an instance of this class. */ struct MemJournal { - sqlite3_io_methods *pMethod; /* Parent class. MUST BE FIRST */ + const sqlite3_io_methods *pMethod; /* Parent class. MUST BE FIRST */ + int nChunkSize; /* In-memory chunk-size */ + + int nSpill; /* Bytes of data before flushing */ + int nSize; /* Bytes of data currently in memory */ FileChunk *pFirst; /* Head of in-memory chunk-list */ FilePoint endpoint; /* Pointer to the end of the file */ FilePoint readpoint; /* Pointer to the end of the last xRead() */ + + int flags; /* xOpen flags */ + sqlite3_vfs *pVfs; /* The "real" underlying VFS */ + const char *zJournal; /* Name of the journal file */ }; /* @@ -83141,36 +86968,94 @@ static int memjrnlRead( int iChunkOffset; FileChunk *pChunk; - /* SQLite never tries to read past the end of a rollback journal file */ - assert( iOfst+iAmt<=p->endpoint.iOffset ); +#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; for(pChunk=p->pFirst; - ALWAYS(pChunk) && (iOff+JOURNAL_CHUNKSIZE)<=iOfst; + ALWAYS(pChunk) && (iOff+p->nChunkSize)<=iOfst; pChunk=pChunk->pNext ){ - iOff += JOURNAL_CHUNKSIZE; + iOff += p->nChunkSize; } }else{ pChunk = p->readpoint.pChunk; + assert( pChunk!=0 ); } - iChunkOffset = (int)(iOfst%JOURNAL_CHUNKSIZE); + iChunkOffset = (int)(iOfst%p->nChunkSize); do { - int iSpace = JOURNAL_CHUNKSIZE - iChunkOffset; - int nCopy = MIN(nRead, (JOURNAL_CHUNKSIZE - iChunkOffset)); - memcpy(zOut, &pChunk->zChunk[iChunkOffset], nCopy); + int iSpace = p->nChunkSize - iChunkOffset; + int nCopy = MIN(nRead, (p->nChunkSize - iChunkOffset)); + memcpy(zOut, (u8*)pChunk->zChunk + iChunkOffset, nCopy); zOut += nCopy; nRead -= iSpace; iChunkOffset = 0; } while( nRead>=0 && (pChunk=pChunk->pNext)!=0 && nRead>0 ); - p->readpoint.iOffset = iOfst+iAmt; + p->readpoint.iOffset = pChunk ? iOfst+iAmt : 0; p->readpoint.pChunk = pChunk; return SQLITE_OK; } +/* +** Free the list of FileChunk structures headed at MemJournal.pFirst. +*/ +static void memjrnlFreeChunks(MemJournal *p){ + FileChunk *pIter; + FileChunk *pNext; + for(pIter=p->pFirst; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } + p->pFirst = 0; +} + +/* +** Flush the contents of memory to a real file on disk. +*/ +static int memjrnlCreateFile(MemJournal *p){ + int rc; + sqlite3_file *pReal = (sqlite3_file*)p; + MemJournal copy = *p; + + memset(p, 0, sizeof(MemJournal)); + rc = sqlite3OsOpen(copy.pVfs, copy.zJournal, pReal, copy.flags, 0); + if( rc==SQLITE_OK ){ + int nChunk = copy.nChunkSize; + i64 iOff = 0; + FileChunk *pIter; + for(pIter=copy.pFirst; pIter; pIter=pIter->pNext){ + if( iOff + nChunk > copy.endpoint.iOffset ){ + nChunk = copy.endpoint.iOffset - iOff; + } + rc = sqlite3OsWrite(pReal, (u8*)pIter->zChunk, nChunk, iOff); + if( rc ) break; + iOff += nChunk; + } + if( rc==SQLITE_OK ){ + /* No error has occurred. Free the in-memory buffers. */ + memjrnlFreeChunks(©); + } + } + if( rc!=SQLITE_OK ){ + /* If an error occurred while creating or writing to the file, restore + ** 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); + *p = copy; + } + return rc; +} + + /* ** Write data to the file. */ @@ -83184,38 +87069,62 @@ static int memjrnlWrite( int nWrite = iAmt; u8 *zWrite = (u8 *)zBuf; - /* An in-memory journal file should only ever be appended to. Random - ** access writes are not required by sqlite. - */ - assert( iOfst==p->endpoint.iOffset ); - UNUSED_PARAMETER(iOfst); - - while( nWrite>0 ){ - FileChunk *pChunk = p->endpoint.pChunk; - int iChunkOffset = (int)(p->endpoint.iOffset%JOURNAL_CHUNKSIZE); - int iSpace = MIN(nWrite, JOURNAL_CHUNKSIZE - iChunkOffset); - - if( iChunkOffset==0 ){ - /* New chunk is required to extend the file. */ - FileChunk *pNew = sqlite3_malloc(sizeof(FileChunk)); - if( !pNew ){ - return SQLITE_IOERR_NOMEM; - } - pNew->pNext = 0; - if( pChunk ){ - assert( p->pFirst ); - pChunk->pNext = pNew; - }else{ - assert( !p->pFirst ); - p->pFirst = pNew; - } - p->endpoint.pChunk = pNew; + /* If the file should be created now, create it and write the new data + ** into the file on disk. */ + if( p->nSpill>0 && (iAmt+iOfst)>p->nSpill ){ + int rc = memjrnlCreateFile(p); + if( rc==SQLITE_OK ){ + rc = sqlite3OsWrite(pJfd, zBuf, iAmt, iOfst); } + return rc; + } - memcpy(&p->endpoint.pChunk->zChunk[iChunkOffset], zWrite, iSpace); - zWrite += iSpace; - nWrite -= iSpace; - p->endpoint.iOffset += iSpace; + /* If the contents of this write should be stored in memory */ + else{ + /* An in-memory journal file should only ever be appended to. Random + ** access writes are not required. The only exception to this is when + ** the in-memory journal is being used by a connection using the + ** 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( iOfst==0 && p->pFirst ){ + assert( p->nChunkSize>iAmt ); + memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt); + }else +#else + assert( iOfst>0 || p->pFirst==0 ); +#endif + { + while( nWrite>0 ){ + FileChunk *pChunk = p->endpoint.pChunk; + int iChunkOffset = (int)(p->endpoint.iOffset%p->nChunkSize); + int iSpace = MIN(nWrite, p->nChunkSize - iChunkOffset); + + if( iChunkOffset==0 ){ + /* New chunk is required to extend the file. */ + FileChunk *pNew = sqlite3_malloc(fileChunkSize(p->nChunkSize)); + if( !pNew ){ + return SQLITE_IOERR_NOMEM_BKPT; + } + pNew->pNext = 0; + if( pChunk ){ + assert( p->pFirst ); + pChunk->pNext = pNew; + }else{ + assert( !p->pFirst ); + p->pFirst = pNew; + } + p->endpoint.pChunk = pNew; + } + + memcpy((u8*)p->endpoint.pChunk->zChunk + iChunkOffset, zWrite, iSpace); + zWrite += iSpace; + nWrite -= iSpace; + p->endpoint.iOffset += iSpace; + } + p->nSize = iAmt + iOfst; + } } return SQLITE_OK; @@ -83223,19 +87132,21 @@ static int memjrnlWrite( /* ** Truncate the file. +** +** If the journal file is already on disk, truncate it there. Or, if it +** is still in main memory but is being truncated to zero bytes in size, +** ignore */ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ MemJournal *p = (MemJournal *)pJfd; - FileChunk *pChunk; - assert(size==0); - UNUSED_PARAMETER(size); - pChunk = p->pFirst; - while( pChunk ){ - FileChunk *pTmp = pChunk; - pChunk = pChunk->pNext; - sqlite3_free(pTmp); + if( ALWAYS(size==0) ){ + memjrnlFreeChunks(p); + p->nSize = 0; + p->endpoint.pChunk = 0; + p->endpoint.iOffset = 0; + p->readpoint.pChunk = 0; + p->readpoint.iOffset = 0; } - sqlite3MemJournalOpen(pJfd); return SQLITE_OK; } @@ -83243,21 +87154,19 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ ** Close the file. */ static int memjrnlClose(sqlite3_file *pJfd){ - memjrnlTruncate(pJfd, 0); + MemJournal *p = (MemJournal *)pJfd; + memjrnlFreeChunks(p); return SQLITE_OK; } - /* ** Sync the file. ** -** Syncing an in-memory journal is a no-op. And, in fact, this routine -** is never called in a working implementation. This implementation -** exists purely as a contingency, in case some malfunction in some other -** part of SQLite causes Sync to be called by mistake. +** 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 *NotUsed, int NotUsed2){ - UNUSED_PARAMETER2(NotUsed, NotUsed2); +static int memjrnlSync(sqlite3_file *pJfd, int flags){ + UNUSED_PARAMETER2(pJfd, flags); return SQLITE_OK; } @@ -83296,28 +87205,88 @@ static const struct sqlite3_io_methods MemJournalMethods = { }; /* -** Open a journal file. +** Open a journal file. +** +** The behaviour of the journal file depends on the value of parameter +** nSpill. If nSpill is 0, then the journal file is always create and +** accessed using the underlying VFS. If nSpill is less than zero, then +** all content is always stored in main-memory. Finally, if nSpill is a +** 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. */ -SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *pJfd){ - MemJournal *p = (MemJournal *)pJfd; - assert( EIGHT_BYTE_ALIGNMENT(p) ); - memset(p, 0, sqlite3MemJournalSize()); - p->pMethod = (sqlite3_io_methods*)&MemJournalMethods; +SQLITE_PRIVATE int sqlite3JournalOpen( + sqlite3_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 */ + 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 + ** 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); + } + + if( nSpill>0 ){ + p->nChunkSize = nSpill; + }else{ + p->nChunkSize = 8 + MEMJOURNAL_DFLT_FILECHUNKSIZE - sizeof(FileChunk); + assert( MEMJOURNAL_DFLT_FILECHUNKSIZE==fileChunkSize(p->nChunkSize) ); + } + + p->pMethod = (const sqlite3_io_methods*)&MemJournalMethods; + p->nSpill = nSpill; + p->flags = flags; + p->zJournal = zName; + p->pVfs = pVfs; + return SQLITE_OK; } /* -** Return true if the file-handle passed as an argument is -** an in-memory journal +** Open an in-memory journal file. */ -SQLITE_PRIVATE int sqlite3IsMemJournal(sqlite3_file *pJfd){ - return pJfd->pMethods==&MemJournalMethods; +SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *pJfd){ + sqlite3JournalOpen(0, 0, pJfd, 0, -1); +} + +#ifdef SQLITE_ENABLE_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. +*/ +SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *p){ + int rc = SQLITE_OK; + if( p->pMethods==&MemJournalMethods && ((MemJournal*)p)->nSpill>0 ){ + rc = memjrnlCreateFile((MemJournal*)p); + } + return rc; +} +#endif + +/* +** The file-handle passed as the only argument is open on a journal file. +** Return true if this "journal file" is currently stored in heap memory, +** or false otherwise. +*/ +SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p){ + return p->pMethods==&MemJournalMethods; } /* -** Return the number of bytes required to store a MemJournal file descriptor. +** Return the number of bytes required to store a JournalFile that uses vfs +** pVfs to create the underlying on-disk files. */ -SQLITE_PRIVATE int sqlite3MemJournalSize(void){ - return sizeof(MemJournal); +SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){ + return MAX(pVfs->szOsFile, (int)sizeof(MemJournal)); } /************** End of memjournal.c ******************************************/ @@ -83360,9 +87329,8 @@ SQLITE_PRIVATE int sqlite3MemJournalSize(void){ ** The return value from this routine is WRC_Abort to abandon the tree walk ** and WRC_Continue to continue. */ -SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ +static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ int rc; - if( pExpr==0 ) return WRC_Continue; testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); rc = pWalker->xExprCallback(pWalker, pExpr); @@ -83378,6 +87346,9 @@ SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ } return rc & WRC_Abort; } +SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ + return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue; +} /* ** Call sqlite3WalkExpr() for every expression in list p or until @@ -83814,7 +87785,6 @@ static int lookupName( } if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && VisibleRowid(pTab) ){ /* IMP: R-51414-32910 */ - /* IMP: R-44911-55124 */ iCol = -1; } if( iColnCol ){ @@ -83849,7 +87819,7 @@ static int lookupName( && VisibleRowid(pMatch->pTab) ){ cnt = 1; - pExpr->iColumn = -1; /* IMP: R-44911-55124 */ + pExpr->iColumn = -1; pExpr->affinity = SQLITE_AFF_INTEGER; } @@ -84143,16 +88113,16 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ notValid(pParse, pNC, "functions", NC_PartIdx); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); - pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0); + pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); if( pDef==0 ){ - pDef = sqlite3FindFunction(pParse->db, zId, nId, -2, enc, 0); + pDef = sqlite3FindFunction(pParse->db, zId, -2, enc, 0); if( pDef==0 ){ no_such_func = 1; }else{ wrong_num_args = 1; } }else{ - is_agg = pDef->xFunc==0; + is_agg = pDef->xFinalize!=0; if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ ExprSetProperty(pExpr, EP_Unlikely|EP_Skip); if( n==2 ){ @@ -84250,6 +88220,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); + pNC->ncFlags |= NC_VarSelect; } } break; @@ -84880,10 +88851,12 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( #endif savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg); pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg); - memset(&w, 0, sizeof(w)); + w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; w.xSelectCallback = resolveSelectStep; - w.pParse = pNC->pParse; + w.xSelectCallback2 = 0; + w.walkerDepth = 0; + w.eCode = 0; w.u.pNC = pNC; sqlite3WalkExpr(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 @@ -84909,9 +88882,10 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames( ExprList *pList /* The expression list to be analyzed. */ ){ int i; - assert( pList!=0 ); - for(i=0; inExpr; i++){ - if( sqlite3ResolveExprNames(pNC, pList->a[i].pExpr) ) return WRC_Abort; + if( pList ){ + for(i=0; inExpr; i++){ + if( sqlite3ResolveExprNames(pNC, pList->a[i].pExpr) ) return WRC_Abort; + } } return WRC_Continue; } @@ -85068,8 +89042,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){ Token s; assert( zC!=0 ); - s.z = zC; - s.n = sqlite3Strlen30(s.z); + sqlite3TokenInit(&s, (char*)zC); return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0); } @@ -85437,6 +89410,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( int nExtra = 0; int iValue = 0; + assert( db!=0 ); if( pToken ){ if( op!=TK_INTEGER || pToken->z==0 || sqlite3GetInt32(pToken->z, &iValue)==0 ){ @@ -85444,8 +89418,9 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( assert( iValue>=0 ); } } - pNew = sqlite3DbMallocZero(db, sizeof(Expr)+nExtra); + pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ + memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; if( pToken ){ @@ -85453,15 +89428,13 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( pNew->flags |= EP_IntValue; pNew->u.iValue = iValue; }else{ - int c; 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 && nExtra>=3 - && ((c = pToken->z[0])=='\'' || c=='"' || c=='[' || c=='`') ){ + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + if( pNew->u.zToken[0]=='"' ) pNew->flags |= EP_DblQuoted; sqlite3Dequote(pNew->u.zToken); - if( c=='"' ) pNew->flags |= EP_DblQuoted; } } } @@ -85531,11 +89504,11 @@ SQLITE_PRIVATE Expr *sqlite3PExpr( const Token *pToken /* Argument token */ ){ Expr *p; - if( op==TK_AND && pLeft && pRight && pParse->nErr==0 ){ + if( op==TK_AND && pParse->nErr==0 ){ /* Take advantage of short-circuit false optimization for AND */ p = sqlite3ExprAnd(pParse->db, pLeft, pRight); }else{ - p = sqlite3ExprAlloc(pParse->db, op, pToken, 1); + p = sqlite3ExprAlloc(pParse->db, op & TKFLG_MASK, pToken, 1); sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); } if( p ) { @@ -85544,6 +89517,22 @@ SQLITE_PRIVATE Expr *sqlite3PExpr( return p; } +/* +** 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){ + if( pExpr ){ + pExpr->x.pSelect = pSelect; + ExprSetProperty(pExpr, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, pExpr); + }else{ + assert( pParse->db->mallocFailed ); + sqlite3SelectDelete(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 @@ -85682,7 +89671,10 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ if( x>pParse->nzVar ){ char **a; a = sqlite3DbRealloc(db, pParse->azVar, x*sizeof(a[0])); - if( a==0 ) return; /* Error reported through db->mallocFailed */ + 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; @@ -85701,8 +89693,8 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ /* ** Recursively delete an expression tree. */ -SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ - if( p==0 ) return; +static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *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 ); if( !ExprHasProperty(p, EP_TokenOnly) ){ @@ -85721,6 +89713,9 @@ SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ sqlite3DbFree(db, p); } } +SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ + if( p ) sqlite3ExprDeleteNN(db, p); +} /* ** Return the number of bytes allocated for the expression structure @@ -85772,7 +89767,7 @@ 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&EXPRDUP_REDUCE) ){ + if( 0==flags ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); @@ -85834,84 +89829,88 @@ static int dupedExprSize(Expr *p, int flags){ ** 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 flags, u8 **pzBuffer){ - Expr *pNew = 0; /* Value to return */ - if( p ){ - const int isReduced = (flags&EXPRDUP_REDUCE); - u8 *zAlloc; - u32 staticFlag = 0; +static Expr *exprDup(sqlite3 *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 */ - assert( pzBuffer==0 || isReduced ); + assert( db!=0 ); + assert( p ); + assert( dupFlags==0 || dupFlags==EXPRDUP_REDUCE ); + assert( pzBuffer==0 || dupFlags==EXPRDUP_REDUCE ); - /* Figure out where to write the new Expr structure. */ - if( pzBuffer ){ - zAlloc = *pzBuffer; - staticFlag = EP_Static; + /* Figure out where to write the new Expr structure. */ + if( pzBuffer ){ + zAlloc = *pzBuffer; + staticFlag = EP_Static; + }else{ + zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); + staticFlag = 0; + } + pNew = (Expr *)zAlloc; + + if( pNew ){ + /* Set nNewSize to the size allocated for the structure pointed to + ** by pNew. This is either EXPR_FULLSIZE, EXPR_REDUCEDSIZE or + ** EXPR_TOKENONLYSIZE. nToken is set to the number of bytes consumed + ** by the copy of the p->u.zToken string (if any). + */ + const unsigned nStructSize = dupedExprStructSize(p, dupFlags); + const int nNewSize = nStructSize & 0xfff; + int nToken; + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30(p->u.zToken) + 1; }else{ - zAlloc = sqlite3DbMallocRaw(db, dupedExprSize(p, flags)); + nToken = 0; } - pNew = (Expr *)zAlloc; - - if( pNew ){ - /* Set nNewSize to the size allocated for the structure pointed to - ** by pNew. This is either EXPR_FULLSIZE, EXPR_REDUCEDSIZE or - ** EXPR_TOKENONLYSIZE. nToken is set to the number of bytes consumed - ** by the copy of the p->u.zToken string (if any). - */ - const unsigned nStructSize = dupedExprStructSize(p, flags); - const int nNewSize = nStructSize & 0xfff; - int nToken; - if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ - nToken = sqlite3Strlen30(p->u.zToken) + 1; - }else{ - nToken = 0; - } - if( isReduced ){ - assert( ExprHasProperty(p, EP_Reduced)==0 ); - memcpy(zAlloc, p, nNewSize); - }else{ - int nSize = exprStructSize(p); - memcpy(zAlloc, p, nSize); + if( dupFlags ){ + assert( ExprHasProperty(p, EP_Reduced)==0 ); + memcpy(zAlloc, p, nNewSize); + }else{ + u32 nSize = (u32)exprStructSize(p); + memcpy(zAlloc, p, nSize); + if( nSizeflags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken); - pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); - pNew->flags |= staticFlag; + /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */ + pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken); + pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); + pNew->flags |= staticFlag; - /* Copy the p->u.zToken string, if any. */ - if( nToken ){ - char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; - memcpy(zToken, p->u.zToken, nToken); - } + /* Copy the p->u.zToken string, if any. */ + if( nToken ){ + char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; + memcpy(zToken, p->u.zToken, nToken); + } - if( 0==((p->flags|pNew->flags) & EP_TokenOnly) ){ - /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ - if( ExprHasProperty(p, EP_xIsSelect) ){ - pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, isReduced); - }else{ - pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, isReduced); - } - } - - /* Fill in pNew->pLeft and pNew->pRight. */ - if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly) ){ - zAlloc += dupedExprNodeSize(p, flags); - if( ExprHasProperty(pNew, EP_Reduced) ){ - pNew->pLeft = exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc); - pNew->pRight = exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc); - } - if( pzBuffer ){ - *pzBuffer = zAlloc; - } + if( 0==((p->flags|pNew->flags) & EP_TokenOnly) ){ + /* 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); }else{ - if( !ExprHasProperty(p, EP_TokenOnly) ){ - pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); - pNew->pRight = sqlite3ExprDup(db, p->pRight, 0); - } + pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); } + } + /* Fill in pNew->pLeft and pNew->pRight. */ + if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly) ){ + zAlloc += dupedExprNodeSize(p, dupFlags); + if( ExprHasProperty(pNew, EP_Reduced) ){ + pNew->pLeft = p->pLeft ? + exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; + pNew->pRight = p->pRight ? + exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0; + } + if( pzBuffer ){ + *pzBuffer = zAlloc; + } + }else{ + if( !ExprHasProperty(p, EP_TokenOnly) ){ + pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + pNew->pRight = sqlite3ExprDup(db, p->pRight, 0); + } } } return pNew; @@ -85962,18 +89961,20 @@ static With *withDup(sqlite3 *db, With *p){ ** part of the in-memory representation of the database schema. */ SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){ - return exprDup(db, p, flags, 0); + assert( flags==0 || flags==EXPRDUP_REDUCE ); + return p ? exprDup(db, p, flags, 0) : 0; } SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ ExprList *pNew; struct ExprList_item *pItem, *pOldItem; int i; + assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRaw(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); if( pNew==0 ) return 0; pNew->nExpr = i = p->nExpr; if( (flags & EXPRDUP_REDUCE)==0 ) for(i=1; inExpr; i+=i){} - pNew->a = pItem = sqlite3DbMallocRaw(db, i*sizeof(p->a[0]) ); + pNew->a = pItem = sqlite3DbMallocRawNN(db, i*sizeof(p->a[0]) ); if( pItem==0 ){ sqlite3DbFree(db, pNew); return 0; @@ -86004,9 +90005,10 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *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 = sqlite3DbMallocRaw(db, nByte ); + pNew = sqlite3DbMallocRawNN(db, nByte ); if( pNew==0 ) return 0; pNew->nSrc = pNew->nAlloc = p->nSrc; for(i=0; inSrc; i++){ @@ -86043,11 +90045,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ IdList *pNew; int i; + assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRaw(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); if( pNew==0 ) return 0; pNew->nId = p->nId; - pNew->a = sqlite3DbMallocRaw(db, p->nId*sizeof(p->a[0]) ); + pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) ); if( pNew->a==0 ){ sqlite3DbFree(db, pNew); return 0; @@ -86065,8 +90068,9 @@ SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ } SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ Select *pNew, *pPrior; + assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRaw(db, sizeof(*p) ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*p) ); if( pNew==0 ) return 0; pNew->pEList = sqlite3ExprListDup(db, p->pEList, flags); pNew->pSrc = sqlite3SrcListDup(db, p->pSrc, flags); @@ -86112,12 +90116,14 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( Expr *pExpr /* Expression to be appended. Might be NULL */ ){ sqlite3 *db = pParse->db; + assert( db!=0 ); if( pList==0 ){ - pList = sqlite3DbMallocZero(db, sizeof(ExprList) ); + pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) ); if( pList==0 ){ goto no_mem; } - pList->a = sqlite3DbMallocRaw(db, sizeof(pList->a[0])); + 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; @@ -86178,7 +90184,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetName( pItem = &pList->a[pList->nExpr-1]; assert( pItem->zName==0 ); pItem->zName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); - if( dequote && pItem->zName ) sqlite3Dequote(pItem->zName); + if( dequote ) sqlite3Dequote(pItem->zName); } } @@ -86227,10 +90233,9 @@ SQLITE_PRIVATE void sqlite3ExprListCheckLength( /* ** Delete an entire expression list. */ -SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ +static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ int i; struct ExprList_item *pItem; - if( pList==0 ) return; assert( pList->a!=0 || pList->nExpr==0 ); for(pItem=pList->a, i=0; inExpr; i++, pItem++){ sqlite3ExprDelete(db, pItem->pExpr); @@ -86240,6 +90245,9 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ sqlite3DbFree(db, pList->a); sqlite3DbFree(db, pList); } +SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ + if( pList ) exprListDeleteNN(db, pList); +} /* ** Return the bitwise-OR of all Expr.flags fields in the given @@ -86251,7 +90259,8 @@ SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){ if( pList ){ for(i=0; inExpr; i++){ Expr *pExpr = pList->a[i].pExpr; - if( ALWAYS(pExpr) ) m |= pExpr->flags; + assert( pExpr!=0 ); + m |= pExpr->flags; } } return m; @@ -86398,6 +90407,22 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ return exprIsConst(p, 4+isInit, 0); } +#ifdef SQLITE_ENABLE_CURSOR_HINTS +/* +** 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){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 1; + w.xExprCallback = sqlite3ExprWalkNoop; + w.xSelectCallback = selectNodeIsConstant; + sqlite3WalkExpr(&w, p); + return w.eCode==0; +} +#endif + /* ** If the expression p codes a constant integer that is small enough ** to fit in a 32-bit integer, return 1 and put the value of the integer @@ -86520,23 +90545,22 @@ SQLITE_PRIVATE int sqlite3IsRowid(const char *z){ } /* -** Return true if we are able to the IN operator optimization on a -** query of the form -** -** x IN (SELECT ...) -** -** Where the SELECT... clause is as specified by the parameter to this -** routine. -** -** The Select object passed in has already been preprocessed and no -** errors have been found. +** pX is the RHS of an IN operator. If pX is a SELECT statement +** that can be simplified to a direct table access, then return +** a pointer to the SELECT statement. If pX is not a SELECT statement, +** or if the SELECT statement needs to be manifested into a transient +** table, then return NULL. */ #ifndef SQLITE_OMIT_SUBQUERY -static int isCandidateForInOpt(Select *p){ +static Select *isCandidateForInOpt(Expr *pX){ + Select *p; SrcList *pSrc; ExprList *pEList; + Expr *pRes; Table *pTab; - if( p==0 ) return 0; /* right-hand side of IN is SELECT */ + if( !ExprHasProperty(pX, EP_xIsSelect) ) return 0; /* Not a subquery */ + if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ + p = pX->x.pSelect; if( p->pPrior ) return 0; /* Not a compound SELECT */ if( p->selFlags & (SF_Distinct|SF_Aggregate) ){ testcase( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct ); @@ -86552,13 +90576,15 @@ static int isCandidateForInOpt(Select *p){ if( pSrc->nSrc!=1 ) return 0; /* Single term in FROM clause */ if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */ pTab = pSrc->a[0].pTab; - if( NEVER(pTab==0) ) return 0; + assert( pTab!=0 ); assert( pTab->pSelect==0 ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; if( pEList->nExpr!=1 ) return 0; /* One column in the result set */ - if( pEList->a[0].pExpr->op!=TK_COLUMN ) return 0; /* Result is a column */ - return 1; + pRes = pEList->a[0].pExpr; + if( pRes->op!=TK_COLUMN ) return 0; /* Result is a column */ + assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + return p; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -86690,15 +90716,13 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int ** satisfy the query. This is preferable to generating a new ** ephemeral table. */ - p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); - if( pParse->nErr==0 && isCandidateForInOpt(p) ){ + if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table . */ Expr *pExpr; /* Expression */ i16 iCol; /* Index of column */ i16 iDb; /* Database idx for pTab */ - assert( p ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ @@ -86856,9 +90880,10 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( #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 + 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); } @@ -87267,6 +91292,19 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ } } +#if defined(SQLITE_DEBUG) +/* +** Verify the consistency of the column cache +*/ +static int cacheIsValid(Parse *pParse){ + int i, n; + for(i=n=0; iaColCache[i].iReg>0 ) n++; + } + return n==pParse->nColCache; +} +#endif + /* ** Clear a cache entry. */ @@ -87277,6 +91315,9 @@ static void cacheEntryClear(Parse *pParse, struct yColCache *p){ } p->tempReg = 0; } + p->iReg = 0; + pParse->nColCache--; + assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); } @@ -87320,6 +91361,8 @@ SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int p->iReg = iReg; p->tempReg = 0; p->lru = pParse->iCacheCnt++; + pParse->nColCache++; + assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); return; } } @@ -87341,6 +91384,7 @@ SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int p->iReg = iReg; p->tempReg = 0; p->lru = pParse->iCacheCnt++; + assert( cacheIsValid(pParse) ); return; } } @@ -87350,15 +91394,13 @@ SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int ** Purge the range of registers from the column cache. */ SQLITE_PRIVATE void sqlite3ExprCacheRemove(Parse *pParse, int iReg, int nReg){ - int i; - int iLast = iReg + nReg - 1; struct yColCache *p; - for(i=0, p=pParse->aColCache; iiReg; - if( r>=iReg && r<=iLast ){ - cacheEntryClear(pParse, p); - p->iReg = 0; - } + if( iReg<=0 || pParse->nColCache==0 ) return; + p = &pParse->aColCache[SQLITE_N_COLCACHE-1]; + while(1){ + if( p->iReg >= iReg && p->iReg < iReg+nReg ) cacheEntryClear(pParse, p); + if( p==pParse->aColCache ) break; + p--; } } @@ -87394,7 +91436,6 @@ SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){ for(i=0, p=pParse->aColCache; iiReg && p->iLevel>pParse->iCacheLevel ){ cacheEntryClear(pParse, p); - p->iReg = 0; } } } @@ -87430,7 +91471,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn( assert( pIdx->aColExpr ); assert( pIdx->aColExpr->nExpr>iIdxCol ); pParse->iSelfTab = iTabCur; - sqlite3ExprCode(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); + sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); }else{ sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur, iTabCol, regOut); @@ -87464,9 +91505,12 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( /* ** 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, but this is -** not guaranteed. The location of the column value is returned. +** 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(). ** ** 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. @@ -87477,7 +91521,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( int iColumn, /* Index of the table column */ int iTable, /* The cursor pointing to the table */ int iReg, /* Store results here */ - u8 p5 /* P5 value for OP_Column */ + u8 p5 /* P5 value for OP_Column + FLAGS */ ){ Vdbe *v = pParse->pVdbe; int i; @@ -87499,6 +91543,17 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( } 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. @@ -87515,7 +91570,6 @@ SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse *pParse){ for(i=0, p=pParse->aColCache; iiReg ){ cacheEntryClear(pParse, p); - p->iReg = 0; } } } @@ -87557,6 +91611,7 @@ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ } #endif /* SQLITE_DEBUG || SQLITE_COVERAGE_TEST */ + /* ** Convert an expression node to a TK_REGISTER */ @@ -87825,7 +91880,6 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ExprList *pFarg; /* List of function arguments */ int nFarg; /* Number of function arguments */ FuncDef *pDef; /* The function definition object */ - int nId; /* Length of the function name in bytes */ const char *zId; /* The function name */ u32 constMask = 0; /* Mask of function arguments that are constant */ int i; /* Loop counter */ @@ -87841,10 +91895,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) nFarg = pFarg ? pFarg->nExpr : 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); zId = pExpr->u.zToken; - nId = sqlite3Strlen30(zId); - pDef = sqlite3FindFunction(db, zId, nId, nFarg, enc, 0); - if( pDef==0 || pDef->xFunc==0 ){ - sqlite3ErrorMsg(pParse, "unknown function: %.*s()", nId, zId); + pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0); + if( pDef==0 || pDef->xFinalize!=0 ){ + sqlite3ErrorMsg(pParse, "unknown function: %s()", zId); break; } @@ -88009,6 +92062,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) sqlite3ReleaseTempReg(pParse, r4); break; } + case TK_SPAN: case TK_COLLATE: case TK_UPLUS: { inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); @@ -88269,13 +92323,25 @@ SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target); }else{ inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); - assert( pParse->pVdbe || pParse->db->mallocFailed ); + assert( pParse->pVdbe!=0 || pParse->db->mallocFailed ); if( inReg!=target && pParse->pVdbe ){ sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target); } } } +/* +** Make a transient copy of expression pExpr and then code it using +** sqlite3ExprCode(). This routine works just like sqlite3ExprCode() +** 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); +} + /* ** Generate code that will evaluate expression pExpr and store the ** results in register target. The results are guaranteed to appear @@ -88325,6 +92391,10 @@ SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int targ ** ** The SQLITE_ECEL_FACTOR argument allows constant arguments to be ** factored out into initialization code. +** +** 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. */ SQLITE_PRIVATE int sqlite3ExprCodeExprList( Parse *pParse, /* Parsing context */ @@ -88471,6 +92541,13 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); break; } + case TK_IS: + case TK_ISNOT: + testcase( op==TK_IS ); + testcase( op==TK_ISNOT ); + op = (op==TK_IS) ? TK_EQ : TK_NE; + jumpIfNull = SQLITE_NULLEQ; + /* Fall thru */ case TK_LT: case TK_LE: case TK_GT: @@ -88486,23 +92563,12 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int 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); assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); - assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); - assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - break; - } - case TK_IS: - case TK_ISNOT: { - testcase( op==TK_IS ); - testcase( op==TK_ISNOT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - op = (op==TK_IS) ? TK_EQ : TK_NE; - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, dest, SQLITE_NULLEQ); - VdbeCoverageIf(v, op==TK_EQ); - VdbeCoverageIf(v, op==TK_NE); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull==SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull!=SQLITE_NULLEQ); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); break; @@ -88627,6 +92693,13 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); break; } + case TK_IS: + case TK_ISNOT: + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_ISNOT ); + op = (pExpr->op==TK_IS) ? TK_NE : TK_EQ; + jumpIfNull = SQLITE_NULLEQ; + /* Fall thru */ case TK_LT: case TK_LE: case TK_GT: @@ -88642,23 +92715,12 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int 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); assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); - assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); - assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - break; - } - case TK_IS: - case TK_ISNOT: { - testcase( pExpr->op==TK_IS ); - testcase( pExpr->op==TK_ISNOT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - op = (pExpr->op==TK_IS) ? TK_NE : TK_EQ; - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, dest, SQLITE_NULLEQ); - VdbeCoverageIf(v, op==TK_EQ); - VdbeCoverageIf(v, op==TK_NE); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull!=SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Eq && jumpIfNull==SQLITE_NULLEQ); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); + VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); break; @@ -88766,7 +92828,7 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){ } return 2; } - if( pA->op!=TK_COLUMN && ALWAYS(pA->op!=TK_AGG_COLUMN) && pA->u.zToken ){ + 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 ){ @@ -89053,7 +93115,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ pItem->iMem = ++pParse->nMem; assert( !ExprHasProperty(pExpr, EP_IntValue) ); pItem->pFunc = sqlite3FindFunction(pParse->db, - pExpr->u.zToken, sqlite3Strlen30(pExpr->u.zToken), + pExpr->u.zToken, pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); if( pExpr->flags & EP_Distinct ){ pItem->iDistinct = pParse->nTab++; @@ -89182,6 +93244,29 @@ SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){ pParse->nRangeReg = 0; } +/* +** Validate that no temporary register falls within the range of +** iFirst..iLast, inclusive. This routine is only call from within assert() +** statements. +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ + int i; + if( pParse->nRangeReg>0 + && pParse->iRangeReg+pParse->nRangeRegiRangeReg>=iFirst + ){ + return 0; + } + for(i=0; inTempReg; i++){ + if( pParse->aTempReg[i]>=iFirst && pParse->aTempReg[i]<=iLast ){ + return 0; + } + } + return 1; +} +#endif /* SQLITE_DEBUG */ + /************** End of expr.c ************************************************/ /************** Begin file alter.c *******************************************/ /* @@ -89415,7 +93500,7 @@ static void renameTriggerFunc( ** Register built-in functions used to help implement ALTER TABLE */ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ - static SQLITE_WSD FuncDef aAlterTableFuncs[] = { + static FuncDef aAlterTableFuncs[] = { FUNCTION(sqlite_rename_table, 2, 0, 0, renameTableFunc), #ifndef SQLITE_OMIT_TRIGGER FUNCTION(sqlite_rename_trigger, 2, 0, 0, renameTriggerFunc), @@ -89424,13 +93509,7 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ FUNCTION(sqlite_rename_parent, 3, 0, 0, renameParentFunc), #endif }; - int i; - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); - FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aAlterTableFuncs); - - for(i=0; iflags = savedDbFlags; } - -/* -** Generate code to make sure the file format number is at least minFormat. -** The generated code will increase the file format number if necessary. -*/ -SQLITE_PRIVATE void sqlite3MinimumFileFormat(Parse *pParse, int iDb, int minFormat){ - Vdbe *v; - v = sqlite3GetVdbe(pParse); - /* The VDBE should have been allocated before this routine is called. - ** If that allocation failed, we would have quit before reaching this - ** point */ - if( ALWAYS(v) ){ - int r1 = sqlite3GetTempReg(pParse); - int r2 = sqlite3GetTempReg(pParse); - int addr1; - sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); - sqlite3VdbeUsesBtree(v, iDb); - sqlite3VdbeAddOp2(v, OP_Integer, minFormat, r2); - addr1 = sqlite3VdbeAddOp3(v, OP_Ge, r2, 0, r1); - sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, r2); - sqlite3VdbeJumpHere(v, addr1); - sqlite3ReleaseTempReg(pParse, r1); - sqlite3ReleaseTempReg(pParse, r2); - } -} - /* ** This function is called after an "ALTER TABLE ... ADD" statement ** has been parsed. Argument pColDef contains the text of the new @@ -89819,9 +93871,11 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ 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 */ db = pParse->db; if( pParse->nErr || db->mallocFailed ) return; + assert( v!=0 ); pNew = pParse->pNewTable; assert( pNew ); @@ -89845,7 +93899,8 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ ** literal NULL, then set pDflt to 0. This simplifies checking ** for an SQL NULL default below. */ - if( pDflt && pDflt->op==TK_NULL ){ + assert( pDflt==0 || pDflt->op==TK_SPAN ); + if( pDflt && pDflt->pLeft->op==TK_NULL ){ pDflt = 0; } @@ -89881,7 +93936,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal); assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); if( rc!=SQLITE_OK ){ - db->mallocFailed = 1; + assert( db->mallocFailed == 1 ); return; } if( !pVal ){ @@ -89911,11 +93966,16 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ db->flags = savedDbFlags; } - /* If the default value of the new column is NULL, then set the file + /* If the default value of the new column is NULL, then the file ** format to 2. If the default value of the new column is not NULL, - ** the file format becomes 3. + ** the file format be 3. Back when this feature was first added + ** in 2006, we went to the trouble to upgrade the file format to the + ** minimum support values. But 10-years on, we can assume that all + ** extent versions of SQLite support file-format 4, so we always and + ** unconditionally upgrade to 4. */ - sqlite3MinimumFileFormat(pParse, iDb, pDflt ? 3 : 2); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, + SQLITE_MAX_FILE_FORMAT); /* Reload the schema of the modified table. */ reloadTableSchema(pParse, pTab, pTab->zName); @@ -89989,7 +94049,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc); pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName); if( !pNew->aCol || !pNew->zName ){ - db->mallocFailed = 1; + assert( db->mallocFailed ); goto exit_begin_add_column; } memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); @@ -89997,9 +94057,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ Column *pCol = &pNew->aCol[i]; pCol->zName = sqlite3DbStrDup(db, pCol->zName); pCol->zColl = 0; - pCol->zType = 0; pCol->pDflt = 0; - pCol->zDflt = 0; } pNew->pSchema = db->aDb[iDb].pSchema; pNew->addColOffset = pTab->addColOffset; @@ -90334,7 +94392,7 @@ static void sampleClear(sqlite3 *db, Stat4Sample *p){ static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){ assert( db!=0 ); if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); - p->u.aRowid = sqlite3DbMallocRaw(db, n); + p->u.aRowid = sqlite3DbMallocRawNN(db, n); if( p->u.aRowid ){ p->nRowid = n; memcpy(p->u.aRowid, pData, n); @@ -90499,12 +94557,10 @@ static const FuncDef statInitFuncdef = { SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ - statInit, /* xFunc */ - 0, /* xStep */ + statInit, /* xSFunc */ 0, /* xFinalize */ "stat_init", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ + {0} }; #ifdef SQLITE_ENABLE_STAT4 @@ -90800,12 +94856,10 @@ static const FuncDef statPushFuncdef = { SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ - statPush, /* xFunc */ - 0, /* xStep */ + statPush, /* xSFunc */ 0, /* xFinalize */ "stat_push", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ + {0} }; #define STAT_GET_STAT1 0 /* "stat" column of stat1 table */ @@ -90947,12 +95001,10 @@ static const FuncDef statGetFuncdef = { SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ - statGet, /* xFunc */ - 0, /* xStep */ + statGet, /* xSFunc */ 0, /* xFinalize */ "stat_get", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ + {0} }; static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){ @@ -90964,8 +95016,8 @@ static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){ #else UNUSED_PARAMETER( iParam ); #endif - sqlite3VdbeAddOp3(v, OP_Function0, 0, regStat4, regOut); - sqlite3VdbeChangeP4(v, -1, (char*)&statGetFuncdef, P4_FUNCDEF); + sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4, regOut, + (char*)&statGetFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 1 + IsStat34); } @@ -91011,7 +95063,7 @@ static void analyzeOneTable( /* Do not gather statistics on views or virtual tables */ return; } - if( sqlite3_strnicmp(pTab->zName, "sqlite_", 7)==0 ){ + if( sqlite3_strlike("sqlite_%", pTab->zName, 0)==0 ){ /* Do not gather statistics on system tables */ return; } @@ -91119,8 +95171,8 @@ static void analyzeOneTable( #endif sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat4+1); sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regStat4+2); - sqlite3VdbeAddOp3(v, OP_Function0, 0, regStat4+1, regStat4); - sqlite3VdbeChangeP4(v, -1, (char*)&statInitFuncdef, P4_FUNCDEF); + sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4+1, regStat4, + (char*)&statInitFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 2+IsStat34); /* Implementation of the following: @@ -91139,7 +95191,7 @@ static void analyzeOneTable( if( nColTest>0 ){ int endDistinctTest = sqlite3VdbeMakeLabel(v); int *aGotoChng; /* Array of jump instruction addresses */ - aGotoChng = sqlite3DbMallocRaw(db, sizeof(int)*nColTest); + aGotoChng = sqlite3DbMallocRawNN(db, sizeof(int)*nColTest); if( aGotoChng==0 ) continue; /* @@ -91216,8 +95268,8 @@ static void analyzeOneTable( } #endif assert( regChng==(regStat4+1) ); - sqlite3VdbeAddOp3(v, OP_Function0, 1, regStat4, regTemp); - sqlite3VdbeChangeP4(v, -1, (char*)&statPushFuncdef, P4_FUNCDEF); + sqlite3VdbeAddOp4(v, OP_Function0, 1, regStat4, regTemp, + (char*)&statPushFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 2+IsStat34); sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); @@ -91547,7 +95599,7 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ ** 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 ) pInfo->db->mallocFailed = 1; + if( pIndex->aiRowEst==0 ) sqlite3OomFault(pInfo->db); } aiRowEst = pIndex->aiRowEst; #endif @@ -91694,10 +95746,10 @@ static int loadStatTbl( Index *pPrevIdx = 0; /* Previous index in the loop */ IndexSample *pSample; /* A slot in pIdx->aSample[] */ - assert( db->lookaside.bEnabled==0 ); + assert( db->lookaside.bDisable ); zSql = sqlite3MPrintf(db, zSql1, zDb); if( !zSql ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); sqlite3DbFree(db, zSql); @@ -91737,7 +95789,7 @@ static int loadStatTbl( pIdx->aSample = sqlite3DbMallocZero(db, nByte); if( pIdx->aSample==0 ){ sqlite3_finalize(pStmt); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pSpace = (tRowcnt*)&pIdx->aSample[nSample]; pIdx->aAvgEq = pSpace; pSpace += nIdxCol; @@ -91753,7 +95805,7 @@ static int loadStatTbl( zSql = sqlite3MPrintf(db, zSql2, zDb); if( !zSql ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); sqlite3DbFree(db, zSql); @@ -91791,7 +95843,7 @@ static int loadStatTbl( pSample->p = sqlite3DbMallocZero(db, pSample->n + 2); if( pSample->p==0 ){ sqlite3_finalize(pStmt); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n); pIdx->nSample++; @@ -91808,7 +95860,7 @@ static int loadStatTbl( static int loadStat4(sqlite3 *db, const char *zDb){ int rc = SQLITE_OK; /* Result codes from subroutines */ - assert( db->lookaside.bEnabled==0 ); + assert( db->lookaside.bDisable ); if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){ rc = loadStatTbl(db, 0, "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", @@ -91853,7 +95905,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ analysisInfo sInfo; HashElem *i; char *zSql; - int rc; + int rc = SQLITE_OK; assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pBt!=0 ); @@ -91862,38 +95914,40 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); - sqlite3DefaultRowEst(pIdx); + pIdx->aiRowLogEst[0] = 0; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 sqlite3DeleteIndexSamples(db, pIdx); pIdx->aSample = 0; #endif } - /* Check to make sure the sqlite_stat1 table exists */ + /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zName; - if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)==0 ){ - return SQLITE_ERROR; + if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ + zSql = sqlite3MPrintf(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); + } } - /* Load new statistics out of the sqlite_stat1 table */ - zSql = sqlite3MPrintf(db, - "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); - if( zSql==0 ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0); - sqlite3DbFree(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)){ + Index *pIdx = sqliteHashData(i); + if( pIdx->aiRowLogEst[0]==0 ) sqlite3DefaultRowEst(pIdx); } - /* Load the statistics from the sqlite_stat4 table. */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){ - int lookasideEnabled = db->lookaside.bEnabled; - db->lookaside.bEnabled = 0; + db->lookaside.bDisable++; rc = loadStat4(db, sInfo.zDatabase); - db->lookaside.bEnabled = lookasideEnabled; + db->lookaside.bDisable--; } for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); @@ -91903,7 +95957,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ #endif if( rc==SQLITE_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } return rc; } @@ -92024,7 +96078,7 @@ static void attachFunc( ** hash tables. */ if( db->aDb==db->aDbStatic ){ - aNew = sqlite3DbMallocRaw(db, sizeof(db->aDb[0])*3 ); + aNew = sqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 ); if( aNew==0 ) return; memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2); }else{ @@ -92042,7 +96096,7 @@ static void attachFunc( flags = db->openFlags; rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr); if( rc!=SQLITE_OK ){ - if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); sqlite3_result_error(context, zErr, -1); sqlite3_free(zErr); return; @@ -92059,7 +96113,7 @@ static void attachFunc( Pager *pPager; aNew->pSchema = sqlite3SchemaGet(db, aNew->pBt); if( !aNew->pSchema ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){ zErrDyn = sqlite3MPrintf(db, "attached databases must use the same text encoding as main database"); @@ -92071,14 +96125,15 @@ static void attachFunc( sqlite3BtreeSecureDelete(aNew->pBt, sqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) ); #ifndef SQLITE_OMIT_PAGER_PRAGMAS - sqlite3BtreeSetPagerFlags(aNew->pBt, 3 | (db->flags & PAGER_FLAGS_MASK)); + sqlite3BtreeSetPagerFlags(aNew->pBt, + PAGER_SYNCHRONOUS_FULL | (db->flags & PAGER_FLAGS_MASK)); #endif sqlite3BtreeLeave(aNew->pBt); } - aNew->safety_level = 3; + aNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; aNew->zName = sqlite3DbStrDup(db, zName); if( rc==SQLITE_OK && aNew->zName==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } @@ -92144,7 +96199,7 @@ static void attachFunc( sqlite3ResetAllSchemasOfConnection(db); db->nDb = iDb; if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); sqlite3DbFree(db, zErrDyn); zErrDyn = sqlite3MPrintf(db, "out of memory"); }else if( zErrDyn==0 ){ @@ -92274,11 +96329,11 @@ static void codeAttach( assert( v || db->mallocFailed ); if( v ){ - sqlite3VdbeAddOp3(v, OP_Function0, 0, regArgs+3-pFunc->nArg, regArgs+3); + 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)); - sqlite3VdbeChangeP4(v, -1, (char *)pFunc, P4_FUNCDEF); - + /* 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). @@ -92303,12 +96358,10 @@ SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ - detachFunc, /* xFunc */ - 0, /* xStep */ + detachFunc, /* xSFunc */ 0, /* xFinalize */ "sqlite_detach", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ + {0} }; codeAttach(pParse, SQLITE_DETACH, &detach_func, pDbname, 0, 0, pDbname); } @@ -92324,12 +96377,10 @@ SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *p SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ - attachFunc, /* xFunc */ - 0, /* xStep */ + attachFunc, /* xSFunc */ 0, /* xFinalize */ "sqlite_attach", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ + {0} }; codeAttach(pParse, SQLITE_ATTACH, &attach_func, p, p, pDbname, pKey); } @@ -92789,15 +96840,6 @@ SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){ */ /* #include "sqliteInt.h" */ -/* -** This routine is called when a new SQL statement is beginning to -** be parsed. Initialize the pParse structure as needed. -*/ -SQLITE_PRIVATE void sqlite3BeginParse(Parse *pParse, int explainFlag){ - pParse->explain = (u8)explainFlag; - pParse->nVar = 0; -} - #ifndef SQLITE_OMIT_SHARED_CACHE /* ** The TableLock structure is only used by the sqlite3TableLock() and @@ -92852,7 +96894,7 @@ SQLITE_PRIVATE void sqlite3TableLock( p->zName = zName; }else{ pToplevel->nTableLock = 0; - pToplevel->db->mallocFailed = 1; + sqlite3OomFault(pToplevel->db); } } @@ -93002,15 +97044,19 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ if( pParse->pAinc!=0 && pParse->nTab==0 ) pParse->nTab = 1; sqlite3VdbeMakeReady(v, pParse); pParse->rc = SQLITE_DONE; - pParse->colNamesSet = 0; }else{ pParse->rc = SQLITE_ERROR; } + + /* We are done with this Parse object. There is no need to de-initialize it */ +#if 0 + pParse->colNamesSet = 0; pParse->nTab = 0; pParse->nMem = 0; pParse->nSet = 0; pParse->nVar = 0; DbMaskZero(pParse->cookieMask); +#endif } /* @@ -93141,12 +97187,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( } pParse->checkSchema = 1; } -#if SQLITE_USER_AUTHENTICATION - else if( pParse->db->auth.authLevelpPartIdxWhere); sqlite3ExprListDelete(db, p->aColExpr); sqlite3DbFree(db, p->zColAff); - if( p->isResized ) sqlite3DbFree(db, p->azColl); + if( p->isResized ) sqlite3DbFree(db, (void *)p->azColl); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 sqlite3_free(p->aiRowEst); #endif @@ -93274,7 +97315,6 @@ SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3 *db){ } j++; } - memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j])); db->nDb = j; if( db->nDb<=2 && db->aDb!=db->aDbStatic ){ memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0])); @@ -93347,8 +97387,6 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ for(i=0; inCol; i++, pCol++){ sqlite3DbFree(db, pCol->zName); sqlite3ExprDelete(db, pCol->pDflt); - sqlite3DbFree(db, pCol->zDflt); - sqlite3DbFree(db, pCol->zType); sqlite3DbFree(db, pCol->zColl); } sqlite3DbFree(db, pTable->aCol); @@ -93370,16 +97408,10 @@ 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. */ -SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ +static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ Index *pIndex, *pNext; TESTONLY( int nLookaside; ) /* Used to verify lookaside not used for schema */ - assert( !pTable || pTable->nRef>0 ); - - /* Do not delete the table until the reference count reaches zero. */ - if( !pTable ) return; - if( ((!db || db->pnBytesFreed==0) && (--pTable->nRef)>0) ) return; - /* 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. */ @@ -93419,6 +97451,13 @@ SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ /* Verify that no lookaside memory was used by schema tables */ assert( nLookaside==0 || nLookaside==db->lookaside.nOut ); } +SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *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; + deleteTable(db, pTable); +} + /* ** Unlink the given table from the hash tables and the delete the @@ -93486,12 +97525,8 @@ SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){ int i = -1; /* Database number */ if( zName ){ Db *pDb; - int n = sqlite3Strlen30(zName); for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){ - if( (!OMIT_TEMPDB || i!=1 ) && n==sqlite3Strlen30(pDb->zName) && - 0==sqlite3StrICmp(pDb->zName, zName) ){ - break; - } + if( 0==sqlite3StrICmp(pDb->zName, zName) ) break; } } return i; @@ -93537,7 +97572,8 @@ SQLITE_PRIVATE int sqlite3TwoPartName( int iDb; /* Database holding the object */ sqlite3 *db = pParse->db; - if( ALWAYS(pName2!=0) && pName2->n>0 ){ + assert( pName2!=0 ); + if( pName2->n>0 ){ if( db->init.busy ) { sqlite3ErrorMsg(pParse, "corrupt database"); return -1; @@ -93626,62 +97662,46 @@ SQLITE_PRIVATE void sqlite3StartTable( int iDb; /* Database number to create the table in */ Token *pName; /* Unqualified name of the table to create */ - /* The table or view name to create is passed to this routine via tokens - ** pName1 and pName2. If the table name was fully qualified, for example: - ** - ** CREATE TABLE xxx.yyy (...); - ** - ** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if - ** the table name is not fully qualified, i.e.: - ** - ** CREATE TABLE yyy(...); - ** - ** Then pName1 is set to "yyy" and pName2 is "". - ** - ** The call below sets the pName pointer to point at the token (pName1 or - ** pName2) that stores the unqualified table name. The variable iDb is - ** set to the index of the database that the table or view is to be - ** created in. - */ - iDb = sqlite3TwoPartName(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"); - return; + 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)); + pName = pName1; + }else{ + /* The common case */ + iDb = sqlite3TwoPartName(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"); + return; + } + if( !OMIT_TEMPDB && isTemp ) iDb = 1; + zName = sqlite3NameFromToken(db, pName); } - if( !OMIT_TEMPDB && isTemp ) iDb = 1; - pParse->sNameToken = *pName; - zName = sqlite3NameFromToken(db, pName); if( zName==0 ) return; if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto begin_table_error; } if( db->init.iDb==1 ) isTemp = 1; #ifndef SQLITE_OMIT_AUTHORIZATION - assert( (isTemp & 1)==isTemp ); + assert( isTemp==0 || isTemp==1 ); + assert( isView==0 || isView==1 ); { - int code; + static const u8 aCode[] = { + SQLITE_CREATE_TABLE, + SQLITE_CREATE_TEMP_TABLE, + SQLITE_CREATE_VIEW, + SQLITE_CREATE_TEMP_VIEW + }; char *zDb = db->aDb[iDb].zName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ goto begin_table_error; } - if( isView ){ - if( !OMIT_TEMPDB && isTemp ){ - code = SQLITE_CREATE_TEMP_VIEW; - }else{ - code = SQLITE_CREATE_VIEW; - } - }else{ - if( !OMIT_TEMPDB && isTemp ){ - code = SQLITE_CREATE_TEMP_TABLE; - }else{ - code = SQLITE_CREATE_TABLE; - } - } - if( !isVirtual && sqlite3AuthCheck(pParse, code, zName, 0, zDb) ){ + if( !isVirtual && sqlite3AuthCheck(pParse, (int)aCode[isTemp+2*isView], + zName, 0, zDb) ){ goto begin_table_error; } } @@ -93717,8 +97737,8 @@ SQLITE_PRIVATE void sqlite3StartTable( pTable = sqlite3DbMallocZero(db, sizeof(Table)); if( pTable==0 ){ - db->mallocFailed = 1; - pParse->rc = SQLITE_NOMEM; + assert( db->mallocFailed ); + pParse->rc = SQLITE_NOMEM_BKPT; pParse->nErr++; goto begin_table_error; } @@ -93774,10 +97794,8 @@ SQLITE_PRIVATE void sqlite3StartTable( addr1 = sqlite3VdbeAddOp1(v, OP_If, reg3); VdbeCoverage(v); fileFormat = (db->flags & SQLITE_LegacyFileFmt)!=0 ? 1 : SQLITE_MAX_FILE_FORMAT; - sqlite3VdbeAddOp2(v, OP_Integer, fileFormat, reg3); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, reg3); - sqlite3VdbeAddOp2(v, OP_Integer, ENC(db), reg3); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, reg3); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, fileFormat); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, ENC(db)); sqlite3VdbeJumpHere(v, addr1); /* This just creates a place-holder record in the sqlite_master table. @@ -93814,18 +97832,19 @@ begin_table_error: return; } -/* -** This macro is used to compare two strings in a case-insensitive manner. -** It is slightly faster than calling sqlite3StrICmp() directly, but -** produces larger code. -** -** WARNING: This macro is not compatible with the strcmp() family. It -** returns true if the two strings are equal, otherwise false. +/* Set properties of a table column based on the (magical) +** name of the column. */ -#define STRICMP(x, y) (\ -sqlite3UpperToLower[*(unsigned char *)(x)]== \ -sqlite3UpperToLower[*(unsigned char *)(y)] \ -&& sqlite3StrICmp((x)+1,(y)+1)==0 ) +#if SQLITE_ENABLE_HIDDEN_COLUMNS +SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ + if( sqlite3_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; + } +} +#endif + /* ** Add a new column to the table currently being constructed. @@ -93835,10 +97854,11 @@ sqlite3UpperToLower[*(unsigned char *)(y)] \ ** first to get things going. Then this routine is called for each ** column. */ -SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName){ +SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ Table *p; int i; char *z; + char *zType; Column *pCol; sqlite3 *db = pParse->db; if( (p = pParse->pNewTable)==0 ) return; @@ -93848,10 +97868,13 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName){ return; } #endif - z = sqlite3NameFromToken(db, pName); + z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2); if( z==0 ) return; + memcpy(z, pName->z, pName->n); + z[pName->n] = 0; + sqlite3Dequote(z); for(i=0; inCol; i++){ - if( STRICMP(z, p->aCol[i].zName) ){ + if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){ sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); sqlite3DbFree(db, z); return; @@ -93869,14 +97892,23 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName){ pCol = &p->aCol[p->nCol]; memset(pCol, 0, sizeof(p->aCol[0])); pCol->zName = z; + sqlite3ColumnPropertiesFromName(p, pCol); - /* If there is no type specified, columns have the default affinity - ** 'BLOB'. If there is a type specified, then sqlite3AddColumnType() will - ** be called next to set pCol->affinity correctly. - */ - pCol->affinity = SQLITE_AFF_BLOB; - pCol->szEst = 1; + if( pType->n==0 ){ + /* If there is no type specified, columns have the default affinity + ** 'BLOB'. */ + pCol->affinity = SQLITE_AFF_BLOB; + pCol->szEst = 1; + }else{ + zType = z + sqlite3Strlen30(z) + 1; + memcpy(zType, pType->z, pType->n); + zType[pType->n] = 0; + sqlite3Dequote(zType); + pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst); + pCol->colFlags |= COLFLAG_HASTYPE; + } p->nCol++; + pParse->constraintName.n = 0; } /* @@ -93922,7 +97954,7 @@ SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){ char aff = SQLITE_AFF_NUMERIC; const char *zChar = 0; - if( zIn==0 ) return aff; + assert( zIn!=0 ); while( zIn[0] ){ h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff]; zIn++; @@ -93979,28 +98011,6 @@ SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){ return aff; } -/* -** This routine is called by the parser while in the middle of -** parsing a CREATE TABLE statement. The pFirst token is the first -** token in the sequence of tokens that describe the type of the -** column currently under construction. pLast is the last token -** in the sequence. Use this information to construct a string -** that contains the typename of the column and store that string -** in zType. -*/ -SQLITE_PRIVATE void sqlite3AddColumnType(Parse *pParse, Token *pType){ - Table *p; - Column *pCol; - - p = pParse->pNewTable; - if( p==0 || NEVER(p->nCol<1) ) return; - pCol = &p->aCol[p->nCol-1]; - assert( pCol->zType==0 || CORRUPT_DB ); - sqlite3DbFree(pParse->db, pCol->zType); - pCol->zType = sqlite3NameFromToken(pParse->db, pType); - pCol->affinity = sqlite3AffinityType(pCol->zType, &pCol->szEst); -} - /* ** The expression is the default value for the most recently added column ** of the table currently under construction. @@ -94026,11 +98036,16 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse *pParse, ExprSpan *pSpan){ ** tokens that point to volatile memory. The 'span' of the expression ** is required by pragma table_info. */ + Expr x; sqlite3ExprDelete(db, pCol->pDflt); - pCol->pDflt = sqlite3ExprDup(db, pSpan->pExpr, EXPRDUP_REDUCE); - sqlite3DbFree(db, pCol->zDflt); - pCol->zDflt = sqlite3DbStrNDup(db, (char*)pSpan->zStart, - (int)(pSpan->zEnd - pSpan->zStart)); + 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.flags = EP_Skip; + pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); + sqlite3DbFree(db, x.u.zToken); } } sqlite3ExprDelete(db, pSpan->pExpr); @@ -94086,7 +98101,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */ ){ Table *pTab = pParse->pNewTable; - char *zType = 0; + Column *pCol = 0; int iCol = -1, i; int nTerm; if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit; @@ -94098,8 +98113,8 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( pTab->tabFlags |= TF_HasPrimaryKey; if( pList==0 ){ iCol = pTab->nCol - 1; - pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; - zType = pTab->aCol[iCol].zType; + pCol = &pTab->aCol[iCol]; + pCol->colFlags |= COLFLAG_PRIMKEY; nTerm = 1; }else{ nTerm = pList->nExpr; @@ -94111,8 +98126,8 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( const char *zCName = pCExpr->u.zToken; for(iCol=0; iColnCol; iCol++){ if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){ - pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; - zType = pTab->aCol[iCol].zType; + pCol = &pTab->aCol[iCol]; + pCol->colFlags |= COLFLAG_PRIMKEY; break; } } @@ -94120,7 +98135,8 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( } } if( nTerm==1 - && zType && sqlite3StrICmp(zType, "INTEGER")==0 + && pCol + && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0 && sortOrder!=SQLITE_SO_DESC ){ pTab->iPKey = iCol; @@ -94260,13 +98276,11 @@ SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ ** 1 chance in 2^32. So we're safe enough. */ SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ - int r1 = sqlite3GetTempReg(pParse); sqlite3 *db = pParse->db; Vdbe *v = pParse->pVdbe; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - sqlite3VdbeAddOp2(v, OP_Integer, db->aDb[iDb].pSchema->schema_cookie+1, r1); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, r1); - sqlite3ReleaseTempReg(pParse, r1); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, + db->aDb[iDb].pSchema->schema_cookie+1); } /* @@ -94348,7 +98362,7 @@ static char *createTableStmt(sqlite3 *db, Table *p){ n += 35 + 6*p->nCol; zStmt = sqlite3DbMallocRaw(0, n); if( zStmt==0 ){ - db->mallocFailed = 1; + sqlite3OomFault(db); return 0; } sqlite3_snprintf(n, zStmt, "CREATE TABLE "); @@ -94401,9 +98415,9 @@ static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){ assert( pIdx->isResized==0 ); nByte = (sizeof(char*) + sizeof(i16) + 1)*N; zExtra = sqlite3DbMallocZero(db, nByte); - if( zExtra==0 ) return SQLITE_NOMEM; + if( zExtra==0 ) return SQLITE_NOMEM_BKPT; memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn); - pIdx->azColl = (char**)zExtra; + pIdx->azColl = (const char**)zExtra; zExtra += sizeof(char*)*N; memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn); pIdx->aiColumn = (i16*)zExtra; @@ -94497,8 +98511,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( pTab->iPKey>=0 ){ ExprList *pList; Token ipkToken; - ipkToken.z = pTab->aCol[pTab->iPKey].zName; - ipkToken.n = sqlite3Strlen30(ipkToken.z); + sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); if( pList==0 ) return; @@ -94542,7 +98555,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ ** do not enforce this for imposter tables.) */ if( !db->init.imposterTable ){ for(i=0; iaCol[pPk->aiColumn[i]].notNull = 1; + pTab->aCol[pPk->aiColumn[i]].notNull = OE_Abort; } pPk->uniqNotNull = 1; } @@ -94584,7 +98597,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ if( !hasColumn(pPk->aiColumn, j, i) ){ assert( jnColumn ); pPk->aiColumn[j] = i; - pPk->azColl[j] = "BINARY"; + pPk->azColl[j] = sqlite3StrBINARY; j++; } } @@ -94641,9 +98654,13 @@ SQLITE_PRIVATE void sqlite3EndTable( ** So do not write to the disk again. Extract the root page number ** for the table from the db->init.newTnum field. (The page number ** should have been put there by the sqliteOpenCb routine.) + ** + ** If the root page number is 1, that means this is the sqlite_master + ** table itself. So mark it read-only. */ if( db->init.busy ){ p->tnum = db->init.newTnum; + if( p->tnum==1 ) p->tabFlags |= TF_Readonly; } /* Special processing for WITHOUT ROWID Tables */ @@ -94744,7 +98761,7 @@ SQLITE_PRIVATE void sqlite3EndTable( sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); sqlite3Select(pParse, pSelect, &dest); - sqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield); + sqlite3VdbeEndCoroutine(v, regYield); sqlite3VdbeJumpHere(v, addrTop - 1); if( pParse->nErr ) return; pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect); @@ -94828,7 +98845,7 @@ SQLITE_PRIVATE void sqlite3EndTable( pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ - db->mallocFailed = 1; + sqlite3OomFault(db); return; } pParse->pNewTable = 0; @@ -94932,7 +98949,6 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ sqlite3_xauth xAuth; /* Saved xAuth pointer */ - u8 bEnabledLA; /* Saved db->lookaside.bEnabled state */ assert( pTable ); @@ -94978,45 +98994,56 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ ** statement that defines the view. */ assert( pTable->pSelect ); - bEnabledLA = db->lookaside.bEnabled; - if( pTable->pCheck ){ - db->lookaside.bEnabled = 0; - sqlite3ColumnsFromExprList(pParse, pTable->pCheck, - &pTable->nCol, &pTable->aCol); - }else{ - pSel = sqlite3SelectDup(db, pTable->pSelect, 0); - if( pSel ){ - n = pParse->nTab; - sqlite3SrcListAssignCursors(pParse, pSel->pSrc); - pTable->nCol = -1; - db->lookaside.bEnabled = 0; + pSel = sqlite3SelectDup(db, pTable->pSelect, 0); + if( pSel ){ + n = pParse->nTab; + sqlite3SrcListAssignCursors(pParse, pSel->pSrc); + pTable->nCol = -1; + db->lookaside.bDisable++; #ifndef SQLITE_OMIT_AUTHORIZATION - xAuth = db->xAuth; - db->xAuth = 0; - pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); - db->xAuth = xAuth; + xAuth = db->xAuth; + db->xAuth = 0; + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); + db->xAuth = xAuth; #else - pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); #endif - pParse->nTab = n; - if( pSelTab ){ - assert( pTable->aCol==0 ); - pTable->nCol = pSelTab->nCol; - pTable->aCol = pSelTab->aCol; - pSelTab->nCol = 0; - pSelTab->aCol = 0; - sqlite3DeleteTable(db, pSelTab); - assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); - }else{ - pTable->nCol = 0; - nErr++; + pParse->nTab = n; + 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, + &pTable->nCol, &pTable->aCol); + if( db->mallocFailed==0 + && pParse->nErr==0 + && pTable->nCol==pSel->pEList->nExpr + ){ + sqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel); } - sqlite3SelectDelete(db, pSel); - } else { + }else if( pSelTab ){ + /* CREATE VIEW name AS... without an argument list. Construct + ** the column names from the SELECT statement that defines the view. + */ + assert( pTable->aCol==0 ); + pTable->nCol = pSelTab->nCol; + pTable->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); + }else{ + pTable->nCol = 0; nErr++; } + sqlite3DeleteTable(db, pSelTab); + sqlite3SelectDelete(db, pSel); + db->lookaside.bDisable--; + } else { + nErr++; } - db->lookaside.bEnabled = bEnabledLA; pTable->pSchema->schemaFlags |= DB_UnresetViews; #endif /* SQLITE_OMIT_VIEW */ return nErr; @@ -95096,6 +99123,7 @@ SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iT 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); #ifndef SQLITE_OMIT_AUTOVACUUM @@ -95481,7 +99509,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( pFKey->zTo, (void *)pFKey ); if( pNextTo==pFKey ){ - db->mallocFailed = 1; + sqlite3OomFault(db); goto fk_end; } if( pNextTo ){ @@ -95563,6 +99591,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ tnum = pIndex->tnum; } pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); + assert( pKey!=0 || db->mallocFailed || pParse->nErr ); /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; @@ -95586,8 +99615,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); VdbeCoverage(v); - assert( pKey!=0 || db->mallocFailed || pParse->nErr ); - if( IsUniqueIndex(pIndex) && pKey!=0 ){ + if( IsUniqueIndex(pIndex) ){ int j2 = sqlite3VdbeCurrentAddr(v) + 3; sqlite3VdbeGoto(v, j2); addr2 = sqlite3VdbeCurrentAddr(v); @@ -95634,7 +99662,7 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( p = sqlite3DbMallocZero(db, nByte + nExtra); if( p ){ char *pExtra = ((char*)p)+ROUND8(sizeof(Index)); - p->azColl = (char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol); + p->azColl = (const char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol); p->aiRowLogEst = (LogEst*)pExtra; pExtra += sizeof(LogEst)*(nCol+1); p->aiColumn = (i16*)pExtra; pExtra += sizeof(i16)*nCol; p->aSortOrder = (u8*)pExtra; @@ -95841,8 +99869,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( */ if( pList==0 ){ Token prevCol; - prevCol.z = pTab->aCol[pTab->nCol-1].zName; - prevCol.n = sqlite3Strlen30(prevCol.z); + sqlite3TokenInit(&prevCol, pTab->aCol[pTab->nCol-1].zName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); if( pList==0 ) goto exit_create_index; @@ -95911,7 +99938,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( for(i=0, pListItem=pList->a; inExpr; i++, pListItem++){ Expr *pCExpr; /* The i-th index expression */ int requestedSortOrder; /* ASC or DESC on the i-th expression */ - char *zColl; /* Collation sequence name */ + const char *zColl; /* Collation sequence name */ sqlite3StringToId(pListItem->pExpr); sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr, pListItem->pExpr, 0); @@ -95957,7 +99984,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( }else if( j>=0 ){ zColl = pTab->aCol[j].zColl; } - if( !zColl ) zColl = "BINARY"; + if( !zColl ) zColl = sqlite3StrBINARY; if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ goto exit_create_index; } @@ -95986,11 +100013,25 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( assert( i==pIndex->nColumn ); }else{ pIndex->aiColumn[i] = XN_ROWID; - pIndex->azColl[i] = "BINARY"; + pIndex->azColl[i] = sqlite3StrBINARY; } sqlite3DefaultRowEst(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 ); + if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){ + pIndex->isCovering = 1; + for(j=0; jnCol; j++){ + if( j==pTab->iPKey ) continue; + if( sqlite3ColumnOfIndex(pIndex,j)>=0 ) continue; + pIndex->isCovering = 0; + break; + } + } + if( pTab==pParse->pNewTable ){ /* This routine has been called to create an automatic index as a ** result of a PRIMARY KEY or UNIQUE clause on a column definition, or @@ -96028,7 +100069,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break; z1 = pIdx->azColl[k]; z2 = pIndex->azColl[k]; - if( z1!=z2 && sqlite3StrICmp(z1, z2) ) break; + if( sqlite3StrICmp(z1, z2) ) break; } if( k==pIdx->nKeyCol ){ if( pIdx->onError!=pIndex->onError ){ @@ -96064,7 +100105,7 @@ SQLITE_PRIVATE Index *sqlite3CreateIndex( pIndex->zName, pIndex); if( p ){ assert( p==pIndex ); /* Malloc must have failed */ - db->mallocFailed = 1; + sqlite3OomFault(db); goto exit_create_index; } db->flags |= SQLITE_InternChanges; @@ -96493,10 +100534,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( ){ struct SrcList_item *pItem; assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */ + assert( db!=0 ); if( pList==0 ){ - pList = sqlite3DbMallocZero(db, sizeof(SrcList) ); + pList = sqlite3DbMallocRawNN(db, sizeof(SrcList) ); if( pList==0 ) return 0; pList->nAlloc = 1; + pList->nSrc = 0; } pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc); if( db->mallocFailed ){ @@ -96639,7 +100682,7 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI ** table-valued-function. */ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){ - if( p && pList ){ + if( p ){ struct SrcList_item *pItem = &p->a[p->nSrc-1]; assert( pItem->fg.notIndexed==0 ); assert( pItem->fg.isIndexedBy==0 ); @@ -96677,7 +100720,7 @@ SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){ } /* -** Begin a transaction +** Generate VDBE code for a BEGIN statement. */ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ sqlite3 *db; @@ -96687,7 +100730,6 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ assert( pParse!=0 ); db = pParse->db; assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ){ return; } @@ -96699,11 +100741,11 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ sqlite3VdbeUsesBtree(v, i); } } - sqlite3VdbeAddOp2(v, OP_AutoCommit, 0, 0); + sqlite3VdbeAddOp0(v, OP_AutoCommit); } /* -** Commit a transaction +** Generate VDBE code for a COMMIT statement. */ SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){ Vdbe *v; @@ -96715,12 +100757,12 @@ SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){ } v = sqlite3GetVdbe(pParse); if( v ){ - sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 0); + sqlite3VdbeAddOp1(v, OP_AutoCommit, 1); } } /* -** Rollback a transaction +** Generate VDBE code for a ROLLBACK statement. */ SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){ Vdbe *v; @@ -96782,7 +100824,7 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ db->aDb[1].pBt = pBt; assert( db->aDb[1].pSchema ); if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){ - db->mallocFailed = 1; + sqlite3OomFault(db); return 1; } } @@ -96899,7 +100941,7 @@ SQLITE_PRIVATE void sqlite3HaltConstraint( sqlite3MayAbort(pParse); } sqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type); - if( p5Errmsg ) sqlite3VdbeChangeP5(v, p5Errmsg); + sqlite3VdbeChangeP5(v, p5Errmsg); } /* @@ -96917,14 +100959,14 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200); if( pIdx->aColExpr ){ - sqlite3XPrintf(&errMsg, 0, "index '%q'", pIdx->zName); + sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName); }else{ for(j=0; jnKeyCol; j++){ char *zCol; assert( pIdx->aiColumn[j]>=0 ); zCol = pTab->aCol[pIdx->aiColumn[j]].zName; if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2); - sqlite3XPrintf(&errMsg, 0, "%s.%s", pTab->zName, zCol); + sqlite3XPrintf(&errMsg, "%s.%s", pTab->zName, zCol); } } zErr = sqlite3StrAccumFinish(&errMsg); @@ -97110,9 +101152,8 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ if( pKey ){ assert( sqlite3KeyInfoIsWriteable(pKey) ); for(i=0; iazColl[i]; - assert( zColl!=0 ); - pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : + const char *zColl = pIdx->azColl[i]; + pKey->aColl[i] = zColl==sqlite3StrBINARY ? 0 : sqlite3LocateCollSeq(pParse, zColl); pKey->aSortOrder[i] = pIdx->aSortOrder[i]; } @@ -97158,10 +101199,9 @@ SQLITE_PRIVATE With *sqlite3WithAdd( }else{ pNew = sqlite3DbMallocZero(db, sizeof(*pWith)); } - assert( zName!=0 || pNew==0 ); - assert( db->mallocFailed==0 || pNew==0 ); + assert( (pNew!=0 && zName!=0) || db->mallocFailed ); - if( pNew==0 ){ + if( db->mallocFailed ){ sqlite3ExprListDelete(db, pArglist); sqlite3SelectDelete(db, pQuery); sqlite3DbFree(db, zName); @@ -97375,7 +101415,7 @@ static CollSeq *findCollSeqEntry( */ assert( pDel==0 || pDel==pColl ); if( pDel!=0 ){ - db->mallocFailed = 1; + sqlite3OomFault(db); sqlite3DbFree(db, pDel); pColl = 0; } @@ -97441,8 +101481,8 @@ SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq( ** 5: UTF16 byte order conversion required - argument count matches exactly ** 6: Perfect match: encoding and argument count match exactly. ** -** If nArg==(-2) then any function with a non-null xStep or xFunc is -** a perfect match and any function with both xStep and xFunc NULL is +** If nArg==(-2) then any function with a non-null xSFunc is +** a perfect match and any function with xSFunc NULL is ** a non-match. */ #define FUNC_PERFECT_MATCH 6 /* The score for a perfect match */ @@ -97454,7 +101494,7 @@ static int matchQuality( int match; /* nArg of -2 is a special case */ - if( nArg==(-2) ) return (p->xFunc==0 && p->xStep==0) ? 0 : FUNC_PERFECT_MATCH; + if( nArg==(-2) ) return (p->xSFunc==0) ? 0 : FUNC_PERFECT_MATCH; /* Wrong number of arguments means "no match" */ if( p->nArg!=nArg && p->nArg>=0 ) return 0; @@ -97482,14 +101522,12 @@ static int matchQuality( ** a pointer to the matching FuncDef if found, or 0 if there is no match. */ static FuncDef *functionSearch( - FuncDefHash *pHash, /* Hash table to search */ int h, /* Hash of the name */ - const char *zFunc, /* Name of function */ - int nFunc /* Number of bytes in zFunc */ + const char *zFunc /* Name of function */ ){ FuncDef *p; - for(p=pHash->a[h]; p; p=p->pHash){ - if( sqlite3StrNICmp(p->zName, zFunc, nFunc)==0 && p->zName[nFunc]==0 ){ + for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){ + if( sqlite3StrICmp(p->zName, zFunc)==0 ){ return p; } } @@ -97499,23 +101537,26 @@ static FuncDef *functionSearch( /* ** Insert a new FuncDef into a FuncDefHash hash table. */ -SQLITE_PRIVATE void sqlite3FuncDefInsert( - FuncDefHash *pHash, /* The hash table into which to insert */ - FuncDef *pDef /* The function definition to insert */ +SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( + FuncDef *aDef, /* List of global functions to be inserted */ + int nDef /* Length of the apDef[] list */ ){ - FuncDef *pOther; - int nName = sqlite3Strlen30(pDef->zName); - u8 c1 = (u8)pDef->zName[0]; - int h = (sqlite3UpperToLower[c1] + nName) % ArraySize(pHash->a); - pOther = functionSearch(pHash, h, pDef->zName, nName); - if( pOther ){ - assert( pOther!=pDef && pOther->pNext!=pDef ); - pDef->pNext = pOther->pNext; - pOther->pNext = pDef; - }else{ - pDef->pNext = 0; - pDef->pHash = pHash->a[h]; - pHash->a[h] = pDef; + int i; + for(i=0; ipNext!=&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]; + } } } @@ -97532,7 +101573,7 @@ SQLITE_PRIVATE void sqlite3FuncDefInsert( ** no matching function previously existed. ** ** If nArg is -2, then the first valid function found is returned. A -** function is valid if either xFunc or xStep is non-zero. The nArg==(-2) +** function is valid if xSFunc is non-zero. The nArg==(-2) ** case is used to see if zName is a valid function name for some number ** of arguments. If nArg is -2, then createFlag must be 0. ** @@ -97542,8 +101583,7 @@ SQLITE_PRIVATE void sqlite3FuncDefInsert( */ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( sqlite3 *db, /* An open database */ - const char *zName, /* Name of the function. Not null-terminated */ - int nName, /* Number of characters in the name */ + const char *zName, /* Name of the function. zero-terminated */ int nArg, /* Number of arguments. -1 means any number */ u8 enc, /* Preferred text encoding */ u8 createFlag /* Create new entry if true and does not otherwise exist */ @@ -97552,14 +101592,15 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( FuncDef *pBest = 0; /* Best match found so far */ int bestScore = 0; /* Score of best match */ int h; /* Hash value */ + int nName; /* Length of the name */ assert( nArg>=(-2) ); assert( nArg>=(-1) || createFlag==0 ); - h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % ArraySize(db->aFunc.a); + nName = sqlite3Strlen30(zName); /* First search for a match amongst the application-defined functions. */ - p = functionSearch(&db->aFunc, h, zName, nName); + p = (FuncDef*)sqlite3HashFind(&db->aFunc, zName); while( p ){ int score = matchQuality(p, nArg, enc); if( score>bestScore ){ @@ -97582,9 +101623,9 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( ** So we must not search for built-ins when creating a new function. */ if( !createFlag && (pBest==0 || (db->flags & SQLITE_PreferBuiltin)!=0) ){ - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); bestScore = 0; - p = functionSearch(pHash, h, zName, nName); + h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % SQLITE_FUNC_HASH_SZ; + p = functionSearch(h, zName); while( p ){ int score = matchQuality(p, nArg, enc); if( score>bestScore ){ @@ -97601,15 +101642,22 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( */ if( createFlag && bestScorezName = (char *)&pBest[1]; + FuncDef *pOther; + pBest->zName = (const char*)&pBest[1]; pBest->nArg = (u16)nArg; pBest->funcFlags = enc; - memcpy(pBest->zName, zName, nName); - pBest->zName[nName] = 0; - sqlite3FuncDefInsert(&db->aFunc, pBest); + memcpy((char*)&pBest[1], zName, nName+1); + pOther = (FuncDef*)sqlite3HashInsert(&db->aFunc, pBest->zName, pBest); + if( pOther==pBest ){ + sqlite3DbFree(db, pBest); + sqlite3OomFault(db); + return 0; + }else{ + pBest->pNext = pOther; + } } - if( pBest && (pBest->xStep || pBest->xFunc || createFlag) ){ + if( pBest && (pBest->xSFunc || createFlag) ){ return pBest; } return 0; @@ -97663,7 +101711,7 @@ SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema)); } if( !p ){ - db->mallocFailed = 1; + sqlite3OomFault(db); }else if ( 0==p->file_format ){ sqlite3HashInit(&p->tblHash); sqlite3HashInit(&p->idxHash); @@ -97784,7 +101832,8 @@ SQLITE_PRIVATE void sqlite3MaterializeView( assert( pFrom->a[0].pOn==0 ); assert( pFrom->a[0].pUsing==0 ); } - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 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); @@ -97820,7 +101869,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( */ if( pOrderBy && (pLimit == 0) ) { sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType); - goto limit_where_cleanup_2; + goto limit_where_cleanup; } /* We only need to generate a select expression if there @@ -97842,16 +101891,16 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( */ pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); - if( pSelectRowid == 0 ) goto limit_where_cleanup_2; + if( pSelectRowid == 0 ) goto limit_where_cleanup; pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid); - if( pEList == 0 ) goto limit_where_cleanup_2; + if( pEList == 0 ) goto limit_where_cleanup; /* 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_2; + goto limit_where_cleanup; } /* generate the SELECT expression tree. */ @@ -97861,21 +101910,11 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); - if( pWhereRowid == 0 ) goto limit_where_cleanup_1; - pInClause = sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0); - if( pInClause == 0 ) goto limit_where_cleanup_1; - - pInClause->x.pSelect = pSelect; - pInClause->flags |= EP_xIsSelect; - sqlite3ExprSetHeightAndFlags(pParse, pInClause); + pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0) : 0; + sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; - /* something went wrong. clean up anything allocated. */ -limit_where_cleanup_1: - sqlite3SelectDelete(pParse->db, pSelect); - return 0; - -limit_where_cleanup_2: +limit_where_cleanup: sqlite3ExprDelete(pParse->db, pWhere); sqlite3ExprListDelete(pParse->db, pOrderBy); sqlite3ExprDelete(pParse->db, pLimit); @@ -97926,11 +101965,12 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( int addrBypass = 0; /* Address of jump over the delete logic */ int addrLoop = 0; /* Top of the delete loop */ int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ + int bComplex; /* True if there are triggers or FKs or + ** subqueries in the WHERE clause */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ - int bComplex; /* True if there are either triggers or FKs */ #endif memset(&sContext, 0, sizeof(sContext)); @@ -97958,7 +101998,6 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( #else # define pTrigger 0 # define isView 0 -# define bComplex 0 #endif #ifdef SQLITE_OMIT_VIEW # undef isView @@ -98043,6 +102082,9 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( && pWhere==0 && !bComplex && !IsVirtual(pTab) +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + && db->xPreUpdateCallback==0 +#endif ){ assert( !isView ); sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); @@ -98057,7 +102099,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ { - u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK; + u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK|WHERE_SEEK_TABLE; + if( sNC.ncFlags & NC_VarSelect ) bComplex = 1; wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW); if( HasRowid(pTab) ){ /* For a rowid table, initialize the RowSet to an empty set */ @@ -98116,7 +102159,7 @@ 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 = sqlite3DbMallocRaw(db, nIdx+2); + aToOpen = sqlite3DbMallocRawNN(db, nIdx+2); if( aToOpen==0 ){ sqlite3WhereEnd(pWInfo); goto delete_from_cleanup; @@ -98160,8 +102203,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); } testcase( IsVirtual(pTab) ); - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iTabCur, aToOpen, - &iDataCur, &iIdxCur); + sqlite3OpenTableAndIndices(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); @@ -98392,17 +102435,27 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to - ** fire the INSTEAD OF triggers). */ + ** fire the INSTEAD OF triggers). + ** + ** If variable 'count' is non-zero, then this OP_Delete instruction should + ** invoke the update-hook. The pre-update-hook, on the other hand should + ** be invoked unless table pTab is a system table. The difference is that + ** the update-hook is not invoked for rows removed by REPLACE, but the + ** pre-update-hook is. + */ if( pTab->pSelect==0 ){ + u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); - if( count ){ - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); + sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + if( eMode!=ONEPASS_OFF ){ + sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); } if( iIdxNoSeek>=0 ){ sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek); } - sqlite3VdbeChangeP5(v, eMode==ONEPASS_MULTI); + if( eMode==ONEPASS_MULTI ) p5 |= OPFLAG_SAVEPOSITION; + sqlite3VdbeChangeP5(v, p5); } /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to @@ -98812,7 +102865,8 @@ static void printfFunc( x.nUsed = 0; x.apArg = argv+1; sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); - sqlite3XPrintf(&str, SQLITE_PRINTF_SQLFUNC, zFormat, &x); + str.printfFlags = SQLITE_PRINTF_SQLFUNC; + sqlite3XPrintf(&str, zFormat, &x); n = str.nChar; sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, SQLITE_DYNAMIC); @@ -99140,10 +103194,10 @@ static void total_changes( ** A structure defining how to do GLOB-style comparisons. */ struct compareInfo { - u8 matchAll; - u8 matchOne; - u8 matchSet; - u8 noCase; + u8 matchAll; /* "*" or "%" */ + u8 matchOne; /* "?" or "_" */ + u8 matchSet; /* "[" or 0 */ + u8 noCase; /* true to ignore case differences */ }; /* @@ -99206,22 +103260,14 @@ static int patternCompare( const u8 *zPattern, /* The glob pattern */ const u8 *zString, /* The string to compare against the glob */ const struct compareInfo *pInfo, /* Information about how to do the compare */ - u32 esc /* The escape character */ + u32 matchOther /* The escape char (LIKE) or '[' (GLOB) */ ){ u32 c, c2; /* Next pattern and input string chars */ u32 matchOne = pInfo->matchOne; /* "?" or "_" */ u32 matchAll = pInfo->matchAll; /* "*" or "%" */ - u32 matchOther; /* "[" or the escape character */ u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */ const u8 *zEscaped = 0; /* One past the last escaped input char */ - /* The GLOB operator does not have an ESCAPE clause. And LIKE does not - ** have the matchSet operator. So we either have to look for one or - ** the other, never both. Hence the single variable matchOther is used - ** to store the one we have to look for. - */ - matchOther = esc ? esc : pInfo->matchSet; - while( (c = Utf8Read(zPattern))!=0 ){ if( c==matchAll ){ /* Match "*" */ /* Skip over multiple "*" characters in the pattern. If there @@ -99235,7 +103281,7 @@ static int patternCompare( if( c==0 ){ return 1; /* "*" at the end of the pattern matches */ }else if( c==matchOther ){ - if( esc ){ + if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); if( c==0 ) return 0; }else{ @@ -99243,7 +103289,7 @@ static int patternCompare( ** 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,esc)==0 ){ + && patternCompare(&zPattern[-1],zString,pInfo,matchOther)==0 ){ SQLITE_SKIP_UTF8(zString); } return *zString!=0; @@ -99269,18 +103315,18 @@ static int patternCompare( } while( (c2 = *(zString++))!=0 ){ if( c2!=c && c2!=cx ) continue; - if( patternCompare(zPattern,zString,pInfo,esc) ) return 1; + if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; } }else{ while( (c2 = Utf8Read(zString))!=0 ){ if( c2!=c ) continue; - if( patternCompare(zPattern,zString,pInfo,esc) ) return 1; + if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; } } return 0; } if( c==matchOther ){ - if( esc ){ + if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); if( c==0 ) return 0; zEscaped = zPattern; @@ -99333,7 +103379,14 @@ static int patternCompare( ** The sqlite3_strglob() interface. */ SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlobPattern, const char *zString){ - return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, 0)==0; + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[')==0; +} + +/* +** The sqlite3_strlike() interface. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc)==0; } /* @@ -99364,10 +103417,22 @@ static void likeFunc( sqlite3_value **argv ){ const unsigned char *zA, *zB; - u32 escape = 0; + u32 escape; int nPat; sqlite3 *db = sqlite3_context_db_handle(context); + struct compareInfo *pInfo = sqlite3_user_data(context); +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + || sqlite3_value_type(argv[1])==SQLITE_BLOB + ){ +#ifdef SQLITE_TEST + sqlite3_like_count++; +#endif + sqlite3_result_int(context, 0); + return; + } +#endif zB = sqlite3_value_text(argv[0]); zA = sqlite3_value_text(argv[1]); @@ -99395,13 +103460,13 @@ static void likeFunc( return; } escape = sqlite3Utf8Read(&zEsc); + }else{ + escape = pInfo->matchSet; } if( zA && zB ){ - struct compareInfo *pInfo = sqlite3_user_data(context); #ifdef SQLITE_TEST sqlite3_like_count++; #endif - sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape)); } } @@ -99947,6 +104012,14 @@ static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ sqlite3 *db = sqlite3_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. + */ + if( (db->flags & SQLITE_LoadExtFunc)==0 ){ + sqlite3_result_error(context, "not authorized", -1); + return; + } + if( argc==2 ){ zProc = (const char *)sqlite3_value_text(argv[1]); }else{ @@ -100172,11 +104245,11 @@ static void groupConcatFinalize(sqlite3_context *context){ ** 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 sqlite3RegisterBuiltinFunctions(sqlite3 *db){ +SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ int rc = sqlite3_overload_function(db, "MATCH", 2); assert( rc==SQLITE_NOMEM || rc==SQLITE_OK ); if( rc==SQLITE_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } } @@ -100185,8 +104258,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(sqlite3 *db){ */ static void setLikeOptFlag(sqlite3 *db, const char *zName, u8 flagVal){ FuncDef *pDef; - pDef = sqlite3FindFunction(db, zName, sqlite3Strlen30(zName), - 2, SQLITE_UTF8, 0); + pDef = sqlite3FindFunction(db, zName, 2, SQLITE_UTF8, 0); if( ALWAYS(pDef) ){ pDef->funcFlags |= flagVal; } @@ -100234,9 +104306,7 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas return 0; } assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - pDef = sqlite3FindFunction(db, pExpr->u.zToken, - sqlite3Strlen30(pExpr->u.zToken), - 2, SQLITE_UTF8, 0); + pDef = sqlite3FindFunction(db, pExpr->u.zToken, 2, SQLITE_UTF8, 0); if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){ return 0; } @@ -100260,7 +104330,7 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas ** ** After this routine runs */ -SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void){ +SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ /* ** The following array holds FuncDef structures for all of the functions ** defined in this file. @@ -100268,8 +104338,27 @@ SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void){ ** The array cannot be constant since changes are made to the ** FuncDef.pHash elements at start-time. The elements of this array ** are read-only after initialization is complete. + ** + ** For peak efficiency, put the most frequently used function last. */ - static SQLITE_WSD FuncDef aBuiltinFunc[] = { + static FuncDef aBuiltinFunc[] = { +#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 ), +#endif +#if SQLITE_USER_AUTHENTICATION + FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), +#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), FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), FUNCTION(rtrim, 1, 2, 0, trimFunc ), @@ -100287,8 +104376,6 @@ SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void){ FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), FUNCTION(instr, 2, 0, 0, instrFunc ), - FUNCTION(substr, 2, 0, 0, substrFunc ), - FUNCTION(substr, 3, 0, 0, substrFunc ), FUNCTION(printf, -1, 0, 0, printfFunc ), FUNCTION(unicode, 1, 0, 0, unicodeFunc ), FUNCTION(char, -1, 0, 0, charFunc ), @@ -100299,40 +104386,22 @@ SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void){ #endif FUNCTION(upper, 1, 0, 0, upperFunc ), FUNCTION(lower, 1, 0, 0, lowerFunc ), - FUNCTION(coalesce, 1, 0, 0, 0 ), - FUNCTION(coalesce, 0, 0, 0, 0 ), - FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQLITE_FUNC_COALESCE), FUNCTION(hex, 1, 0, 0, hexFunc ), FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE), - 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), VFUNCTION(random, 0, 0, 0, randomFunc ), VFUNCTION(randomblob, 1, 0, 0, randomBlob ), FUNCTION(nullif, 2, 0, 1, nullifFunc ), DFUNCTION(sqlite_version, 0, 0, 0, versionFunc ), DFUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ), FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ), -#if SQLITE_USER_AUTHENTICATION - FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), -#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 */ FUNCTION(quote, 1, 0, 0, quoteFunc ), VFUNCTION(last_insert_rowid, 0, 0, 0, last_insert_rowid), VFUNCTION(changes, 0, 0, 0, changes ), VFUNCTION(total_changes, 0, 0, 0, total_changes ), FUNCTION(replace, 3, 0, 0, replaceFunc ), FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc ), - #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 ), - #endif + 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 ), @@ -100350,21 +104419,33 @@ SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void){ LIKEFUNC(like, 2, &likeInfoNorm, SQLITE_FUNC_LIKE), LIKEFUNC(like, 3, &likeInfoNorm, SQLITE_FUNC_LIKE), #endif + FUNCTION(coalesce, 1, 0, 0, 0 ), + FUNCTION(coalesce, 0, 0, 0, 0 ), + FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQLITE_FUNC_COALESCE), }; - - int i; - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); - FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aBuiltinFunc); - - for(i=0; iu.pHash){ + int n = sqlite3Strlen30(p->zName); + int h = p->zName[0] + n; + printf(" %s(%d)", p->zName, h); + } + printf("\n"); + } + } #endif } @@ -100591,7 +104672,7 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( } }else if( paiCol ){ assert( nCol>1 ); - aiCol = (int *)sqlite3DbMallocRaw(pParse->db, nCol*sizeof(int)); + aiCol = (int *)sqlite3DbMallocRawNN(pParse->db, nCol*sizeof(int)); if( !aiCol ) return 1; *paiCol = aiCol; } @@ -100621,7 +104702,7 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( int i, j; for(i=0; iaiColumn[i]; /* Index of column in parent tbl */ - char *zDfltColl; /* Def. collation for column */ + const char *zDfltColl; /* Def. collation for column */ char *zIdxCol; /* Name of indexed column */ if( iCol<0 ) break; /* No foreign keys against expression indexes */ @@ -100630,9 +104711,7 @@ 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 = "BINARY"; - } + if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; zIdxCol = pParent->aCol[iCol].zName; @@ -101536,10 +105615,12 @@ static Trigger *fkActionTrigger( int iAction = (pChanges!=0); /* 1 for UPDATE, 0 for DELETE */ action = pFKey->aAction[iAction]; + if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){ + return 0; + } pTrigger = pFKey->apTrigger[iAction]; if( action!=OE_None && !pTrigger ){ - u8 enableLookaside; /* Copy of db->lookaside.bEnabled */ char const *zFrom; /* Name of child table */ int nFrom; /* Length in bytes of zFrom */ Index *pIdx = 0; /* Parent key index for this FK */ @@ -101566,11 +105647,9 @@ static Trigger *fkActionTrigger( assert( iFromCol>=0 ); assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKeynCol) ); assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); - tToCol.z = pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName; - tFromCol.z = pFKey->pFrom->aCol[iFromCol].zName; - - tToCol.n = sqlite3Strlen30(tToCol.z); - tFromCol.n = sqlite3Strlen30(tFromCol.z); + sqlite3TokenInit(&tToCol, + pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName); + sqlite3TokenInit(&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 @@ -101650,8 +105729,7 @@ static Trigger *fkActionTrigger( } /* Disable lookaside memory allocation */ - enableLookaside = db->lookaside.bEnabled; - db->lookaside.bEnabled = 0; + db->lookaside.bDisable++; pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ @@ -101673,7 +105751,7 @@ static Trigger *fkActionTrigger( } /* Re-enable the lookaside buffer, if it was disabled earlier. */ - db->lookaside.bEnabled = enableLookaside; + db->lookaside.bDisable--; sqlite3ExprDelete(db, pWhere); sqlite3ExprDelete(db, pWhen); @@ -101868,7 +105946,7 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ Table *pTab = pIdx->pTable; pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); if( !pIdx->zColAff ){ - db->mallocFailed = 1; + sqlite3OomFault(db); return 0; } for(n=0; nnColumn; n++){ @@ -101919,7 +105997,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ sqlite3 *db = sqlite3VdbeDb(v); zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); if( !zColAff ){ - db->mallocFailed = 1; + sqlite3OomFault(db); return; } @@ -102015,7 +106093,7 @@ static int autoIncBegin( pInfo = pToplevel->pAinc; while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; } if( pInfo==0 ){ - pInfo = sqlite3DbMallocRaw(pParse->db, sizeof(*pInfo)); + pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo)); if( pInfo==0 ) return 0; pInfo->pNext = pToplevel->pAinc; pToplevel->pAinc = pInfo; @@ -102039,7 +106117,6 @@ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* Database only autoinc table */ int memId; /* Register holding max rowid */ - int addr; /* A VDBE address */ Vdbe *v = pParse->pVdbe; /* VDBE under construction */ /* This routine is never called during trigger-generation. It is @@ -102049,33 +106126,46 @@ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *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}, + /* 2 */ {OP_Column, 0, 0, 0}, + /* 3 */ {OP_Ne, 0, 7, 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} + }; + 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); - sqlite3VdbeAddOp3(v, OP_Null, 0, memId, memId+1); - addr = sqlite3VdbeCurrentAddr(v); sqlite3VdbeLoadString(v, memId-1, p->pTab->zName); - sqlite3VdbeAddOp2(v, OP_Rewind, 0, addr+9); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_Column, 0, 0, memId); - sqlite3VdbeAddOp3(v, OP_Ne, memId-1, addr+7, memId); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); - sqlite3VdbeAddOp2(v, OP_Rowid, 0, memId+1); - sqlite3VdbeAddOp3(v, OP_Column, 0, 1, memId); - sqlite3VdbeGoto(v, addr+9); - sqlite3VdbeAddOp2(v, OP_Next, 0, addr+2); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Integer, 0, memId); - sqlite3VdbeAddOp0(v, OP_Close); + aOp = sqlite3VdbeAddOpList(v, ArraySize(autoInc), autoInc, iLn); + if( aOp==0 ) break; + aOp[0].p2 = memId; + aOp[0].p3 = memId+1; + 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; } } /* ** Update the maximum rowid for an autoincrement calculation. ** -** This routine should be called when the top of the stack holds a +** This routine should be called when the regRowid register holds a ** new rowid that is about to be inserted. If that new rowid is ** larger than the maximum rowid in the memId memory cell, then the -** memory cell is updated. The stack is unchanged. +** memory cell is updated. */ static void autoIncStep(Parse *pParse, int memId, int regRowid){ if( memId>0 ){ @@ -102090,31 +106180,44 @@ static void autoIncStep(Parse *pParse, int memId, int regRowid){ ** table (either directly or through triggers) needs to call this ** routine just before the "exit" code. */ -SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse){ +static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){ AutoincInfo *p; Vdbe *v = pParse->pVdbe; sqlite3 *db = pParse->db; assert( v ); for(p = pParse->pAinc; p; p = p->pNext){ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList autoIncEnd[] = { + /* 0 */ {OP_NotNull, 0, 2, 0}, + /* 1 */ {OP_NewRowid, 0, 0, 0}, + /* 2 */ {OP_MakeRecord, 0, 2, 0}, + /* 3 */ {OP_Insert, 0, 0, 0}, + /* 4 */ {OP_Close, 0, 0, 0} + }; + VdbeOp *aOp; Db *pDb = &db->aDb[p->iDb]; - int addr1; 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); - addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, memId+1); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_NewRowid, 0, memId+1); - sqlite3VdbeJumpHere(v, addr1); - sqlite3VdbeAddOp3(v, OP_MakeRecord, memId-1, 2, iRec); - sqlite3VdbeAddOp3(v, OP_Insert, 0, iRec, memId+1); - sqlite3VdbeChangeP5(v, OPFLAG_APPEND); - sqlite3VdbeAddOp0(v, OP_Close); + aOp = sqlite3VdbeAddOpList(v, ArraySize(autoIncEnd), autoIncEnd, iLn); + if( aOp==0 ) break; + aOp[0].p1 = memId+1; + aOp[1].p2 = memId+1; + aOp[2].p1 = memId-1; + aOp[2].p3 = iRec; + aOp[3].p2 = iRec; + aOp[3].p3 = memId+1; + aOp[3].p5 = OPFLAG_APPEND; sqlite3ReleaseTempReg(pParse, iRec); } } +SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse){ + if( pParse->pAinc ) autoIncrementEnd(pParse); +} #else /* ** If SQLITE_OMIT_AUTOINCREMENT is defined, then the three routines @@ -102445,7 +106548,7 @@ SQLITE_PRIVATE void sqlite3Insert( rc = sqlite3Select(pParse, pSelect, &dest); regFromSelect = dest.iSdst; if( rc || db->mallocFailed || pParse->nErr ) goto insert_cleanup; - sqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield); + sqlite3VdbeEndCoroutine(v, regYield); sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ assert( pSelect->pEList ); nColumn = pSelect->pEList->nExpr; @@ -102521,10 +106624,8 @@ SQLITE_PRIVATE void sqlite3Insert( /* Make sure the number of columns in the source data matches the number ** of columns to be inserted into the table. */ - if( IsVirtual(pTab) ){ - for(i=0; inCol; i++){ - nHidden += (IsHiddenColumn(&pTab->aCol[i]) ? 1 : 0); - } + for(i=0; inCol; i++){ + nHidden += (IsHiddenColumn(&pTab->aCol[i]) ? 1 : 0); } if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){ sqlite3ErrorMsg(pParse, @@ -102547,9 +106648,9 @@ SQLITE_PRIVATE void sqlite3Insert( /* If this is not a view, open the table and and all indices */ if( !isView ){ int nIdx; - nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, -1, 0, + nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0, &iDataCur, &iIdxCur); - aRegIdx = sqlite3DbMallocRaw(db, sizeof(int)*(nIdx+1)); + aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+1)); if( aRegIdx==0 ){ goto insert_cleanup; } @@ -102620,15 +106721,14 @@ SQLITE_PRIVATE void sqlite3Insert( /* Create the new column data */ - for(i=0; inCol; i++){ - if( pColumn==0 ){ - j = i; - }else{ + for(i=j=0; inCol; i++){ + if( pColumn ){ for(j=0; jnId; j++){ if( pColumn->a[j].idx==i ) break; } } - if( (!useTempTable && !pList) || (pColumn && j>=pColumn->nId) ){ + 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); @@ -102636,6 +106736,7 @@ SQLITE_PRIVATE void sqlite3Insert( assert( pSelect==0 ); /* Otherwise useTempTable is true */ sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i+1); } + if( pColumn==0 && !IsOrdinaryHiddenColumn(&pTab->aCol[i]) ) j++; } /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger, @@ -102719,7 +106820,6 @@ SQLITE_PRIVATE void sqlite3Insert( } if( pColumn==0 ){ if( IsHiddenColumn(&pTab->aCol[i]) ){ - assert( IsVirtual(pTab) ); j = -1; nHidden++; }else{ @@ -102758,7 +106858,7 @@ SQLITE_PRIVATE void sqlite3Insert( { int isReplace; /* Set to true if constraints may cause a replace */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace + regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0 ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, @@ -102840,6 +106940,59 @@ insert_cleanup: #undef tmask #endif +/* +** Meanings of bits in of pWalker->eCode for checkConstraintUnchanged() +*/ +#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 +** columns that are being modifed by an UPDATE statement. +*/ +static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN ){ + assert( pExpr->iColumn>=0 || pExpr->iColumn==-1 ); + if( pExpr->iColumn>=0 ){ + if( pWalker->u.aiCol[pExpr->iColumn]>=0 ){ + pWalker->eCode |= CKCNSTRNT_COLUMN; + } + }else{ + pWalker->eCode |= CKCNSTRNT_ROWID; + } + } + return WRC_Continue; +} + +/* +** pExpr is a CHECK constraint on a row that is being UPDATE-ed. The +** 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 +** changing columns (or the rowid if it is changing). In other words, +** return true if this CHECK constraint can be skipped when validating +** the new row in the UPDATE statement. +*/ +static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){ + Walker w; + memset(&w, 0, sizeof(w)); + w.eCode = 0; + w.xExprCallback = checkConstraintExprNode; + w.u.aiCol = aiChng; + sqlite3WalkExpr(&w, pExpr); + if( !chngRowid ){ + testcase( (w.eCode & CKCNSTRNT_ROWID)!=0 ); + w.eCode &= ~CKCNSTRNT_ROWID; + } + testcase( w.eCode==0 ); + testcase( w.eCode==CKCNSTRNT_COLUMN ); + testcase( w.eCode==CKCNSTRNT_ROWID ); + testcase( w.eCode==(CKCNSTRNT_ROWID|CKCNSTRNT_COLUMN) ); + return !w.eCode; +} + /* ** Generate code to do constraint checks prior to an INSERT or an UPDATE ** on table pTab. @@ -102934,7 +107087,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( u8 pkChng, /* Non-zero if the rowid or PRIMARY KEY changed */ 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 *pbMayReplace, /* OUT: Set to true if constraint may cause a replace */ + int *aiChng /* column i is unchanged if aiChng[i]<0 */ ){ Vdbe *v; /* VDBE under constrution */ Index *pIdx; /* Pointer to one of the indices */ @@ -102980,10 +107134,14 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( */ for(i=0; iiPKey ){ + 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; + if( onError==OE_None ) continue; /* This column is allowed to be NULL */ if( overrideError!=OE_Default ){ onError = overrideError; }else if( onError==OE_Default ){ @@ -103032,8 +107190,11 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( pParse->ckBase = regNewData+1; onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; inExpr; i++){ - int allOk = sqlite3VdbeMakeLabel(v); - sqlite3ExprIfTrue(pParse, pCheck->a[i].pExpr, allOk, SQLITE_JUMPIFNULL); + 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( onError==OE_Ignore ){ sqlite3VdbeGoto(v, ignoreDest); }else{ @@ -103133,9 +107294,18 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, - regNewData, 1, 0, OE_Replace, - ONEPASS_SINGLE, -1); + regNewData, 1, 0, OE_Replace, 1, -1); }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); + } +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ if( pTab->pIndex ){ sqlite3MultiWrite(pParse); sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1); @@ -103196,7 +107366,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int x; if( iField==XN_EXPR ){ pParse->ckBase = regNewData+1; - sqlite3ExprCode(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); + sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); pParse->ckBase = 0; VdbeComment((v, "%s column %d", pIdx->zName, i)); }else{ @@ -103207,7 +107377,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( }else{ x = iField + regNewData + 1; } - sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); + sqlite3VdbeAddOp2(v, iField<0 ? OP_IntCopy : OP_SCopy, x, regIdx+i); VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName)); } } @@ -103383,7 +107553,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; } - if( pik_flags ) sqlite3VdbeChangeP5(v, pik_flags); + sqlite3VdbeChangeP5(v, pik_flags); } if( !HasRowid(pTab) ) return; regData = regNewData + 1; @@ -103405,7 +107575,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( } sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); + sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); } sqlite3VdbeChangeP5(v, pik_flags); } @@ -103435,6 +107605,7 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( Parse *pParse, /* Parsing context */ Table *pTab, /* Table to be opened */ int op, /* OP_OpenRead or OP_OpenWrite */ + u8 p5, /* P5 value for OP_Open* opcodes (except on WITHOUT ROWID) */ int iBase, /* Use this for the table cursor, if there is one */ u8 *aToOpen, /* If not NULL: boolean for each table and index */ int *piDataCur, /* Write the database source cursor number here */ @@ -103447,6 +107618,7 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( Vdbe *v; assert( op==OP_OpenRead || op==OP_OpenWrite ); + assert( op==OP_OpenWrite || p5==0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output ** variables *piDataCur and *piIdxCur uninitialized so that valgrind @@ -103468,14 +107640,16 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); - if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) && piDataCur ){ - *piDataCur = iIdxCur; - } if( aToOpen==0 || aToOpen[i+1] ){ sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); } + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + if( piDataCur ) *piDataCur = iIdxCur; + }else{ + sqlite3VdbeChangeP5(v, p5); + } } if( iBase>pParse->nTab ) pParse->nTab = iBase; return i; @@ -103494,20 +107668,6 @@ SQLITE_API int sqlite3_xferopt_count; #ifndef SQLITE_OMIT_XFER_OPT -/* -** Check to collation names to see if they are compatible. -*/ -static int xferCompatibleCollation(const char *z1, const char *z2){ - if( z1==0 ){ - return z2==0; - } - if( z2==0 ){ - return 0; - } - return sqlite3StrICmp(z1, z2)==0; -} - - /* ** Check to see if index pSrc is compatible as a source of data ** for index pDest in an insert transfer optimization. The rules @@ -103543,7 +107703,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){ return 0; /* Different sort orders */ } - if( !xferCompatibleCollation(pSrc->azColl[i],pDest->azColl[i]) ){ + if( sqlite3_stricmp(pSrc->azColl[i],pDest->azColl[i])!=0 ){ return 0; /* Different collating sequences */ } } @@ -103658,7 +107818,7 @@ static int xferOptimization( return 0; /* The result set must have exactly one column */ } assert( pEList->a[0].pExpr ); - if( pEList->a[0].pExpr->op!=TK_ALL ){ + if( pEList->a[0].pExpr->op!=TK_ASTERISK ){ return 0; /* The result set must be the special operator "*" */ } @@ -103694,21 +107854,32 @@ static int xferOptimization( for(i=0; inCol; i++){ Column *pDestCol = &pDest->aCol[i]; Column *pSrcCol = &pSrc->aCol[i]; +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS + if( (db->flags & SQLITE_Vacuum)==0 + && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN + ){ + return 0; /* Neither table may have __hidden__ columns */ + } +#endif if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( !xferCompatibleCollation(pDestCol->zColl, pSrcCol->zColl) ){ + if( sqlite3_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 - && ((pDestCol->zDflt==0)!=(pSrcCol->zDflt==0) - || (pDestCol->zDflt && strcmp(pDestCol->zDflt, pSrcCol->zDflt)!=0)) - ){ - return 0; /* Default values must be the same for all columns */ + if( 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) + || (pDestCol->pDflt && strcmp(pDestCol->pDflt->u.zToken, + pSrcCol->pDflt->u.zToken)!=0) + ){ + return 0; /* Default values must be the same for all columns */ + } } } for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ @@ -103803,9 +107974,9 @@ static int xferOptimization( assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); - sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid, + (char*)pDest, P4_TABLE); sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); - sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -103844,9 +108015,10 @@ static int xferOptimization( ** a VACUUM command. In that case keys may not be written in strictly ** sorted order. */ for(i=0; inColumn; i++){ - char *zColl = pSrcIdx->azColl[i]; - assert( zColl!=0 ); - if( sqlite3_stricmp("BINARY", zColl) ) break; + const char *zColl = pSrcIdx->azColl[i]; + assert( sqlite3_stricmp(sqlite3StrBINARY, zColl)!=0 + || sqlite3StrBINARY==zColl ); + if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break; } if( i==pSrcIdx->nColumn ){ idxInsFlags = OPFLAG_USESEEKRESULT; @@ -103971,7 +108143,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_exec( for(i=0; imallocFailed = 1; + sqlite3OomFault(db); goto exec_out; } } @@ -104012,7 +108184,7 @@ exec_out: if( *pzErrMsg ){ memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg); }else{ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; sqlite3Error(db, SQLITE_NOMEM); } }else if( pzErrMsg ){ @@ -104323,6 +108495,12 @@ struct sqlite3_api_routines { /* Version 3.9.0 and later */ unsigned int (*value_subtype)(sqlite3_value*); void (*result_subtype)(sqlite3_context*,unsigned int); + /* Version 3.10.0 and later */ + int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int); + int (*strlike)(const char*,const char*,unsigned int); + int (*db_cacheflush)(sqlite3*); + /* Version 3.12.0 and later */ + int (*system_errno)(sqlite3*); }; /* @@ -104562,6 +108740,12 @@ struct sqlite3_api_routines { /* Version 3.9.0 and later */ #define sqlite3_value_subtype sqlite3_api->value_subtype #define sqlite3_result_subtype sqlite3_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 +/* Version 3.12.0 and later */ +#define sqlite3_system_errno sqlite3_api->system_errno #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -104976,7 +109160,13 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_bind_zeroblob64, /* Version 3.9.0 and later */ sqlite3_value_subtype, - sqlite3_result_subtype + sqlite3_result_subtype, + /* Version 3.10.0 and later */ + sqlite3_status64, + sqlite3_strlike, + sqlite3_db_cacheflush, + /* Version 3.12.0 and later */ + sqlite3_system_errno }; /* @@ -105024,8 +109214,9 @@ 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 sqlite3_enable_load_extension() to turn on extension - ** loading. Otherwise you get the following error. + ** must call either sqlite3_enable_load_extension(db) or + ** sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0) + ** to turn on extension loading. */ if( (db->flags & SQLITE_LoadExtension)==0 ){ if( pzErrMsg ){ @@ -105040,7 +109231,7 @@ static int sqlite3LoadExtension( #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; ii=0 && zFile[iFile]!='/'; iFile--){} @@ -105119,7 +109310,7 @@ static int sqlite3LoadExtension( /* Append the new shared library handle to the db->aExtension array. */ aHandle = sqlite3DbMallocZero(db, sizeof(handle)*(db->nExtension+1)); if( aHandle==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } if( db->nExtension>0 ){ memcpy(aHandle, db->aExtension, sizeof(handle)*db->nExtension); @@ -105164,9 +109355,9 @@ SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3 *db){ SQLITE_API int SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff){ sqlite3_mutex_enter(db->mutex); if( onoff ){ - db->flags |= SQLITE_LoadExtension; + db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc; }else{ - db->flags &= ~SQLITE_LoadExtension; + db->flags &= ~(SQLITE_LoadExtension|SQLITE_LoadExtFunc); } sqlite3_mutex_leave(db->mutex); return SQLITE_OK; @@ -105181,7 +109372,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int ono ** dummy pointer. */ #ifdef SQLITE_OMIT_LOAD_EXTENSION -static const sqlite3_api_routines sqlite3Apis; +static const sqlite3_api_routines sqlite3Apis = { 0 }; #endif @@ -105241,7 +109432,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_auto_extension(void (*xInit)(void)){ void (**aNew)(void); aNew = sqlite3_realloc64(wsdAutoext.aExt, nByte); if( aNew==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ wsdAutoext.aExt = aNew; wsdAutoext.aExt[wsdAutoext.nExt] = xInit; @@ -105387,43 +109578,44 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_FLAG 2 #define PragTyp_BUSY_TIMEOUT 3 #define PragTyp_CACHE_SIZE 4 -#define PragTyp_CASE_SENSITIVE_LIKE 5 -#define PragTyp_COLLATION_LIST 6 -#define PragTyp_COMPILE_OPTIONS 7 -#define PragTyp_DATA_STORE_DIRECTORY 8 -#define PragTyp_DATABASE_LIST 9 -#define PragTyp_DEFAULT_CACHE_SIZE 10 -#define PragTyp_ENCODING 11 -#define PragTyp_FOREIGN_KEY_CHECK 12 -#define PragTyp_FOREIGN_KEY_LIST 13 -#define PragTyp_INCREMENTAL_VACUUM 14 -#define PragTyp_INDEX_INFO 15 -#define PragTyp_INDEX_LIST 16 -#define PragTyp_INTEGRITY_CHECK 17 -#define PragTyp_JOURNAL_MODE 18 -#define PragTyp_JOURNAL_SIZE_LIMIT 19 -#define PragTyp_LOCK_PROXY_FILE 20 -#define PragTyp_LOCKING_MODE 21 -#define PragTyp_PAGE_COUNT 22 -#define PragTyp_MMAP_SIZE 23 -#define PragTyp_PAGE_SIZE 24 -#define PragTyp_SECURE_DELETE 25 -#define PragTyp_SHRINK_MEMORY 26 -#define PragTyp_SOFT_HEAP_LIMIT 27 -#define PragTyp_STATS 28 -#define PragTyp_SYNCHRONOUS 29 -#define PragTyp_TABLE_INFO 30 -#define PragTyp_TEMP_STORE 31 -#define PragTyp_TEMP_STORE_DIRECTORY 32 -#define PragTyp_THREADS 33 -#define PragTyp_WAL_AUTOCHECKPOINT 34 -#define PragTyp_WAL_CHECKPOINT 35 -#define PragTyp_ACTIVATE_EXTENSIONS 36 -#define PragTyp_HEXKEY 37 -#define PragTyp_KEY 38 -#define PragTyp_REKEY 39 -#define PragTyp_LOCK_STATUS 40 -#define PragTyp_PARSER_TRACE 41 +#define PragTyp_CACHE_SPILL 5 +#define PragTyp_CASE_SENSITIVE_LIKE 6 +#define PragTyp_COLLATION_LIST 7 +#define PragTyp_COMPILE_OPTIONS 8 +#define PragTyp_DATA_STORE_DIRECTORY 9 +#define PragTyp_DATABASE_LIST 10 +#define PragTyp_DEFAULT_CACHE_SIZE 11 +#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 { @@ -105465,14 +109657,14 @@ static const struct sPragmaNames { #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) { /* zName: */ "cache_size", /* ePragTyp: */ PragTyp_CACHE_SIZE, - /* ePragFlag: */ 0, + /* ePragFlag: */ PragFlag_NeedSchema, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) { /* zName: */ "cache_spill", - /* ePragTyp: */ PragTyp_FLAG, + /* ePragTyp: */ PragTyp_CACHE_SPILL, /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CacheSpill }, + /* iArg: */ 0 }, #endif { /* zName: */ "case_sensitive_like", /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, @@ -105686,7 +109878,7 @@ static const struct sPragmaNames { /* ePragFlag: */ 0, /* iArg: */ 0 }, #endif -#if defined(SQLITE_DEBUG) +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE) { /* zName: */ "parser_trace", /* ePragTyp: */ PragTyp_PARSER_TRACE, /* ePragFlag: */ 0, @@ -105846,8 +110038,8 @@ static const struct sPragmaNames { /* ** Interpret the given string as a safety level. Return 0 for OFF, -** 1 for ON or NORMAL and 2 for FULL. Return 1 for an empty or -** unrecognized string argument. The FULL option is disallowed +** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or +** unrecognized string argument. The FULL and EXTRA option is disallowed ** if the omitFull parameter it 1. ** ** Note that the values returned are one less that the values that @@ -105856,18 +110048,21 @@ static const struct sPragmaNames { ** and older scripts may have used numbers 0 for OFF and 1 for ON. */ static u8 getSafetyLevel(const char *z, int omitFull, u8 dflt){ - /* 123456789 123456789 */ - static const char zText[] = "onoffalseyestruefull"; - static const u8 iOffset[] = {0, 1, 2, 4, 9, 12, 16}; - static const u8 iLength[] = {2, 2, 3, 5, 3, 4, 4}; - static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 2}; + /* 123456789 123456789 123 */ + static const char zText[] = "onoffalseyestruextrafull"; + static const u8 iOffset[] = {0, 1, 2, 4, 9, 12, 15, 20}; + static const u8 iLength[] = {2, 2, 3, 5, 3, 4, 5, 4}; + 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); } n = sqlite3Strlen30(z); - for(i=0; i, or NULL */ int minusFlag /* True if a '-' sign preceded */ ){ @@ -106127,7 +110322,7 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeRunOnlyOnce(v); pParse->nMem = 2; - /* Interpret the [database.] part of the pragma statement. iDb is the + /* 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); if( iDb<0 ) return; @@ -106216,8 +110411,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) /* - ** PRAGMA [database.]default_cache_size - ** PRAGMA [database.]default_cache_size=N + ** PRAGMA [schema.]default_cache_size + ** PRAGMA [schema.]default_cache_size=N ** ** The first form reports the current persistent setting for the ** page cache size. The value returned is the maximum number of @@ -106244,20 +110439,21 @@ SQLITE_PRIVATE void sqlite3Pragma( { OP_Noop, 0, 0, 0}, { OP_ResultRow, 1, 1, 0}, }; - int addr; + VdbeOp *aOp; sqlite3VdbeUsesBtree(v, iDb); if( !zRight ){ setOneColumnName(v, "cache_size"); pParse->nMem += 2; - addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize,iLn); - sqlite3VdbeChangeP1(v, addr, iDb); - sqlite3VdbeChangeP1(v, addr+1, iDb); - sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE); + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize)); + aOp = sqlite3VdbeAddOpList(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); - sqlite3VdbeAddOp2(v, OP_Integer, size, 1); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, 1); + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, size); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); pDb->pSchema->cache_size = size; sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); @@ -106268,8 +110464,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) /* - ** PRAGMA [database.]page_size - ** PRAGMA [database.]page_size=N + ** PRAGMA [schema.]page_size + ** PRAGMA [schema.]page_size=N ** ** The first form reports the current setting for the ** database page size in bytes. The second form sets the @@ -106288,15 +110484,15 @@ SQLITE_PRIVATE void sqlite3Pragma( */ db->nextPagesize = sqlite3Atoi(zRight); if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize,-1,0) ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } } break; } /* - ** PRAGMA [database.]secure_delete - ** PRAGMA [database.]secure_delete=ON/OFF + ** PRAGMA [schema.]secure_delete + ** PRAGMA [schema.]secure_delete=ON/OFF ** ** The first form reports the current setting for the ** secure_delete flag. The second form changes the secure_delete @@ -106321,8 +110517,8 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* - ** PRAGMA [database.]max_page_count - ** PRAGMA [database.]max_page_count=N + ** PRAGMA [schema.]max_page_count + ** PRAGMA [schema.]max_page_count=N ** ** The first form reports the current setting for the ** maximum number of pages in the database file. The @@ -106333,7 +110529,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** change. The only purpose is to provide an easy way to test ** the sqlite3AbsInt32() function. ** - ** PRAGMA [database.]page_count + ** PRAGMA [schema.]page_count ** ** Return the number of pages in the specified database. */ @@ -106354,8 +110550,8 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* - ** PRAGMA [database.]locking_mode - ** PRAGMA [database.]locking_mode = (normal|exclusive) + ** PRAGMA [schema.]locking_mode + ** PRAGMA [schema.]locking_mode = (normal|exclusive) */ case PragTyp_LOCKING_MODE: { const char *zRet = "normal"; @@ -106400,8 +110596,8 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* - ** PRAGMA [database.]journal_mode - ** PRAGMA [database.]journal_mode = + ** PRAGMA [schema.]journal_mode + ** PRAGMA [schema.]journal_mode = ** (delete|persist|off|truncate|memory|wal|off) */ case PragTyp_JOURNAL_MODE: { @@ -106441,8 +110637,8 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* - ** PRAGMA [database.]journal_size_limit - ** PRAGMA [database.]journal_size_limit=N + ** PRAGMA [schema.]journal_size_limit + ** PRAGMA [schema.]journal_size_limit=N ** ** Get or set the size limit on rollback journal files. */ @@ -106461,8 +110657,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif /* SQLITE_OMIT_PAGER_PRAGMAS */ /* - ** PRAGMA [database.]auto_vacuum - ** PRAGMA [database.]auto_vacuum=N + ** PRAGMA [schema.]auto_vacuum + ** PRAGMA [schema.]auto_vacuum=N ** ** Get or set the value of the database 'auto-vacuum' parameter. ** The value is one of: 0 NONE 1 FULL 2 INCREMENTAL @@ -106495,16 +110691,18 @@ SQLITE_PRIVATE void sqlite3Pragma( { OP_ReadCookie, 0, 1, BTREE_LARGEST_ROOT_PAGE}, { OP_If, 1, 0, 0}, /* 2 */ { OP_Halt, SQLITE_OK, OE_Abort, 0}, /* 3 */ - { OP_Integer, 0, 1, 0}, /* 4 */ - { OP_SetCookie, 0, BTREE_INCR_VACUUM, 1}, /* 5 */ + { OP_SetCookie, 0, BTREE_INCR_VACUUM, 0}, /* 4 */ }; - int iAddr; - iAddr = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6, iLn); - sqlite3VdbeChangeP1(v, iAddr, iDb); - sqlite3VdbeChangeP1(v, iAddr+1, iDb); - sqlite3VdbeChangeP2(v, iAddr+2, iAddr+4); - sqlite3VdbeChangeP1(v, iAddr+4, eAuto-1); - sqlite3VdbeChangeP1(v, iAddr+5, iDb); + VdbeOp *aOp; + int iAddr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setMeta6)); + aOp = sqlite3VdbeAddOpList(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); } } @@ -106513,7 +110711,7 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif /* - ** PRAGMA [database.]incremental_vacuum(N) + ** PRAGMA [schema.]incremental_vacuum(N) ** ** Do N steps of incremental vacuuming on a database. */ @@ -106536,8 +110734,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #ifndef SQLITE_OMIT_PAGER_PRAGMAS /* - ** PRAGMA [database.]cache_size - ** PRAGMA [database.]cache_size=N + ** PRAGMA [schema.]cache_size + ** PRAGMA [schema.]cache_size=N ** ** The first form reports the current local setting for the ** page cache size. The second form sets the local @@ -106549,19 +110747,60 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_CACHE_SIZE: { assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ - if( sqlite3ReadSchema(pParse) ) goto pragma_out; returnSingleInt(v, "cache_size", pDb->pSchema->cache_size); }else{ int size = sqlite3Atoi(zRight); pDb->pSchema->cache_size = size; sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); - if( sqlite3ReadSchema(pParse) ) goto pragma_out; } break; } /* - ** PRAGMA [database.]mmap_size(N) + ** PRAGMA [schema.]cache_spill + ** PRAGMA cache_spill=BOOLEAN + ** PRAGMA [schema.]cache_spill=N + ** + ** The first form reports the current local setting for the + ** page cache spill size. The second form turns cache spill on + ** or off. When turnning cache spill on, the size is set to the + ** current cache_size. The third form sets a spill size that + ** may be different form the cache size. + ** If N is positive then that is the + ** number of pages in the cache. If N is negative, then the + ** number of pages is adjusted so that the cache uses -N kibibytes + ** of memory. + ** + ** If the number of cache_spill pages is less then the number of + ** cache_size pages, no spilling occurs until the page count exceeds + ** the number of cache_size pages. + ** + ** The cache_spill=BOOLEAN setting applies to all attached schemas, + ** not just the schema specified. + */ + case PragTyp_CACHE_SPILL: { + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( !zRight ){ + returnSingleInt(v, "cache_spill", + (db->flags & SQLITE_CacheSpill)==0 ? 0 : + sqlite3BtreeSetSpillSize(pDb->pBt,0)); + }else{ + int size = 1; + if( sqlite3GetInt32(zRight, &size) ){ + sqlite3BtreeSetSpillSize(pDb->pBt, size); + } + if( sqlite3GetBoolean(zRight, size!=0) ){ + db->flags |= SQLITE_CacheSpill; + }else{ + db->flags &= ~SQLITE_CacheSpill; + } + setAllPagerFlags(db); + } + break; + } + + /* + ** PRAGMA [schema.]mmap_size(N) ** ** Used to set mapping size limit. The mapping size limit is ** used to limit the aggregate size of all memory mapped regions of the @@ -106705,8 +110944,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #if SQLITE_ENABLE_LOCKING_STYLE /* - ** PRAGMA [database.]lock_proxy_file - ** PRAGMA [database.]lock_proxy_file = ":auto:"|"lock_file_path" + ** PRAGMA [schema.]lock_proxy_file + ** PRAGMA [schema.]lock_proxy_file = ":auto:"|"lock_file_path" ** ** Return or set the value of the lock_proxy_file flag. Changing ** the value sets a specific file to be used for database access locks. @@ -106741,8 +110980,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif /* SQLITE_ENABLE_LOCKING_STYLE */ /* - ** PRAGMA [database.]synchronous - ** PRAGMA [database.]synchronous=OFF|ON|NORMAL|FULL + ** PRAGMA [schema.]synchronous + ** PRAGMA [schema.]synchronous=OFF|ON|NORMAL|FULL|EXTRA ** ** Return or set the local value of the synchronous flag. Changing ** the local value does not make changes to the disk file and the @@ -106760,6 +110999,7 @@ SQLITE_PRIVATE void sqlite3Pragma( int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK; if( iLevel==0 ) iLevel = 1; pDb->safety_level = iLevel; + pDb->bSyncSet = 1; setAllPagerFlags(db); } } @@ -106843,12 +111083,13 @@ 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", i-nHidden, pCol->zName, - pCol->zType ? pCol->zType : "", + sqlite3ColumnType(pCol,""), pCol->notNull ? 1 : 0, - pCol->zDflt, + pCol->pDflt ? pCol->pDflt->u.zToken : 0, k); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6); } @@ -106869,14 +111110,14 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeMultiLoad(v, 1, "ssii", pTab->zName, 0, - (int)sqlite3LogEstToInt(pTab->szTabRow), - (int)sqlite3LogEstToInt(pTab->nRowLogEst)); + pTab->szTabRow, + pTab->nRowLogEst); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ sqlite3VdbeMultiLoad(v, 2, "sii", pIdx->zName, - (int)sqlite3LogEstToInt(pIdx->szIdxRow), - (int)sqlite3LogEstToInt(pIdx->aiRowLogEst[0])); + pIdx->szIdxRow, + pIdx->aiRowLogEst[0]); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); } } @@ -107138,7 +111379,7 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_PARSER_TRACE: { if( zRight ){ if( sqlite3GetBoolean(zRight, 0) ){ - sqlite3ParserTrace(stderr, "parser: "); + sqlite3ParserTrace(stdout, "parser: "); }else{ sqlite3ParserTrace(0, 0); } @@ -107169,18 +111410,6 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_INTEGRITY_CHECK: { int i, j, addr, mxErr; - /* Code that appears at the end of the integrity check. If no error - ** messages have been generated, output OK. Otherwise output the - ** error message - */ - static const int iLn = VDBE_OFFSET_LINENO(2); - static const VdbeOpList endCode[] = { - { OP_AddImm, 1, 0, 0}, /* 0 */ - { OP_If, 1, 0, 0}, /* 1 */ - { OP_String8, 0, 3, 0}, /* 2 */ - { OP_ResultRow, 3, 1, 0}, - }; - int isQuick = (sqlite3Tolower(zLeft[0])=='q'); /* If the PRAGMA command was of the form "PRAGMA .integrity_check", @@ -107214,7 +111443,10 @@ SQLITE_PRIVATE void sqlite3Pragma( for(i=0; inDb; i++){ HashElem *x; Hash *pTbls; + int *aRoot; int cnt = 0; + int mxIdx = 0; + int nIdx; if( OMIT_TEMPDB && i==1 ) continue; if( iDb>=0 && i!=iDb ) continue; @@ -107227,31 +111459,35 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Do an integrity check of the B-Tree ** - ** Begin by filling registers 2, 3, ... with the root pages numbers + ** Begin by finding the root pages numbers ** for all tables and indices in the database. */ assert( sqlite3SchemaMutexHeld(db, i, 0) ); pTbls = &db->aDb[i].pSchema->tblHash; - for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; - if( HasRowid(pTab) ){ - sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt); - VdbeComment((v, "%s", pTab->zName)); - cnt++; - } + 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)); + 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; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - sqlite3VdbeAddOp2(v, OP_Integer, pIdx->tnum, 2+cnt); - VdbeComment((v, "%s", pIdx->zName)); - cnt++; + aRoot[cnt++] = pIdx->tnum; } } + aRoot[cnt] = 0; /* Make sure sufficient number of registers have been allocated */ - pParse->nMem = MAX( pParse->nMem, cnt+8 ); + pParse->nMem = MAX( pParse->nMem, 8+mxIdx ); /* Do the b-tree integrity checks */ - sqlite3VdbeAddOp3(v, OP_IntegrityCk, 2, cnt, 1); + 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, @@ -107279,13 +111515,14 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); sqlite3VdbeJumpHere(v, addr); sqlite3ExprCacheClear(pParse); - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); sqlite3VdbeAddOp2(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 */ } - pParse->nMem = MAX(pParse->nMem, 8+j); + 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); /* Verify that all NOT NULL columns really are NOT NULL */ @@ -107377,10 +111614,23 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif /* SQLITE_OMIT_BTREECOUNT */ } } - addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); - sqlite3VdbeChangeP2(v, addr, -mxErr); - sqlite3VdbeJumpHere(v, addr+1); - sqlite3VdbeChangeP4(v, addr+2, "ok", P4_STATIC); + { + 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_String8, 0, 3, 0}, /* 2 */ + { OP_ResultRow, 3, 1, 0}, /* 3 */ + }; + VdbeOp *aOp; + + aOp = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); + if( aOp ){ + aOp[0].p2 = -mxErr; + aOp[2].p4type = P4_STATIC; + aOp[2].p4.z = "ok"; + } + } } break; #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ @@ -107458,16 +111708,18 @@ SQLITE_PRIVATE void sqlite3Pragma( #ifndef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS /* - ** PRAGMA [database.]schema_version - ** PRAGMA [database.]schema_version = + ** PRAGMA [schema.]schema_version + ** PRAGMA [schema.]schema_version = ** - ** PRAGMA [database.]user_version - ** PRAGMA [database.]user_version = + ** PRAGMA [schema.]user_version + ** PRAGMA [schema.]user_version = ** - ** PRAGMA [database.]freelist_count = + ** PRAGMA [schema.]freelist_count ** - ** PRAGMA [database.]application_id - ** PRAGMA [database.]application_id = + ** PRAGMA [schema.]data_version + ** + ** PRAGMA [schema.]application_id + ** PRAGMA [schema.]application_id = ** ** The pragma's schema_version and user_version are used to set or get ** the value of the schema-version and user-version, respectively. Both @@ -107494,14 +111746,16 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ - { OP_Integer, 0, 1, 0}, /* 1 */ - { OP_SetCookie, 0, 0, 1}, /* 2 */ + { OP_SetCookie, 0, 0, 0}, /* 1 */ }; - int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie, 0); - sqlite3VdbeChangeP1(v, addr, iDb); - sqlite3VdbeChangeP1(v, addr+1, sqlite3Atoi(zRight)); - sqlite3VdbeChangeP1(v, addr+2, iDb); - sqlite3VdbeChangeP2(v, addr+2, iCookie); + VdbeOp *aOp; + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setCookie)); + aOp = sqlite3VdbeAddOpList(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); }else{ /* Read the specified cookie value */ static const VdbeOpList readCookie[] = { @@ -107509,12 +111763,16 @@ SQLITE_PRIVATE void sqlite3Pragma( { OP_ReadCookie, 0, 1, 0}, /* 1 */ { OP_ResultRow, 1, 1, 0} }; - int addr = sqlite3VdbeAddOpList(v, ArraySize(readCookie), readCookie, 0); - sqlite3VdbeChangeP1(v, addr, iDb); - sqlite3VdbeChangeP1(v, addr+1, iDb); - sqlite3VdbeChangeP3(v, addr+1, iCookie); + VdbeOp *aOp; + sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(readCookie)); + aOp = sqlite3VdbeAddOpList(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); } } break; @@ -107536,13 +111794,14 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeLoadString(v, 1, zOpt); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); } + sqlite3VdbeReusable(v); } break; #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ #ifndef SQLITE_OMIT_WAL /* - ** PRAGMA [database.]wal_checkpoint = passive|full|restart|truncate + ** PRAGMA [schema.]wal_checkpoint = passive|full|restart|truncate ** ** Checkpoint the database. */ @@ -107770,13 +112029,12 @@ static void corruptSchema( if( !db->mallocFailed && (db->flags & SQLITE_RecoveryMode)==0 ){ char *z; if( zObj==0 ) zObj = "?"; - z = sqlite3_mprintf("malformed database schema (%s)", zObj); - if( z && zExtra ) z = sqlite3_mprintf("%z - %s", z, zExtra); + z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); + if( zExtra ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra); sqlite3DbFree(db, *pData->pzErrMsg); *pData->pzErrMsg = z; - if( z==0 ) db->mallocFailed = 1; } - pData->rc = db->mallocFailed ? SQLITE_NOMEM : SQLITE_CORRUPT_BKPT; + pData->rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_CORRUPT_BKPT; } /* @@ -107833,7 +112091,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char }else{ pData->rc = rc; if( rc==SQLITE_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){ corruptSchema(pData, argv[0], sqlite3_errmsg(db)); } @@ -107879,61 +112137,27 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ #ifndef SQLITE_OMIT_DEPRECATED int size; #endif - Table *pTab; Db *pDb; char const *azArg[4]; int meta[5]; InitData initData; - char const *zMasterSchema; - char const *zMasterName; + const char *zMasterName; int openedTransaction = 0; - /* - ** The master database table has a structure like this - */ - static const char master_schema[] = - "CREATE TABLE sqlite_master(\n" - " type text,\n" - " name text,\n" - " tbl_name text,\n" - " rootpage integer,\n" - " sql text\n" - ")" - ; -#ifndef SQLITE_OMIT_TEMPDB - static const char temp_master_schema[] = - "CREATE TEMP TABLE sqlite_temp_master(\n" - " type text,\n" - " name text,\n" - " tbl_name text,\n" - " rootpage integer,\n" - " sql text\n" - ")" - ; -#else - #define temp_master_schema 0 -#endif - assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pSchema ); assert( sqlite3_mutex_held(db->mutex) ); assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); - /* zMasterSchema and zInitScript are set to point at the master schema - ** and initialisation script appropriate for the database being - ** initialized. zMasterName is the name of the master table. - */ - if( !OMIT_TEMPDB && iDb==1 ){ - zMasterSchema = temp_master_schema; - }else{ - zMasterSchema = master_schema; - } - zMasterName = SCHEMA_TABLE(iDb); - - /* Construct the schema tables. */ - azArg[0] = zMasterName; + /* 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] = zMasterSchema; + azArg[2] = "CREATE TABLE x(type text,name text,tbl_name text," + "rootpage integer,sql text)"; azArg[3] = 0; initData.db = db; initData.iDb = iDb; @@ -107944,10 +112168,6 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ rc = initData.rc; goto error_out; } - pTab = sqlite3FindTable(db, zMasterName, db->aDb[iDb].zName); - if( ALWAYS(pTab) ){ - pTab->tabFlags |= TF_Readonly; - } /* Create a cursor to hold the database open */ @@ -108066,7 +112286,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ { char *zSql; zSql = sqlite3MPrintf(db, - "SELECT name, rootpage, sql FROM '%q'.%s ORDER BY rowid", + "SELECT name, rootpage, sql FROM \"%w\".%s ORDER BY rowid", db->aDb[iDb].zName, zMasterName); #ifndef SQLITE_OMIT_AUTHORIZATION { @@ -108088,7 +112308,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ #endif } if( db->mallocFailed ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; sqlite3ResetAllSchemasOfConnection(db); } if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){ @@ -108116,7 +112336,7 @@ initone_error_out: error_out: if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } return rc; } @@ -108214,7 +112434,7 @@ static void schemaIsValid(Parse *pParse){ if( !sqlite3BtreeIsInReadTrans(pBt) ){ rc = sqlite3BtreeBeginTrans(pBt, 0); if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } if( rc!=SQLITE_OK ) return; openedTransaction = 1; @@ -108277,6 +112497,11 @@ SQLITE_PRIVATE void sqlite3ParserReset(Parse *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; } } @@ -108300,12 +112525,12 @@ static int sqlite3Prepare( /* Allocate the parsing context */ pParse = sqlite3StackAllocZero(db, sizeof(*pParse)); if( pParse==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto end_prepare; } pParse->pReprepare = pReprepare; assert( ppStmt && *ppStmt==0 ); - assert( !db->mallocFailed ); + /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ assert( sqlite3_mutex_held(db->mutex) ); /* Check to verify that it is possible to get a read lock on all @@ -108362,8 +112587,8 @@ static int sqlite3Prepare( zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ sqlite3RunParser(pParse, zSqlCopy, &zErrMsg); - sqlite3DbFree(db, zSqlCopy); pParse->zTail = &zSql[pParse->zTail-zSqlCopy]; + sqlite3DbFree(db, zSqlCopy); }else{ pParse->zTail = &zSql[nBytes]; } @@ -108372,15 +112597,12 @@ static int sqlite3Prepare( } assert( 0==pParse->nQueryLoop ); - if( db->mallocFailed ){ - pParse->rc = SQLITE_NOMEM; - } if( pParse->rc==SQLITE_DONE ) pParse->rc = SQLITE_OK; if( pParse->checkSchema ){ schemaIsValid(pParse); } if( db->mallocFailed ){ - pParse->rc = SQLITE_NOMEM; + pParse->rc = SQLITE_NOMEM_BKPT; } if( pzTail ){ *pzTail = pParse->zTail; @@ -108496,7 +112718,7 @@ SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){ rc = sqlite3LockAndPrepare(db, zSql, -1, 0, p, &pNew, 0); if( rc ){ if( rc==SQLITE_NOMEM ){ - db->mallocFailed = 1; + sqlite3OomFault(db); } assert( pNew==0 ); return rc; @@ -108692,6 +112914,7 @@ struct SortCtx { int regReturn; /* Register holding block-output return address */ 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 */ u8 sortFlags; /* Zero or more SORTFLAG_* bits */ }; #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ @@ -108711,7 +112934,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3ExprListDelete(db, p->pOrderBy); sqlite3ExprDelete(db, p->pLimit); sqlite3ExprDelete(db, p->pOffset); - sqlite3WithDelete(db, p->pWith); + if( p->pWith ) sqlite3WithDelete(db, p->pWith); if( bFree ) sqlite3DbFree(db, p); p = pPrior; bFree = 1; @@ -108742,36 +112965,44 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( ExprList *pGroupBy, /* the GROUP BY clause */ Expr *pHaving, /* the HAVING clause */ ExprList *pOrderBy, /* the ORDER BY clause */ - u16 selFlags, /* Flag parameters, such as SF_Distinct */ + u32 selFlags, /* Flag parameters, such as SF_Distinct */ Expr *pLimit, /* LIMIT value. NULL means not used */ Expr *pOffset /* OFFSET value. NULL means no offset */ ){ Select *pNew; Select standin; sqlite3 *db = pParse->db; - pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); if( pNew==0 ){ assert( db->mallocFailed ); pNew = &standin; - memset(pNew, 0, sizeof(*pNew)); } if( pEList==0 ){ - pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0)); + pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(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->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; + pNew->nSelectRow = 0; if( pSrc==0 ) pSrc = sqlite3DbMallocZero(db, sizeof(*pSrc)); pNew->pSrc = pSrc; pNew->pWhere = pWhere; pNew->pGroupBy = pGroupBy; pNew->pHaving = pHaving; pNew->pOrderBy = pOrderBy; - pNew->selFlags = selFlags; - pNew->op = TK_SELECT; + 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 ); - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; if( db->mallocFailed ) { clearSelect(db, pNew, pNew!=&standin); pNew = 0; @@ -108798,7 +113029,7 @@ SQLITE_PRIVATE void sqlite3SelectSetName(Select *p, const char *zName){ ** Delete the given Select structure and all of its substructures. */ SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){ - clearSelect(db, p, 1); + if( p ) clearSelect(db, p, 1); } /* @@ -109146,6 +113377,7 @@ static void pushOntoSorter( int regRecord = ++pParse->nMem; /* 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 */ assert( bSeq==0 || bSeq==1 ); assert( nData==1 || regData==regOrigData ); @@ -109156,6 +113388,9 @@ static void pushOntoSorter( 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); if( bSeq ){ @@ -109164,7 +113399,6 @@ static void pushOntoSorter( if( nPrefixReg==0 ){ sqlite3ExprCodeMove(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 */ @@ -109199,6 +113433,10 @@ static void pushOntoSorter( pSort->regReturn = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); sqlite3VdbeAddOp1(v, OP_ResetSorter, pSort->iECursor); + if( iLimit ){ + sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, pSort->labelDone); + VdbeCoverage(v); + } sqlite3VdbeJumpHere(v, addrFirst); sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat); sqlite3VdbeJumpHere(v, addrJmp); @@ -109209,14 +113447,8 @@ static void pushOntoSorter( op = OP_IdxInsert; } sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord); - if( pSelect->iLimit ){ + if( iLimit ){ int addr; - int iLimit; - if( pSelect->iOffset ){ - iLimit = pSelect->iOffset+1; - }else{ - iLimit = pSelect->iLimit; - } addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, 1); VdbeCoverage(v); sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor); sqlite3VdbeAddOp1(v, OP_Delete, pSort->iECursor); @@ -109633,8 +113865,8 @@ static void selectInnerLoop( ** X extra columns. */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ - KeyInfo *p = sqlite3DbMallocZero(0, - sizeof(KeyInfo) + (N+X)*(sizeof(CollSeq*)+1)); + int nExtra = (N+X)*(sizeof(CollSeq*)+1); + KeyInfo *p = sqlite3Malloc(sizeof(KeyInfo) + nExtra); if( p ){ p->aSortOrder = (u8*)&p->aColl[N+X]; p->nField = (u16)N; @@ -109642,8 +113874,9 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ p->enc = ENC(db); p->db = db; p->nRef = 1; + memset(&p[1], 0, nExtra); }else{ - db->mallocFailed = 1; + sqlite3OomFault(db); } return p; } @@ -109820,7 +114053,7 @@ static void generateSortTail( SelectDest *pDest /* Write the sorted results here */ ){ Vdbe *v = pParse->pVdbe; /* The prepared statement */ - int addrBreak = sqlite3VdbeMakeLabel(v); /* Jump here to exit loop */ + int addrBreak = pSort->labelDone; /* Jump here to exit loop */ int addrContinue = sqlite3VdbeMakeLabel(v); /* Jump here for next cycle */ int addr; int addrOnce = 0; @@ -109839,6 +114072,7 @@ static void generateSortTail( 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); @@ -109978,7 +114212,8 @@ static const char *columnTypeImpl( char const *zOrigCol = 0; #endif - if( NEVER(pExpr==0) || pNC->pSrcList==0 ) return 0; + assert( pExpr!=0 ); + assert( pNC->pSrcList!=0 ); switch( pExpr->op ){ case TK_AGG_COLUMN: case TK_COLUMN: { @@ -110054,8 +114289,8 @@ static const char *columnTypeImpl( zType = "INTEGER"; zOrigCol = "rowid"; }else{ - zType = pTab->aCol[iCol].zType; zOrigCol = pTab->aCol[iCol].zName; + zType = sqlite3ColumnType(&pTab->aCol[iCol],0); estWidth = pTab->aCol[iCol].szEst; } zOrigTab = pTab->zName; @@ -110067,7 +114302,7 @@ static const char *columnTypeImpl( if( iCol<0 ){ zType = "INTEGER"; }else{ - zType = pTab->aCol[iCol].zType; + zType = sqlite3ColumnType(&pTab->aCol[iCol],0); estWidth = pTab->aCol[iCol].szEst; } #endif @@ -110166,7 +114401,9 @@ static void generateColumnNames( } #endif - if( pParse->colNamesSet || NEVER(v==0) || db->mallocFailed ) return; + if( pParse->colNamesSet || db->mallocFailed ) return; + assert( v!=0 ); + assert( pTabList!=0 ); pParse->colNamesSet = 1; fullNames = (db->flags & SQLITE_FullColNames)!=0; shortNames = (db->flags & SQLITE_ShortColNames)!=0; @@ -110178,7 +114415,7 @@ static void generateColumnNames( 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) && pTabList ){ + }else if( p->op==TK_COLUMN || p->op==TK_AGG_COLUMN ){ Table *pTab; char *zCol; int iCol = p->iColumn; @@ -110234,13 +114471,15 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( ){ sqlite3 *db = pParse->db; /* Database connection */ int i, j; /* Loop counters */ - int cnt; /* Index added to make the name unique */ + 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); if( pEList ){ nCol = pEList->nExpr; aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); @@ -110249,16 +114488,16 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( nCol = 0; aCol = 0; } + assert( nCol==(i16)nCol ); *pnCol = nCol; *paCol = aCol; - for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ /* Get an appropriate name for the column */ p = sqlite3ExprSkipCollate(pEList->a[i].pExpr); if( (zName = pEList->a[i].zName)!=0 ){ /* If the column contains an "AS " phrase, use as the name */ - zName = sqlite3DbStrDup(db, zName); }else{ Expr *pColExpr = p; /* The expression that is the result column name */ Table *pTab; /* Table associated with this expression */ @@ -110271,41 +114510,37 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( int iCol = pColExpr->iColumn; pTab = pColExpr->pTab; if( iCol<0 ) iCol = pTab->iPKey; - zName = sqlite3MPrintf(db, "%s", - iCol>=0 ? pTab->aCol[iCol].zName : "rowid"); + zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; }else if( pColExpr->op==TK_ID ){ assert( !ExprHasProperty(pColExpr, EP_IntValue) ); - zName = sqlite3MPrintf(db, "%s", pColExpr->u.zToken); + zName = pColExpr->u.zToken; }else{ /* Use the original text of the column expression as its name */ - zName = sqlite3MPrintf(db, "%s", pEList->a[i].zSpan); + zName = pEList->a[i].zSpan; } } - if( db->mallocFailed ){ - sqlite3DbFree(db, zName); - break; - } + zName = sqlite3MPrintf(db, "%s", zName); /* Make sure the column name is unique. If the name is not unique, ** append an integer to the name so that it becomes unique. */ - nName = sqlite3Strlen30(zName); - for(j=cnt=0; j1 && sqlite3Isdigit(zName[k]); k--){} - if( k>=0 && zName[k]==':' ) nName = k; - zName[nName] = 0; - zNewName = sqlite3MPrintf(db, "%s:%d", zName, ++cnt); - sqlite3DbFree(db, zName); - zName = zNewName; - j = -1; - if( zName==0 ) break; + cnt = 0; + while( zName && sqlite3HashFind(&ht, zName)!=0 ){ + nName = sqlite3Strlen30(zName); + if( nName>0 ){ + for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){} + if( zName[j]==':' ) nName = j; } + zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); + if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt); } pCol->zName = zName; + sqlite3ColumnPropertiesFromName(0, pCol); + if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){ + sqlite3OomFault(db); + } } + sqlite3HashClear(&ht); if( db->mallocFailed ){ for(j=0; jpSrc; a = pSelect->pEList->a; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ + const char *zType; + int n, m; p = a[i].pExpr; - if( pCol->zType==0 ){ - pCol->zType = sqlite3DbStrDup(db, - columnType(&sNC, p,0,0,0, &pCol->szEst)); - } + 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); + 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( pColl && pCol->zColl==0 ){ @@ -110389,12 +114631,12 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ } /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside ** is disabled */ - assert( db->lookaside.bEnabled==0 ); + assert( db->lookaside.bDisable ); pTab->nRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); - selectAddColumnTypeAndCollation(pParse, pTab, pSelect); + sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSelect); pTab->iPKey = -1; if( db->mallocFailed ){ sqlite3DeleteTable(db, pTab); @@ -110407,20 +114649,20 @@ 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. */ -SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){ - Vdbe *v = pParse->pVdbe; - if( v==0 ){ - v = pParse->pVdbe = sqlite3VdbeCreate(pParse); - if( v ) sqlite3VdbeAddOp0(v, OP_Init); - if( pParse->pToplevel==0 - && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst) - ){ - pParse->okConstFactor = 1; - } - +static SQLITE_NOINLINE Vdbe *allocVdbe(Parse *pParse){ + Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(pParse); + if( v ) sqlite3VdbeAddOp0(v, OP_Init); + 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); +} /* @@ -110470,8 +114712,9 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ VdbeComment((v, "LIMIT counter")); if( n==0 ){ sqlite3VdbeGoto(v, iBreak); - }else if( n>=0 && p->nSelectRow>(u64)n ){ - p->nSelectRow = n; + }else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){ + p->nSelectRow = sqlite3LogEst((u64)n); + p->selFlags |= SF_FixedLimit; } }else{ sqlite3ExprCode(pParse, p->pLimit, iLimit); @@ -110485,10 +114728,8 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ sqlite3ExprCode(pParse, p->pOffset, iOffset); sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v); VdbeComment((v, "OFFSET counter")); - sqlite3VdbeAddOp3(v, OP_SetIfNotPos, iOffset, iOffset, 0); - sqlite3VdbeAddOp3(v, OP_Add, iLimit, iOffset, iOffset+1); + sqlite3VdbeAddOp3(v, OP_OffsetLimit, iLimit, iOffset+1, iOffset); VdbeComment((v, "LIMIT+OFFSET")); - sqlite3VdbeAddOp3(v, OP_SetIfNotPos, iLimit, iOffset+1, -1); } } } @@ -110851,7 +115092,6 @@ static int multiSelect( if( dest.eDest==SRT_EphemTab ){ assert( p->pEList ); sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iSDParm, p->pEList->nExpr); - sqlite3VdbeChangeP5(v, BTREE_UNORDERED); dest.eDest = SRT_Table; } @@ -110905,9 +115145,8 @@ static int multiSelect( addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); VdbeComment((v, "Jump ahead if LIMIT reached")); if( p->iOffset ){ - sqlite3VdbeAddOp3(v, OP_SetIfNotPos, p->iOffset, p->iOffset, 0); - sqlite3VdbeAddOp3(v, OP_Add, p->iLimit, p->iOffset, p->iOffset+1); - sqlite3VdbeAddOp3(v, OP_SetIfNotPos, p->iLimit, p->iOffset+1, -1); + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); } } explainSetInteger(iSub2, pParse->iNextSelectId); @@ -110915,12 +115154,12 @@ static int multiSelect( testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; - p->nSelectRow += pPrior->nSelectRow; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); if( pPrior->pLimit && sqlite3ExprIsInteger(pPrior->pLimit, &nLimit) - && nLimit>0 && p->nSelectRow > (u64)nLimit + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) ){ - p->nSelectRow = nLimit; + p->nSelectRow = sqlite3LogEst((u64)nLimit); } if( addr ){ sqlite3VdbeJumpHere(v, addr); @@ -110992,7 +115231,9 @@ static int multiSelect( pDelete = p->pPrior; p->pPrior = pPrior; p->pOrderBy = 0; - if( p->op==TK_UNION ) p->nSelectRow += pPrior->nSelectRow; + if( p->op==TK_UNION ){ + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + } sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; p->pOffset = pOffset; @@ -111009,7 +115250,7 @@ static int multiSelect( if( dest.eDest==SRT_Output ){ Select *pFirst = p; while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, 0, pFirst->pEList); + generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); @@ -111084,7 +115325,7 @@ static int multiSelect( if( dest.eDest==SRT_Output ){ Select *pFirst = p; while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, 0, pFirst->pEList); + generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); @@ -111127,7 +115368,7 @@ static int multiSelect( nCol = p->pEList->nExpr; pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); if( !pKeyInfo ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto multi_select_end; } for(i=0, apColl=pKeyInfo->aColl; iflags |= EP_IntValue; pNew->u.iValue = i; pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); @@ -111498,10 +115739,11 @@ static int multiSelectOrderBy( ** to the right and the left are evaluated, they use the correct ** collation. */ - aPermute = sqlite3DbMallocRaw(db, sizeof(int)*nOrderBy); + aPermute = sqlite3DbMallocRawNN(db, sizeof(int)*(nOrderBy + 1)); if( aPermute ){ struct ExprList_item *pItem; - for(i=0, pItem=pOrderBy->a; ia; i<=nOrderBy; i++, pItem++){ assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; @@ -111579,7 +115821,7 @@ static int multiSelectOrderBy( pPrior->iLimit = regLimitA; explainSetInteger(iSub1, pParse->iNextSelectId); sqlite3Select(pParse, pPrior, &destA); - sqlite3VdbeAddOp1(v, OP_EndCoroutine, regAddrA); + sqlite3VdbeEndCoroutine(v, regAddrA); sqlite3VdbeJumpHere(v, addr1); /* Generate a coroutine to evaluate the SELECT statement on @@ -111596,7 +115838,7 @@ static int multiSelectOrderBy( sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; p->iOffset = savedOffset; - sqlite3VdbeAddOp1(v, OP_EndCoroutine, regAddrB); + sqlite3VdbeEndCoroutine(v, regAddrB); /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. @@ -111628,7 +115870,7 @@ static int multiSelectOrderBy( addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); VdbeCoverage(v); sqlite3VdbeGoto(v, addrEofA); - p->nSelectRow += pPrior->nSelectRow; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } /* Generate a subroutine to run when the results from select B @@ -111699,7 +115941,7 @@ static int multiSelectOrderBy( if( pDest->eDest==SRT_Output ){ Select *pFirst = pPrior; while( pFirst->pPrior ) pFirst = pFirst->pPrior; - generateColumnNames(pParse, 0, pFirst->pEList); + generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList); } /* Reassembly the compound query so that it will be freed correctly @@ -112264,6 +116506,7 @@ static int flattenSubquery( */ for(i=0; ia[i+iFrom].pUsing); + assert( pSrc->a[i+iFrom].fg.isTabFunc==0 ); pSrc->a[i+iFrom] = pSubSrc->a[i]; memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } @@ -112402,12 +116645,18 @@ static int pushDownWhereTerms( ){ Expr *pNew; int nChng = 0; + Select *pX; /* For looping over compound SELECTs in pSubq */ if( pWhere==0 ) return 0; - if( (pSubq->selFlags & (SF_Aggregate|SF_Recursive))!=0 ){ - return 0; /* restrictions (1) and (2) */ + 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->pLimit!=0 ){ - return 0; /* restriction (3) */ + return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ nChng += pushDownWhereTerms(db, pSubq, pWhere->pRight, iCursor); @@ -112579,7 +116828,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ if( pNewSrc==0 ) return WRC_Abort; *pNew = *p; p->pSrc = pNewSrc; - p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ALL, 0)); + p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ASTERISK, 0)); p->op = TK_SELECT; p->pWhere = 0; pNew->pGroupBy = 0; @@ -112598,6 +116847,19 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ return WRC_Continue; } +/* +** Check to see if the FROM clause term pFrom has table-valued function +** arguments. If it does, leave an error message in pParse and return +** non-zero, since pFrom is not allowed to be a table-valued function. +*/ +static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ + if( pFrom->fg.isTabFunc ){ + sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName); + return 1; + } + return 0; +} + #ifndef SQLITE_OMIT_CTE /* ** Argument pWith (which may be NULL) points to a linked list of nested @@ -112610,7 +116872,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ ** object that the returned CTE belongs to. */ static struct Cte *searchWith( - With *pWith, /* Current outermost WITH clause */ + With *pWith, /* Current innermost WITH clause */ struct SrcList_item *pItem, /* FROM clause element to resolve */ With **ppContext /* OUT: WITH clause return value belongs to */ ){ @@ -112641,11 +116903,12 @@ static struct Cte *searchWith( ** statement with which it is associated. */ SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ - assert( bFree==0 || pParse->pWith==0 ); + assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) ); if( pWith ){ + assert( pParse->pWith!=pWith ); pWith->pOuter = pParse->pWith; pParse->pWith = pWith; - pParse->bFreeWith = bFree; + if( bFree ) pParse->pWithToFree = pWith; } } @@ -112692,6 +116955,7 @@ static int withExpand( sqlite3ErrorMsg(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)); @@ -112702,7 +116966,7 @@ static int withExpand( pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); - if( db->mallocFailed ) return SQLITE_NOMEM; + if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; assert( pFrom->pSelect ); /* Check if this is a recursive CTE. */ @@ -112738,6 +117002,7 @@ static int withExpand( pSavedWith = pParse->pWith; pParse->pWith = pWith; sqlite3WalkSelect(pWalker, bMayRecursive ? pSel->pPrior : pSel); + pParse->pWith = pWith; for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior); pEList = pLeft->pEList; @@ -112884,15 +117149,14 @@ static int selectExpander(Walker *pWalker, Select *p){ return WRC_Abort; } pTab->nRef++; + if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ + return WRC_Abort; + } #if !defined(SQLITE_OMIT_VIEW) || !defined (SQLITE_OMIT_VIRTUALTABLE) - if( pTab->pSelect || IsVirtual(pTab) ){ + if( IsVirtual(pTab) || pTab->pSelect ){ i16 nCol; if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; assert( pFrom->pSelect==0 ); - if( pFrom->fg.isTabFunc && !IsVirtual(pTab) ){ - sqlite3ErrorMsg(pParse, "'%s' is not a function", pTab->zName); - return WRC_Abort; - } pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); sqlite3SelectSetName(pFrom->pSelect, pTab->zName); nCol = pTab->nCol; @@ -112918,19 +117182,20 @@ static int selectExpander(Walker *pWalker, Select *p){ /* For every "*" that occurs in the column list, insert the names of ** all columns in all tables. And for every TABLE.* insert the names ** of all columns in TABLE. The parser inserted a special expression - ** with the TK_ALL operator for each "*" that it found in the column list. - ** The following code just has to locate the TK_ALL expressions and expand - ** each one to the list of all columns in all tables. + ** with the TK_ASTERISK operator for each "*" that it found in the column + ** list. The following code just has to locate the TK_ASTERISK + ** expressions and expand each one to the list of all columns in + ** all tables. ** ** The first loop just checks to see if there are any "*" operators ** that need expanding. */ for(k=0; knExpr; k++){ pE = pEList->a[k].pExpr; - if( pE->op==TK_ALL ) break; + if( pE->op==TK_ASTERISK ) break; 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_ALL ) break; + if( pE->op==TK_DOT && pE->pRight->op==TK_ASTERISK ) break; } if( knExpr ){ /* @@ -112948,7 +117213,9 @@ static int selectExpander(Walker *pWalker, Select *p){ pE = a[k].pExpr; pRight = pE->pRight; assert( pE->op!=TK_DOT || pRight!=0 ); - if( pE->op!=TK_ALL && (pE->op!=TK_DOT || pRight->op!=TK_ALL) ){ + if( pE->op!=TK_ASTERISK + && (pE->op!=TK_DOT || pRight->op!=TK_ASTERISK) + ){ /* This particular expression does not need to be expanded. */ pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr); @@ -113000,12 +117267,13 @@ static int selectExpander(Walker *pWalker, Select *p){ continue; } - /* If a column is marked as 'hidden' (currently only possible - ** for virtual tables), do not include it in the expanded - ** result-set list. + /* If a column is marked as 'hidden', omit it from the expanded + ** result-set list unless the SELECT has the SF_IncludeHidden + ** bit set. */ - if( IsHiddenColumn(&pTab->aCol[j]) ){ - assert(IsVirtual(pTab)); + if( (p->selFlags & SF_IncludeHidden)==0 + && IsHiddenColumn(&pTab->aCol[j]) + ){ continue; } tableSeen = 1; @@ -113043,8 +117311,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pExpr = pRight; } pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); - sColname.z = zColname; - sColname.n = sqlite3Strlen30(zColname); + sqlite3TokenInit(&sColname, zColname); sqlite3ExprListSetName(pParse, pNew, &sColname, 0); if( pNew && (p->selFlags & SF_NestedFrom)!=0 ){ struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; @@ -113076,6 +117343,7 @@ static int selectExpander(Walker *pWalker, Select *p){ #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; } #endif return WRC_Continue; @@ -113090,7 +117358,7 @@ static int selectExpander(Walker *pWalker, Select *p){ ** Walker.xSelectCallback is set to do something useful for every ** subquery in the parser tree. */ -static int exprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ +SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ UNUSED_PARAMETER2(NotUsed, NotUsed2); return WRC_Continue; } @@ -113111,7 +117379,7 @@ static int exprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ Walker w; memset(&w, 0, sizeof(w)); - w.xExprCallback = exprWalkNoop; + w.xExprCallback = sqlite3ExprWalkNoop; w.pParse = pParse; if( pParse->hasCompound ){ w.xSelectCallback = convertCompoundSelectToSubquery; @@ -113158,7 +117426,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Select *pSel = pFrom->pSelect; if( pSel ){ while( pSel->pPrior ) pSel = pSel->pPrior; - selectAddColumnTypeAndCollation(pParse, pTab, pSel); + sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel); } } } @@ -113178,7 +117446,7 @@ static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){ Walker w; memset(&w, 0, sizeof(w)); w.xSelectCallback2 = selectAddSubqueryTypeInfo; - w.xExprCallback = exprWalkNoop; + w.xExprCallback = sqlite3ExprWalkNoop; w.pParse = pParse; sqlite3WalkSelect(&w, pSelect); #endif @@ -113578,10 +117846,24 @@ SQLITE_PRIVATE int sqlite3Select( } /* 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. + ** + ** TODO: Are there other reasons beside (1) to use a co-routine + ** implementation? */ - if( pTabList->nSrc==1 - && (p->selFlags & SF_All)==0 - && OptimizationEnabled(db, SQLITE_SubqCoroutine) + 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. @@ -113594,10 +117876,10 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); sqlite3Select(pParse, pSub, &dest); - pItem->pTab->nRowLogEst = sqlite3LogEst(pSub->nSelectRow); + pItem->pTab->nRowLogEst = pSub->nSelectRow; pItem->fg.viaCoroutine = 1; pItem->regResult = dest.iSdst; - sqlite3VdbeAddOp1(v, OP_EndCoroutine, pItem->regReturn); + sqlite3VdbeEndCoroutine(v, pItem->regReturn); sqlite3VdbeJumpHere(v, addrTop-1); sqlite3ClearTempRegCache(pParse); }else{ @@ -113625,7 +117907,7 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); sqlite3Select(pParse, pSub, &dest); - pItem->pTab->nRowLogEst = sqlite3LogEst(pSub->nSelectRow); + pItem->pTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); VdbeComment((v, "end %s", pItem->pTab->zName)); @@ -113676,6 +117958,13 @@ SQLITE_PRIVATE int sqlite3Select( ** 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 ){ + SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif } /* If there is an ORDER BY clause, then create an ephemeral index to @@ -113708,7 +117997,7 @@ SQLITE_PRIVATE int sqlite3Select( /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); - p->nSelectRow = LARGEST_INT64; + p->nSelectRow = 320; /* 4 billion rows */ computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); @@ -113732,10 +118021,12 @@ SQLITE_PRIVATE int sqlite3Select( if( !isAgg && pGroupBy==0 ){ /* No aggregate functions and no GROUP BY clause */ u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0); + assert( WHERE_USE_LIMIT==SF_FixedLimit ); + wctrlFlags |= p->selFlags & SF_FixedLimit; /* Begin the database scan. */ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, - p->pEList, wctrlFlags, 0); + p->pEList, wctrlFlags, p->nSelectRow); if( pWInfo==0 ) goto select_end; if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); @@ -113795,9 +118086,11 @@ SQLITE_PRIVATE int sqlite3Select( for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){ pItem->u.x.iAlias = 0; } - if( p->nSelectRow>100 ) p->nSelectRow = 100; + assert( 66==sqlite3LogEst(100) ); + if( p->nSelectRow>66 ) p->nSelectRow = 66; }else{ - p->nSelectRow = 1; + assert( 0==sqlite3LogEst(1) ); + p->nSelectRow = 0; } /* If there is both a GROUP BY and an ORDER BY clause and they are @@ -113933,13 +118226,8 @@ SQLITE_PRIVATE int sqlite3Select( struct AggInfo_col *pCol = &sAggInfo.aCol[i]; if( pCol->iSorterColumn>=j ){ int r1 = j + regBase; - int r2; - - r2 = sqlite3ExprCodeGetColumn(pParse, - pCol->pTab, pCol->iColumn, pCol->iTable, r1, 0); - if( r1!=r2 ){ - sqlite3VdbeAddOp2(v, OP_SCopy, r2, r1); - } + sqlite3ExprCodeGetColumnToReg(pParse, + pCol->pTab, pCol->iColumn, pCol->iTable, r1); j++; } } @@ -114174,7 +118462,8 @@ SQLITE_PRIVATE int sqlite3Select( if( flag ){ pMinMax = sqlite3ExprListDup(db, pMinMax, 0); pDel = pMinMax; - if( pMinMax && !db->mallocFailed ){ + 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; } @@ -114358,7 +118647,7 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){ return 0; malloc_failed: - p->rc = SQLITE_NOMEM; + p->rc = SQLITE_NOMEM_BKPT; return 1; } @@ -114399,7 +118688,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_get_table( res.azResult = sqlite3_malloc64(sizeof(char*)*res.nAlloc ); if( res.azResult==0 ){ db->errCode = SQLITE_NOMEM; - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } res.azResult[0] = 0; rc = sqlite3_exec(db, zSql, sqlite3_get_table_cb, &res, pzErrMsg); @@ -114428,7 +118717,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_get_table( if( azNew==0 ){ sqlite3_free_table(&res.azResult[1]); db->errCode = SQLITE_NOMEM; - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } res.azResult = azNew; } @@ -114747,8 +119036,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( pStepList->pTrig = pTrig; pStepList = pStepList->pNext; } - nameToken.z = pTrig->zName; - nameToken.n = sqlite3Strlen30(nameToken.z); + sqlite3TokenInit(&nameToken, pTrig->zName); sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken); if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) || sqlite3FixExpr(&sFix, pTrig->pWhen) @@ -114784,7 +119072,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); pTrig = sqlite3HashInsert(pHash, zName, pTrig); if( pTrig ){ - db->mallocFailed = 1; + sqlite3OomFault(db); }else if( pLink->pSchema==pLink->pTabSchema ){ Table *pTab; pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table); @@ -115019,31 +119307,12 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ */ assert( pTable!=0 ); if( (v = sqlite3GetVdbe(pParse))!=0 ){ - int base; - static const int iLn = VDBE_OFFSET_LINENO(2); - static const VdbeOpList dropTrigger[] = { - { OP_Rewind, 0, ADDR(9), 0}, - { OP_String8, 0, 1, 0}, /* 1 */ - { OP_Column, 0, 1, 2}, - { OP_Ne, 2, ADDR(8), 1}, - { OP_String8, 0, 1, 0}, /* 4: "trigger" */ - { OP_Column, 0, 0, 2}, - { OP_Ne, 2, ADDR(8), 1}, - { OP_Delete, 0, 0, 0}, - { OP_Next, 0, ADDR(1), 0}, /* 8 */ - }; - - sqlite3BeginWriteOperation(pParse, 0, iDb); - sqlite3OpenMasterTable(pParse, iDb); - base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger, iLn); - sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, P4_TRANSIENT); - sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC); + sqlite3NestedParse(pParse, + "DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'", + db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pTrigger->zName + ); sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddOp2(v, OP_Close, 0, 0); sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); - if( pParse->nMem<3 ){ - pParse->nMem = 3; - } } } @@ -115431,8 +119700,8 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( if( pPrg ){ int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers)); - sqlite3VdbeAddOp3(v, OP_Program, reg, ignoreJump, ++pParse->nMem); - sqlite3VdbeChangeP4(v, -1, (const char *)pPrg->pProgram, P4_SUBPROGRAM); + sqlite3VdbeAddOp4(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))); @@ -115779,7 +120048,7 @@ SQLITE_PRIVATE void sqlite3Update( /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. ** Initialize aXRef[] and aToOpen[] to their default values. */ - aXRef = sqlite3DbMallocRaw(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 ); + aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 ); if( aXRef==0 ) goto update_cleanup; aRegIdx = aXRef+pTab->nCol; aToOpen = (u8*)(aRegIdx+nIdx); @@ -115845,10 +120114,12 @@ SQLITE_PRIVATE void sqlite3Update( assert( chngPk==0 || chngPk==1 ); chngKey = chngRowid + chngPk; - /* The SET expressions are not actually used inside the WHERE loop. - ** So reset the colUsed mask + /* 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 + ** table implementation makes all columns available). */ - pTabList->a[0].colUsed = 0; + pTabList->a[0].colUsed = IsVirtual(pTab) ? ALLBITS : 0; hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey); @@ -115932,7 +120203,8 @@ SQLITE_PRIVATE void sqlite3Update( if( HasRowid(pTab) ){ sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); pWInfo = sqlite3WhereBegin( - pParse, pTabList, pWhere, 0, 0, WHERE_ONEPASS_DESIRED, iIdxCur + pParse, pTabList, pWhere, 0, 0, + WHERE_ONEPASS_DESIRED | WHERE_SEEK_TABLE, iIdxCur ); if( pWInfo==0 ) goto update_cleanup; okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); @@ -116011,7 +120283,7 @@ SQLITE_PRIVATE void sqlite3Update( if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; } - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iBaseCur, aToOpen, + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen, 0, 0); } @@ -116104,7 +120376,7 @@ SQLITE_PRIVATE void sqlite3Update( */ testcase( i==31 ); testcase( i==32 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); + sqlite3ExprCodeGetColumnToReg(pParse, pTab, i, iDataCur, regNew+i); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); } @@ -116152,7 +120424,8 @@ SQLITE_PRIVATE void sqlite3Update( /* Do constraint checks. */ assert( regOldRowid>0 ); sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace); + regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace, + aXRef); /* Do FK constraint checks. */ if( hasFK ){ @@ -116169,11 +120442,30 @@ SQLITE_PRIVATE void sqlite3Update( VdbeCoverageNeverTaken(v); } sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1); - - /* If changing the record number, delete the old record. */ + + /* If changing the rowid value, or if there are foreign key constraints + ** to process, delete the old record. Otherwise, add a noop OP_Delete + ** to invoke the pre-update hook. + ** + ** That (regNew==regnewRowid+1) is true is also important for the + ** pre-update hook. If the caller invokes preupdate_new(), the returned + ** value is copied from memory cell (regNewRowid+1+iCol), where iCol + ** is the column index supplied by the user. + */ + assert( regNew==regNewRowid+1 ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeAddOp3(v, OP_Delete, iDataCur, + OPFLAG_ISUPDATE | ((hasFK || chngKey || pPk!=0) ? 0 : OPFLAG_ISNOOP), + regNewRowid + ); + if( !pParse->nested ){ + sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + } +#else if( hasFK || chngKey || pPk!=0 ){ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } +#endif if( bReplace || chngKey ){ sqlite3VdbeJumpHere(v, addr1); } @@ -116430,7 +120722,7 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ sqlite3_stmt *pStmt; VVA_ONLY( int rc; ) if( !zSql ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){ sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); @@ -116611,7 +120903,7 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ || (!isMemDb && sqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes, 0)) || NEVER(db->mallocFailed) ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; goto end_of_vacuum; } @@ -116817,7 +121109,7 @@ static int createModule( rc = SQLITE_MISUSE_BKPT; }else{ Module *pMod; - pMod = (Module *)sqlite3DbMallocRaw(db, sizeof(Module) + nName + 1); + pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1); if( pMod ){ Module *pDel; char *zCopy = (char *)(&pMod[1]); @@ -116830,7 +121122,7 @@ static int createModule( pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); assert( pDel==0 || pDel==pMod ); if( pDel ){ - db->mallocFailed = 1; + sqlite3OomFault(db); sqlite3DbFree(db, pDel); } } @@ -117207,7 +121499,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ assert( sqlite3SchemaMutexHeld(db, 0, pSchema) ); pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); if( pOld ){ - db->mallocFailed = 1; + sqlite3OomFault(db); assert( pTab==pOld ); /* Malloc must have failed inside HashInsert() */ return; } @@ -117274,13 +121566,13 @@ static int vtabCallConstructor( zModuleName = sqlite3MPrintf(db, "%s", pTab->zName); if( !zModuleName ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pVTable = sqlite3DbMallocZero(db, sizeof(VTable)); if( !pVTable ){ sqlite3DbFree(db, zModuleName); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } pVTable->db = db; pVTable->pMod = pMod; @@ -117298,7 +121590,7 @@ static int vtabCallConstructor( db->pVtabCtx = &sCtx; rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); db->pVtabCtx = sCtx.pPrior; - if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); assert( sCtx.pTab==pTab ); if( SQLITE_OK!=rc ){ @@ -117332,22 +121624,16 @@ static int vtabCallConstructor( pTab->pVTable = pVTable; for(iCol=0; iColnCol; iCol++){ - char *zType = pTab->aCol[iCol].zType; + char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); int nType; int i = 0; - if( !zType ){ - pTab->tabFlags |= oooHidden; - continue; - } nType = sqlite3Strlen30(zType); - if( sqlite3StrNICmp("hidden", zType, 6)||(zType[6] && zType[6]!=' ') ){ - for(i=0; inVTrans + ARRAY_INCR); aVTrans = sqlite3DbRealloc(db, (void *)db->aVTrans, nBytes); if( !aVTrans ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memset(&aVTrans[db->nVTrans], 0, sizeof(sqlite3_vtab *)*ARRAY_INCR); db->aVTrans = aVTrans; @@ -117515,7 +121801,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_declare_vtab(sqlite3 *db, const char *zCre pParse = sqlite3StackAllocZero(db, sizeof(*pParse)); if( pParse==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; }else{ pParse->declareVtab = 1; pParse->db = db; @@ -117784,7 +122070,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( Table *pTab; sqlite3_vtab *pVtab; sqlite3_module *pMod; - void (*xFunc)(sqlite3_context*,int,sqlite3_value**) = 0; + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**) = 0; void *pArg = 0; FuncDef *pNew; int rc = 0; @@ -117812,7 +122098,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( for(z=(unsigned char*)zLowerName; *z; z++){ *z = sqlite3UpperToLower[*z]; } - rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xFunc, &pArg); + rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xSFunc, &pArg); sqlite3DbFree(db, zLowerName); } if( rc==0 ){ @@ -117827,9 +122113,9 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( return pDef; } *pNew = *pDef; - pNew->zName = (char *)&pNew[1]; - memcpy(pNew->zName, pDef->zName, sqlite3Strlen30(pDef->zName)+1); - pNew->xFunc = xFunc; + pNew->zName = (const char*)&pNew[1]; + memcpy((char*)&pNew[1], pDef->zName, sqlite3Strlen30(pDef->zName)+1); + pNew->xSFunc = xSFunc; pNew->pUserData = pArg; pNew->funcFlags |= SQLITE_FUNC_EPHEM; return pNew; @@ -117856,7 +122142,7 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ pToplevel->apVtabLock = apVtabLock; pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab; }else{ - pToplevel->db->mallocFailed = 1; + sqlite3OomFault(pToplevel->db); } } @@ -118074,8 +122360,10 @@ 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 iLikeRepCntr; /* LIKE range processing counter register */ +#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 */ @@ -118258,6 +122546,7 @@ struct WhereTerm { u16 eOperator; /* A WO_xx value describing */ u16 wtFlags; /* TERM_xxx bit flags. See below */ u8 nChild; /* Number of children that must disable us */ + u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ WhereClause *pWC; /* The clause this term is part of */ Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */ Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */ @@ -118290,7 +122579,7 @@ struct WhereTerm { struct WhereScan { WhereClause *pOrigWC; /* Original, innermost WhereClause */ WhereClause *pWC; /* WhereClause currently being scanned */ - char *zCollName; /* Required collating sequence, if not NULL */ + const char *zCollName; /* Required collating sequence, if not NULL */ Expr *pIdxExpr; /* Search for this index expression */ char idxaff; /* Must match this affinity, if zCollName!=NULL */ unsigned char nEquiv; /* Number of entries in aEquiv[] */ @@ -118410,10 +122699,11 @@ 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 *pResultSet; /* Result set. DISTINCT operates on these */ + ExprList *pDistinctSet; /* DISTINCT over all these values */ WhereLoop *pLoops; /* List of all WhereLoop objects */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ LogEst nRowOut; /* Estimated number of output rows */ + LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ u8 sorted; /* True if really sorted (not just grouped) */ @@ -118493,6 +122783,14 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC ** operators that are of interest to the query planner. An ** OR-ed combination of these values can be used when searching for ** particular WhereTerms within a WhereClause. +** +** Value constraints: +** WO_EQ == SQLITE_INDEX_CONSTRAINT_EQ +** WO_LT == SQLITE_INDEX_CONSTRAINT_LT +** 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 @@ -118595,7 +122893,7 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ for(i=0; i=nSkip ? "%s=?" : "ANY(%s)", z); + sqlite3XPrintf(pStr, i>=nSkip ? "%s=?" : "ANY(%s)", z); } j = i; @@ -118654,13 +122952,13 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); sqlite3StrAccumAppendAll(&str, isSearch ? "SEARCH" : "SCAN"); if( pItem->pSelect ){ - sqlite3XPrintf(&str, 0, " SUBQUERY %d", pItem->iSelectId); + sqlite3XPrintf(&str, " SUBQUERY %d", pItem->iSelectId); }else{ - sqlite3XPrintf(&str, 0, " TABLE %s", pItem->zName); + sqlite3XPrintf(&str, " TABLE %s", pItem->zName); } if( pItem->zAlias ){ - sqlite3XPrintf(&str, 0, " AS %s", pItem->zAlias); + sqlite3XPrintf(&str, " AS %s", pItem->zAlias); } if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; @@ -118684,7 +122982,7 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( } if( zFmt ){ sqlite3StrAccumAppend(&str, " USING ", 7); - sqlite3XPrintf(&str, 0, zFmt, pIdx->zName); + sqlite3XPrintf(&str, zFmt, pIdx->zName); explainIndexRange(&str, pLoop); } }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ @@ -118699,17 +122997,17 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( assert( flags&WHERE_TOP_LIMIT); zRangeOp = "<"; } - sqlite3XPrintf(&str, 0, " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp); + sqlite3XPrintf(&str, " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp); } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ - sqlite3XPrintf(&str, 0, " VIRTUAL TABLE INDEX %d:%s", + sqlite3XPrintf(&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, 0, " (~%llu rows)", sqlite3LogEstToInt(pLoop->nOut)); + sqlite3XPrintf(&str, " (~%llu rows)", sqlite3LogEstToInt(pLoop->nOut)); }else{ sqlite3StrAccumAppend(&str, " (~1 row)", 9); } @@ -118846,8 +123144,7 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ /* Code the OP_Affinity opcode if there is anything left to do. */ if( n>0 ){ - sqlite3VdbeAddOp2(v, OP_Affinity, base, n); - sqlite3VdbeChangeP4(v, -1, zAff, n); + sqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n); sqlite3ExprCacheAffinityChange(pParse, base, n); } } @@ -119015,9 +123312,7 @@ static int codeAllEqualityTerms( pParse->nMem += nReg; zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx)); - if( !zAff ){ - pParse->db->mallocFailed = 1; - } + assert( zAff!=0 || pParse->db->mallocFailed ); if( nSkip ){ int iIdxCur = pLevel->iIdxCur; @@ -119080,10 +123375,12 @@ static int codeAllEqualityTerms( return regBase; } +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS /* -** If the most recently coded instruction is a constant range contraint -** that originated from the LIKE optimization, then change the P3 to be -** pLoop->iLikeRepCntr and set P5. +** If the most recently coded instruction is a constant range constraint +** (a string literal) that originated from the LIKE optimization, then +** set P3 and P5 on the OP_String opcode so that the string will be cast +** to a BLOB at appropriate times. ** ** The LIKE optimization trys to evaluate "x LIKE 'abc%'" as a range ** expression: "x>='ABC' AND x<'abd'". But this requires that the range @@ -119091,6 +123388,10 @@ static int codeAllEqualityTerms( ** The OP_String opcodes on the second pass convert the upper and lower ** bound string contants to blobs. This routine makes the necessary changes ** to the OP_String opcodes for that to happen. +** +** Except, of course, if SQLITE_LIKE_DOESNT_MATCH_BLOBS is defined, then +** only the one pass through the string space is required, so this routine +** becomes a no-op. */ static void whereLikeOptimizationStringFixup( Vdbe *v, /* prepared statement under construction */ @@ -119104,11 +123405,210 @@ static void whereLikeOptimizationStringFixup( assert( pOp!=0 ); assert( pOp->opcode==OP_String8 || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); - pOp->p3 = pLevel->iLikeRepCntr; - pOp->p5 = 1; + pOp->p3 = (int)(pLevel->iLikeRepCntr>>1); /* Register holding counter */ + pOp->p5 = (u8)(pLevel->iLikeRepCntr&1); /* ASC or DESC */ } } +#else +# define whereLikeOptimizationStringFixup(A,B,C) +#endif +#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 +** structure. +*/ +struct CCurHint { + int iTabCur; /* Cursor for the main table */ + int iIdxCur; /* Cursor for the index, if pIdx!=0. Unused otherwise */ + Index *pIdx; /* The index used to access the table */ +}; + +/* +** This function is called for every node of an expression that is a candidate +** for a cursor hint on an index cursor. For TK_COLUMN nodes that reference +** the table CCurHint.iTabCur, verify that the same column can be +** accessed through the index. If it cannot, then set pWalker->eCode to 1. +*/ +static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ + struct CCurHint *pHint = pWalker->u.pCCurHint; + assert( pHint->pIdx!=0 ); + if( pExpr->op==TK_COLUMN + && pExpr->iTable==pHint->iTabCur + && sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn)<0 + ){ + pWalker->eCode = 1; + } + return WRC_Continue; +} + + +/* +** This function is called on every node of an expression tree used as an +** argument to the OP_CursorHint instruction. If the node is a TK_COLUMN +** that accesses any table other than the one identified by +** CCurHint.iTabCur, then do the following: +** +** 1) allocate a register and code an OP_Column instruction to read +** the specified column into the new register, and +** +** 2) transform the expression node to a TK_REGISTER node that reads +** from the newly populated register. +** +** Also, if the node is a TK_COLUMN that does access the table idenified +** by pCCurHint.iTabCur, and an index is being used (which we will +** know because CCurHint.pIdx!=0) then transform the TK_COLUMN into +** an access of the index rather than the original table. +*/ +static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ + int rc = WRC_Continue; + 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 + ); + pExpr->op = TK_REGISTER; + pExpr->iTable = reg; + }else if( pHint->pIdx!=0 ){ + pExpr->iTable = pHint->iIdxCur; + pExpr->iColumn = sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn); + assert( pExpr->iColumn>=0 ); + } + }else if( pExpr->op==TK_AGG_FUNCTION ){ + /* An aggregate function in the WHERE clause of a query means this must + ** be a correlated sub-query, and expression pExpr is an aggregate from + ** the parent context. Do not walk the function arguments in this case. + ** + ** todo: It should be possible to replace this node with a TK_REGISTER + ** expression, as the result of the expression must be stored in a + ** register at this point. The same holds for TK_AGG_COLUMN nodes. */ + rc = WRC_Prune; + } + return rc; +} + +/* +** Insert an OP_CursorHint instruction if it is appropriate to do so. +*/ +static void codeCursorHint( + WhereInfo *pWInfo, /* The where clause */ + WhereLevel *pLevel, /* Which loop to provide hints for */ + WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */ +){ + Parse *pParse = pWInfo->pParse; + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + Expr *pExpr = 0; + WhereLoop *pLoop = pLevel->pWLoop; + int iCur; + WhereClause *pWC; + WhereTerm *pTerm; + int i, j; + struct CCurHint sHint; + Walker sWalker; + + if( OptimizationDisabled(db, SQLITE_CursorHints) ) return; + iCur = pLevel->iTabCur; + assert( iCur==pWInfo->pTabList->a[pLevel->iFrom].iCursor ); + sHint.iTabCur = iCur; + sHint.iIdxCur = pLevel->iIdxCur; + sHint.pIdx = pLoop->u.btree.pIndex; + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.pParse = pParse; + sWalker.u.pCCurHint = &sHint; + pWC = &pWInfo->sWC; + for(i=0; inTerm; i++){ + pTerm = &pWC->a[i]; + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( pTerm->prereqAll & pLevel->notReady ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) continue; + + /* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize + ** the cursor. These terms are not needed as hints for a pure range + ** scan (that has no == terms) so omit them. */ + if( pLoop->u.btree.nEq==0 && pTerm!=pEndRange ){ + for(j=0; jnLTerm && pLoop->aLTerm[j]!=pTerm; j++){} + if( jnLTerm ) continue; + } + + /* No subqueries or non-deterministic functions allowed */ + if( sqlite3ExprContainsSubquery(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); + 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)); + } + if( pExpr!=0 ){ + sWalker.xExprCallback = codeCursorHintFixExpr; + sqlite3WalkExpr(&sWalker, pExpr); + sqlite3VdbeAddOp4(v, OP_CursorHint, + (sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0, + (const char*)pExpr, P4_EXPR); + } +} +#else +# define codeCursorHint(A,B,C) /* No-op */ +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + +/* +** Cursor iCur is open on an intkey b-tree (a table). Register iRowid contains +** a rowid value just read from cursor iIdxCur, open on index pIdx. This +** function generates code to do a deferred seek of cursor iCur to the +** rowid stored in register iRowid. +** +** Normally, this is just: +** +** OP_Seek $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 +** 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 +** index, then the corresponding array entry is set to (i+1). If the column +** does not appear in the index at all, the array entry is set to 0. +*/ +static void codeDeferredSeek( + WhereInfo *pWInfo, /* Where clause context */ + Index *pIdx, /* Index scan is using */ + int iCur, /* Cursor for IPK b-tree */ + int iIdxCur /* Index cursor */ +){ + Parse *pParse = pWInfo->pParse; /* Parse context */ + Vdbe *v = pParse->pVdbe; /* Vdbe to generate code within */ + + assert( iIdxCur>0 ); + assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 ); + + sqlite3VdbeAddOp3(v, OP_Seek, iIdxCur, 0, iCur); + if( (pWInfo->wctrlFlags & WHERE_FORCE_TABLE) + && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) + ){ + int i; + Table *pTab = pIdx->pTable; + int *ai = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*(pTab->nCol+1)); + if( ai ){ + ai[0] = pTab->nCol; + for(i=0; inColumn-1; i++){ + assert( pIdx->aiColumn[i]nCol ); + if( pIdx->aiColumn[i]>=0 ) ai[pIdx->aiColumn[i]+1] = i+1; + } + sqlite3VdbeChangeP4(v, -1, (char*)ai, P4_INTARRAY); + } + } +} /* ** Generate code for the start of the iLevel-th loop in the WHERE clause @@ -119192,6 +123692,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int iReg; /* P3 Value for OP_VFilter */ int addrNotFound; int nConstraint = pLoop->nLTerm; + int iIn; /* Counter for IN constraints */ sqlite3ExprCachePush(pParse); iReg = sqlite3GetTempRange(pParse, nConstraint+2); @@ -119199,7 +123700,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( for(j=0; jaLTerm[j]; - if( pTerm==0 ) continue; + if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); addrNotFound = pLevel->addrNxt; @@ -119214,15 +123715,57 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC); VdbeCoverage(v); pLoop->u.vtab.needFree = 0; - for(j=0; ju.vtab.omitMask>>j)&1 ){ - disableTerm(pLevel, pLoop->aLTerm[j]); - } - } pLevel->p1 = iCur; pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); - sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2); + iIn = pLevel->u.in.nIn; + for(j=nConstraint-1; j>=0; j--){ + pTerm = pLoop->aLTerm[j]; + if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ + disableTerm(pLevel, pTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ + Expr *pCompare; /* The comparison operator */ + Expr *pRight; /* RHS of the comparison */ + VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ + + /* Reload the constraint value into reg[iReg+j+2]. The same value + ** was loaded into the same register prior to the OP_VFilter, but + ** the xFilter implementation might have changed the datatype or + ** 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( 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); + } + + /* Generate code that will continue to the next row if + ** the IN constraint is not satisfied */ + pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0, 0); + assert( pCompare!=0 || db->mallocFailed ); + if( pCompare ){ + pCompare->pLeft = pTerm->pExpr->pLeft; + pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0); + if( pRight ){ + pRight->iTable = iReg+j+2; + sqlite3ExprIfFalse(pParse, pCompare, pLevel->addrCont, 0); + } + pCompare->pLeft = 0; + sqlite3ExprDelete(db, pCompare); + } + } + } + /* 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); + */ sqlite3ExprCachePop(pParse); }else #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -119273,6 +123816,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pStart = pEnd; pEnd = pTerm; } + codeCursorHint(pWInfo, pLevel, pEnd); if( pStart ){ Expr *pX; /* The expression that defines the start bound */ int r1, rTemp; /* Registers for holding the start boundary */ @@ -119445,18 +123989,23 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ pRangeEnd = pLoop->aLTerm[j++]; nExtraReg = 1; +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ assert( pRangeStart!=0 ); /* LIKE opt constraints */ assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */ - pLevel->iLikeRepCntr = ++pParse->nMem; - testcase( bRev ); - testcase( pIdx->aSortOrder[nEq]==SQLITE_SO_DESC ); - sqlite3VdbeAddOp2(v, OP_Integer, - bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC), - pLevel->iLikeRepCntr); + pLevel->iLikeRepCntr = (u32)++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 1, (int)pLevel->iLikeRepCntr); VdbeComment((v, "LIKE loop counter")); pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v); + /* iLikeRepCntr actually stores 2x the counter register number. The + ** bottom bit indicates whether the search order is ASC or DESC. */ + testcase( bRev ); + testcase( pIdx->aSortOrder[nEq]==SQLITE_SO_DESC ); + assert( (bRev & ~1)==0 ); + pLevel->iLikeRepCntr <<=1; + pLevel->iLikeRepCntr |= bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC); } +#endif if( pRangeStart==0 && (j = pIdx->aiColumn[nEq])>=0 && pIdx->pTable->aCol[j].notNull==0 @@ -119466,15 +124015,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); - /* Generate code to evaluate all constraint terms using == or IN - ** and store the values of those terms in an array of registers - ** starting at regBase. - */ - regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); - assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); - if( zStartAff ) cEndAff = zStartAff[nEq]; - addrNxt = pLevel->addrNxt; - /* 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). @@ -119486,6 +124026,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( SWAP(u8, bSeekPastNull, bStopAtNull); } + /* Generate code to evaluate all constraint terms using == or IN + ** and store the values of those terms in an array of registers + ** starting at regBase. + */ + codeCursorHint(pWInfo, pLevel, pRangeEnd); + regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); + assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); + if( zStartAff ) cEndAff = zStartAff[nEq]; + addrNxt = pLevel->addrNxt; + testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 ); testcase( pRangeEnd && (pRangeEnd->eOperator & WO_LE)!=0 ); @@ -119526,16 +124076,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( start_constraints = 1; } codeApplyAffinity(pParse, regBase, nConstraint - bSeekPastNull, zStartAff); - op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; - assert( op!=0 ); - sqlite3VdbeAddOp4Int(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 ); - VdbeCoverageIf(v, op==OP_SeekGT); testcase( op==OP_SeekGT ); - 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 ); + if( pLoop->nSkip>0 && nConstraint==pLoop->nSkip ){ + /* The skip-scan logic inside the call to codeAllEqualityConstraints() + ** above has already left the cursor sitting on the correct row, + ** so no further seeking is needed */ + }else{ + op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; + assert( op!=0 ); + sqlite3VdbeAddOp4Int(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 ); + VdbeCoverageIf(v, op==OP_SeekGT); testcase( op==OP_SeekGT ); + 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 ); + } /* Load the value for the inequality constraint at the end of the ** range (if any). @@ -119585,14 +124141,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ - iRowidReg = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); - sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); - if( pWInfo->eOnePass!=ONEPASS_OFF ){ + if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE)!=0 ){ + iRowidReg = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); + sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg); VdbeCoverage(v); }else{ - sqlite3VdbeAddOp2(v, OP_Seek, iCur, iRowidReg); /* Deferred seek */ + codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur); } }else if( iCur!=iIdxCur ){ Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); @@ -119761,14 +124317,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( Expr *pExpr = pWC->a[iTerm].pExpr; if( &pWC->a[iTerm] == pTerm ) continue; if( ExprHasProperty(pExpr, EP_FromJoin) ) continue; - if( (pWC->a[iTerm].wtFlags & TERM_VIRTUAL)!=0 ) 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); } if( pAndExpr ){ - pAndExpr = sqlite3PExpr(pParse, TK_AND, 0, pAndExpr, 0); + pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr, 0); } } @@ -119779,7 +124337,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( wctrlFlags = WHERE_OMIT_OPEN_CLOSE | WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY - | WHERE_NO_AUTOINDEX; + | WHERE_NO_AUTOINDEX + | (pWInfo->wctrlFlags & WHERE_SEEK_TABLE); for(ii=0; iinTerm; ii++){ WhereTerm *pOrTerm = &pOrWc->a[ii]; if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ @@ -119824,11 +124383,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( r = sqlite3GetTempRange(pParse, nPk); for(iPk=0; iPkaiColumn[iPk]; - int rx; - rx = sqlite3ExprCodeGetColumn(pParse, pTab, iCol, iCur,r+iPk,0); - if( rx!=r+iPk ){ - sqlite3VdbeAddOp2(v, OP_SCopy, rx, r+iPk); - } + sqlite3ExprCodeGetColumnToReg(pParse, pTab, iCol, iCur, r+iPk); } /* Check if the temp table already contains this key. If so, @@ -119928,6 +124483,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** a pseudo-cursor. No need to Rewind or Next such cursors. */ pLevel->op = OP_Noop; }else{ + codeCursorHint(pWInfo, pLevel, 0); pLevel->op = aStep[bRev]; pLevel->p1 = iCur; pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); @@ -119962,9 +124518,19 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( continue; } if( pTerm->wtFlags & TERM_LIKECOND ){ - assert( pLevel->iLikeRepCntr>0 ); - skipLikeAddr = sqlite3VdbeAddOp1(v, OP_IfNot, pLevel->iLikeRepCntr); + /* 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; +#else + u32 x = pLevel->iLikeRepCntr; + assert( x>0 ); + skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)? OP_IfNot : OP_If, (int)(x>>1)); VdbeCoverage(v); +#endif } sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); @@ -120100,7 +124666,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ if( pWC->nTerm>=pWC->nSlot ){ WhereTerm *pOld = pWC->a; sqlite3 *db = pWC->pWInfo->pParse->db; - pWC->a = sqlite3DbMallocRaw(db, sizeof(pWC->a[0])*pWC->nSlot*2 ); + pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 ); if( pWC->a==0 ){ if( wtFlags & TERM_DYNAMIC ){ sqlite3ExprDelete(db, p); @@ -120238,6 +124804,7 @@ static int isLikeOrGlob( sqlite3 *db = pParse->db; /* Database connection */ sqlite3_value *pVal = 0; int op; /* Opcode of pRight */ + int rc; /* Result code to return */ if( !sqlite3IsLikeFunction(db, pExpr, pnoCase, wc) ){ return 0; @@ -120303,8 +124870,9 @@ static int isLikeOrGlob( } } + rc = (z!=0); sqlite3ValueFree(pVal); - return (z!=0); + return rc; } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ @@ -120313,29 +124881,48 @@ static int isLikeOrGlob( /* ** Check to see if the given expression is of the form ** -** column MATCH expr +** 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( - Expr *pExpr /* Test this expression */ + Expr *pExpr, /* Test this expression */ + unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */ ){ + 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; } - if( sqlite3StrICmp(pExpr->u.zToken,"match")!=0 ){ - return 0; - } pList = pExpr->x.pList; - if( pList->nExpr!=2 ){ + if( pList==0 || pList->nExpr!=2 ){ return 0; } - if( pList->a[1].pExpr->op != TK_COLUMN ){ + pCol = pList->a[1].pExpr; + if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){ return 0; } - return 1; + for(i=0; iu.zToken, aOp[i].zOp)==0 ){ + *peOp2 = aOp[i].eOp2; + return 1; + } + } + return 0; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -120548,6 +125135,7 @@ static void exprAnalyzeOrTerm( 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); @@ -120564,7 +125152,7 @@ static void exprAnalyzeOrTerm( WhereAndInfo *pAndInfo; assert( (pOrTerm->wtFlags & (TERM_ANDINFO|TERM_ORINFO))==0 ); chngToIN = 0; - pAndInfo = sqlite3DbMallocRaw(db, sizeof(*pAndInfo)); + pAndInfo = sqlite3DbMallocRawNN(db, sizeof(*pAndInfo)); if( pAndInfo ){ WhereClause *pAndWC; WhereTerm *pAndTerm; @@ -120574,11 +125162,11 @@ static void exprAnalyzeOrTerm( pOrTerm->wtFlags |= TERM_ANDINFO; 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); pAndWC->pOuter = pWC; - testcase( db->mallocFailed ); if( !db->mallocFailed ){ for(j=0, pAndTerm=pAndWC->a; jnTerm; j++, pAndTerm++){ assert( pAndTerm->pExpr ); @@ -120912,6 +125500,7 @@ static void exprAnalyze( 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 */ if( db->mallocFailed ){ return; @@ -121135,7 +125724,7 @@ static void exprAnalyze( ** virtual tables. The native query optimizer does not attempt ** to do anything with MATCH functions. */ - if( isMatchOfColumn(pExpr) ){ + if( isMatchOfColumn(pExpr, &eOp2) ){ int idxNew; Expr *pRight, *pLeft; WhereTerm *pNewTerm; @@ -121156,6 +125745,7 @@ static void exprAnalyze( 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; @@ -121258,7 +125848,8 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit( /* ** Deallocate a WhereClause structure. The WhereClause structure -** itself is not freed. This routine is the inverse of sqlite3WhereClauseInit(). +** itself is not freed. This routine is the inverse of +** sqlite3WhereClauseInit(). */ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ int i; @@ -121293,10 +125884,10 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ return mask; } mask = sqlite3WhereExprUsage(pMaskSet, p->pRight); - mask |= sqlite3WhereExprUsage(pMaskSet, p->pLeft); + if( p->pLeft ) mask |= sqlite3WhereExprUsage(pMaskSet, p->pLeft); if( ExprHasProperty(p, EP_xIsSelect) ){ mask |= exprSelectUsage(pMaskSet, p->x.pSelect); - }else{ + }else if( p->x.pList ){ mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); } return mask; @@ -121352,9 +125943,9 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( pTab = pItem->pTab; assert( pTab!=0 ); pArgs = pItem->u1.pFuncArg; - assert( pArgs!=0 ); + if( pArgs==0 ) return; for(j=k=0; jnExpr; j++){ - while( knCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){ k++; } + while( knCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;} if( k>=pTab->nCol ){ sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d", pTab->zName, j); @@ -121406,8 +125997,8 @@ static int whereLoopResize(sqlite3*, WhereLoop*, int); /* ** Return the estimated number of output rows from a WHERE clause */ -SQLITE_PRIVATE u64 sqlite3WhereOutputRowCount(WhereInfo *pWInfo){ - return sqlite3LogEstToInt(pWInfo->nRowOut); +SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo *pWInfo){ + return pWInfo->nRowOut; } /* @@ -121636,7 +126227,10 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ ** ** The scanner will be searching the WHERE clause pWC. It will look ** for terms of the form "X " where X is column iColumn of table -** iCur. The must be one of the operators described by opMask. +** iCur. Or if pIdx!=0 then X is column iColumn of index pIdx. pIdx +** must be one of the indexes of table iCur. +** +** The must be one of the operators described by opMask. ** ** If the search is for X and the WHERE clause contains terms of the ** form X=Y then this routine might also return terms of the form @@ -121664,6 +126258,7 @@ static WhereTerm *whereScanInit( 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; @@ -121683,11 +126278,12 @@ static WhereTerm *whereScanInit( /* ** Search for a term in the WHERE clause that is of the form "X " -** where X is a reference to the iColumn of table iCur and is one of -** the WO_xx operator codes specified by the op parameter. -** Return a pointer to the term. Return 0 if not found. +** where X is a reference to the iColumn of table iCur or of index pIdx +** if pIdx!=0 and is one of the WO_xx operator codes specified by +** the op parameter. Return a pointer to the term. Return 0 if not found. ** -** If pIdx!=0 then search for terms matching the iColumn-th column of pIdx +** If pIdx!=0 then it must be one of the indexes of table iCur. +** Search for terms matching the iColumn-th column of pIdx ** rather than the iColumn-th column of table iCur. ** ** The term returned might by Y= if there is another constraint in @@ -122002,7 +126598,7 @@ static void constructAutomaticIndex( Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ struct SrcList_item *pTabItem; /* FROM clause term being indexed */ - int addrCounter; /* Address where integer counter is initialized */ + int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ /* Generate code to skip over the creation and initialization of the @@ -122093,7 +126689,7 @@ static void constructAutomaticIndex( idxCols |= cMask; pIdx->aiColumn[n] = pTerm->u.leftColumn; pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); - pIdx->azColl[n] = pColl ? pColl->zName : "BINARY"; + pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY; n++; } } @@ -122105,20 +126701,20 @@ static void constructAutomaticIndex( for(i=0; iaiColumn[n] = i; - pIdx->azColl[n] = "BINARY"; + pIdx->azColl[n] = sqlite3StrBINARY; n++; } } if( pSrc->colUsed & MASKBIT(BMS-1) ){ for(i=BMS-1; inCol; i++){ pIdx->aiColumn[n] = i; - pIdx->azColl[n] = "BINARY"; + pIdx->azColl[n] = sqlite3StrBINARY; n++; } } assert( n==nKeyCol ); pIdx->aiColumn[n] = XN_ROWID; - pIdx->azColl[n] = "BINARY"; + pIdx->azColl[n] = sqlite3StrBINARY; /* Create the automatic index */ assert( pLevel->iIdxCur>=0 ); @@ -122268,6 +126864,9 @@ static sqlite3_index_info *allocateIndexInfo( pIdxCons[j].iTermOffset = i; op = (u8)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 @@ -122306,7 +126905,6 @@ static sqlite3_index_info *allocateIndexInfo( */ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; - int i; int rc; TRACE_IDX_INPUTS(p); @@ -122315,7 +126913,7 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ if( rc!=SQLITE_OK ){ if( rc==SQLITE_NOMEM ){ - pParse->db->mallocFailed = 1; + sqlite3OomFault(pParse->db); }else if( !pVtab->zErrMsg ){ sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc)); }else{ @@ -122325,12 +126923,16 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; +#if 0 + /* This error is now caught by the caller. + ** Search for "xBestIndex malfunction" below */ for(i=0; inConstraint; 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; } @@ -122933,7 +127535,8 @@ static int whereEqualScanEst( pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); - WHERETRACE(0x10,("equality scan regions: %d\n", (int)a[1])); + WHERETRACE(0x10,("equality scan regions %s(%d): %d\n", + p->zName, nEq-1, (int)a[1])); *pnRow = a[1]; return rc; @@ -123018,11 +127621,12 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){ */ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ WhereInfo *pWInfo = pWC->pWInfo; - int nb = 1+(pWInfo->pTabList->nSrc+7)/8; + 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, - p->iTab, nb, p->maskSelf, nb, p->prereq); + p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); sqlite3DebugPrintf(" %12s", pItem->zAlias ? pItem->zAlias : pTab->zName); if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ @@ -123107,8 +127711,8 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ WhereTerm **paNew; if( p->nLSlot>=n ) return SQLITE_OK; n = (n+7)&~7; - paNew = sqlite3DbMallocRaw(db, sizeof(p->aLTerm[0])*n); - if( paNew==0 ) return SQLITE_NOMEM; + paNew = sqlite3DbMallocRawNN(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); p->aLTerm = paNew; @@ -123123,7 +127727,7 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ whereLoopClearUnion(db, pTo); if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ memset(&pTo->u, 0, sizeof(pTo->u)); - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); memcpy(pTo->aLTerm, pFrom->aLTerm, pTo->nLTerm*sizeof(pTo->aLTerm[0])); @@ -123347,6 +127951,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ WhereLoop **ppPrev, *p; WhereInfo *pWInfo = pBuilder->pWInfo; sqlite3 *db = pWInfo->pParse->db; + int rc; /* If pBuilder->pOrSet is defined, then only keep track of the costs ** and prereqs. @@ -123404,8 +128009,8 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ #endif if( p==0 ){ /* Allocate a new WhereLoop to add to the end of the list */ - *ppPrev = p = sqlite3DbMallocRaw(db, sizeof(WhereLoop)); - if( p==0 ) return SQLITE_NOMEM; + *ppPrev = p = sqlite3DbMallocRawNN(db, sizeof(WhereLoop)); + if( p==0 ) return SQLITE_NOMEM_BKPT; whereLoopInit(p); p->pNextLoop = 0; }else{ @@ -123429,14 +128034,14 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ whereLoopDelete(db, pToDel); } } - whereLoopXfer(db, p, pTemplate); + rc = whereLoopXfer(db, p, pTemplate); if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ Index *pIndex = p->u.btree.pIndex; if( pIndex && pIndex->tnum==0 ){ p->u.btree.pIndex = 0; } } - return SQLITE_OK; + return rc; } /* @@ -123561,14 +128166,12 @@ static int whereLoopAddBtreeIndex( WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */ pNew = pBuilder->pNew; - if( db->mallocFailed ) return SQLITE_NOMEM; + if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 ); assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 ); if( pNew->wsFlags & WHERE_BTM_LIMIT ){ opMask = WO_LT|WO_LE; - }else if( /*pProbe->tnum<=0 ||*/ (pSrc->fg.jointype & JT_LEFT)!=0 ){ - opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE; }else{ opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } @@ -123606,6 +128209,18 @@ 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 */ + 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; + } + pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; pNew->nLTerm = saved_nLTerm; @@ -123925,7 +128540,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ */ static int whereLoopAddBtree( WhereLoopBuilder *pBuilder, /* WHERE clause information */ - Bitmask mExtra /* Extra prerequesites for using this table */ + Bitmask mPrereq /* Extra prerequesites for using this table */ ){ WhereInfo *pWInfo; /* WHERE analysis context */ Index *pProbe; /* An index we are evaluating */ @@ -124025,7 +128640,7 @@ static int whereLoopAddBtree( pNew->nOut = 43; assert( 43==sqlite3LogEst(20) ); pNew->rRun = sqlite3LogEstAdd(rLogSize,pNew->nOut); pNew->wsFlags = WHERE_AUTO_INDEX; - pNew->prereq = mExtra | pTerm->prereqRight; + pNew->prereq = mPrereq | pTerm->prereqRight; rc = whereLoopInsert(pBuilder, pNew); } } @@ -124046,7 +128661,7 @@ static int whereLoopAddBtree( pNew->nLTerm = 0; pNew->iSortIdx = 0; pNew->rSetup = 0; - pNew->prereq = mExtra; + pNew->prereq = mPrereq; pNew->nOut = rSize; pNew->u.btree.pIndex = pProbe; b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor); @@ -124119,12 +128734,160 @@ static int whereLoopAddBtree( } #ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Argument pIdxInfo is already populated with all constraints that may +** be used by the virtual table identified by pBuilder->pNew->iTab. This +** function marks a subset of those constraints usable, invokes the +** xBestIndex method and adds the returned plan to pBuilder. +** +** A constraint is marked usable if: +** +** * Argument mUsable indicates that its prerequisites are available, and +** +** * It is not one of the operators specified in the mExclude mask passed +** as the fourth argument (which in practice is either WO_IN or 0). +** +** Argument mPrereq is a mask of tables that must be scanned before the +** virtual table in question. These are added to the plans prerequisites +** before it is added to pBuilder. +** +** Output parameter *pbIn is set to true if the plan added to pBuilder +** uses one or more WO_IN terms, or false otherwise. +*/ +static int whereLoopAddVirtualOne( + WhereLoopBuilder *pBuilder, + 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 */ + 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; + int i; + int mxTerm; + int rc = SQLITE_OK; + WhereLoop *pNew = pBuilder->pNew; + Parse *pParse = pBuilder->pWInfo->pParse; + struct SrcList_item *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; + int nConstraint = pIdxInfo->nConstraint; + + assert( (mUsable & mPrereq)==mPrereq ); + *pbIn = 0; + pNew->prereq = mPrereq; + + /* Set the usable flag on the subset of constraints identified by + ** arguments mUsable and mExclude. */ + pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; + for(i=0; ia[pIdxCons->iTermOffset]; + pIdxCons->usable = 0; + if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight + && (pTerm->eOperator & mExclude)==0 + ){ + pIdxCons->usable = 1; + } + } + + /* Initialize the output fields of the sqlite3_index_info structure */ + memset(pUsage, 0, sizeof(pUsage[0])*nConstraint); + assert( pIdxInfo->needToFreeIdxStr==0 ); + pIdxInfo->idxStr = 0; + pIdxInfo->idxNum = 0; + pIdxInfo->orderByConsumed = 0; + pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; + pIdxInfo->estimatedRows = 25; + pIdxInfo->idxFlags = 0; + pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + + /* Invoke the virtual table xBestIndex() method */ + rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); + if( rc ) return rc; + + mxTerm = -1; + assert( pNew->nLSlot>=nConstraint ); + for(i=0; iaLTerm[i] = 0; + pNew->u.vtab.omitMask = 0; + pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; + for(i=0; i=0 ){ + WhereTerm *pTerm; + int j = pIdxCons->iTermOffset; + if( iTerm>=nConstraint + || j<0 + || j>=pWC->nTerm + || pNew->aLTerm[iTerm]!=0 + || pIdxCons->usable==0 + ){ + rc = SQLITE_ERROR; + sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); + return rc; + } + testcase( iTerm==nConstraint-1 ); + testcase( j==0 ); + testcase( j==pWC->nTerm-1 ); + pTerm = &pWC->a[j]; + pNew->prereq |= pTerm->prereqRight; + assert( iTermnLSlot ); + pNew->aLTerm[iTerm] = pTerm; + if( iTerm>mxTerm ) mxTerm = iTerm; + testcase( iTerm==15 ); + testcase( iTerm==16 ); + if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<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 + ** is not necessarily related to the order of output terms and + ** (2) Multiple outputs from a single IN value will not merge + ** together. */ + pIdxInfo->orderByConsumed = 0; + pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; + *pbIn = 1; assert( (mExclude & WO_IN)==0 ); + } + } + } + + pNew->nLTerm = mxTerm+1; + assert( pNew->nLTerm<=pNew->nLSlot ); + pNew->u.vtab.idxNum = pIdxInfo->idxNum; + pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr; + pIdxInfo->needToFreeIdxStr = 0; + pNew->u.vtab.idxStr = pIdxInfo->idxStr; + pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ? + pIdxInfo->nOrderBy : 0); + pNew->rSetup = 0; + pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); + pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); + + /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated + ** that the scan will visit at most one row. Clear it otherwise. */ + if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){ + pNew->wsFlags |= WHERE_ONEROW; + }else{ + pNew->wsFlags &= ~WHERE_ONEROW; + } + rc = whereLoopInsert(pBuilder, pNew); + if( pNew->u.vtab.needFree ){ + sqlite3_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))); + + return rc; +} + + /* ** Add all WhereLoop objects for a table of the join identified by ** pBuilder->pNew->iTab. That table is guaranteed to be a virtual table. ** -** If there are no LEFT or CROSS JOIN joins in the query, both mExtra and -** mUnusable are set to 0. Otherwise, mExtra is a mask of all FROM clause +** If there are no LEFT or CROSS JOIN joins in the query, both mPrereq and +** mUnusable are set to 0. Otherwise, mPrereq is a mask of all FROM clause ** entries that occur before the virtual table in the FROM clause and are ** separated from it by at least one LEFT or CROSS JOIN. Similarly, the ** mUnusable mask contains all FROM clause entries that occur after the @@ -124135,187 +128898,122 @@ static int whereLoopAddBtree( ** ** ... FROM t1, t2 LEFT JOIN t3, t4, vt CROSS JOIN t5, t6; ** -** then mExtra corresponds to (t1, t2) and mUnusable to (t5, t6). +** then mPrereq corresponds to (t1, t2) and mUnusable to (t5, t6). ** -** All the tables in mExtra must be scanned before the current virtual +** All the tables in mPrereq must be scanned before the current virtual ** table. So any terms for which all prerequisites are satisfied by -** mExtra may be specified as "usable" in all calls to xBestIndex. +** mPrereq may be specified as "usable" in all calls to xBestIndex. ** Conversely, all tables in mUnusable must be scanned after the current ** virtual table, so any terms for which the prerequisites overlap with ** mUnusable should always be configured as "not-usable" for xBestIndex. */ static int whereLoopAddVirtual( WhereLoopBuilder *pBuilder, /* WHERE clause information */ - Bitmask mExtra, /* Tables that must be scanned before this one */ + Bitmask mPrereq, /* Tables that must be scanned before this one */ Bitmask mUnusable /* Tables that must be scanned after this one */ ){ + int rc = SQLITE_OK; /* Return code */ WhereInfo *pWInfo; /* WHERE analysis context */ Parse *pParse; /* The parsing context */ WhereClause *pWC; /* The WHERE clause */ struct SrcList_item *pSrc; /* The FROM clause term to search */ - Table *pTab; - sqlite3 *db; - sqlite3_index_info *pIdxInfo; - struct sqlite3_index_constraint *pIdxCons; - struct sqlite3_index_constraint_usage *pUsage; - WhereTerm *pTerm; - int i, j; - int iTerm, mxTerm; - int nConstraint; - int seenIn = 0; /* True if an IN operator is seen */ - int seenVar = 0; /* True if a non-constant constraint is seen */ - int iPhase; /* 0: const w/o IN, 1: const, 2: no IN, 2: IN */ + sqlite3_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; - int rc = SQLITE_OK; + Bitmask mBest; /* Tables used by best possible plan */ - assert( (mExtra & mUnusable)==0 ); + assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; pParse = pWInfo->pParse; - db = pParse->db; pWC = pBuilder->pWC; pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; - pTab = pSrc->pTab; - assert( IsVirtual(pTab) ); - pIdxInfo = allocateIndexInfo(pParse, pWC, mUnusable, pSrc,pBuilder->pOrderBy); - if( pIdxInfo==0 ) return SQLITE_NOMEM; - pNew->prereq = 0; + assert( IsVirtual(pSrc->pTab) ); + p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy); + if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; pNew->nLTerm = 0; pNew->u.vtab.needFree = 0; - pUsage = pIdxInfo->aConstraintUsage; - nConstraint = pIdxInfo->nConstraint; - if( whereLoopResize(db, pNew, nConstraint) ){ - sqlite3DbFree(db, pIdxInfo); - return SQLITE_NOMEM; + nConstraint = p->nConstraint; + if( whereLoopResize(pParse->db, pNew, nConstraint) ){ + sqlite3DbFree(pParse->db, p); + return SQLITE_NOMEM_BKPT; } - for(iPhase=0; iPhase<=3; iPhase++){ - if( !seenIn && (iPhase&1)!=0 ){ - iPhase++; - if( iPhase>3 ) break; - } - if( !seenVar && iPhase>1 ) break; - pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; - for(i=0; inConstraint; i++, pIdxCons++){ - j = pIdxCons->iTermOffset; - pTerm = &pWC->a[j]; - switch( iPhase ){ - case 0: /* Constants without IN operator */ - pIdxCons->usable = 0; - if( (pTerm->eOperator & WO_IN)!=0 ){ - seenIn = 1; - } - if( (pTerm->prereqRight & ~mExtra)!=0 ){ - seenVar = 1; - }else if( (pTerm->eOperator & WO_IN)==0 ){ - pIdxCons->usable = 1; - } - break; - case 1: /* Constants with IN operators */ - assert( seenIn ); - pIdxCons->usable = (pTerm->prereqRight & ~mExtra)==0; - break; - case 2: /* Variables without IN */ - assert( seenVar ); - pIdxCons->usable = (pTerm->eOperator & WO_IN)==0; - break; - default: /* Variables with IN */ - assert( seenVar && seenIn ); - pIdxCons->usable = 1; - break; - } - } - memset(pUsage, 0, sizeof(pUsage[0])*pIdxInfo->nConstraint); - if( pIdxInfo->needToFreeIdxStr ) sqlite3_free(pIdxInfo->idxStr); - pIdxInfo->idxStr = 0; - pIdxInfo->idxNum = 0; - pIdxInfo->needToFreeIdxStr = 0; - pIdxInfo->orderByConsumed = 0; - pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; - pIdxInfo->estimatedRows = 25; - pIdxInfo->idxFlags = 0; - rc = vtabBestIndex(pParse, pTab, pIdxInfo); - if( rc ) goto whereLoopAddVtab_exit; - pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; - pNew->prereq = mExtra; - mxTerm = -1; - assert( pNew->nLSlot>=nConstraint ); - for(i=0; iaLTerm[i] = 0; - pNew->u.vtab.omitMask = 0; - for(i=0; i=0 ){ - j = pIdxCons->iTermOffset; - if( iTerm>=nConstraint - || j<0 - || j>=pWC->nTerm - || pNew->aLTerm[iTerm]!=0 - ){ - rc = SQLITE_ERROR; - sqlite3ErrorMsg(pParse, "%s.xBestIndex() malfunction", pTab->zName); - goto whereLoopAddVtab_exit; - } - testcase( iTerm==nConstraint-1 ); - testcase( j==0 ); - testcase( j==pWC->nTerm-1 ); - pTerm = &pWC->a[j]; - pNew->prereq |= pTerm->prereqRight; - assert( iTermnLSlot ); - pNew->aLTerm[iTerm] = pTerm; - if( iTerm>mxTerm ) mxTerm = iTerm; - testcase( iTerm==15 ); - testcase( iTerm==16 ); - if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<eOperator & WO_IN)!=0 ){ - if( pUsage[i].omit==0 ){ - /* Do not attempt to use an IN constraint if the virtual table - ** says that the equivalent EQ constraint cannot be safely omitted. - ** If we do attempt to use such a constraint, some rows might be - ** repeated in the output. */ - break; - } - /* A virtual table that is constrained by an IN clause may not - ** consume the ORDER BY clause because (1) the order of IN terms - ** is not necessarily related to the order of output terms and - ** (2) Multiple outputs from a single IN value will not merge - ** together. */ - pIdxInfo->orderByConsumed = 0; - pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; - } - } - } - if( i>=nConstraint ){ - pNew->nLTerm = mxTerm+1; - assert( pNew->nLTerm<=pNew->nLSlot ); - pNew->u.vtab.idxNum = pIdxInfo->idxNum; - pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr; - pIdxInfo->needToFreeIdxStr = 0; - pNew->u.vtab.idxStr = pIdxInfo->idxStr; - pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ? - pIdxInfo->nOrderBy : 0); - pNew->rSetup = 0; - pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); - pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); + /* First call xBestIndex() with all constraints usable. */ + WHERETRACE(0x40, (" VirtualOne: all usable\n")); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, &bIn); - /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated - ** that the scan will visit at most one row. Clear it otherwise. */ - if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){ - pNew->wsFlags |= WHERE_ONEROW; - }else{ - pNew->wsFlags &= ~WHERE_ONEROW; - } - whereLoopInsert(pBuilder, pNew); - if( pNew->u.vtab.needFree ){ - sqlite3_free(pNew->u.vtab.idxStr); - pNew->u.vtab.needFree = 0; + /* 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 ){ + 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; + Bitmask mBestNoIn = 0; + + /* If the plan produced by the earlier call uses an IN(...) term, call + ** xBestIndex again, this time with IN(...) terms disabled. */ + if( bIn ){ + WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, WO_IN, p, &bIn); + assert( bIn==0 ); + mBestNoIn = pNew->prereq & ~mPrereq; + if( mBestNoIn==0 ){ + seenZero = 1; + seenZeroNoIN = 1; } } - } -whereLoopAddVtab_exit: - if( pIdxInfo->needToFreeIdxStr ) sqlite3_free(pIdxInfo->idxStr); - sqlite3DbFree(db, pIdxInfo); + /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) + ** in the set of terms that apply to the current virtual table. */ + while( rc==SQLITE_OK ){ + int i; + Bitmask mNext = ALLBITS; + assert( mNext>0 ); + for(i=0; ia[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq + ); + if( mThis>mPrev && mThisprereq==mPrereq ){ + seenZero = 1; + if( bIn==0 ) seenZeroNoIN = 1; + } + } + + /* If the calls to xBestIndex() in the above loop did not find a plan + ** that requires no source tables at all (i.e. one guaranteed to be + ** usable), make a call here with all source tables disabled */ + if( rc==SQLITE_OK && seenZero==0 ){ + WHERETRACE(0x40, (" VirtualOne: all disabled\n")); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, 0, p, &bIn); + if( bIn==0 ) seenZeroNoIN = 1; + } + + /* If the calls to xBestIndex() have so far failed to find a plan + ** that requires no source tables at all and does not use an IN(...) + ** operator, make a final call to obtain one here. */ + if( rc==SQLITE_OK && seenZeroNoIN==0 ){ + WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, WO_IN, p, &bIn); + } + } + + if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); + sqlite3DbFree(pParse->db, p); return rc; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -124326,7 +129024,7 @@ whereLoopAddVtab_exit: */ static int whereLoopAddOr( WhereLoopBuilder *pBuilder, - Bitmask mExtra, + Bitmask mPrereq, Bitmask mUnusable ){ WhereInfo *pWInfo = pBuilder->pWInfo; @@ -124387,14 +129085,14 @@ static int whereLoopAddOr( #endif #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ - rc = whereLoopAddVirtual(&sSubBuild, mExtra, mUnusable); + rc = whereLoopAddVirtual(&sSubBuild, mPrereq, mUnusable); }else #endif { - rc = whereLoopAddBtree(&sSubBuild, mExtra); + rc = whereLoopAddBtree(&sSubBuild, mPrereq); } if( rc==SQLITE_OK ){ - rc = whereLoopAddOr(&sSubBuild, mExtra, mUnusable); + rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); } assert( rc==SQLITE_OK || sCur.n==0 ); if( sCur.n==0 ){ @@ -124451,7 +129149,7 @@ static int whereLoopAddOr( */ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ WhereInfo *pWInfo = pBuilder->pWInfo; - Bitmask mExtra = 0; + Bitmask mPrereq = 0; Bitmask mPrior = 0; int iTab; SrcList *pTabList = pWInfo->pTabList; @@ -124472,7 +129170,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ 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. */ - mExtra = mPrior; + mPrereq = mPrior; } priorJointype = pItem->fg.jointype; if( IsVirtual(pItem->pTab) ){ @@ -124482,12 +129180,12 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); } } - rc = whereLoopAddVirtual(pBuilder, mExtra, mUnusable); + rc = whereLoopAddVirtual(pBuilder, mPrereq, mUnusable); }else{ - rc = whereLoopAddBtree(pBuilder, mExtra); + rc = whereLoopAddBtree(pBuilder, mPrereq); } if( rc==SQLITE_OK ){ - rc = whereLoopAddOr(pBuilder, mExtra, mUnusable); + rc = whereLoopAddOr(pBuilder, mPrereq, mUnusable); } mPrior |= pNew->maskSelf; if( rc || db->mallocFailed ) break; @@ -124830,15 +129528,14 @@ static LogEst whereSortingCost( LogEst rScale, rSortCost; assert( nOrderBy>0 && 66==sqlite3LogEst(100) ); rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; - rSortCost = nRow + estLog(nRow) + rScale + 16; + rSortCost = nRow + rScale + 16; - /* TUNING: The cost of implementing DISTINCT using a B-TREE is - ** similar but with a larger constant of proportionality. - ** Multiply by an additional factor of 3.0. */ - if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ - rSortCost += 16; + /* Multiple by log(M) where M is the number of output rows. + ** Use the LIMIT for M if it is smaller */ + if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 && pWInfo->iLimitiLimit; } - + rSortCost += estLog(nRow); return rSortCost; } @@ -124900,8 +129597,8 @@ 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 = sqlite3DbMallocRaw(db, nSpace); - if( pSpace==0 ) return SQLITE_NOMEM; + pSpace = sqlite3DbMallocRawNN(db, nSpace); + if( pSpace==0 ) return SQLITE_NOMEM_BKPT; aTo = (WherePath*)pSpace; aFrom = aTo+mxChoice; memset(aFrom, 0, sizeof(aFrom[0])); @@ -124956,6 +129653,12 @@ 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 ){ + /* Do not use an automatic index if the this loop is expected + ** to run less than 2 times. */ + assert( 10==sqlite3LogEst(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); @@ -125148,9 +129851,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ && nRowEst ){ Bitmask notUsed; - int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pResultSet, pFrom, + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pDistinctSet, pFrom, WHERE_DISTINCTBY, nLoop-1, pFrom->aLoop[nLoop-1], ¬Used); - if( rc==pWInfo->pResultSet->nExpr ){ + if( rc==pWInfo->pDistinctSet->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } } @@ -125208,7 +129911,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ int j; Table *pTab; Index *pIdx; - + pWInfo = pBuilder->pWInfo; if( pWInfo->wctrlFlags & WHERE_FORCE_TABLE ) return 0; assert( pWInfo->pTabList->nSrc>=1 ); @@ -125365,13 +130068,14 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ ** used. */ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( - 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 *pResultSet, /* Result set of the query */ - u16 wctrlFlags, /* One of the WHERE_* flags defined in sqliteInt.h */ - int iIdxCur /* If WHERE_ONETABLE_ONLY is set, index cursor number */ + 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 */ + u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ + int iAuxArg /* If WHERE_ONETABLE_ONLY is set, index cursor number + ** If WHERE_USE_LIMIT, then the limit amount */ ){ int nByteWInfo; /* Num. bytes allocated for WhereInfo struct */ int nTabList; /* Number of elements in pTabList */ @@ -125385,12 +130089,17 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( int ii; /* Loop counter */ sqlite3 *db; /* Database connection */ int rc; /* Return code */ + u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */ assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || ( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 )); + /* Only one of WHERE_ONETABLE_ONLY or WHERE_USE_LIMIT */ + assert( (wctrlFlags & WHERE_ONETABLE_ONLY)==0 + || (wctrlFlags & WHERE_USE_LIMIT)==0 ); + /* Variable initialization */ db = pParse->db; memset(&sWLB, 0, sizeof(sWLB)); @@ -125441,9 +130150,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; - pWInfo->pResultSet = pResultSet; + pWInfo->pDistinctSet = pDistinctSet; pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v); pWInfo->wctrlFlags = wctrlFlags; + pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; @@ -125513,20 +130223,25 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( db->mallocFailed ) goto whereBeginError; if( wctrlFlags & WHERE_WANT_DISTINCT ){ - if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ + if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pDistinctSet) ){ /* 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 = pResultSet; + pWInfo->pOrderBy = pDistinctSet; } } /* Construct the WhereLoop objects */ - WHERETRACE(0xffff,("*** Optimizer Start *** (wctrlFlags: 0x%x)\n", - wctrlFlags)); #if defined(WHERETRACE_ENABLED) + if( sqlite3WhereTrace & 0xffff ){ + sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags); + if( wctrlFlags & WHERE_USE_LIMIT ){ + sqlite3DebugPrintf(", limit: %d", iAuxArg); + } + sqlite3DebugPrintf(")\n"); + } if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ int i; for(i=0; inTerm; i++){ @@ -125560,7 +130275,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } } if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ - pWInfo->revMask = (Bitmask)(-1); + pWInfo->revMask = ALLBITS; } if( pParse->nErr || NEVER(db->mallocFailed) ){ goto whereBeginError; @@ -125593,10 +130308,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( #endif /* Attempt to omit tables from the join that do not effect the result */ if( pWInfo->nLevel>=2 - && pResultSet!=0 + && pDistinctSet!=0 && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ - Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); + Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pDistinctSet); if( sWLB.pOrderBy ){ tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); } @@ -125629,19 +130344,21 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* If the caller is an UPDATE or DELETE statement that is requesting ** to use a one-pass algorithm, determine if this is appropriate. - ** The one-pass algorithm only works if the WHERE clause constrains - ** the statement to update or delete a single row. */ 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==(wsFlags & WHERE_VIRTUALTABLE) - )){ + if( bOnerow + || ((wctrlFlags & WHERE_ONEPASS_MULTIROW)!=0 + && 0==(wsFlags & WHERE_VIRTUALTABLE)) + ){ pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; - if( HasRowid(pTabList->a[0].pTab) ){ - pWInfo->a[0].pWLoop->wsFlags &= ~WHERE_IDX_ONLY; + if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ + if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){ + bFordelete = OPFLAG_FORDELETE; + } + pWInfo->a[0].pWLoop->wsFlags = (wsFlags & ~WHERE_IDX_ONLY); } } } @@ -125685,10 +130402,17 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( Bitmask b = pTabItem->colUsed; int n = 0; for(; b; b=b>>1, n++){} - sqlite3VdbeChangeP4(v, sqlite3VdbeCurrentAddr(v)-1, - SQLITE_INT_TO_PTR(n), P4_INT32); + sqlite3VdbeChangeP4(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); + }else +#endif + { + sqlite3VdbeChangeP5(v, bFordelete); + } #ifdef SQLITE_ENABLE_COLUMN_USED_MASK sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0, (const u8*)&pTabItem->colUsed, P4_INT64); @@ -125700,8 +130424,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( Index *pIx = pLoop->u.btree.pIndex; int iIndexCur; int op = OP_OpenRead; - /* iIdxCur is always set if to a positive value if ONEPASS is possible */ - assert( iIdxCur!=0 || (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 ); + /* iAuxArg is always set if to a positive value if ONEPASS is possible */ + assert( iAuxArg!=0 || (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 ); if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIx) && (wctrlFlags & WHERE_ONETABLE_ONLY)!=0 ){ @@ -125711,7 +130435,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( op = 0; }else if( pWInfo->eOnePass!=ONEPASS_OFF ){ Index *pJ = pTabItem->pTab->pIndex; - iIndexCur = iIdxCur; + iIndexCur = iAuxArg; assert( wctrlFlags & WHERE_ONEPASS_DESIRED ); while( ALWAYS(pJ) && pJ!=pIx ){ iIndexCur++; @@ -125719,8 +130443,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } op = OP_OpenWrite; pWInfo->aiCurOnePass[1] = iIndexCur; - }else if( iIdxCur && (wctrlFlags & WHERE_ONETABLE_ONLY)!=0 ){ - iIndexCur = iIdxCur; + }else if( iAuxArg && (wctrlFlags & WHERE_ONETABLE_ONLY)!=0 ){ + iIndexCur = iAuxArg; if( wctrlFlags & WHERE_REOPEN_IDX ) op = OP_ReopenIdx; }else{ iIndexCur = pParse->nTab++; @@ -125851,16 +130575,13 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeJumpHere(v, pLevel->addrSkip); sqlite3VdbeJumpHere(v, pLevel->addrSkip-2); } +#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( pLevel->addrLikeRep ){ - int op; - if( sqlite3VdbeGetOp(v, pLevel->addrLikeRep-1)->p1 ){ - op = OP_DecrJumpZero; - }else{ - op = OP_JumpZeroIncr; - } - sqlite3VdbeAddOp2(v, op, pLevel->iLikeRepCntr, pLevel->addrLikeRep); + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1), + pLevel->addrLikeRep); VdbeCoverage(v); } +#endif if( pLevel->iLeftJoin ){ addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 @@ -125984,18 +130705,32 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ /************** End of where.c ***********************************************/ /************** Begin file parse.c *******************************************/ -/* Driver template for the LEMON parser generator. -** The author disclaims copyright to this source code. +/* +** 2000-05-29 ** -** This version of "lempar.c" is modified, slightly, for use by SQLite. -** The only modifications are the addition of a couple of NEVER() -** macros to disable tests that are needed in the case of a general -** LALR(1) grammar but which are always false in the -** specific grammar used by SQLite. +** 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. +** +************************************************************************* +** Driver template for the LEMON parser generator. +** +** The "lemon" program processes an LALR(1) input grammar file, then uses +** this template to construct a parser. The "lemon" program inserts text +** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** interstitial "-" characters) contained in this template is changed into +** the value of the %name directive from the grammar. Otherwise, the content +** of this template is copied straight through into the generate parser +** source file. +** +** The following is the concatenation of all %include directives from the +** input grammar file: */ -/* First off, code is included that follows the "include" declaration -** in the input grammar file. */ /* #include */ +/************ Begin %include sections from the grammar ************************/ /* #include "sqliteInt.h" */ @@ -126010,6 +130745,18 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ */ #define yytestcase(X) testcase(X) +/* +** Indicate that sqlite3ParserFree() 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. +*/ +#define YYMALLOCARGTYPE u64 + /* ** An instance of this structure holds information about the ** LIMIT clause of a SELECT statement. @@ -126044,6 +130791,15 @@ struct TrigEvent { int a; IdList * b; }; */ struct AttachKey { int type; Token key; }; +/* +** Disable lookaside memory allocation for objects that might be +** shared across database connections. +*/ +static void disableLookaside(Parse *pParse){ + pParse->disableLookaside++; + pParse->db->lookaside.bDisable++; +} + /* ** For a compound SELECT statement, make sure p->pPrior->pNext==p for @@ -126080,46 +130836,51 @@ struct AttachKey { int type; Token key; }; ** 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 *pValue){ - pOut->pExpr = sqlite3PExpr(pParse, op, 0, 0, pValue); - pOut->zStart = pValue->z; - pOut->zEnd = &pValue->z[pValue->n]; + static void spanExpr(ExprSpan *pOut, Parse *pParse, int op, Token t){ + pOut->pExpr = sqlite3PExpr(pParse, op, 0, 0, &t); + 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( - ExprSpan *pOut, /* Write the result here */ Parse *pParse, /* The parsing context. Errors accumulate here */ int op, /* The binary operation */ - ExprSpan *pLeft, /* The left operand */ + ExprSpan *pLeft, /* The left operand, and output */ ExprSpan *pRight /* The right operand */ ){ - pOut->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr, 0); - pOut->zStart = pLeft->zStart; - pOut->zEnd = pRight->zEnd; + 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); + } } /* Construct an expression node for a unary postfix operator */ static void spanUnaryPostfix( - ExprSpan *pOut, /* Write the new expression node here */ Parse *pParse, /* Parsing context to record errors */ int op, /* The operator */ - ExprSpan *pOperand, /* The operand */ + ExprSpan *pOperand, /* The operand, and output */ Token *pPostOp /* The operand token for setting the span */ ){ - pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); - pOut->zStart = pOperand->zStart; - pOut->zEnd = &pPostOp->z[pPostOp->n]; + 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( pY && pA && pY->op==TK_NULL ){ + if( pA && pY && pY->op==TK_NULL ){ pA->op = (u8)op; sqlite3ExprDelete(db, pA->pRight); pA->pRight = 0; @@ -126135,8 +130896,8 @@ struct AttachKey { int type; Token key; }; ExprSpan *pOperand, /* The operand */ Token *pPreOp /* The operand token for setting the span */ ){ - pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); pOut->zStart = pPreOp->z; + pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); pOut->zEnd = pOperand->zEnd; } @@ -126162,44 +130923,42 @@ struct AttachKey { int type; Token key; }; sqlite3ExprListSetName(pParse, p, pIdToken, 1); return p; } -/* Next is all token values, in a form suitable for use by makeheaders. -** This section will be null unless lemon is run with the -m switch. -*/ -/* -** These constants (all generated automatically by the parser generator) -** specify the various kinds of tokens (terminals) that the parser -** understands. -** -** Each symbol here is a terminal symbol in the grammar. -*/ -/* Make sure the INTERFACE macro is defined. -*/ -#ifndef INTERFACE -# define INTERFACE 1 -#endif -/* The next thing included is series of defines which control +/**************** 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 +** "lemon" is run with the "-m" command-line option. +***************** Begin makeheaders token definitions *************************/ +/**************** End makeheaders token definitions ***************************/ + +/* The next sections is a series of control #defines. ** various aspects of the generated parser. -** YYCODETYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 terminals -** and nonterminals. "int" is used otherwise. -** YYNOCODE is a number of type YYCODETYPE which corresponds -** to no legal terminal or nonterminal number. This -** number is used to fill in empty slots of the hash -** table. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. ** YYFALLBACK If defined, this indicates that one or more tokens -** have fall-back values which should be used if the -** original value of the token will not parse. -** YYACTIONTYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 rules and -** states combined. "int" is used otherwise. -** sqlite3ParserTOKENTYPE is the data type used for minor tokens given -** directly to the parser from the tokenizer. -** YYMINORTYPE is the data type used for all minor tokens. +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** 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 +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** 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 -** for base tokens is called "yy0". +** 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 @@ -126218,29 +130977,31 @@ struct AttachKey { int type; Token key; }; ** YY_ACCEPT_ACTION The yy_action[] code for accept ** YY_NO_ACTION The yy_action[] code for no-op */ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ #define YYCODETYPE unsigned char -#define YYNOCODE 254 +#define YYNOCODE 251 #define YYACTIONTYPE unsigned short int -#define YYWILDCARD 70 +#define YYWILDCARD 96 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; sqlite3ParserTOKENTYPE yy0; - Select* yy3; - ExprList* yy14; - With* yy59; - SrcList* yy65; - struct LikeOp yy96; - Expr* yy132; - u8 yy186; - int yy328; - ExprSpan yy346; - struct TrigEvent yy378; - u16 yy381; - IdList* yy408; - struct {int value; int mask;} yy429; - TriggerStep* yy473; - struct LimitVal yy476; + struct LimitVal yy64; + Expr* yy122; + Select* yy159; + IdList* yy180; + struct {int value; int mask;} yy207; + struct LikeOp yy318; + TriggerStep* yy327; + With* yy331; + ExprSpan yy342; + SrcList* yy347; + int yy392; + struct TrigEvent yy410; + ExprList* yy442; } YYMINORTYPE; #ifndef YYSTACKDEPTH #define YYSTACKDEPTH 100 @@ -126250,20 +131011,17 @@ typedef union { #define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse #define sqlite3ParserARG_STORE yypParser->pParse = pParse #define YYFALLBACK 1 -#define YYNSTATE 436 -#define YYNRULE 328 -#define YY_MAX_SHIFT 435 +#define YYNSTATE 440 +#define YYNRULE 326 +#define YY_MAX_SHIFT 439 #define YY_MIN_SHIFTREDUCE 649 -#define YY_MAX_SHIFTREDUCE 976 -#define YY_MIN_REDUCE 977 -#define YY_MAX_REDUCE 1304 -#define YY_ERROR_ACTION 1305 -#define YY_ACCEPT_ACTION 1306 -#define YY_NO_ACTION 1307 - -/* The yyzerominor constant is used to initialize instances of -** YYMINORTYPE objects to zero. */ -static const YYMINORTYPE yyzerominor = { 0 }; +#define YY_MAX_SHIFTREDUCE 974 +#define YY_MIN_REDUCE 975 +#define YY_MAX_REDUCE 1300 +#define YY_ERROR_ACTION 1301 +#define YY_ACCEPT_ACTION 1302 +#define YY_NO_ACTION 1303 +/************* End control #defines *******************************************/ /* Define the yytestcase() macro to be a no-op if is not already defined ** otherwise. @@ -126328,451 +131086,453 @@ static const YYMINORTYPE yyzerominor = { 0 }; ** yy_reduce_ofst[] For each state, the offset into yy_action for ** shifting non-terminals after a reduce. ** yy_default[] Default action for each state. -*/ +** +*********** Begin parsing tables **********************************************/ #define YY_ACTTAB_COUNT (1501) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 311, 1306, 145, 651, 2, 192, 652, 338, 780, 92, - /* 10 */ 92, 92, 92, 85, 90, 90, 90, 90, 89, 89, - /* 20 */ 88, 88, 88, 87, 335, 88, 88, 88, 87, 335, - /* 30 */ 327, 856, 856, 92, 92, 92, 92, 776, 90, 90, - /* 40 */ 90, 90, 89, 89, 88, 88, 88, 87, 335, 86, - /* 50 */ 83, 166, 93, 94, 84, 868, 871, 860, 860, 91, - /* 60 */ 91, 92, 92, 92, 92, 335, 90, 90, 90, 90, - /* 70 */ 89, 89, 88, 88, 88, 87, 335, 311, 780, 90, - /* 80 */ 90, 90, 90, 89, 89, 88, 88, 88, 87, 335, - /* 90 */ 123, 808, 689, 689, 689, 689, 112, 230, 430, 257, - /* 100 */ 809, 698, 430, 86, 83, 166, 324, 55, 856, 856, - /* 110 */ 201, 158, 276, 387, 271, 386, 188, 689, 689, 828, - /* 120 */ 833, 49, 944, 269, 833, 49, 123, 87, 335, 93, - /* 130 */ 94, 84, 868, 871, 860, 860, 91, 91, 92, 92, - /* 140 */ 92, 92, 342, 90, 90, 90, 90, 89, 89, 88, - /* 150 */ 88, 88, 87, 335, 311, 328, 333, 332, 701, 408, - /* 160 */ 394, 69, 690, 691, 690, 691, 715, 910, 251, 354, - /* 170 */ 250, 698, 704, 430, 908, 430, 909, 89, 89, 88, - /* 180 */ 88, 88, 87, 335, 391, 856, 856, 690, 691, 183, - /* 190 */ 95, 340, 384, 381, 380, 833, 31, 833, 49, 912, - /* 200 */ 912, 333, 332, 379, 123, 311, 93, 94, 84, 868, - /* 210 */ 871, 860, 860, 91, 91, 92, 92, 92, 92, 114, - /* 220 */ 90, 90, 90, 90, 89, 89, 88, 88, 88, 87, - /* 230 */ 335, 430, 408, 399, 435, 657, 856, 856, 346, 57, - /* 240 */ 232, 828, 109, 20, 912, 912, 231, 393, 937, 760, - /* 250 */ 97, 751, 752, 833, 49, 708, 708, 93, 94, 84, - /* 260 */ 868, 871, 860, 860, 91, 91, 92, 92, 92, 92, - /* 270 */ 707, 90, 90, 90, 90, 89, 89, 88, 88, 88, - /* 280 */ 87, 335, 311, 114, 22, 706, 688, 58, 408, 390, - /* 290 */ 251, 349, 240, 749, 752, 689, 689, 847, 685, 115, - /* 300 */ 21, 231, 393, 689, 689, 697, 183, 355, 430, 384, - /* 310 */ 381, 380, 192, 856, 856, 780, 123, 160, 159, 223, - /* 320 */ 379, 738, 25, 315, 362, 841, 143, 689, 689, 835, - /* 330 */ 833, 48, 339, 937, 93, 94, 84, 868, 871, 860, - /* 340 */ 860, 91, 91, 92, 92, 92, 92, 914, 90, 90, - /* 350 */ 90, 90, 89, 89, 88, 88, 88, 87, 335, 311, - /* 360 */ 840, 840, 840, 266, 430, 690, 691, 778, 114, 1300, - /* 370 */ 1300, 430, 1, 690, 691, 697, 688, 689, 689, 689, - /* 380 */ 689, 689, 689, 287, 298, 780, 833, 10, 686, 115, - /* 390 */ 856, 856, 355, 833, 10, 828, 366, 690, 691, 363, - /* 400 */ 321, 76, 123, 74, 23, 737, 807, 323, 356, 353, - /* 410 */ 847, 93, 94, 84, 868, 871, 860, 860, 91, 91, - /* 420 */ 92, 92, 92, 92, 940, 90, 90, 90, 90, 89, - /* 430 */ 89, 88, 88, 88, 87, 335, 311, 806, 841, 429, - /* 440 */ 713, 941, 835, 430, 251, 354, 250, 690, 691, 690, - /* 450 */ 691, 690, 691, 86, 83, 166, 24, 942, 151, 753, - /* 460 */ 285, 907, 403, 907, 164, 833, 10, 856, 856, 965, - /* 470 */ 306, 754, 679, 840, 840, 840, 795, 216, 794, 222, - /* 480 */ 906, 344, 906, 904, 86, 83, 166, 286, 93, 94, - /* 490 */ 84, 868, 871, 860, 860, 91, 91, 92, 92, 92, - /* 500 */ 92, 430, 90, 90, 90, 90, 89, 89, 88, 88, - /* 510 */ 88, 87, 335, 311, 430, 724, 352, 705, 427, 699, - /* 520 */ 700, 376, 210, 833, 49, 793, 397, 857, 857, 940, - /* 530 */ 213, 762, 727, 334, 699, 700, 833, 10, 86, 83, - /* 540 */ 166, 345, 396, 902, 856, 856, 941, 385, 833, 9, - /* 550 */ 406, 869, 872, 187, 890, 728, 347, 398, 404, 977, - /* 560 */ 652, 338, 942, 954, 413, 93, 94, 84, 868, 871, - /* 570 */ 860, 860, 91, 91, 92, 92, 92, 92, 861, 90, - /* 580 */ 90, 90, 90, 89, 89, 88, 88, 88, 87, 335, - /* 590 */ 311, 1219, 114, 430, 834, 430, 5, 165, 192, 688, - /* 600 */ 832, 780, 430, 723, 430, 234, 325, 189, 163, 316, - /* 610 */ 356, 955, 115, 235, 269, 833, 35, 833, 36, 747, - /* 620 */ 720, 856, 856, 793, 833, 12, 833, 27, 745, 174, - /* 630 */ 968, 1290, 968, 1291, 1290, 310, 1291, 693, 317, 245, - /* 640 */ 264, 311, 93, 94, 84, 868, 871, 860, 860, 91, - /* 650 */ 91, 92, 92, 92, 92, 832, 90, 90, 90, 90, - /* 660 */ 89, 89, 88, 88, 88, 87, 335, 430, 320, 213, - /* 670 */ 762, 780, 856, 856, 920, 920, 369, 257, 966, 220, - /* 680 */ 966, 396, 663, 664, 665, 242, 259, 244, 262, 833, - /* 690 */ 37, 650, 2, 93, 94, 84, 868, 871, 860, 860, - /* 700 */ 91, 91, 92, 92, 92, 92, 430, 90, 90, 90, - /* 710 */ 90, 89, 89, 88, 88, 88, 87, 335, 311, 430, - /* 720 */ 239, 430, 917, 368, 430, 238, 916, 793, 833, 38, - /* 730 */ 430, 825, 430, 66, 430, 392, 430, 766, 766, 430, - /* 740 */ 367, 833, 39, 833, 28, 430, 833, 29, 68, 856, - /* 750 */ 856, 900, 833, 40, 833, 41, 833, 42, 833, 11, - /* 760 */ 72, 833, 43, 243, 305, 970, 114, 833, 99, 961, - /* 770 */ 93, 94, 84, 868, 871, 860, 860, 91, 91, 92, - /* 780 */ 92, 92, 92, 430, 90, 90, 90, 90, 89, 89, - /* 790 */ 88, 88, 88, 87, 335, 311, 430, 361, 430, 165, - /* 800 */ 147, 430, 186, 185, 184, 833, 44, 430, 289, 430, - /* 810 */ 246, 430, 971, 430, 212, 163, 430, 357, 833, 45, - /* 820 */ 833, 32, 932, 833, 46, 793, 856, 856, 718, 833, - /* 830 */ 47, 833, 33, 833, 117, 833, 118, 75, 833, 119, - /* 840 */ 288, 305, 967, 214, 935, 322, 311, 93, 94, 84, - /* 850 */ 868, 871, 860, 860, 91, 91, 92, 92, 92, 92, - /* 860 */ 430, 90, 90, 90, 90, 89, 89, 88, 88, 88, - /* 870 */ 87, 335, 430, 832, 426, 317, 288, 856, 856, 114, - /* 880 */ 763, 257, 833, 53, 930, 219, 364, 257, 257, 971, - /* 890 */ 361, 396, 257, 257, 833, 34, 257, 311, 93, 94, - /* 900 */ 84, 868, 871, 860, 860, 91, 91, 92, 92, 92, - /* 910 */ 92, 430, 90, 90, 90, 90, 89, 89, 88, 88, - /* 920 */ 88, 87, 335, 430, 217, 318, 124, 253, 856, 856, - /* 930 */ 218, 943, 257, 833, 100, 898, 759, 774, 361, 755, - /* 940 */ 423, 329, 758, 1017, 289, 833, 50, 682, 311, 93, - /* 950 */ 82, 84, 868, 871, 860, 860, 91, 91, 92, 92, - /* 960 */ 92, 92, 430, 90, 90, 90, 90, 89, 89, 88, - /* 970 */ 88, 88, 87, 335, 430, 256, 419, 114, 249, 856, - /* 980 */ 856, 331, 114, 400, 833, 101, 359, 187, 1064, 726, - /* 990 */ 725, 739, 401, 416, 420, 360, 833, 102, 424, 311, - /* 1000 */ 258, 94, 84, 868, 871, 860, 860, 91, 91, 92, - /* 1010 */ 92, 92, 92, 430, 90, 90, 90, 90, 89, 89, - /* 1020 */ 88, 88, 88, 87, 335, 430, 221, 261, 114, 114, - /* 1030 */ 856, 856, 808, 114, 156, 833, 98, 772, 733, 734, - /* 1040 */ 275, 809, 771, 316, 263, 265, 960, 833, 116, 307, - /* 1050 */ 741, 274, 722, 84, 868, 871, 860, 860, 91, 91, - /* 1060 */ 92, 92, 92, 92, 430, 90, 90, 90, 90, 89, - /* 1070 */ 89, 88, 88, 88, 87, 335, 80, 425, 830, 3, - /* 1080 */ 1214, 191, 430, 721, 336, 336, 833, 113, 252, 80, - /* 1090 */ 425, 68, 3, 913, 913, 428, 270, 336, 336, 430, - /* 1100 */ 377, 784, 430, 197, 833, 106, 430, 716, 428, 430, - /* 1110 */ 267, 430, 897, 68, 414, 430, 769, 409, 430, 71, - /* 1120 */ 430, 833, 105, 123, 833, 103, 847, 414, 833, 49, - /* 1130 */ 843, 833, 104, 833, 52, 800, 123, 833, 54, 847, - /* 1140 */ 833, 51, 833, 26, 831, 802, 77, 78, 191, 389, - /* 1150 */ 430, 372, 114, 79, 432, 431, 911, 911, 835, 77, - /* 1160 */ 78, 779, 893, 408, 410, 197, 79, 432, 431, 791, - /* 1170 */ 226, 835, 833, 30, 772, 80, 425, 716, 3, 771, - /* 1180 */ 411, 412, 897, 336, 336, 290, 291, 839, 703, 840, - /* 1190 */ 840, 840, 842, 19, 428, 695, 684, 672, 111, 671, - /* 1200 */ 843, 673, 840, 840, 840, 842, 19, 207, 661, 278, - /* 1210 */ 148, 304, 280, 414, 282, 6, 822, 348, 248, 241, - /* 1220 */ 358, 934, 720, 80, 425, 847, 3, 161, 382, 273, - /* 1230 */ 284, 336, 336, 415, 296, 958, 895, 894, 157, 674, - /* 1240 */ 107, 194, 428, 948, 135, 77, 78, 777, 953, 951, - /* 1250 */ 56, 319, 79, 432, 431, 121, 66, 835, 59, 128, - /* 1260 */ 146, 414, 350, 130, 351, 819, 131, 132, 133, 375, - /* 1270 */ 173, 149, 138, 847, 936, 365, 178, 70, 425, 827, - /* 1280 */ 3, 889, 62, 371, 915, 336, 336, 792, 840, 840, - /* 1290 */ 840, 842, 19, 77, 78, 208, 428, 144, 179, 373, - /* 1300 */ 79, 432, 431, 255, 180, 835, 260, 675, 181, 308, - /* 1310 */ 388, 744, 326, 743, 742, 414, 731, 718, 712, 402, - /* 1320 */ 309, 711, 788, 65, 277, 272, 789, 847, 730, 710, - /* 1330 */ 709, 279, 193, 787, 281, 876, 840, 840, 840, 842, - /* 1340 */ 19, 786, 283, 73, 418, 330, 422, 77, 78, 227, - /* 1350 */ 96, 407, 67, 405, 79, 432, 431, 292, 228, 835, - /* 1360 */ 215, 202, 229, 293, 767, 303, 302, 301, 204, 299, - /* 1370 */ 294, 295, 676, 7, 681, 433, 669, 206, 110, 224, - /* 1380 */ 203, 205, 434, 667, 666, 658, 120, 168, 656, 237, - /* 1390 */ 840, 840, 840, 842, 19, 337, 155, 233, 236, 341, - /* 1400 */ 167, 905, 108, 313, 903, 826, 314, 125, 126, 127, - /* 1410 */ 129, 170, 247, 756, 172, 928, 134, 136, 171, 60, - /* 1420 */ 61, 123, 169, 137, 175, 933, 176, 927, 8, 13, - /* 1430 */ 177, 254, 191, 918, 139, 370, 924, 140, 678, 150, - /* 1440 */ 374, 274, 182, 378, 141, 122, 63, 14, 383, 729, - /* 1450 */ 268, 15, 64, 225, 846, 845, 874, 16, 765, 770, - /* 1460 */ 4, 162, 209, 395, 211, 142, 878, 796, 801, 312, - /* 1470 */ 190, 71, 68, 875, 873, 939, 199, 938, 17, 195, - /* 1480 */ 18, 196, 417, 975, 152, 653, 976, 198, 153, 421, - /* 1490 */ 877, 154, 200, 844, 696, 81, 343, 297, 1019, 1018, - /* 1500 */ 300, + /* 0 */ 315, 810, 339, 804, 5, 194, 194, 798, 92, 93, + /* 10 */ 83, 819, 819, 831, 834, 823, 823, 90, 90, 91, + /* 20 */ 91, 91, 91, 290, 89, 89, 89, 89, 88, 88, + /* 30 */ 87, 87, 87, 86, 339, 315, 952, 952, 803, 803, + /* 40 */ 803, 922, 342, 92, 93, 83, 819, 819, 831, 834, + /* 50 */ 823, 823, 90, 90, 91, 91, 91, 91, 123, 89, + /* 60 */ 89, 89, 89, 88, 88, 87, 87, 87, 86, 339, + /* 70 */ 88, 88, 87, 87, 87, 86, 339, 772, 952, 952, + /* 80 */ 315, 87, 87, 87, 86, 339, 773, 68, 92, 93, + /* 90 */ 83, 819, 819, 831, 834, 823, 823, 90, 90, 91, + /* 100 */ 91, 91, 91, 434, 89, 89, 89, 89, 88, 88, + /* 110 */ 87, 87, 87, 86, 339, 1302, 146, 921, 2, 315, + /* 120 */ 427, 24, 679, 953, 48, 86, 339, 92, 93, 83, + /* 130 */ 819, 819, 831, 834, 823, 823, 90, 90, 91, 91, + /* 140 */ 91, 91, 94, 89, 89, 89, 89, 88, 88, 87, + /* 150 */ 87, 87, 86, 339, 933, 933, 315, 259, 412, 398, + /* 160 */ 396, 57, 733, 733, 92, 93, 83, 819, 819, 831, + /* 170 */ 834, 823, 823, 90, 90, 91, 91, 91, 91, 56, + /* 180 */ 89, 89, 89, 89, 88, 88, 87, 87, 87, 86, + /* 190 */ 339, 315, 1245, 922, 342, 268, 934, 935, 241, 92, + /* 200 */ 93, 83, 819, 819, 831, 834, 823, 823, 90, 90, + /* 210 */ 91, 91, 91, 91, 291, 89, 89, 89, 89, 88, + /* 220 */ 88, 87, 87, 87, 86, 339, 315, 913, 1295, 682, + /* 230 */ 687, 1295, 233, 397, 92, 93, 83, 819, 819, 831, + /* 240 */ 834, 823, 823, 90, 90, 91, 91, 91, 91, 326, + /* 250 */ 89, 89, 89, 89, 88, 88, 87, 87, 87, 86, + /* 260 */ 339, 315, 85, 82, 168, 680, 431, 938, 939, 92, + /* 270 */ 93, 83, 819, 819, 831, 834, 823, 823, 90, 90, + /* 280 */ 91, 91, 91, 91, 291, 89, 89, 89, 89, 88, + /* 290 */ 88, 87, 87, 87, 86, 339, 315, 319, 913, 1296, + /* 300 */ 797, 911, 1296, 681, 92, 93, 83, 819, 819, 831, + /* 310 */ 834, 823, 823, 90, 90, 91, 91, 91, 91, 335, + /* 320 */ 89, 89, 89, 89, 88, 88, 87, 87, 87, 86, + /* 330 */ 339, 315, 876, 876, 373, 85, 82, 168, 944, 92, + /* 340 */ 93, 83, 819, 819, 831, 834, 823, 823, 90, 90, + /* 350 */ 91, 91, 91, 91, 896, 89, 89, 89, 89, 88, + /* 360 */ 88, 87, 87, 87, 86, 339, 315, 370, 307, 973, + /* 370 */ 367, 1, 911, 433, 92, 93, 83, 819, 819, 831, + /* 380 */ 834, 823, 823, 90, 90, 91, 91, 91, 91, 189, + /* 390 */ 89, 89, 89, 89, 88, 88, 87, 87, 87, 86, + /* 400 */ 339, 315, 720, 948, 933, 933, 149, 718, 948, 92, + /* 410 */ 93, 83, 819, 819, 831, 834, 823, 823, 90, 90, + /* 420 */ 91, 91, 91, 91, 434, 89, 89, 89, 89, 88, + /* 430 */ 88, 87, 87, 87, 86, 339, 338, 938, 939, 947, + /* 440 */ 694, 940, 974, 315, 953, 48, 934, 935, 715, 689, + /* 450 */ 71, 92, 93, 83, 819, 819, 831, 834, 823, 823, + /* 460 */ 90, 90, 91, 91, 91, 91, 320, 89, 89, 89, + /* 470 */ 89, 88, 88, 87, 87, 87, 86, 339, 315, 412, + /* 480 */ 403, 820, 820, 832, 835, 74, 92, 81, 83, 819, + /* 490 */ 819, 831, 834, 823, 823, 90, 90, 91, 91, 91, + /* 500 */ 91, 698, 89, 89, 89, 89, 88, 88, 87, 87, + /* 510 */ 87, 86, 339, 315, 259, 654, 655, 656, 393, 111, + /* 520 */ 331, 153, 93, 83, 819, 819, 831, 834, 823, 823, + /* 530 */ 90, 90, 91, 91, 91, 91, 434, 89, 89, 89, + /* 540 */ 89, 88, 88, 87, 87, 87, 86, 339, 315, 188, + /* 550 */ 187, 186, 824, 937, 328, 219, 953, 48, 83, 819, + /* 560 */ 819, 831, 834, 823, 823, 90, 90, 91, 91, 91, + /* 570 */ 91, 956, 89, 89, 89, 89, 88, 88, 87, 87, + /* 580 */ 87, 86, 339, 79, 429, 738, 3, 1174, 955, 348, + /* 590 */ 737, 332, 792, 933, 933, 937, 79, 429, 730, 3, + /* 600 */ 203, 160, 278, 391, 273, 390, 190, 892, 434, 400, + /* 610 */ 741, 76, 77, 271, 287, 253, 353, 242, 78, 340, + /* 620 */ 340, 85, 82, 168, 76, 77, 233, 397, 953, 48, + /* 630 */ 432, 78, 340, 340, 277, 934, 935, 185, 439, 651, + /* 640 */ 388, 385, 384, 432, 234, 276, 107, 418, 349, 337, + /* 650 */ 336, 383, 893, 728, 215, 949, 123, 971, 308, 810, + /* 660 */ 418, 436, 435, 412, 394, 798, 400, 873, 894, 123, + /* 670 */ 721, 872, 810, 889, 436, 435, 215, 949, 798, 351, + /* 680 */ 722, 697, 380, 434, 771, 371, 22, 434, 400, 79, + /* 690 */ 429, 232, 3, 189, 413, 870, 803, 803, 803, 805, + /* 700 */ 18, 54, 148, 953, 48, 956, 113, 953, 9, 803, + /* 710 */ 803, 803, 805, 18, 310, 123, 748, 76, 77, 742, + /* 720 */ 123, 325, 955, 866, 78, 340, 340, 113, 350, 359, + /* 730 */ 85, 82, 168, 343, 960, 960, 432, 770, 412, 414, + /* 740 */ 407, 23, 1240, 1240, 79, 429, 357, 3, 166, 91, + /* 750 */ 91, 91, 91, 418, 89, 89, 89, 89, 88, 88, + /* 760 */ 87, 87, 87, 86, 339, 810, 434, 436, 435, 792, + /* 770 */ 320, 798, 76, 77, 789, 271, 123, 434, 360, 78, + /* 780 */ 340, 340, 864, 85, 82, 168, 953, 9, 395, 743, + /* 790 */ 360, 432, 253, 358, 252, 933, 933, 953, 30, 889, + /* 800 */ 327, 216, 803, 803, 803, 805, 18, 113, 418, 89, + /* 810 */ 89, 89, 89, 88, 88, 87, 87, 87, 86, 339, + /* 820 */ 810, 113, 436, 435, 792, 185, 798, 288, 388, 385, + /* 830 */ 384, 123, 113, 920, 2, 796, 696, 934, 935, 383, + /* 840 */ 69, 429, 434, 3, 218, 110, 738, 253, 358, 252, + /* 850 */ 434, 737, 933, 933, 892, 359, 222, 803, 803, 803, + /* 860 */ 805, 18, 953, 47, 933, 933, 933, 933, 76, 77, + /* 870 */ 953, 9, 366, 904, 217, 78, 340, 340, 677, 305, + /* 880 */ 304, 303, 206, 301, 224, 259, 664, 432, 337, 336, + /* 890 */ 434, 228, 247, 144, 934, 935, 933, 933, 667, 893, + /* 900 */ 324, 1259, 96, 434, 418, 796, 934, 935, 934, 935, + /* 910 */ 953, 48, 401, 148, 289, 894, 810, 417, 436, 435, + /* 920 */ 677, 759, 798, 953, 9, 314, 220, 162, 161, 170, + /* 930 */ 402, 239, 953, 8, 194, 683, 683, 410, 934, 935, + /* 940 */ 238, 959, 933, 933, 225, 408, 945, 365, 957, 212, + /* 950 */ 958, 172, 757, 803, 803, 803, 805, 18, 173, 365, + /* 960 */ 176, 123, 171, 113, 244, 952, 246, 434, 356, 796, + /* 970 */ 372, 365, 236, 960, 960, 810, 290, 804, 191, 165, + /* 980 */ 852, 798, 259, 316, 934, 935, 237, 953, 34, 404, + /* 990 */ 91, 91, 91, 91, 84, 89, 89, 89, 89, 88, + /* 1000 */ 88, 87, 87, 87, 86, 339, 701, 952, 434, 240, + /* 1010 */ 347, 758, 803, 803, 803, 434, 245, 1179, 434, 389, + /* 1020 */ 434, 376, 434, 895, 167, 434, 405, 702, 953, 35, + /* 1030 */ 673, 321, 221, 434, 333, 953, 11, 434, 953, 26, + /* 1040 */ 953, 36, 953, 37, 251, 953, 38, 434, 259, 434, + /* 1050 */ 757, 434, 329, 953, 27, 434, 223, 953, 28, 434, + /* 1060 */ 690, 434, 67, 434, 65, 434, 862, 953, 39, 953, + /* 1070 */ 40, 953, 41, 423, 434, 953, 10, 434, 772, 953, + /* 1080 */ 42, 953, 98, 953, 43, 953, 44, 773, 434, 346, + /* 1090 */ 434, 75, 434, 73, 953, 31, 434, 953, 45, 434, + /* 1100 */ 259, 434, 690, 434, 757, 434, 887, 434, 953, 46, + /* 1110 */ 953, 32, 953, 115, 434, 266, 953, 116, 951, 953, + /* 1120 */ 117, 953, 52, 953, 33, 953, 99, 953, 49, 726, + /* 1130 */ 434, 909, 434, 19, 953, 100, 434, 344, 434, 113, + /* 1140 */ 434, 258, 692, 434, 259, 434, 670, 434, 20, 434, + /* 1150 */ 953, 101, 953, 97, 434, 259, 953, 114, 953, 112, + /* 1160 */ 953, 105, 113, 953, 104, 953, 102, 953, 103, 953, + /* 1170 */ 51, 434, 148, 434, 953, 53, 167, 434, 259, 113, + /* 1180 */ 300, 307, 912, 363, 311, 860, 248, 261, 209, 264, + /* 1190 */ 416, 953, 50, 953, 25, 420, 727, 953, 29, 430, + /* 1200 */ 321, 424, 757, 428, 322, 124, 1269, 214, 165, 710, + /* 1210 */ 859, 908, 806, 794, 309, 158, 193, 361, 254, 723, + /* 1220 */ 364, 67, 381, 269, 735, 199, 67, 70, 113, 700, + /* 1230 */ 699, 707, 708, 884, 113, 766, 113, 855, 193, 883, + /* 1240 */ 199, 869, 869, 675, 868, 868, 109, 368, 255, 260, + /* 1250 */ 263, 280, 859, 265, 806, 974, 267, 711, 695, 272, + /* 1260 */ 764, 282, 795, 284, 150, 744, 755, 415, 292, 293, + /* 1270 */ 802, 678, 672, 661, 660, 662, 927, 6, 306, 386, + /* 1280 */ 352, 786, 243, 250, 886, 362, 163, 286, 419, 298, + /* 1290 */ 930, 159, 968, 196, 126, 903, 901, 965, 55, 58, + /* 1300 */ 323, 275, 857, 136, 147, 694, 856, 121, 65, 354, + /* 1310 */ 355, 379, 175, 61, 151, 369, 180, 871, 375, 129, + /* 1320 */ 257, 756, 210, 181, 145, 131, 132, 377, 262, 663, + /* 1330 */ 133, 134, 139, 783, 791, 182, 392, 183, 312, 330, + /* 1340 */ 714, 888, 713, 851, 692, 195, 712, 406, 686, 705, + /* 1350 */ 313, 685, 64, 839, 274, 72, 684, 334, 942, 95, + /* 1360 */ 752, 279, 281, 704, 753, 751, 422, 283, 411, 750, + /* 1370 */ 426, 66, 204, 409, 21, 285, 928, 669, 437, 205, + /* 1380 */ 207, 208, 438, 658, 657, 652, 118, 108, 119, 226, + /* 1390 */ 650, 341, 157, 235, 169, 345, 106, 734, 790, 296, + /* 1400 */ 294, 295, 120, 297, 867, 865, 127, 128, 130, 724, + /* 1410 */ 229, 174, 249, 882, 137, 230, 138, 135, 885, 231, + /* 1420 */ 59, 60, 177, 881, 7, 178, 12, 179, 256, 874, + /* 1430 */ 140, 193, 962, 374, 141, 152, 666, 378, 276, 184, + /* 1440 */ 270, 122, 142, 382, 387, 62, 13, 14, 703, 63, + /* 1450 */ 125, 317, 318, 227, 809, 808, 837, 732, 15, 164, + /* 1460 */ 736, 4, 765, 211, 399, 213, 192, 143, 760, 70, + /* 1470 */ 67, 16, 17, 838, 836, 891, 841, 890, 198, 197, + /* 1480 */ 917, 154, 421, 923, 918, 155, 200, 977, 425, 840, + /* 1490 */ 156, 201, 807, 676, 80, 302, 299, 977, 202, 1261, + /* 1500 */ 1260, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 19, 144, 145, 146, 147, 24, 1, 2, 27, 80, - /* 10 */ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, - /* 20 */ 91, 92, 93, 94, 95, 91, 92, 93, 94, 95, - /* 30 */ 19, 50, 51, 80, 81, 82, 83, 212, 85, 86, - /* 40 */ 87, 88, 89, 90, 91, 92, 93, 94, 95, 224, - /* 50 */ 225, 226, 71, 72, 73, 74, 75, 76, 77, 78, - /* 60 */ 79, 80, 81, 82, 83, 95, 85, 86, 87, 88, - /* 70 */ 89, 90, 91, 92, 93, 94, 95, 19, 97, 85, - /* 80 */ 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - /* 90 */ 66, 33, 27, 28, 27, 28, 22, 201, 152, 152, - /* 100 */ 42, 27, 152, 224, 225, 226, 95, 211, 50, 51, - /* 110 */ 99, 100, 101, 102, 103, 104, 105, 27, 28, 59, - /* 120 */ 174, 175, 243, 112, 174, 175, 66, 94, 95, 71, - /* 130 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, - /* 140 */ 82, 83, 195, 85, 86, 87, 88, 89, 90, 91, - /* 150 */ 92, 93, 94, 95, 19, 209, 89, 90, 173, 209, - /* 160 */ 210, 26, 97, 98, 97, 98, 181, 100, 108, 109, - /* 170 */ 110, 97, 174, 152, 107, 152, 109, 89, 90, 91, - /* 180 */ 92, 93, 94, 95, 163, 50, 51, 97, 98, 99, - /* 190 */ 55, 244, 102, 103, 104, 174, 175, 174, 175, 132, - /* 200 */ 133, 89, 90, 113, 66, 19, 71, 72, 73, 74, - /* 210 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 198, - /* 220 */ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, - /* 230 */ 95, 152, 209, 210, 148, 149, 50, 51, 100, 53, - /* 240 */ 154, 59, 156, 22, 132, 133, 119, 120, 163, 163, - /* 250 */ 22, 192, 193, 174, 175, 27, 28, 71, 72, 73, - /* 260 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, - /* 270 */ 174, 85, 86, 87, 88, 89, 90, 91, 92, 93, - /* 280 */ 94, 95, 19, 198, 198, 174, 152, 24, 209, 210, - /* 290 */ 108, 109, 110, 192, 193, 27, 28, 69, 164, 165, - /* 300 */ 79, 119, 120, 27, 28, 27, 99, 222, 152, 102, - /* 310 */ 103, 104, 24, 50, 51, 27, 66, 89, 90, 185, - /* 320 */ 113, 187, 22, 157, 239, 97, 58, 27, 28, 101, - /* 330 */ 174, 175, 246, 163, 71, 72, 73, 74, 75, 76, - /* 340 */ 77, 78, 79, 80, 81, 82, 83, 11, 85, 86, - /* 350 */ 87, 88, 89, 90, 91, 92, 93, 94, 95, 19, - /* 360 */ 132, 133, 134, 23, 152, 97, 98, 91, 198, 119, - /* 370 */ 120, 152, 22, 97, 98, 97, 152, 27, 28, 27, - /* 380 */ 28, 27, 28, 227, 160, 97, 174, 175, 164, 165, - /* 390 */ 50, 51, 222, 174, 175, 59, 230, 97, 98, 233, - /* 400 */ 188, 137, 66, 139, 234, 187, 177, 188, 152, 239, - /* 410 */ 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, - /* 420 */ 80, 81, 82, 83, 12, 85, 86, 87, 88, 89, - /* 430 */ 90, 91, 92, 93, 94, 95, 19, 177, 97, 152, - /* 440 */ 23, 29, 101, 152, 108, 109, 110, 97, 98, 97, - /* 450 */ 98, 97, 98, 224, 225, 226, 22, 45, 24, 47, - /* 460 */ 152, 152, 152, 152, 152, 174, 175, 50, 51, 249, - /* 470 */ 250, 59, 21, 132, 133, 134, 124, 221, 124, 188, - /* 480 */ 171, 172, 171, 172, 224, 225, 226, 152, 71, 72, - /* 490 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, - /* 500 */ 83, 152, 85, 86, 87, 88, 89, 90, 91, 92, - /* 510 */ 93, 94, 95, 19, 152, 183, 65, 23, 170, 171, - /* 520 */ 172, 19, 23, 174, 175, 26, 152, 50, 51, 12, - /* 530 */ 196, 197, 37, 170, 171, 172, 174, 175, 224, 225, - /* 540 */ 226, 232, 208, 232, 50, 51, 29, 52, 174, 175, - /* 550 */ 188, 74, 75, 51, 103, 60, 222, 163, 209, 0, - /* 560 */ 1, 2, 45, 152, 47, 71, 72, 73, 74, 75, - /* 570 */ 76, 77, 78, 79, 80, 81, 82, 83, 101, 85, - /* 580 */ 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - /* 590 */ 19, 140, 198, 152, 23, 152, 22, 98, 24, 152, - /* 600 */ 152, 27, 152, 183, 152, 152, 111, 213, 214, 107, - /* 610 */ 152, 164, 165, 152, 112, 174, 175, 174, 175, 181, - /* 620 */ 182, 50, 51, 124, 174, 175, 174, 175, 190, 26, - /* 630 */ 22, 23, 22, 23, 26, 166, 26, 168, 169, 16, - /* 640 */ 16, 19, 71, 72, 73, 74, 75, 76, 77, 78, - /* 650 */ 79, 80, 81, 82, 83, 152, 85, 86, 87, 88, - /* 660 */ 89, 90, 91, 92, 93, 94, 95, 152, 220, 196, - /* 670 */ 197, 97, 50, 51, 108, 109, 110, 152, 70, 221, - /* 680 */ 70, 208, 7, 8, 9, 62, 62, 64, 64, 174, - /* 690 */ 175, 146, 147, 71, 72, 73, 74, 75, 76, 77, - /* 700 */ 78, 79, 80, 81, 82, 83, 152, 85, 86, 87, - /* 710 */ 88, 89, 90, 91, 92, 93, 94, 95, 19, 152, - /* 720 */ 195, 152, 31, 220, 152, 152, 35, 26, 174, 175, - /* 730 */ 152, 163, 152, 130, 152, 115, 152, 117, 118, 152, - /* 740 */ 49, 174, 175, 174, 175, 152, 174, 175, 26, 50, - /* 750 */ 51, 152, 174, 175, 174, 175, 174, 175, 174, 175, - /* 760 */ 138, 174, 175, 140, 22, 23, 198, 174, 175, 152, - /* 770 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - /* 780 */ 81, 82, 83, 152, 85, 86, 87, 88, 89, 90, - /* 790 */ 91, 92, 93, 94, 95, 19, 152, 152, 152, 98, - /* 800 */ 24, 152, 108, 109, 110, 174, 175, 152, 152, 152, - /* 810 */ 152, 152, 70, 152, 213, 214, 152, 152, 174, 175, - /* 820 */ 174, 175, 152, 174, 175, 124, 50, 51, 106, 174, - /* 830 */ 175, 174, 175, 174, 175, 174, 175, 138, 174, 175, - /* 840 */ 152, 22, 23, 22, 163, 189, 19, 71, 72, 73, - /* 850 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, - /* 860 */ 152, 85, 86, 87, 88, 89, 90, 91, 92, 93, - /* 870 */ 94, 95, 152, 152, 168, 169, 152, 50, 51, 198, - /* 880 */ 197, 152, 174, 175, 152, 240, 152, 152, 152, 70, - /* 890 */ 152, 208, 152, 152, 174, 175, 152, 19, 71, 72, - /* 900 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, - /* 910 */ 83, 152, 85, 86, 87, 88, 89, 90, 91, 92, - /* 920 */ 93, 94, 95, 152, 195, 247, 248, 152, 50, 51, - /* 930 */ 195, 195, 152, 174, 175, 195, 195, 26, 152, 195, - /* 940 */ 252, 220, 163, 122, 152, 174, 175, 163, 19, 71, - /* 950 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, - /* 960 */ 82, 83, 152, 85, 86, 87, 88, 89, 90, 91, - /* 970 */ 92, 93, 94, 95, 152, 195, 252, 198, 240, 50, - /* 980 */ 51, 189, 198, 19, 174, 175, 19, 51, 23, 100, - /* 990 */ 101, 26, 28, 163, 163, 28, 174, 175, 163, 19, - /* 1000 */ 152, 72, 73, 74, 75, 76, 77, 78, 79, 80, - /* 1010 */ 81, 82, 83, 152, 85, 86, 87, 88, 89, 90, - /* 1020 */ 91, 92, 93, 94, 95, 152, 240, 152, 198, 198, - /* 1030 */ 50, 51, 33, 198, 123, 174, 175, 116, 7, 8, - /* 1040 */ 101, 42, 121, 107, 152, 152, 23, 174, 175, 26, - /* 1050 */ 152, 112, 183, 73, 74, 75, 76, 77, 78, 79, - /* 1060 */ 80, 81, 82, 83, 152, 85, 86, 87, 88, 89, - /* 1070 */ 90, 91, 92, 93, 94, 95, 19, 20, 23, 22, - /* 1080 */ 23, 26, 152, 152, 27, 28, 174, 175, 23, 19, - /* 1090 */ 20, 26, 22, 132, 133, 38, 152, 27, 28, 152, - /* 1100 */ 23, 215, 152, 26, 174, 175, 152, 27, 38, 152, - /* 1110 */ 23, 152, 27, 26, 57, 152, 23, 163, 152, 26, - /* 1120 */ 152, 174, 175, 66, 174, 175, 69, 57, 174, 175, - /* 1130 */ 27, 174, 175, 174, 175, 152, 66, 174, 175, 69, - /* 1140 */ 174, 175, 174, 175, 152, 23, 89, 90, 26, 91, - /* 1150 */ 152, 236, 198, 96, 97, 98, 132, 133, 101, 89, - /* 1160 */ 90, 152, 23, 209, 210, 26, 96, 97, 98, 152, - /* 1170 */ 212, 101, 174, 175, 116, 19, 20, 97, 22, 121, - /* 1180 */ 152, 193, 97, 27, 28, 152, 152, 152, 152, 132, - /* 1190 */ 133, 134, 135, 136, 38, 23, 152, 152, 26, 152, - /* 1200 */ 97, 152, 132, 133, 134, 135, 136, 235, 152, 212, - /* 1210 */ 199, 150, 212, 57, 212, 200, 203, 216, 241, 216, - /* 1220 */ 241, 203, 182, 19, 20, 69, 22, 186, 178, 177, - /* 1230 */ 216, 27, 28, 229, 202, 39, 177, 177, 200, 155, - /* 1240 */ 245, 122, 38, 41, 22, 89, 90, 91, 159, 159, - /* 1250 */ 242, 159, 96, 97, 98, 71, 130, 101, 242, 191, - /* 1260 */ 223, 57, 18, 194, 159, 203, 194, 194, 194, 18, - /* 1270 */ 158, 223, 191, 69, 203, 159, 158, 19, 20, 191, - /* 1280 */ 22, 203, 137, 46, 238, 27, 28, 159, 132, 133, - /* 1290 */ 134, 135, 136, 89, 90, 159, 38, 22, 158, 179, - /* 1300 */ 96, 97, 98, 237, 158, 101, 159, 159, 158, 179, - /* 1310 */ 107, 176, 48, 176, 176, 57, 184, 106, 176, 125, - /* 1320 */ 179, 178, 218, 107, 217, 176, 218, 69, 184, 176, - /* 1330 */ 176, 217, 159, 218, 217, 159, 132, 133, 134, 135, - /* 1340 */ 136, 218, 217, 137, 179, 95, 179, 89, 90, 228, - /* 1350 */ 129, 126, 128, 127, 96, 97, 98, 206, 231, 101, - /* 1360 */ 5, 25, 231, 205, 207, 10, 11, 12, 13, 14, - /* 1370 */ 204, 203, 17, 26, 162, 161, 13, 6, 180, 180, - /* 1380 */ 153, 153, 151, 151, 151, 151, 167, 32, 4, 34, - /* 1390 */ 132, 133, 134, 135, 136, 3, 22, 142, 43, 68, - /* 1400 */ 15, 23, 16, 251, 23, 120, 251, 248, 131, 111, - /* 1410 */ 123, 56, 16, 20, 125, 1, 123, 131, 63, 79, - /* 1420 */ 79, 66, 67, 111, 36, 28, 122, 1, 5, 22, - /* 1430 */ 107, 140, 26, 54, 54, 44, 61, 107, 20, 24, - /* 1440 */ 19, 112, 105, 53, 22, 40, 22, 22, 53, 30, - /* 1450 */ 23, 22, 22, 53, 23, 23, 23, 22, 116, 23, - /* 1460 */ 22, 122, 23, 26, 23, 22, 11, 124, 28, 114, - /* 1470 */ 36, 26, 26, 23, 23, 23, 122, 23, 36, 26, - /* 1480 */ 36, 22, 24, 23, 22, 1, 23, 26, 22, 24, - /* 1490 */ 23, 22, 122, 23, 23, 22, 141, 23, 122, 122, - /* 1500 */ 15, + /* 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, 92, 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 */ 249, 22, 172, 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, 0, 1, 2, 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, 221, 222, 223, 23, 168, 169, 170, 27, + /* 270 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + /* 280 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47, + /* 290 */ 48, 49, 50, 51, 52, 53, 19, 157, 22, 23, + /* 300 */ 23, 96, 26, 172, 27, 28, 29, 30, 31, 32, + /* 310 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 187, + /* 320 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 330 */ 53, 19, 108, 109, 110, 221, 222, 223, 185, 27, + /* 340 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + /* 350 */ 38, 39, 40, 41, 240, 43, 44, 45, 46, 47, + /* 360 */ 48, 49, 50, 51, 52, 53, 19, 227, 22, 23, + /* 370 */ 230, 22, 96, 152, 27, 28, 29, 30, 31, 32, + /* 380 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 30, + /* 390 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 400 */ 53, 19, 190, 191, 55, 56, 24, 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, 168, 169, 170, 179, + /* 440 */ 180, 171, 96, 19, 172, 173, 97, 98, 188, 179, + /* 450 */ 138, 27, 28, 29, 30, 31, 32, 33, 34, 35, + /* 460 */ 36, 37, 38, 39, 40, 41, 107, 43, 44, 45, + /* 470 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 207, + /* 480 */ 208, 30, 31, 32, 33, 138, 27, 28, 29, 30, + /* 490 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + /* 500 */ 41, 181, 43, 44, 45, 46, 47, 48, 49, 50, + /* 510 */ 51, 52, 53, 19, 152, 7, 8, 9, 49, 22, + /* 520 */ 19, 24, 28, 29, 30, 31, 32, 33, 34, 35, + /* 530 */ 36, 37, 38, 39, 40, 41, 152, 43, 44, 45, + /* 540 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 108, + /* 550 */ 109, 110, 101, 55, 53, 193, 172, 173, 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, 20, 116, 22, 23, 169, 170, + /* 590 */ 121, 207, 85, 55, 56, 97, 19, 20, 195, 22, + /* 600 */ 99, 100, 101, 102, 103, 104, 105, 12, 152, 206, + /* 610 */ 210, 47, 48, 112, 152, 108, 109, 110, 54, 55, + /* 620 */ 56, 221, 222, 223, 47, 48, 119, 120, 172, 173, + /* 630 */ 66, 54, 55, 56, 101, 97, 98, 99, 148, 149, + /* 640 */ 102, 103, 104, 66, 154, 112, 156, 83, 229, 47, + /* 650 */ 48, 113, 57, 163, 194, 195, 92, 246, 247, 95, + /* 660 */ 83, 97, 98, 207, 208, 101, 206, 59, 73, 92, + /* 670 */ 75, 63, 95, 163, 97, 98, 194, 195, 101, 219, + /* 680 */ 85, 181, 19, 152, 175, 77, 196, 152, 206, 19, + /* 690 */ 20, 199, 22, 30, 163, 11, 132, 133, 134, 135, + /* 700 */ 136, 209, 152, 172, 173, 152, 196, 172, 173, 132, + /* 710 */ 133, 134, 135, 136, 164, 92, 213, 47, 48, 49, + /* 720 */ 92, 186, 169, 170, 54, 55, 56, 196, 100, 219, + /* 730 */ 221, 222, 223, 243, 132, 133, 66, 175, 207, 208, + /* 740 */ 152, 231, 119, 120, 19, 20, 236, 22, 152, 38, + /* 750 */ 39, 40, 41, 83, 43, 44, 45, 46, 47, 48, + /* 760 */ 49, 50, 51, 52, 53, 95, 152, 97, 98, 85, + /* 770 */ 107, 101, 47, 48, 163, 112, 92, 152, 152, 54, + /* 780 */ 55, 56, 229, 221, 222, 223, 172, 173, 163, 49, + /* 790 */ 152, 66, 108, 109, 110, 55, 56, 172, 173, 163, + /* 800 */ 186, 22, 132, 133, 134, 135, 136, 196, 83, 43, + /* 810 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 820 */ 95, 196, 97, 98, 85, 99, 101, 152, 102, 103, + /* 830 */ 104, 92, 196, 146, 147, 152, 181, 97, 98, 113, + /* 840 */ 19, 20, 152, 22, 218, 22, 116, 108, 109, 110, + /* 850 */ 152, 121, 55, 56, 12, 219, 218, 132, 133, 134, + /* 860 */ 135, 136, 172, 173, 55, 56, 55, 56, 47, 48, + /* 870 */ 172, 173, 236, 152, 5, 54, 55, 56, 55, 10, + /* 880 */ 11, 12, 13, 14, 186, 152, 17, 66, 47, 48, + /* 890 */ 152, 210, 16, 84, 97, 98, 55, 56, 21, 57, + /* 900 */ 217, 122, 22, 152, 83, 152, 97, 98, 97, 98, + /* 910 */ 172, 173, 152, 152, 224, 73, 95, 75, 97, 98, + /* 920 */ 97, 124, 101, 172, 173, 164, 193, 47, 48, 60, + /* 930 */ 163, 62, 172, 173, 24, 55, 56, 186, 97, 98, + /* 940 */ 71, 100, 55, 56, 183, 207, 185, 152, 107, 23, + /* 950 */ 109, 82, 26, 132, 133, 134, 135, 136, 89, 152, + /* 960 */ 26, 92, 93, 196, 88, 55, 90, 152, 91, 152, + /* 970 */ 217, 152, 152, 132, 133, 95, 152, 97, 211, 212, + /* 980 */ 103, 101, 152, 114, 97, 98, 152, 172, 173, 19, + /* 990 */ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + /* 1000 */ 48, 49, 50, 51, 52, 53, 65, 97, 152, 152, + /* 1010 */ 141, 124, 132, 133, 134, 152, 140, 140, 152, 78, + /* 1020 */ 152, 233, 152, 193, 98, 152, 56, 86, 172, 173, + /* 1030 */ 166, 167, 237, 152, 217, 172, 173, 152, 172, 173, + /* 1040 */ 172, 173, 172, 173, 237, 172, 173, 152, 152, 152, + /* 1050 */ 124, 152, 111, 172, 173, 152, 237, 172, 173, 152, + /* 1060 */ 55, 152, 26, 152, 130, 152, 152, 172, 173, 172, + /* 1070 */ 173, 172, 173, 249, 152, 172, 173, 152, 61, 172, + /* 1080 */ 173, 172, 173, 172, 173, 172, 173, 70, 152, 193, + /* 1090 */ 152, 137, 152, 139, 172, 173, 152, 172, 173, 152, + /* 1100 */ 152, 152, 97, 152, 26, 152, 163, 152, 172, 173, + /* 1110 */ 172, 173, 172, 173, 152, 16, 172, 173, 26, 172, + /* 1120 */ 173, 172, 173, 172, 173, 172, 173, 172, 173, 163, + /* 1130 */ 152, 152, 152, 22, 172, 173, 152, 241, 152, 196, + /* 1140 */ 152, 193, 106, 152, 152, 152, 163, 152, 37, 152, + /* 1150 */ 172, 173, 172, 173, 152, 152, 172, 173, 172, 173, + /* 1160 */ 172, 173, 196, 172, 173, 172, 173, 172, 173, 172, + /* 1170 */ 173, 152, 152, 152, 172, 173, 98, 152, 152, 196, + /* 1180 */ 160, 22, 23, 19, 164, 193, 152, 88, 232, 90, + /* 1190 */ 191, 172, 173, 172, 173, 163, 193, 172, 173, 166, + /* 1200 */ 167, 163, 124, 163, 244, 245, 23, 211, 212, 26, + /* 1210 */ 55, 23, 55, 23, 26, 123, 26, 152, 23, 193, + /* 1220 */ 56, 26, 23, 23, 23, 26, 26, 26, 196, 100, + /* 1230 */ 101, 7, 8, 152, 196, 23, 196, 23, 26, 152, + /* 1240 */ 26, 132, 133, 23, 132, 133, 26, 152, 152, 152, + /* 1250 */ 152, 210, 97, 152, 97, 96, 152, 152, 152, 152, + /* 1260 */ 152, 210, 152, 210, 197, 152, 152, 152, 152, 152, + /* 1270 */ 152, 152, 152, 152, 152, 152, 152, 198, 150, 176, + /* 1280 */ 214, 201, 214, 238, 201, 238, 184, 214, 226, 200, + /* 1290 */ 155, 198, 67, 122, 242, 159, 159, 69, 239, 239, + /* 1300 */ 159, 175, 175, 22, 220, 180, 175, 27, 130, 18, + /* 1310 */ 159, 18, 158, 137, 220, 159, 158, 235, 74, 189, + /* 1320 */ 234, 159, 159, 158, 22, 192, 192, 177, 159, 159, + /* 1330 */ 192, 192, 189, 201, 189, 158, 107, 158, 177, 76, + /* 1340 */ 174, 201, 174, 201, 106, 159, 174, 125, 174, 182, + /* 1350 */ 177, 176, 107, 159, 174, 137, 174, 53, 174, 129, + /* 1360 */ 216, 215, 215, 182, 216, 216, 177, 215, 126, 216, + /* 1370 */ 177, 128, 25, 127, 26, 215, 13, 162, 161, 153, + /* 1380 */ 153, 6, 151, 151, 151, 151, 165, 178, 165, 178, + /* 1390 */ 4, 3, 22, 142, 15, 94, 16, 205, 120, 202, + /* 1400 */ 204, 203, 165, 201, 23, 23, 131, 111, 123, 20, + /* 1410 */ 225, 125, 16, 1, 131, 228, 111, 123, 56, 228, + /* 1420 */ 37, 37, 64, 1, 5, 122, 22, 107, 140, 80, + /* 1430 */ 80, 26, 87, 72, 107, 24, 20, 19, 112, 105, + /* 1440 */ 23, 68, 22, 79, 79, 22, 22, 22, 58, 22, + /* 1450 */ 245, 248, 248, 79, 23, 23, 23, 116, 22, 122, + /* 1460 */ 23, 22, 56, 23, 26, 23, 64, 22, 124, 26, + /* 1470 */ 26, 64, 64, 23, 23, 23, 11, 23, 22, 26, + /* 1480 */ 23, 22, 24, 1, 23, 22, 26, 250, 24, 23, + /* 1490 */ 22, 122, 23, 23, 22, 15, 23, 250, 122, 122, + /* 1500 */ 122, }; -#define YY_SHIFT_USE_DFLT (-72) -#define YY_SHIFT_COUNT (435) -#define YY_SHIFT_MIN (-71) -#define YY_SHIFT_MAX (1485) +#define YY_SHIFT_USE_DFLT (-95) +#define YY_SHIFT_COUNT (439) +#define YY_SHIFT_MIN (-94) +#define YY_SHIFT_MAX (1482) static const short yy_shift_ofst[] = { - /* 0 */ 5, 1057, 1355, 1070, 1204, 1204, 1204, 90, 60, -19, - /* 10 */ 58, 58, 186, 1204, 1204, 1204, 1204, 1204, 1204, 1204, - /* 20 */ 67, 67, 182, 336, 65, 250, 135, 263, 340, 417, - /* 30 */ 494, 571, 622, 699, 776, 827, 827, 827, 827, 827, - /* 40 */ 827, 827, 827, 827, 827, 827, 827, 827, 827, 827, - /* 50 */ 878, 827, 929, 980, 980, 1156, 1204, 1204, 1204, 1204, - /* 60 */ 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, - /* 70 */ 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, - /* 80 */ 1204, 1204, 1204, 1204, 1258, 1204, 1204, 1204, 1204, 1204, - /* 90 */ 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, -71, -47, - /* 100 */ -47, -47, -47, -47, -6, 88, -66, 65, 65, 451, - /* 110 */ 502, 112, 112, 33, 127, 278, -30, -72, -72, -72, - /* 120 */ 11, 412, 412, 268, 608, 610, 65, 65, 65, 65, - /* 130 */ 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, - /* 140 */ 65, 65, 65, 65, 65, 559, 138, 278, 127, 24, - /* 150 */ 24, 24, 24, 24, 24, -72, -72, -72, 228, 341, - /* 160 */ 341, 207, 276, 300, 352, 354, 350, 65, 65, 65, - /* 170 */ 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, - /* 180 */ 65, 65, 65, 65, 495, 495, 495, 65, 65, 499, - /* 190 */ 65, 65, 65, 574, 65, 65, 517, 65, 65, 65, - /* 200 */ 65, 65, 65, 65, 65, 65, 65, 566, 691, 288, - /* 210 */ 288, 288, 701, 620, 1058, 675, 603, 964, 964, 967, - /* 220 */ 603, 967, 722, 965, 936, 999, 964, 264, 999, 999, - /* 230 */ 911, 921, 434, 1196, 1119, 1119, 1202, 1202, 1119, 1222, - /* 240 */ 1184, 1126, 1244, 1244, 1244, 1244, 1119, 1251, 1126, 1222, - /* 250 */ 1184, 1184, 1126, 1119, 1251, 1145, 1237, 1119, 1119, 1251, - /* 260 */ 1275, 1119, 1251, 1119, 1251, 1275, 1203, 1203, 1203, 1264, - /* 270 */ 1275, 1203, 1211, 1203, 1264, 1203, 1203, 1194, 1216, 1194, - /* 280 */ 1216, 1194, 1216, 1194, 1216, 1119, 1119, 1206, 1275, 1250, - /* 290 */ 1250, 1275, 1221, 1225, 1224, 1226, 1126, 1336, 1347, 1363, - /* 300 */ 1363, 1371, 1371, 1371, 1371, -72, -72, -72, -72, -72, - /* 310 */ -72, 477, 623, 742, 819, 624, 694, 74, 1023, 221, - /* 320 */ 1055, 1065, 1077, 1087, 1080, 889, 1031, 939, 1093, 1122, - /* 330 */ 1085, 1139, 961, 1024, 1172, 1103, 821, 1384, 1392, 1374, - /* 340 */ 1255, 1385, 1331, 1386, 1378, 1381, 1285, 1277, 1298, 1287, - /* 350 */ 1393, 1289, 1396, 1414, 1293, 1286, 1340, 1341, 1312, 1397, - /* 360 */ 1388, 1304, 1426, 1423, 1407, 1323, 1291, 1379, 1406, 1380, - /* 370 */ 1375, 1391, 1330, 1415, 1418, 1421, 1329, 1337, 1422, 1390, - /* 380 */ 1424, 1425, 1427, 1429, 1395, 1419, 1430, 1400, 1405, 1431, - /* 390 */ 1432, 1433, 1342, 1435, 1436, 1438, 1437, 1339, 1439, 1441, - /* 400 */ 1440, 1434, 1443, 1343, 1445, 1442, 1446, 1444, 1445, 1450, - /* 410 */ 1451, 1452, 1453, 1454, 1459, 1455, 1460, 1462, 1458, 1461, - /* 420 */ 1463, 1466, 1465, 1461, 1467, 1469, 1470, 1471, 1473, 1354, - /* 430 */ 1370, 1376, 1377, 1474, 1485, 1484, + /* 0 */ 40, 564, 869, 577, 725, 725, 725, 739, -19, 16, + /* 10 */ 16, 100, 725, 725, 725, 725, 725, 725, 725, 841, + /* 20 */ 841, 538, 507, 684, 623, 61, 137, 172, 207, 242, + /* 30 */ 277, 312, 347, 382, 424, 424, 424, 424, 424, 424, + /* 40 */ 424, 424, 424, 424, 424, 424, 424, 424, 424, 459, + /* 50 */ 424, 494, 529, 529, 670, 725, 725, 725, 725, 725, + /* 60 */ 725, 725, 725, 725, 725, 725, 725, 725, 725, 725, + /* 70 */ 725, 725, 725, 725, 725, 725, 725, 725, 725, 725, + /* 80 */ 725, 725, 725, 821, 725, 725, 725, 725, 725, 725, + /* 90 */ 725, 725, 725, 725, 725, 725, 725, 952, 711, 711, + /* 100 */ 711, 711, 711, 766, 23, 32, 811, 877, 663, 602, + /* 110 */ 602, 811, 73, 113, -51, -95, -95, -95, 501, 501, + /* 120 */ 501, 595, 595, 809, 205, 276, 811, 811, 811, 811, + /* 130 */ 811, 811, 811, 811, 811, 811, 811, 811, 811, 811, + /* 140 */ 811, 811, 811, 811, 811, 811, 192, 628, 498, 498, + /* 150 */ 113, -34, -34, -34, -34, -34, -34, -95, -95, -95, + /* 160 */ 880, -94, -94, 726, 740, 99, 797, 887, 349, 811, + /* 170 */ 811, 811, 811, 811, 811, 811, 811, 811, 811, 811, + /* 180 */ 811, 811, 811, 811, 811, 811, 941, 941, 941, 811, + /* 190 */ 811, 926, 811, 811, 811, -18, 811, 811, 842, 811, + /* 200 */ 811, 811, 811, 811, 811, 811, 811, 811, 811, 224, + /* 210 */ 608, 910, 910, 910, 1078, 45, 469, 508, 934, 970, + /* 220 */ 970, 1164, 934, 1164, 1036, 1183, 359, 1017, 970, 954, + /* 230 */ 1017, 1017, 1092, 730, 497, 1225, 1171, 1171, 1228, 1228, + /* 240 */ 1171, 1281, 1280, 1178, 1291, 1291, 1291, 1291, 1171, 1293, + /* 250 */ 1178, 1281, 1280, 1280, 1178, 1171, 1293, 1176, 1244, 1171, + /* 260 */ 1171, 1293, 1302, 1171, 1293, 1171, 1293, 1302, 1229, 1229, + /* 270 */ 1229, 1263, 1302, 1229, 1238, 1229, 1263, 1229, 1229, 1222, + /* 280 */ 1245, 1222, 1245, 1222, 1245, 1222, 1245, 1171, 1171, 1218, + /* 290 */ 1302, 1304, 1304, 1302, 1230, 1242, 1243, 1246, 1178, 1347, + /* 300 */ 1348, 1363, 1363, 1375, 1375, 1375, 1375, -95, -95, -95, + /* 310 */ -95, -95, -95, -95, -95, 451, 876, 346, 1159, 1099, + /* 320 */ 441, 823, 1188, 1111, 1190, 1195, 1199, 1200, 1005, 1129, + /* 330 */ 1224, 533, 1201, 1212, 1155, 1214, 1109, 1112, 1220, 1157, + /* 340 */ 779, 1386, 1388, 1370, 1251, 1379, 1301, 1380, 1381, 1382, + /* 350 */ 1278, 1275, 1296, 1285, 1389, 1286, 1396, 1412, 1294, 1283, + /* 360 */ 1383, 1384, 1305, 1362, 1358, 1303, 1422, 1419, 1404, 1320, + /* 370 */ 1288, 1349, 1405, 1350, 1345, 1361, 1327, 1411, 1416, 1418, + /* 380 */ 1326, 1334, 1420, 1364, 1423, 1424, 1417, 1425, 1365, 1390, + /* 390 */ 1427, 1374, 1373, 1431, 1432, 1433, 1341, 1436, 1437, 1439, + /* 400 */ 1438, 1337, 1440, 1442, 1406, 1402, 1445, 1344, 1443, 1407, + /* 410 */ 1444, 1408, 1443, 1450, 1451, 1452, 1453, 1454, 1456, 1465, + /* 420 */ 1457, 1459, 1458, 1460, 1461, 1463, 1464, 1460, 1466, 1468, + /* 430 */ 1469, 1470, 1472, 1369, 1376, 1377, 1378, 1473, 1480, 1482, }; -#define YY_REDUCE_USE_DFLT (-176) -#define YY_REDUCE_COUNT (310) -#define YY_REDUCE_MIN (-175) -#define YY_REDUCE_MAX (1234) +#define YY_REDUCE_USE_DFLT (-130) +#define YY_REDUCE_COUNT (314) +#define YY_REDUCE_MIN (-129) +#define YY_REDUCE_MAX (1237) static const short yy_reduce_ofst[] = { - /* 0 */ -143, 954, 86, 21, -50, 23, 79, 134, 170, -175, - /* 10 */ 229, 260, -121, 212, 219, 291, -54, 349, 362, 156, - /* 20 */ 309, 311, 334, 85, 224, 394, 314, 314, 314, 314, - /* 30 */ 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, - /* 40 */ 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, - /* 50 */ 314, 314, 314, 314, 314, 374, 441, 443, 450, 452, - /* 60 */ 515, 554, 567, 569, 572, 578, 580, 582, 584, 587, - /* 70 */ 593, 631, 644, 646, 649, 655, 657, 659, 661, 664, - /* 80 */ 708, 720, 759, 771, 810, 822, 861, 873, 912, 930, - /* 90 */ 947, 950, 957, 959, 963, 966, 968, 998, 314, 314, - /* 100 */ 314, 314, 314, 314, 314, 314, 314, 447, -53, 166, - /* 110 */ 438, 348, 363, 314, 473, 469, 314, 314, 314, 314, - /* 120 */ -15, 59, 101, 688, 220, 220, 525, 256, 729, 735, - /* 130 */ 736, 740, 741, 744, 645, 448, 738, 458, 786, 503, - /* 140 */ 780, 656, 721, 724, 792, 545, 568, 706, 683, 681, - /* 150 */ 779, 784, 830, 831, 835, 678, 601, -104, -2, 96, - /* 160 */ 111, 218, 287, 308, 310, 312, 335, 411, 453, 461, - /* 170 */ 573, 599, 617, 658, 665, 670, 732, 734, 775, 848, - /* 180 */ 875, 892, 893, 898, 332, 420, 869, 931, 944, 886, - /* 190 */ 983, 992, 1009, 958, 1017, 1028, 988, 1033, 1034, 1035, - /* 200 */ 287, 1036, 1044, 1045, 1047, 1049, 1056, 915, 972, 997, - /* 210 */ 1000, 1002, 886, 1011, 1015, 1061, 1013, 1001, 1003, 977, - /* 220 */ 1018, 979, 1050, 1041, 1040, 1052, 1014, 1004, 1059, 1060, - /* 230 */ 1032, 1038, 1084, 995, 1089, 1090, 1008, 1016, 1092, 1037, - /* 240 */ 1068, 1062, 1069, 1072, 1073, 1074, 1105, 1112, 1071, 1048, - /* 250 */ 1081, 1088, 1078, 1116, 1118, 1046, 1066, 1128, 1136, 1140, - /* 260 */ 1120, 1147, 1146, 1148, 1150, 1130, 1135, 1137, 1138, 1132, - /* 270 */ 1141, 1142, 1143, 1149, 1144, 1153, 1154, 1104, 1107, 1108, - /* 280 */ 1114, 1115, 1117, 1123, 1125, 1173, 1176, 1121, 1165, 1127, - /* 290 */ 1131, 1167, 1157, 1151, 1158, 1166, 1168, 1212, 1214, 1227, - /* 300 */ 1228, 1231, 1232, 1233, 1234, 1152, 1155, 1159, 1198, 1199, - /* 310 */ 1219, + /* 0 */ -29, 531, 490, 625, -49, 272, 456, 510, 400, 509, + /* 10 */ 562, 114, 535, 614, 698, 384, 738, 751, 690, 419, + /* 20 */ 553, 761, 460, 636, 767, 41, 41, 41, 41, 41, + /* 30 */ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + /* 40 */ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + /* 50 */ 41, 41, 41, 41, 760, 815, 856, 863, 866, 868, + /* 60 */ 870, 873, 881, 885, 895, 897, 899, 903, 907, 909, + /* 70 */ 911, 913, 922, 925, 936, 938, 940, 944, 947, 949, + /* 80 */ 951, 953, 955, 962, 978, 980, 984, 986, 988, 991, + /* 90 */ 993, 995, 997, 1002, 1019, 1021, 1025, 41, 41, 41, + /* 100 */ 41, 41, 41, 41, 41, 41, 896, 140, 260, 98, + /* 110 */ 268, 1020, 41, 482, 41, 41, 41, 41, 270, 270, + /* 120 */ 270, 212, 217, -129, 411, 411, 550, 5, 626, 362, + /* 130 */ 733, 830, 992, 1003, 1026, 795, 683, 807, 638, 819, + /* 140 */ 753, 948, 62, 817, 824, 132, 687, 611, 864, 1033, + /* 150 */ 403, 943, 966, 983, 1032, 1038, 1040, 960, 996, 492, + /* 160 */ -50, 57, 131, 153, 221, 462, 588, 596, 675, 721, + /* 170 */ 820, 834, 857, 914, 979, 1034, 1065, 1081, 1087, 1095, + /* 180 */ 1096, 1097, 1098, 1101, 1104, 1105, 320, 500, 655, 1106, + /* 190 */ 1107, 503, 1108, 1110, 1113, 681, 1114, 1115, 999, 1116, + /* 200 */ 1117, 1118, 221, 1119, 1120, 1121, 1122, 1123, 1124, 788, + /* 210 */ 956, 1041, 1051, 1053, 503, 1067, 1079, 1128, 1080, 1066, + /* 220 */ 1068, 1045, 1083, 1047, 1103, 1102, 1125, 1126, 1073, 1062, + /* 230 */ 1127, 1131, 1089, 1093, 1135, 1052, 1136, 1137, 1059, 1060, + /* 240 */ 1141, 1084, 1130, 1132, 1133, 1134, 1138, 1139, 1151, 1154, + /* 250 */ 1140, 1094, 1143, 1145, 1142, 1156, 1158, 1082, 1086, 1162, + /* 260 */ 1163, 1165, 1150, 1169, 1177, 1170, 1179, 1161, 1166, 1168, + /* 270 */ 1172, 1167, 1173, 1174, 1175, 1180, 1181, 1182, 1184, 1144, + /* 280 */ 1146, 1148, 1147, 1149, 1152, 1153, 1160, 1186, 1194, 1185, + /* 290 */ 1189, 1187, 1191, 1193, 1192, 1196, 1198, 1197, 1202, 1215, + /* 300 */ 1217, 1226, 1227, 1231, 1232, 1233, 1234, 1203, 1204, 1205, + /* 310 */ 1221, 1223, 1209, 1211, 1237, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 982, 1300, 1300, 1300, 1214, 1214, 1214, 1305, 1300, 1109, - /* 10 */ 1138, 1138, 1274, 1305, 1305, 1305, 1305, 1305, 1305, 1212, - /* 20 */ 1305, 1305, 1305, 1300, 1305, 1113, 1144, 1305, 1305, 1305, - /* 30 */ 1305, 1305, 1305, 1305, 1305, 1273, 1275, 1152, 1151, 1254, - /* 40 */ 1125, 1149, 1142, 1146, 1215, 1208, 1209, 1207, 1211, 1216, - /* 50 */ 1305, 1145, 1177, 1192, 1176, 1305, 1305, 1305, 1305, 1305, - /* 60 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 70 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 80 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 90 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1186, 1191, - /* 100 */ 1198, 1190, 1187, 1179, 1178, 1180, 1181, 1305, 1305, 1008, - /* 110 */ 1074, 1305, 1305, 1182, 1305, 1020, 1183, 1195, 1194, 1193, - /* 120 */ 1015, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 130 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 140 */ 1305, 1305, 1305, 1305, 1305, 982, 1300, 1305, 1305, 1300, - /* 150 */ 1300, 1300, 1300, 1300, 1300, 1292, 1113, 1103, 1305, 1305, - /* 160 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1280, 1278, - /* 170 */ 1305, 1227, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 180 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 190 */ 1305, 1305, 1305, 1109, 1305, 1305, 1305, 1305, 1305, 1305, - /* 200 */ 1305, 1305, 1305, 1305, 1305, 1305, 988, 1305, 1247, 1109, - /* 210 */ 1109, 1109, 1111, 1089, 1101, 990, 1148, 1127, 1127, 1259, - /* 220 */ 1148, 1259, 1045, 1068, 1042, 1138, 1127, 1210, 1138, 1138, - /* 230 */ 1110, 1101, 1305, 1285, 1118, 1118, 1277, 1277, 1118, 1157, - /* 240 */ 1078, 1148, 1085, 1085, 1085, 1085, 1118, 1005, 1148, 1157, - /* 250 */ 1078, 1078, 1148, 1118, 1005, 1253, 1251, 1118, 1118, 1005, - /* 260 */ 1220, 1118, 1005, 1118, 1005, 1220, 1076, 1076, 1076, 1060, - /* 270 */ 1220, 1076, 1045, 1076, 1060, 1076, 1076, 1131, 1126, 1131, - /* 280 */ 1126, 1131, 1126, 1131, 1126, 1118, 1118, 1305, 1220, 1224, - /* 290 */ 1224, 1220, 1143, 1132, 1141, 1139, 1148, 1011, 1063, 998, - /* 300 */ 998, 987, 987, 987, 987, 1297, 1297, 1292, 1047, 1047, - /* 310 */ 1030, 1305, 1305, 1305, 1305, 1305, 1305, 1022, 1305, 1229, - /* 320 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 330 */ 1305, 1305, 1305, 1305, 1305, 1305, 1164, 1305, 983, 1287, - /* 340 */ 1305, 1305, 1284, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 350 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 360 */ 1305, 1257, 1305, 1305, 1305, 1305, 1305, 1305, 1250, 1249, - /* 370 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 380 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, - /* 390 */ 1305, 1305, 1092, 1305, 1305, 1305, 1096, 1305, 1305, 1305, - /* 400 */ 1305, 1305, 1305, 1305, 1140, 1305, 1133, 1305, 1213, 1305, - /* 410 */ 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1305, 1302, - /* 420 */ 1305, 1305, 1305, 1301, 1305, 1305, 1305, 1305, 1305, 1166, - /* 430 */ 1305, 1165, 1169, 1305, 996, 1305, + /* 0 */ 1250, 1240, 1240, 1240, 1174, 1174, 1174, 1240, 1071, 1100, + /* 10 */ 1100, 1224, 1301, 1301, 1301, 1301, 1301, 1301, 1173, 1301, + /* 20 */ 1301, 1301, 1301, 1240, 1075, 1106, 1301, 1301, 1301, 1301, + /* 30 */ 1301, 1301, 1301, 1301, 1223, 1225, 1114, 1113, 1206, 1087, + /* 40 */ 1111, 1104, 1108, 1175, 1169, 1170, 1168, 1172, 1176, 1301, + /* 50 */ 1107, 1138, 1153, 1137, 1301, 1301, 1301, 1301, 1301, 1301, + /* 60 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 70 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 80 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 90 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1147, 1152, 1159, + /* 100 */ 1151, 1148, 1140, 1139, 1141, 1142, 1301, 994, 1042, 1301, + /* 110 */ 1301, 1301, 1143, 1301, 1144, 1156, 1155, 1154, 1231, 1258, + /* 120 */ 1257, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 130 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 140 */ 1301, 1301, 1301, 1301, 1301, 1301, 1250, 1240, 1000, 1000, + /* 150 */ 1301, 1240, 1240, 1240, 1240, 1240, 1240, 1236, 1075, 1066, + /* 160 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 170 */ 1228, 1226, 1301, 1187, 1301, 1301, 1301, 1301, 1301, 1301, + /* 180 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 190 */ 1301, 1301, 1301, 1301, 1301, 1071, 1301, 1301, 1301, 1301, + /* 200 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1252, 1301, + /* 210 */ 1201, 1071, 1071, 1071, 1073, 1055, 1065, 979, 1110, 1089, + /* 220 */ 1089, 1290, 1110, 1290, 1017, 1272, 1014, 1100, 1089, 1171, + /* 230 */ 1100, 1100, 1072, 1065, 1301, 1293, 1080, 1080, 1292, 1292, + /* 240 */ 1080, 1119, 1045, 1110, 1051, 1051, 1051, 1051, 1080, 991, + /* 250 */ 1110, 1119, 1045, 1045, 1110, 1080, 991, 1205, 1287, 1080, + /* 260 */ 1080, 991, 1180, 1080, 991, 1080, 991, 1180, 1043, 1043, + /* 270 */ 1043, 1032, 1180, 1043, 1017, 1043, 1032, 1043, 1043, 1093, + /* 280 */ 1088, 1093, 1088, 1093, 1088, 1093, 1088, 1080, 1080, 1301, + /* 290 */ 1180, 1184, 1184, 1180, 1105, 1094, 1103, 1101, 1110, 997, + /* 300 */ 1035, 1255, 1255, 1251, 1251, 1251, 1251, 1298, 1298, 1236, + /* 310 */ 1267, 1267, 1019, 1019, 1267, 1301, 1301, 1301, 1301, 1301, + /* 320 */ 1301, 1262, 1301, 1189, 1301, 1301, 1301, 1301, 1301, 1301, + /* 330 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 340 */ 1125, 1301, 975, 1233, 1301, 1301, 1232, 1301, 1301, 1301, + /* 350 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 360 */ 1301, 1301, 1301, 1301, 1301, 1289, 1301, 1301, 1301, 1301, + /* 370 */ 1301, 1301, 1204, 1203, 1301, 1301, 1301, 1301, 1301, 1301, + /* 380 */ 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 390 */ 1301, 1301, 1301, 1301, 1301, 1301, 1057, 1301, 1301, 1301, + /* 400 */ 1276, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1102, 1301, + /* 410 */ 1095, 1301, 1280, 1301, 1301, 1301, 1301, 1301, 1301, 1301, + /* 420 */ 1301, 1301, 1301, 1242, 1301, 1301, 1301, 1241, 1301, 1301, + /* 430 */ 1301, 1301, 1301, 1127, 1301, 1126, 1130, 1301, 985, 1301, }; +/********** End of lemon-generated parsing tables *****************************/ -/* The next table maps tokens into fallback tokens. If a construct -** like the following: +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: ** ** %fallback ID X Y Z. ** @@ -126780,79 +131540,109 @@ static const YYACTIONTYPE yy_default[] = { ** and Z. Whenever one of the tokens X, Y, or Z is input to the parser ** but it does not parse, the type of the token is changed to ID and ** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. */ #ifdef YYFALLBACK static const YYCODETYPE yyFallback[] = { 0, /* $ => nothing */ 0, /* SEMI => nothing */ - 27, /* EXPLAIN => ID */ - 27, /* QUERY => ID */ - 27, /* PLAN => ID */ - 27, /* BEGIN => ID */ + 55, /* EXPLAIN => ID */ + 55, /* QUERY => ID */ + 55, /* PLAN => ID */ + 55, /* BEGIN => ID */ 0, /* TRANSACTION => nothing */ - 27, /* DEFERRED => ID */ - 27, /* IMMEDIATE => ID */ - 27, /* EXCLUSIVE => ID */ + 55, /* DEFERRED => ID */ + 55, /* IMMEDIATE => ID */ + 55, /* EXCLUSIVE => ID */ 0, /* COMMIT => nothing */ - 27, /* END => ID */ - 27, /* ROLLBACK => ID */ - 27, /* SAVEPOINT => ID */ - 27, /* RELEASE => ID */ + 55, /* END => ID */ + 55, /* ROLLBACK => ID */ + 55, /* SAVEPOINT => ID */ + 55, /* RELEASE => ID */ 0, /* TO => nothing */ 0, /* TABLE => nothing */ 0, /* CREATE => nothing */ - 27, /* IF => ID */ + 55, /* IF => ID */ 0, /* NOT => nothing */ 0, /* EXISTS => nothing */ - 27, /* TEMP => ID */ + 55, /* TEMP => ID */ 0, /* LP => nothing */ 0, /* RP => nothing */ 0, /* AS => nothing */ - 27, /* WITHOUT => ID */ + 55, /* WITHOUT => ID */ 0, /* COMMA => nothing */ + 0, /* OR => nothing */ + 0, /* AND => nothing */ + 0, /* IS => nothing */ + 55, /* MATCH => ID */ + 55, /* LIKE_KW => ID */ + 0, /* BETWEEN => nothing */ + 0, /* IN => nothing */ + 0, /* ISNULL => nothing */ + 0, /* NOTNULL => nothing */ + 0, /* NE => nothing */ + 0, /* EQ => nothing */ + 0, /* GT => nothing */ + 0, /* LE => nothing */ + 0, /* LT => nothing */ + 0, /* GE => nothing */ + 0, /* ESCAPE => nothing */ + 0, /* BITAND => nothing */ + 0, /* BITOR => nothing */ + 0, /* LSHIFT => nothing */ + 0, /* RSHIFT => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* REM => nothing */ + 0, /* CONCAT => nothing */ + 0, /* COLLATE => nothing */ + 0, /* BITNOT => nothing */ 0, /* ID => nothing */ 0, /* INDEXED => nothing */ - 27, /* ABORT => ID */ - 27, /* ACTION => ID */ - 27, /* AFTER => ID */ - 27, /* ANALYZE => ID */ - 27, /* ASC => ID */ - 27, /* ATTACH => ID */ - 27, /* BEFORE => ID */ - 27, /* BY => ID */ - 27, /* CASCADE => ID */ - 27, /* CAST => ID */ - 27, /* COLUMNKW => ID */ - 27, /* CONFLICT => ID */ - 27, /* DATABASE => ID */ - 27, /* DESC => ID */ - 27, /* DETACH => ID */ - 27, /* EACH => ID */ - 27, /* FAIL => ID */ - 27, /* FOR => ID */ - 27, /* IGNORE => ID */ - 27, /* INITIALLY => ID */ - 27, /* INSTEAD => ID */ - 27, /* LIKE_KW => ID */ - 27, /* MATCH => ID */ - 27, /* NO => ID */ - 27, /* KEY => ID */ - 27, /* OF => ID */ - 27, /* OFFSET => ID */ - 27, /* PRAGMA => ID */ - 27, /* RAISE => ID */ - 27, /* RECURSIVE => ID */ - 27, /* REPLACE => ID */ - 27, /* RESTRICT => ID */ - 27, /* ROW => ID */ - 27, /* TRIGGER => ID */ - 27, /* VACUUM => ID */ - 27, /* VIEW => ID */ - 27, /* VIRTUAL => ID */ - 27, /* WITH => ID */ - 27, /* REINDEX => ID */ - 27, /* RENAME => ID */ - 27, /* CTIME_KW => ID */ + 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 */ }; #endif /* YYFALLBACK */ @@ -126888,7 +131678,9 @@ struct yyParser { #ifdef YYTRACKMAXSTACKDEPTH int yyidxMax; /* Maximum value of yyidx */ #endif +#ifndef YYNOERRORRECOVERY int yyerrcnt; /* Shifts left before out of the error */ +#endif sqlite3ParserARG_SDECL /* A place to hold %extra_argument */ #if YYSTACKDEPTH<=0 int yystksz; /* Current side of the stack */ @@ -126941,25 +131733,25 @@ static const char *const yyTokenName[] = { "ROLLBACK", "SAVEPOINT", "RELEASE", "TO", "TABLE", "CREATE", "IF", "NOT", "EXISTS", "TEMP", "LP", "RP", - "AS", "WITHOUT", "COMMA", "ID", + "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", "LIKE_KW", "MATCH", - "NO", "KEY", "OF", "OFFSET", - "PRAGMA", "RAISE", "RECURSIVE", "REPLACE", - "RESTRICT", "ROW", "TRIGGER", "VACUUM", - "VIEW", "VIRTUAL", "WITH", "REINDEX", - "RENAME", "CTIME_KW", "ANY", "OR", - "AND", "IS", "BETWEEN", "IN", - "ISNULL", "NOTNULL", "NE", "EQ", - "GT", "LE", "LT", "GE", - "ESCAPE", "BITAND", "BITOR", "LSHIFT", - "RSHIFT", "PLUS", "MINUS", "STAR", - "SLASH", "REM", "CONCAT", "COLLATE", - "BITNOT", "STRING", "JOIN_KW", "CONSTRAINT", + "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", @@ -126976,29 +131768,28 @@ static const char *const yyTokenName[] = { "nm", "savepoint_opt", "create_table", "create_table_args", "createkw", "temp", "ifnotexists", "dbnm", "columnlist", "conslist_opt", "table_options", "select", - "column", "columnid", "type", "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", "joinop2", - "idlist", "setlist", "insert_cmd", "idlist_opt", - "likeop", "between_op", "in_op", "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", + "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", + "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", }; #endif /* NDEBUG */ @@ -127006,334 +131797,332 @@ static const char *const yyTokenName[] = { /* For tracing reduce actions, the names of all rules are required. */ static const char *const yyRuleName[] = { - /* 0 */ "input ::= cmdlist", - /* 1 */ "cmdlist ::= cmdlist ecmd", - /* 2 */ "cmdlist ::= ecmd", - /* 3 */ "ecmd ::= SEMI", - /* 4 */ "ecmd ::= explain cmdx SEMI", - /* 5 */ "explain ::=", - /* 6 */ "explain ::= EXPLAIN", - /* 7 */ "explain ::= EXPLAIN QUERY PLAN", - /* 8 */ "cmdx ::= cmd", - /* 9 */ "cmd ::= BEGIN transtype trans_opt", - /* 10 */ "trans_opt ::=", - /* 11 */ "trans_opt ::= TRANSACTION", - /* 12 */ "trans_opt ::= TRANSACTION nm", - /* 13 */ "transtype ::=", - /* 14 */ "transtype ::= DEFERRED", - /* 15 */ "transtype ::= IMMEDIATE", - /* 16 */ "transtype ::= EXCLUSIVE", - /* 17 */ "cmd ::= COMMIT trans_opt", - /* 18 */ "cmd ::= END trans_opt", - /* 19 */ "cmd ::= ROLLBACK trans_opt", - /* 20 */ "savepoint_opt ::= SAVEPOINT", - /* 21 */ "savepoint_opt ::=", - /* 22 */ "cmd ::= SAVEPOINT nm", - /* 23 */ "cmd ::= RELEASE savepoint_opt nm", - /* 24 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", - /* 25 */ "cmd ::= create_table create_table_args", - /* 26 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm", - /* 27 */ "createkw ::= CREATE", - /* 28 */ "ifnotexists ::=", - /* 29 */ "ifnotexists ::= IF NOT EXISTS", - /* 30 */ "temp ::= TEMP", - /* 31 */ "temp ::=", - /* 32 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", - /* 33 */ "create_table_args ::= AS select", - /* 34 */ "table_options ::=", - /* 35 */ "table_options ::= WITHOUT nm", - /* 36 */ "columnlist ::= columnlist COMMA column", - /* 37 */ "columnlist ::= column", - /* 38 */ "column ::= columnid type carglist", - /* 39 */ "columnid ::= nm", - /* 40 */ "nm ::= ID|INDEXED", - /* 41 */ "nm ::= STRING", - /* 42 */ "nm ::= JOIN_KW", - /* 43 */ "type ::=", - /* 44 */ "type ::= typetoken", - /* 45 */ "typetoken ::= typename", - /* 46 */ "typetoken ::= typename LP signed RP", - /* 47 */ "typetoken ::= typename LP signed COMMA signed RP", - /* 48 */ "typename ::= ID|STRING", - /* 49 */ "typename ::= typename ID|STRING", - /* 50 */ "signed ::= plus_num", - /* 51 */ "signed ::= minus_num", - /* 52 */ "carglist ::= carglist ccons", - /* 53 */ "carglist ::=", - /* 54 */ "ccons ::= CONSTRAINT nm", - /* 55 */ "ccons ::= DEFAULT term", - /* 56 */ "ccons ::= DEFAULT LP expr RP", - /* 57 */ "ccons ::= DEFAULT PLUS term", - /* 58 */ "ccons ::= DEFAULT MINUS term", - /* 59 */ "ccons ::= DEFAULT ID|INDEXED", - /* 60 */ "ccons ::= NULL onconf", - /* 61 */ "ccons ::= NOT NULL onconf", - /* 62 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", - /* 63 */ "ccons ::= UNIQUE onconf", - /* 64 */ "ccons ::= CHECK LP expr RP", - /* 65 */ "ccons ::= REFERENCES nm eidlist_opt refargs", - /* 66 */ "ccons ::= defer_subclause", - /* 67 */ "ccons ::= COLLATE ID|STRING", - /* 68 */ "autoinc ::=", - /* 69 */ "autoinc ::= AUTOINCR", - /* 70 */ "refargs ::=", - /* 71 */ "refargs ::= refargs refarg", - /* 72 */ "refarg ::= MATCH nm", - /* 73 */ "refarg ::= ON INSERT refact", - /* 74 */ "refarg ::= ON DELETE refact", - /* 75 */ "refarg ::= ON UPDATE refact", - /* 76 */ "refact ::= SET NULL", - /* 77 */ "refact ::= SET DEFAULT", - /* 78 */ "refact ::= CASCADE", - /* 79 */ "refact ::= RESTRICT", - /* 80 */ "refact ::= NO ACTION", - /* 81 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", - /* 82 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", - /* 83 */ "init_deferred_pred_opt ::=", - /* 84 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", - /* 85 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", - /* 86 */ "conslist_opt ::=", - /* 87 */ "conslist_opt ::= COMMA conslist", - /* 88 */ "conslist ::= conslist tconscomma tcons", - /* 89 */ "conslist ::= tcons", - /* 90 */ "tconscomma ::= COMMA", - /* 91 */ "tconscomma ::=", - /* 92 */ "tcons ::= CONSTRAINT nm", - /* 93 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", - /* 94 */ "tcons ::= UNIQUE LP sortlist RP onconf", - /* 95 */ "tcons ::= CHECK LP expr RP onconf", - /* 96 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", - /* 97 */ "defer_subclause_opt ::=", - /* 98 */ "defer_subclause_opt ::= defer_subclause", - /* 99 */ "onconf ::=", - /* 100 */ "onconf ::= ON CONFLICT resolvetype", - /* 101 */ "orconf ::=", - /* 102 */ "orconf ::= OR resolvetype", - /* 103 */ "resolvetype ::= raisetype", - /* 104 */ "resolvetype ::= IGNORE", - /* 105 */ "resolvetype ::= REPLACE", - /* 106 */ "cmd ::= DROP TABLE ifexists fullname", - /* 107 */ "ifexists ::= IF EXISTS", - /* 108 */ "ifexists ::=", - /* 109 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", - /* 110 */ "cmd ::= DROP VIEW ifexists fullname", - /* 111 */ "cmd ::= select", - /* 112 */ "select ::= with selectnowith", - /* 113 */ "selectnowith ::= oneselect", - /* 114 */ "selectnowith ::= selectnowith multiselect_op oneselect", - /* 115 */ "multiselect_op ::= UNION", - /* 116 */ "multiselect_op ::= UNION ALL", - /* 117 */ "multiselect_op ::= EXCEPT|INTERSECT", - /* 118 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", - /* 119 */ "oneselect ::= values", - /* 120 */ "values ::= VALUES LP nexprlist RP", - /* 121 */ "values ::= values COMMA LP exprlist RP", - /* 122 */ "distinct ::= DISTINCT", - /* 123 */ "distinct ::= ALL", - /* 124 */ "distinct ::=", - /* 125 */ "sclp ::= selcollist COMMA", - /* 126 */ "sclp ::=", - /* 127 */ "selcollist ::= sclp expr as", - /* 128 */ "selcollist ::= sclp STAR", - /* 129 */ "selcollist ::= sclp nm DOT STAR", - /* 130 */ "as ::= AS nm", - /* 131 */ "as ::= ID|STRING", - /* 132 */ "as ::=", - /* 133 */ "from ::=", - /* 134 */ "from ::= FROM seltablist", - /* 135 */ "stl_prefix ::= seltablist joinop", - /* 136 */ "stl_prefix ::=", - /* 137 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", - /* 138 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt", - /* 139 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", - /* 140 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", - /* 141 */ "dbnm ::=", - /* 142 */ "dbnm ::= DOT nm", - /* 143 */ "fullname ::= nm dbnm", - /* 144 */ "joinop ::= COMMA|JOIN", - /* 145 */ "joinop ::= JOIN_KW JOIN", - /* 146 */ "joinop ::= JOIN_KW nm JOIN", - /* 147 */ "joinop ::= JOIN_KW nm nm JOIN", - /* 148 */ "on_opt ::= ON expr", - /* 149 */ "on_opt ::=", - /* 150 */ "indexed_opt ::=", - /* 151 */ "indexed_opt ::= INDEXED BY nm", - /* 152 */ "indexed_opt ::= NOT INDEXED", - /* 153 */ "using_opt ::= USING LP idlist RP", - /* 154 */ "using_opt ::=", - /* 155 */ "orderby_opt ::=", - /* 156 */ "orderby_opt ::= ORDER BY sortlist", - /* 157 */ "sortlist ::= sortlist COMMA expr sortorder", - /* 158 */ "sortlist ::= expr sortorder", - /* 159 */ "sortorder ::= ASC", - /* 160 */ "sortorder ::= DESC", - /* 161 */ "sortorder ::=", - /* 162 */ "groupby_opt ::=", - /* 163 */ "groupby_opt ::= GROUP BY nexprlist", - /* 164 */ "having_opt ::=", - /* 165 */ "having_opt ::= HAVING expr", - /* 166 */ "limit_opt ::=", - /* 167 */ "limit_opt ::= LIMIT expr", - /* 168 */ "limit_opt ::= LIMIT expr OFFSET expr", - /* 169 */ "limit_opt ::= LIMIT expr COMMA expr", - /* 170 */ "cmd ::= with DELETE FROM fullname indexed_opt where_opt", - /* 171 */ "where_opt ::=", - /* 172 */ "where_opt ::= WHERE expr", - /* 173 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", - /* 174 */ "setlist ::= setlist COMMA nm EQ expr", - /* 175 */ "setlist ::= nm EQ expr", - /* 176 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", - /* 177 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", - /* 178 */ "insert_cmd ::= INSERT orconf", - /* 179 */ "insert_cmd ::= REPLACE", - /* 180 */ "idlist_opt ::=", - /* 181 */ "idlist_opt ::= LP idlist RP", - /* 182 */ "idlist ::= idlist COMMA nm", - /* 183 */ "idlist ::= nm", - /* 184 */ "expr ::= term", - /* 185 */ "expr ::= LP expr RP", - /* 186 */ "term ::= NULL", - /* 187 */ "expr ::= ID|INDEXED", - /* 188 */ "expr ::= JOIN_KW", - /* 189 */ "expr ::= nm DOT nm", - /* 190 */ "expr ::= nm DOT nm DOT nm", - /* 191 */ "term ::= INTEGER|FLOAT|BLOB", - /* 192 */ "term ::= STRING", - /* 193 */ "expr ::= VARIABLE", - /* 194 */ "expr ::= expr COLLATE ID|STRING", - /* 195 */ "expr ::= CAST LP expr AS typetoken RP", - /* 196 */ "expr ::= ID|INDEXED LP distinct exprlist RP", - /* 197 */ "expr ::= ID|INDEXED LP STAR RP", - /* 198 */ "term ::= CTIME_KW", - /* 199 */ "expr ::= expr AND expr", - /* 200 */ "expr ::= expr OR expr", - /* 201 */ "expr ::= expr LT|GT|GE|LE expr", - /* 202 */ "expr ::= expr EQ|NE expr", - /* 203 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 204 */ "expr ::= expr PLUS|MINUS expr", - /* 205 */ "expr ::= expr STAR|SLASH|REM expr", - /* 206 */ "expr ::= expr CONCAT expr", - /* 207 */ "likeop ::= LIKE_KW|MATCH", - /* 208 */ "likeop ::= NOT LIKE_KW|MATCH", - /* 209 */ "expr ::= expr likeop expr", - /* 210 */ "expr ::= expr likeop expr ESCAPE expr", - /* 211 */ "expr ::= expr ISNULL|NOTNULL", - /* 212 */ "expr ::= expr NOT NULL", - /* 213 */ "expr ::= expr IS expr", - /* 214 */ "expr ::= expr IS NOT expr", - /* 215 */ "expr ::= NOT expr", - /* 216 */ "expr ::= BITNOT expr", - /* 217 */ "expr ::= MINUS expr", - /* 218 */ "expr ::= PLUS expr", - /* 219 */ "between_op ::= BETWEEN", - /* 220 */ "between_op ::= NOT BETWEEN", - /* 221 */ "expr ::= expr between_op expr AND expr", - /* 222 */ "in_op ::= IN", - /* 223 */ "in_op ::= NOT IN", - /* 224 */ "expr ::= expr in_op LP exprlist RP", - /* 225 */ "expr ::= LP select RP", - /* 226 */ "expr ::= expr in_op LP select RP", - /* 227 */ "expr ::= expr in_op nm dbnm", - /* 228 */ "expr ::= EXISTS LP select RP", - /* 229 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 230 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 231 */ "case_exprlist ::= WHEN expr THEN expr", - /* 232 */ "case_else ::= ELSE expr", - /* 233 */ "case_else ::=", - /* 234 */ "case_operand ::= expr", - /* 235 */ "case_operand ::=", - /* 236 */ "exprlist ::= nexprlist", - /* 237 */ "exprlist ::=", - /* 238 */ "nexprlist ::= nexprlist COMMA expr", - /* 239 */ "nexprlist ::= expr", - /* 240 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", - /* 241 */ "uniqueflag ::= UNIQUE", - /* 242 */ "uniqueflag ::=", - /* 243 */ "eidlist_opt ::=", - /* 244 */ "eidlist_opt ::= LP eidlist RP", - /* 245 */ "eidlist ::= eidlist COMMA nm collate sortorder", - /* 246 */ "eidlist ::= nm collate sortorder", - /* 247 */ "collate ::=", - /* 248 */ "collate ::= COLLATE ID|STRING", - /* 249 */ "cmd ::= DROP INDEX ifexists fullname", - /* 250 */ "cmd ::= VACUUM", - /* 251 */ "cmd ::= VACUUM nm", - /* 252 */ "cmd ::= PRAGMA nm dbnm", - /* 253 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 254 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 255 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 256 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 257 */ "nmnum ::= plus_num", - /* 258 */ "nmnum ::= nm", - /* 259 */ "nmnum ::= ON", - /* 260 */ "nmnum ::= DELETE", - /* 261 */ "nmnum ::= DEFAULT", - /* 262 */ "plus_num ::= PLUS INTEGER|FLOAT", - /* 263 */ "plus_num ::= INTEGER|FLOAT", - /* 264 */ "minus_num ::= MINUS INTEGER|FLOAT", - /* 265 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 266 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 267 */ "trigger_time ::= BEFORE", - /* 268 */ "trigger_time ::= AFTER", - /* 269 */ "trigger_time ::= INSTEAD OF", - /* 270 */ "trigger_time ::=", - /* 271 */ "trigger_event ::= DELETE|INSERT", - /* 272 */ "trigger_event ::= UPDATE", - /* 273 */ "trigger_event ::= UPDATE OF idlist", - /* 274 */ "foreach_clause ::=", - /* 275 */ "foreach_clause ::= FOR EACH ROW", - /* 276 */ "when_clause ::=", - /* 277 */ "when_clause ::= WHEN expr", - /* 278 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 279 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 280 */ "trnm ::= nm", - /* 281 */ "trnm ::= nm DOT nm", - /* 282 */ "tridxby ::=", - /* 283 */ "tridxby ::= INDEXED BY nm", - /* 284 */ "tridxby ::= NOT INDEXED", - /* 285 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", - /* 286 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", - /* 287 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", - /* 288 */ "trigger_cmd ::= select", - /* 289 */ "expr ::= RAISE LP IGNORE RP", - /* 290 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 291 */ "raisetype ::= ROLLBACK", - /* 292 */ "raisetype ::= ABORT", - /* 293 */ "raisetype ::= FAIL", - /* 294 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 295 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 296 */ "cmd ::= DETACH database_kw_opt expr", - /* 297 */ "key_opt ::=", - /* 298 */ "key_opt ::= KEY expr", - /* 299 */ "database_kw_opt ::= DATABASE", - /* 300 */ "database_kw_opt ::=", - /* 301 */ "cmd ::= REINDEX", - /* 302 */ "cmd ::= REINDEX nm dbnm", - /* 303 */ "cmd ::= ANALYZE", - /* 304 */ "cmd ::= ANALYZE nm dbnm", - /* 305 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 306 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column", - /* 307 */ "add_column_fullname ::= fullname", - /* 308 */ "kwcolumn_opt ::=", - /* 309 */ "kwcolumn_opt ::= COLUMNKW", - /* 310 */ "cmd ::= create_vtab", - /* 311 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 312 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 313 */ "vtabarglist ::= vtabarg", - /* 314 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 315 */ "vtabarg ::=", - /* 316 */ "vtabarg ::= vtabarg vtabargtoken", - /* 317 */ "vtabargtoken ::= ANY", - /* 318 */ "vtabargtoken ::= lp anylist RP", - /* 319 */ "lp ::= LP", - /* 320 */ "anylist ::=", - /* 321 */ "anylist ::= anylist LP anylist RP", - /* 322 */ "anylist ::= anylist ANY", - /* 323 */ "with ::=", - /* 324 */ "with ::= WITH wqlist", - /* 325 */ "with ::= WITH RECURSIVE wqlist", - /* 326 */ "wqlist ::= nm eidlist_opt AS LP select RP", - /* 327 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", + /* 0 */ "explain ::= EXPLAIN", + /* 1 */ "explain ::= EXPLAIN QUERY PLAN", + /* 2 */ "cmdx ::= cmd", + /* 3 */ "cmd ::= BEGIN transtype trans_opt", + /* 4 */ "transtype ::=", + /* 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 ::= nm EQ expr", + /* 140 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", + /* 141 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", + /* 142 */ "insert_cmd ::= INSERT orconf", + /* 143 */ "insert_cmd ::= REPLACE", + /* 144 */ "idlist_opt ::=", + /* 145 */ "idlist_opt ::= LP idlist RP", + /* 146 */ "idlist ::= idlist COMMA nm", + /* 147 */ "idlist ::= nm", + /* 148 */ "expr ::= LP expr RP", + /* 149 */ "term ::= NULL", + /* 150 */ "expr ::= ID|INDEXED", + /* 151 */ "expr ::= JOIN_KW", + /* 152 */ "expr ::= nm DOT nm", + /* 153 */ "expr ::= nm DOT nm DOT nm", + /* 154 */ "term ::= INTEGER|FLOAT|BLOB", + /* 155 */ "term ::= STRING", + /* 156 */ "expr ::= VARIABLE", + /* 157 */ "expr ::= expr COLLATE ID|STRING", + /* 158 */ "expr ::= CAST LP expr AS typetoken RP", + /* 159 */ "expr ::= ID|INDEXED LP distinct exprlist RP", + /* 160 */ "expr ::= ID|INDEXED LP STAR RP", + /* 161 */ "term ::= CTIME_KW", + /* 162 */ "expr ::= expr AND expr", + /* 163 */ "expr ::= expr OR expr", + /* 164 */ "expr ::= expr LT|GT|GE|LE expr", + /* 165 */ "expr ::= expr EQ|NE expr", + /* 166 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 167 */ "expr ::= expr PLUS|MINUS expr", + /* 168 */ "expr ::= expr STAR|SLASH|REM expr", + /* 169 */ "expr ::= expr CONCAT expr", + /* 170 */ "likeop ::= LIKE_KW|MATCH", + /* 171 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 172 */ "expr ::= expr likeop expr", + /* 173 */ "expr ::= expr likeop expr ESCAPE expr", + /* 174 */ "expr ::= expr ISNULL|NOTNULL", + /* 175 */ "expr ::= expr NOT NULL", + /* 176 */ "expr ::= expr IS expr", + /* 177 */ "expr ::= expr IS NOT expr", + /* 178 */ "expr ::= NOT expr", + /* 179 */ "expr ::= BITNOT expr", + /* 180 */ "expr ::= MINUS expr", + /* 181 */ "expr ::= PLUS expr", + /* 182 */ "between_op ::= BETWEEN", + /* 183 */ "between_op ::= NOT BETWEEN", + /* 184 */ "expr ::= expr between_op expr AND expr", + /* 185 */ "in_op ::= IN", + /* 186 */ "in_op ::= NOT IN", + /* 187 */ "expr ::= expr in_op LP exprlist RP", + /* 188 */ "expr ::= LP select RP", + /* 189 */ "expr ::= expr in_op LP select RP", + /* 190 */ "expr ::= expr in_op nm dbnm", + /* 191 */ "expr ::= EXISTS LP select RP", + /* 192 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 193 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 194 */ "case_exprlist ::= WHEN expr THEN expr", + /* 195 */ "case_else ::= ELSE expr", + /* 196 */ "case_else ::=", + /* 197 */ "case_operand ::= expr", + /* 198 */ "case_operand ::=", + /* 199 */ "exprlist ::=", + /* 200 */ "nexprlist ::= nexprlist COMMA expr", + /* 201 */ "nexprlist ::= expr", + /* 202 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 203 */ "uniqueflag ::= UNIQUE", + /* 204 */ "uniqueflag ::=", + /* 205 */ "eidlist_opt ::=", + /* 206 */ "eidlist_opt ::= LP eidlist RP", + /* 207 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 208 */ "eidlist ::= nm collate sortorder", + /* 209 */ "collate ::=", + /* 210 */ "collate ::= COLLATE ID|STRING", + /* 211 */ "cmd ::= DROP INDEX ifexists fullname", + /* 212 */ "cmd ::= VACUUM", + /* 213 */ "cmd ::= VACUUM nm", + /* 214 */ "cmd ::= PRAGMA nm dbnm", + /* 215 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 216 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 217 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 218 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 219 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 220 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 221 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 222 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 223 */ "trigger_time ::= BEFORE", + /* 224 */ "trigger_time ::= AFTER", + /* 225 */ "trigger_time ::= INSTEAD OF", + /* 226 */ "trigger_time ::=", + /* 227 */ "trigger_event ::= DELETE|INSERT", + /* 228 */ "trigger_event ::= UPDATE", + /* 229 */ "trigger_event ::= UPDATE OF idlist", + /* 230 */ "when_clause ::=", + /* 231 */ "when_clause ::= WHEN expr", + /* 232 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 233 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 234 */ "trnm ::= nm DOT nm", + /* 235 */ "tridxby ::= INDEXED BY nm", + /* 236 */ "tridxby ::= NOT INDEXED", + /* 237 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", + /* 238 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", + /* 239 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", + /* 240 */ "trigger_cmd ::= select", + /* 241 */ "expr ::= RAISE LP IGNORE RP", + /* 242 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 243 */ "raisetype ::= ROLLBACK", + /* 244 */ "raisetype ::= ABORT", + /* 245 */ "raisetype ::= FAIL", + /* 246 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 247 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 248 */ "cmd ::= DETACH database_kw_opt expr", + /* 249 */ "key_opt ::=", + /* 250 */ "key_opt ::= KEY expr", + /* 251 */ "cmd ::= REINDEX", + /* 252 */ "cmd ::= REINDEX nm dbnm", + /* 253 */ "cmd ::= ANALYZE", + /* 254 */ "cmd ::= ANALYZE nm dbnm", + /* 255 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 256 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", + /* 257 */ "add_column_fullname ::= fullname", + /* 258 */ "cmd ::= create_vtab", + /* 259 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 260 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 261 */ "vtabarg ::=", + /* 262 */ "vtabargtoken ::= ANY", + /* 263 */ "vtabargtoken ::= lp anylist RP", + /* 264 */ "lp ::= LP", + /* 265 */ "with ::=", + /* 266 */ "with ::= WITH wqlist", + /* 267 */ "with ::= WITH RECURSIVE wqlist", + /* 268 */ "wqlist ::= nm eidlist_opt AS LP select RP", + /* 269 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", + /* 270 */ "input ::= cmdlist", + /* 271 */ "cmdlist ::= cmdlist ecmd", + /* 272 */ "cmdlist ::= ecmd", + /* 273 */ "ecmd ::= SEMI", + /* 274 */ "ecmd ::= explain cmdx SEMI", + /* 275 */ "explain ::=", + /* 276 */ "trans_opt ::=", + /* 277 */ "trans_opt ::= TRANSACTION", + /* 278 */ "trans_opt ::= TRANSACTION nm", + /* 279 */ "savepoint_opt ::= SAVEPOINT", + /* 280 */ "savepoint_opt ::=", + /* 281 */ "cmd ::= create_table create_table_args", + /* 282 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 283 */ "columnlist ::= columnname carglist", + /* 284 */ "nm ::= ID|INDEXED", + /* 285 */ "nm ::= STRING", + /* 286 */ "nm ::= JOIN_KW", + /* 287 */ "typetoken ::= typename", + /* 288 */ "typename ::= ID|STRING", + /* 289 */ "signed ::= plus_num", + /* 290 */ "signed ::= minus_num", + /* 291 */ "carglist ::= carglist ccons", + /* 292 */ "carglist ::=", + /* 293 */ "ccons ::= NULL onconf", + /* 294 */ "conslist_opt ::= COMMA conslist", + /* 295 */ "conslist ::= conslist tconscomma tcons", + /* 296 */ "conslist ::= tcons", + /* 297 */ "tconscomma ::=", + /* 298 */ "defer_subclause_opt ::= defer_subclause", + /* 299 */ "resolvetype ::= raisetype", + /* 300 */ "selectnowith ::= oneselect", + /* 301 */ "oneselect ::= values", + /* 302 */ "sclp ::= selcollist COMMA", + /* 303 */ "as ::= ID|STRING", + /* 304 */ "expr ::= term", + /* 305 */ "exprlist ::= nexprlist", + /* 306 */ "nmnum ::= plus_num", + /* 307 */ "nmnum ::= nm", + /* 308 */ "nmnum ::= ON", + /* 309 */ "nmnum ::= DELETE", + /* 310 */ "nmnum ::= DEFAULT", + /* 311 */ "plus_num ::= INTEGER|FLOAT", + /* 312 */ "foreach_clause ::=", + /* 313 */ "foreach_clause ::= FOR EACH ROW", + /* 314 */ "trnm ::= nm", + /* 315 */ "tridxby ::=", + /* 316 */ "database_kw_opt ::= DATABASE", + /* 317 */ "database_kw_opt ::=", + /* 318 */ "kwcolumn_opt ::=", + /* 319 */ "kwcolumn_opt ::= COLUMNKW", + /* 320 */ "vtabarglist ::= vtabarg", + /* 321 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 322 */ "vtabarg ::= vtabarg vtabargtoken", + /* 323 */ "anylist ::=", + /* 324 */ "anylist ::= anylist LP anylist RP", + /* 325 */ "anylist ::= anylist ANY", }; #endif /* NDEBUG */ @@ -127361,6 +132150,15 @@ static void 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 +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + /* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like @@ -127373,9 +132171,9 @@ static void yyGrowStack(yyParser *p){ ** A pointer to a parser. This pointer is used in subsequent calls ** to sqlite3Parser and sqlite3ParserFree. */ -SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(u64)){ +SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE)){ yyParser *pParser; - pParser = (yyParser*)(*mallocProc)( (u64)sizeof(yyParser) ); + pParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); if( pParser ){ pParser->yyidx = -1; #ifdef YYTRACKMAXSTACKDEPTH @@ -127390,10 +132188,12 @@ SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(u64)){ return pParser; } -/* The following function deletes the value associated with a -** symbol. The symbol can be either a terminal or nonterminal. -** "yymajor" is the symbol code, and "yypminor" is a pointer to -** the value. +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. */ static void yy_destructor( yyParser *yypParser, /* The parser */ @@ -127409,81 +132209,83 @@ static void yy_destructor( ** being destroyed before it is finished parsing. ** ** Note: during a reduce, the only symbols destroyed are those - ** which appear on the RHS of the rule, but which are not used + ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ +/********* Begin destructor definitions ***************************************/ case 163: /* select */ - case 196: /* selectnowith */ - case 197: /* oneselect */ - case 208: /* values */ + case 194: /* selectnowith */ + case 195: /* oneselect */ + case 206: /* values */ { -sqlite3SelectDelete(pParse->db, (yypminor->yy3)); +sqlite3SelectDelete(pParse->db, (yypminor->yy159)); } break; - case 174: /* term */ - case 175: /* expr */ + case 172: /* term */ + case 173: /* expr */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy346).pExpr); +sqlite3ExprDelete(pParse->db, (yypminor->yy342).pExpr); } break; - case 179: /* eidlist_opt */ - case 188: /* sortlist */ - case 189: /* eidlist */ - case 201: /* selcollist */ - case 204: /* groupby_opt */ - case 206: /* orderby_opt */ - case 209: /* nexprlist */ - case 210: /* exprlist */ - case 211: /* sclp */ - case 221: /* setlist */ - case 228: /* case_exprlist */ + 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 225: /* case_exprlist */ { -sqlite3ExprListDelete(pParse->db, (yypminor->yy14)); +sqlite3ExprListDelete(pParse->db, (yypminor->yy442)); } break; - case 195: /* fullname */ - case 202: /* from */ - case 213: /* seltablist */ - case 214: /* stl_prefix */ + case 193: /* fullname */ + case 200: /* from */ + case 211: /* seltablist */ + case 212: /* stl_prefix */ { -sqlite3SrcListDelete(pParse->db, (yypminor->yy65)); +sqlite3SrcListDelete(pParse->db, (yypminor->yy347)); } break; - case 198: /* with */ - case 252: /* wqlist */ + case 196: /* with */ + case 249: /* wqlist */ { -sqlite3WithDelete(pParse->db, (yypminor->yy59)); +sqlite3WithDelete(pParse->db, (yypminor->yy331)); } break; - case 203: /* where_opt */ - case 205: /* having_opt */ - case 217: /* on_opt */ - case 227: /* case_operand */ - case 229: /* case_else */ - case 238: /* when_clause */ - case 243: /* key_opt */ + case 201: /* where_opt */ + case 203: /* having_opt */ + case 215: /* on_opt */ + case 224: /* case_operand */ + case 226: /* case_else */ + case 235: /* when_clause */ + case 240: /* key_opt */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy132)); +sqlite3ExprDelete(pParse->db, (yypminor->yy122)); } break; - case 218: /* using_opt */ - case 220: /* idlist */ - case 223: /* idlist_opt */ + case 216: /* using_opt */ + case 217: /* idlist */ + case 220: /* idlist_opt */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy408)); +sqlite3IdListDelete(pParse->db, (yypminor->yy180)); } break; - case 234: /* trigger_cmd_list */ - case 239: /* trigger_cmd */ + case 231: /* trigger_cmd_list */ + case 236: /* trigger_cmd */ { -sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy473)); +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy327)); } break; - case 236: /* trigger_event */ + case 233: /* trigger_event */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy378).b); +sqlite3IdListDelete(pParse->db, (yypminor->yy410).b); } break; +/********* End destructor definitions *****************************************/ default: break; /* If no destructor action specified: do nothing */ } } @@ -127493,49 +132295,37 @@ sqlite3IdListDelete(pParse->db, (yypminor->yy378).b); ** ** If there is a destructor routine associated with the token which ** is popped from the stack, then call it. -** -** Return the major token number for the symbol popped. */ -static int yy_pop_parser_stack(yyParser *pParser){ - YYCODETYPE yymajor; - yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; - - /* There is no mechanism by which the parser stack can be popped below - ** empty in SQLite. */ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; assert( pParser->yyidx>=0 ); + yytos = &pParser->yystack[pParser->yyidx--]; #ifndef NDEBUG - if( yyTraceFILE && pParser->yyidx>=0 ){ + if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sPopping %s\n", yyTracePrompt, yyTokenName[yytos->major]); } #endif - yymajor = yytos->major; - yy_destructor(pParser, yymajor, &yytos->minor); - pParser->yyidx--; - return yymajor; + yy_destructor(pParser, yytos->major, &yytos->minor); } /* -** Deallocate and destroy a parser. Destructors are all called for +** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. ** -** Inputs: -**
      -**
    • A pointer to the parser. This should be a pointer -** obtained from sqlite3ParserAlloc. -**
    • A pointer to a function used to reclaim memory obtained -** from malloc. -**
    +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** 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( void *p, /* The parser to be deleted */ void (*freeProc)(void*) /* Function used to reclaim memory */ ){ yyParser *pParser = (yyParser*)p; - /* In SQLite, we never try to destroy a parser that was not successfully - ** created in the first place. */ - if( NEVER(pParser==0) ) return; +#ifndef YYPARSEFREENEVERNULL + if( pParser==0 ) return; +#endif while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); #if YYSTACKDEPTH<=0 free(pParser->yystack); @@ -127556,12 +132346,8 @@ SQLITE_PRIVATE int sqlite3ParserStackPeak(void *p){ /* ** Find the appropriate action for a parser given the terminal ** look-ahead token iLookAhead. -** -** If the look-ahead token is YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return YY_NO_ACTION. */ -static int yy_find_shift_action( +static unsigned int yy_find_shift_action( yyParser *pParser, /* The parser */ YYCODETYPE iLookAhead /* The look-ahead token */ ){ @@ -127570,61 +132356,62 @@ static int yy_find_shift_action( if( stateno>=YY_MIN_REDUCE ) return stateno; assert( stateno <= YY_SHIFT_COUNT ); - i = yy_shift_ofst[stateno]; - if( i==YY_SHIFT_USE_DFLT ) return yy_default[stateno]; - assert( iLookAhead!=YYNOCODE ); - i += iLookAhead; - if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ - if( iLookAhead>0 ){ + do{ + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ) return yy_default[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ #ifdef YYFALLBACK - YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); - } -#endif - return yy_find_shift_action(pParser, iFallback); - } -#endif -#ifdef YYWILDCARD - { - int j = i - iLookAhead + YYWILDCARD; - if( -#if YY_SHIFT_MIN+YYWILDCARD<0 - j>=0 && -#endif -#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT - j %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; } -#endif /* NDEBUG */ - return yy_action[j]; } - } #endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; } - return yy_default[stateno]; - }else{ - return yy_action[i]; - } + }while(1); } /* ** Find the appropriate action for a parser given the non-terminal ** look-ahead token iLookAhead. -** -** If the look-ahead token is YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return YY_NO_ACTION. */ static int yy_find_reduce_action( int stateno, /* Current state number */ @@ -127656,7 +132443,7 @@ static int yy_find_reduce_action( /* ** The following routine is called if the stack overflows. */ -static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ +static void yyStackOverflow(yyParser *yypParser){ sqlite3ParserARG_FETCH; yypParser->yyidx--; #ifndef NDEBUG @@ -127667,9 +132454,10 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); /* Here code is inserted which will execute if the parser ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ - UNUSED_PARAMETER(yypMinor); /* Silence some compiler warnings */ sqlite3ErrorMsg(pParse, "parser stack overflow"); +/******** End %stack_overflow code ********************************************/ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument var */ } @@ -127679,15 +132467,13 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ #ifndef NDEBUG static void yyTraceShift(yyParser *yypParser, int yyNewState){ if( yyTraceFILE ){ - int i; if( yyNewStateyyidx; i++) - fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); - fprintf(yyTraceFILE,"\n"); + fprintf(yyTraceFILE,"%sShift '%s', go to state %d\n", + yyTracePrompt,yyTokenName[yypParser->yystack[yypParser->yyidx].major], + yyNewState); }else{ - fprintf(yyTraceFILE,"%sShift *\n",yyTracePrompt); + fprintf(yyTraceFILE,"%sShift '%s'\n", + yyTracePrompt,yyTokenName[yypParser->yystack[yypParser->yyidx].major]); } } } @@ -127696,13 +132482,13 @@ static void yyTraceShift(yyParser *yypParser, int yyNewState){ #endif /* -** Perform a shift action. Return the number of errors. +** Perform a shift action. */ static void yy_shift( yyParser *yypParser, /* The parser to be shifted */ int yyNewState, /* The new state to shift in */ int yyMajor, /* The major token to shift in */ - YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ + sqlite3ParserTOKENTYPE yyMinor /* The minor token to shift in */ ){ yyStackEntry *yytos; yypParser->yyidx++; @@ -127713,14 +132499,14 @@ static void yy_shift( #endif #if YYSTACKDEPTH>0 if( yypParser->yyidx>=YYSTACKDEPTH ){ - yyStackOverflow(yypParser, yypMinor); + yyStackOverflow(yypParser); return; } #else if( yypParser->yyidx>=yypParser->yystksz ){ yyGrowStack(yypParser); if( yypParser->yyidx>=yypParser->yystksz ){ - yyStackOverflow(yypParser, yypMinor); + yyStackOverflow(yypParser); return; } } @@ -127728,7 +132514,7 @@ static void yy_shift( yytos = &yypParser->yystack[yypParser->yyidx]; yytos->stateno = (YYACTIONTYPE)yyNewState; yytos->major = (YYCODETYPE)yyMajor; - yytos->minor = *yypMinor; + yytos->minor.yy0 = yyMinor; yyTraceShift(yypParser, yyNewState); } @@ -127739,19 +132525,10 @@ 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[] = { - { 144, 1 }, - { 145, 2 }, - { 145, 1 }, - { 146, 1 }, - { 146, 3 }, - { 147, 0 }, { 147, 1 }, { 147, 3 }, { 148, 1 }, { 149, 3 }, - { 151, 0 }, - { 151, 1 }, - { 151, 2 }, { 150, 0 }, { 150, 1 }, { 150, 1 }, @@ -127759,12 +132536,9 @@ static const struct { { 149, 2 }, { 149, 2 }, { 149, 2 }, - { 153, 1 }, - { 153, 0 }, { 149, 2 }, { 149, 3 }, { 149, 5 }, - { 149, 2 }, { 154, 6 }, { 156, 1 }, { 158, 0 }, @@ -127775,219 +132549,193 @@ static const struct { { 155, 2 }, { 162, 0 }, { 162, 2 }, - { 160, 3 }, - { 160, 1 }, - { 164, 3 }, - { 165, 1 }, - { 152, 1 }, - { 152, 1 }, - { 152, 1 }, + { 164, 2 }, { 166, 0 }, - { 166, 1 }, - { 168, 1 }, - { 168, 4 }, - { 168, 6 }, - { 169, 1 }, - { 169, 2 }, - { 170, 1 }, - { 170, 1 }, + { 166, 4 }, + { 166, 6 }, { 167, 2 }, - { 167, 0 }, - { 173, 2 }, - { 173, 2 }, - { 173, 4 }, - { 173, 3 }, - { 173, 3 }, - { 173, 2 }, - { 173, 2 }, - { 173, 3 }, - { 173, 5 }, - { 173, 2 }, - { 173, 4 }, - { 173, 4 }, - { 173, 1 }, - { 173, 2 }, - { 178, 0 }, - { 178, 1 }, - { 180, 0 }, - { 180, 2 }, - { 182, 2 }, - { 182, 3 }, - { 182, 3 }, - { 182, 3 }, - { 183, 2 }, - { 183, 2 }, - { 183, 1 }, - { 183, 1 }, - { 183, 2 }, - { 181, 3 }, - { 181, 2 }, - { 184, 0 }, - { 184, 2 }, - { 184, 2 }, - { 161, 0 }, - { 161, 2 }, - { 185, 3 }, - { 185, 1 }, - { 186, 1 }, - { 186, 0 }, - { 187, 2 }, - { 187, 7 }, - { 187, 5 }, - { 187, 5 }, - { 187, 10 }, - { 190, 0 }, - { 190, 1 }, + { 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, 3 }, - { 191, 0 }, - { 191, 2 }, - { 192, 1 }, - { 192, 1 }, - { 192, 1 }, + { 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 }, - { 194, 2 }, - { 194, 0 }, + { 192, 2 }, + { 192, 0 }, { 149, 9 }, { 149, 4 }, { 149, 1 }, { 163, 2 }, - { 196, 1 }, - { 196, 3 }, - { 199, 1 }, - { 199, 2 }, - { 199, 1 }, - { 197, 9 }, + { 194, 3 }, { 197, 1 }, - { 208, 4 }, - { 208, 5 }, - { 200, 1 }, - { 200, 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 }, - { 211, 2 }, - { 211, 0 }, - { 201, 3 }, - { 201, 2 }, - { 201, 4 }, + { 200, 2 }, { 212, 2 }, - { 212, 1 }, { 212, 0 }, - { 202, 0 }, - { 202, 2 }, - { 214, 2 }, - { 214, 0 }, - { 213, 7 }, - { 213, 9 }, - { 213, 7 }, - { 213, 7 }, + { 211, 7 }, + { 211, 9 }, + { 211, 7 }, + { 211, 7 }, { 159, 0 }, { 159, 2 }, - { 195, 2 }, - { 215, 1 }, + { 193, 2 }, + { 213, 1 }, + { 213, 2 }, + { 213, 3 }, + { 213, 4 }, { 215, 2 }, - { 215, 3 }, - { 215, 4 }, - { 217, 2 }, - { 217, 0 }, + { 215, 0 }, + { 214, 0 }, + { 214, 3 }, + { 214, 2 }, + { 216, 4 }, { 216, 0 }, - { 216, 3 }, - { 216, 2 }, - { 218, 4 }, - { 218, 0 }, - { 206, 0 }, - { 206, 3 }, - { 188, 4 }, - { 188, 2 }, - { 177, 1 }, - { 177, 1 }, - { 177, 0 }, { 204, 0 }, { 204, 3 }, - { 205, 0 }, - { 205, 2 }, - { 207, 0 }, - { 207, 2 }, - { 207, 4 }, - { 207, 4 }, - { 149, 6 }, + { 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 }, - { 221, 5 }, - { 221, 3 }, + { 218, 5 }, + { 218, 3 }, { 149, 6 }, { 149, 7 }, - { 222, 2 }, - { 222, 1 }, - { 223, 0 }, - { 223, 3 }, + { 219, 2 }, + { 219, 1 }, + { 220, 0 }, { 220, 3 }, - { 220, 1 }, - { 175, 1 }, - { 175, 3 }, - { 174, 1 }, - { 175, 1 }, - { 175, 1 }, - { 175, 3 }, - { 175, 5 }, - { 174, 1 }, - { 174, 1 }, - { 175, 1 }, - { 175, 3 }, - { 175, 6 }, - { 175, 5 }, - { 175, 4 }, - { 174, 1 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 175, 3 }, - { 224, 1 }, - { 224, 2 }, - { 175, 3 }, - { 175, 5 }, - { 175, 2 }, - { 175, 3 }, - { 175, 3 }, - { 175, 4 }, - { 175, 2 }, - { 175, 2 }, - { 175, 2 }, - { 175, 2 }, - { 225, 1 }, - { 225, 2 }, - { 175, 5 }, - { 226, 1 }, + { 217, 3 }, + { 217, 1 }, + { 173, 3 }, + { 172, 1 }, + { 173, 1 }, + { 173, 1 }, + { 173, 3 }, + { 173, 5 }, + { 172, 1 }, + { 172, 1 }, + { 173, 1 }, + { 173, 3 }, + { 173, 6 }, + { 173, 5 }, + { 173, 4 }, + { 172, 1 }, + { 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, 4 }, + { 173, 4 }, + { 173, 5 }, + { 225, 5 }, + { 225, 4 }, { 226, 2 }, - { 175, 5 }, - { 175, 3 }, - { 175, 5 }, - { 175, 4 }, - { 175, 4 }, - { 175, 5 }, - { 228, 5 }, - { 228, 4 }, - { 229, 2 }, - { 229, 0 }, + { 226, 0 }, + { 224, 1 }, + { 224, 0 }, + { 208, 0 }, + { 207, 3 }, + { 207, 1 }, + { 149, 12 }, { 227, 1 }, { 227, 0 }, - { 210, 1 }, - { 210, 0 }, - { 209, 3 }, - { 209, 1 }, - { 149, 12 }, - { 230, 1 }, - { 230, 0 }, - { 179, 0 }, - { 179, 3 }, - { 189, 5 }, - { 189, 3 }, - { 231, 0 }, - { 231, 2 }, + { 177, 0 }, + { 177, 3 }, + { 187, 5 }, + { 187, 3 }, + { 228, 0 }, + { 228, 2 }, { 149, 4 }, { 149, 1 }, { 149, 2 }, @@ -127996,77 +132744,113 @@ static const struct { { 149, 6 }, { 149, 5 }, { 149, 6 }, - { 232, 1 }, - { 232, 1 }, - { 232, 1 }, - { 232, 1 }, - { 232, 1 }, - { 171, 2 }, - { 171, 1 }, - { 172, 2 }, + { 169, 2 }, + { 170, 2 }, { 149, 5 }, - { 233, 11 }, - { 235, 1 }, - { 235, 1 }, - { 235, 2 }, + { 230, 11 }, + { 232, 1 }, + { 232, 1 }, + { 232, 2 }, + { 232, 0 }, + { 233, 1 }, + { 233, 1 }, + { 233, 3 }, { 235, 0 }, - { 236, 1 }, - { 236, 1 }, - { 236, 3 }, - { 237, 0 }, + { 235, 2 }, + { 231, 3 }, + { 231, 2 }, { 237, 3 }, - { 238, 0 }, + { 238, 3 }, { 238, 2 }, - { 234, 3 }, - { 234, 2 }, - { 240, 1 }, - { 240, 3 }, - { 241, 0 }, - { 241, 3 }, - { 241, 2 }, - { 239, 7 }, - { 239, 5 }, - { 239, 5 }, - { 239, 1 }, - { 175, 4 }, - { 175, 6 }, - { 193, 1 }, - { 193, 1 }, - { 193, 1 }, + { 236, 7 }, + { 236, 5 }, + { 236, 5 }, + { 236, 1 }, + { 173, 4 }, + { 173, 6 }, + { 191, 1 }, + { 191, 1 }, + { 191, 1 }, { 149, 4 }, { 149, 6 }, { 149, 3 }, - { 243, 0 }, - { 243, 2 }, - { 242, 1 }, - { 242, 0 }, + { 240, 0 }, + { 240, 2 }, { 149, 1 }, { 149, 3 }, { 149, 1 }, { 149, 3 }, { 149, 6 }, - { 149, 6 }, - { 244, 1 }, + { 149, 7 }, + { 241, 1 }, + { 149, 1 }, + { 149, 4 }, + { 243, 8 }, { 245, 0 }, - { 245, 1 }, - { 149, 1 }, - { 149, 4 }, - { 246, 8 }, + { 246, 1 }, + { 246, 3 }, { 247, 1 }, - { 247, 3 }, + { 196, 0 }, + { 196, 2 }, + { 196, 3 }, + { 249, 6 }, + { 249, 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 }, + { 229, 1 }, + { 229, 1 }, + { 229, 1 }, + { 229, 1 }, + { 229, 1 }, + { 169, 1 }, + { 234, 0 }, + { 234, 3 }, + { 237, 1 }, + { 238, 0 }, + { 239, 1 }, + { 239, 0 }, + { 242, 0 }, + { 242, 1 }, + { 244, 1 }, + { 244, 3 }, + { 245, 2 }, { 248, 0 }, + { 248, 4 }, { 248, 2 }, - { 249, 1 }, - { 249, 3 }, - { 250, 1 }, - { 251, 0 }, - { 251, 4 }, - { 251, 2 }, - { 198, 0 }, - { 198, 2 }, - { 198, 3 }, - { 252, 6 }, - { 252, 8 }, }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -128077,41 +132861,46 @@ static void yy_accept(yyParser*); /* Forward Declaration */ */ static void yy_reduce( yyParser *yypParser, /* The parser */ - int yyruleno /* Number of the rule by which to reduce */ + unsigned int yyruleno /* Number of the rule by which to reduce */ ){ int yygoto; /* The next state */ int yyact; /* The next action */ - YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ yyStackEntry *yymsp; /* The top of the parser's stack */ int yysize; /* Amount to pop the stack */ sqlite3ParserARG_FETCH; yymsp = &yypParser->yystack[yypParser->yyidx]; #ifndef NDEBUG - if( yyTraceFILE && yyruleno>=0 - && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ yysize = yyRuleInfo[yyruleno].nrhs; - fprintf(yyTraceFILE, "%sReduce [%s] -> state %d.\n", yyTracePrompt, + fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt, yyRuleName[yyruleno], yymsp[-yysize].stateno); } #endif /* NDEBUG */ - /* Silence complaints from purify about yygotominor being uninitialized - ** in some cases when it is copied into the stack after the following - ** switch. yygotominor is uninitialized when a rule reduces that does - ** not set the value of its left-hand side nonterminal. Leaving the - ** value of the nonterminal uninitialized is utterly harmless as long - ** as the value is never used. So really the only thing this code - ** accomplishes is to quieten purify. - ** - ** 2007-01-16: The wireshark project (www.wireshark.org) reports that - ** without this code, their parser segfaults. I'm not sure what there - ** parser is doing to make this happen. This is the second bug report - ** from wireshark this week. Clearly they are stressing Lemon in ways - ** that it has not been previously stressed... (SQLite ticket #2172) - */ - /*memset(&yygotominor, 0, sizeof(yygotominor));*/ - yygotominor = yyzerominor; - + /* 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 ){ +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH-1 ){ + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz-1 ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz-1 ){ + yyStackOverflow(yypParser); + return; + } + } +#endif + } switch( yyruleno ){ /* Beginning here are the reduction cases. A typical example @@ -128122,328 +132911,287 @@ static void yy_reduce( ** #line ** break; */ - case 5: /* explain ::= */ -{ sqlite3BeginParse(pParse, 0); } +/********** Begin reduce actions **********************************************/ + YYMINORTYPE yylhsminor; + case 0: /* explain ::= EXPLAIN */ +{ pParse->explain = 1; } break; - case 6: /* explain ::= EXPLAIN */ -{ sqlite3BeginParse(pParse, 1); } + case 1: /* explain ::= EXPLAIN QUERY PLAN */ +{ pParse->explain = 2; } break; - case 7: /* explain ::= EXPLAIN QUERY PLAN */ -{ sqlite3BeginParse(pParse, 2); } - break; - case 8: /* cmdx ::= cmd */ + case 2: /* cmdx ::= cmd */ { sqlite3FinishCoding(pParse); } break; - case 9: /* cmd ::= BEGIN transtype trans_opt */ -{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy328);} + case 3: /* cmd ::= BEGIN transtype trans_opt */ +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy392);} break; - case 13: /* transtype ::= */ -{yygotominor.yy328 = TK_DEFERRED;} + case 4: /* transtype ::= */ +{yymsp[1].minor.yy392 = TK_DEFERRED;} break; - case 14: /* transtype ::= DEFERRED */ - case 15: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==15); - case 16: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==16); - case 115: /* multiselect_op ::= UNION */ yytestcase(yyruleno==115); - case 117: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==117); -{yygotominor.yy328 = yymsp[0].major;} + case 5: /* transtype ::= DEFERRED */ + case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); + case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); +{yymsp[0].minor.yy392 = yymsp[0].major; /*A-overwrites-X*/} break; - case 17: /* cmd ::= COMMIT trans_opt */ - case 18: /* cmd ::= END trans_opt */ yytestcase(yyruleno==18); + case 8: /* cmd ::= COMMIT trans_opt */ + case 9: /* cmd ::= END trans_opt */ yytestcase(yyruleno==9); {sqlite3CommitTransaction(pParse);} break; - case 19: /* cmd ::= ROLLBACK trans_opt */ + case 10: /* cmd ::= ROLLBACK trans_opt */ {sqlite3RollbackTransaction(pParse);} break; - case 22: /* cmd ::= SAVEPOINT nm */ + case 11: /* cmd ::= SAVEPOINT nm */ { sqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &yymsp[0].minor.yy0); } break; - case 23: /* cmd ::= RELEASE savepoint_opt nm */ + case 12: /* cmd ::= RELEASE savepoint_opt nm */ { sqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &yymsp[0].minor.yy0); } break; - case 24: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + case 13: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ { sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0); } break; - case 26: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + case 14: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { - sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy328,0,0,yymsp[-2].minor.yy328); + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy392,0,0,yymsp[-2].minor.yy392); } break; - case 27: /* createkw ::= CREATE */ + case 15: /* 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 209: /* collate ::= */ yytestcase(yyruleno==209); +{yymsp[1].minor.yy392 = 0;} + break; + case 17: /* ifnotexists ::= IF NOT EXISTS */ +{yymsp[-2].minor.yy392 = 1;} + break; + case 18: /* temp ::= TEMP */ + case 43: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==43); +{yymsp[0].minor.yy392 = 1;} + break; + case 20: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ { - pParse->db->lookaside.bEnabled = 0; - yygotominor.yy0 = yymsp[0].minor.yy0; + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy392,0); } break; - case 28: /* ifnotexists ::= */ - case 31: /* temp ::= */ yytestcase(yyruleno==31); - case 68: /* autoinc ::= */ yytestcase(yyruleno==68); - case 81: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ yytestcase(yyruleno==81); - case 83: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==83); - case 85: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ yytestcase(yyruleno==85); - case 97: /* defer_subclause_opt ::= */ yytestcase(yyruleno==97); - case 108: /* ifexists ::= */ yytestcase(yyruleno==108); - case 219: /* between_op ::= BETWEEN */ yytestcase(yyruleno==219); - case 222: /* in_op ::= IN */ yytestcase(yyruleno==222); - case 247: /* collate ::= */ yytestcase(yyruleno==247); -{yygotominor.yy328 = 0;} - break; - case 29: /* ifnotexists ::= IF NOT EXISTS */ - case 30: /* temp ::= TEMP */ yytestcase(yyruleno==30); - case 69: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==69); - case 84: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ yytestcase(yyruleno==84); - case 107: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==107); - case 220: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==220); - case 223: /* in_op ::= NOT IN */ yytestcase(yyruleno==223); - case 248: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==248); -{yygotominor.yy328 = 1;} - break; - case 32: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ + case 21: /* create_table_args ::= AS select */ { - sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy186,0); + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy159); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy159); } break; - case 33: /* create_table_args ::= AS select */ -{ - sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy3); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy3); -} - break; - case 34: /* table_options ::= */ -{yygotominor.yy186 = 0;} - break; - case 35: /* table_options ::= WITHOUT nm */ + case 23: /* table_options ::= WITHOUT nm */ { if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ - yygotominor.yy186 = TF_WithoutRowid | TF_NoVisibleRowid; + yymsp[-1].minor.yy392 = TF_WithoutRowid | TF_NoVisibleRowid; }else{ - yygotominor.yy186 = 0; + yymsp[-1].minor.yy392 = 0; sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); } } break; - case 38: /* column ::= columnid type carglist */ + case 24: /* columnname ::= nm typetoken */ +{sqlite3AddColumn(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); +{yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;} + break; + case 26: /* typetoken ::= typename LP signed RP */ { - yygotominor.yy0.z = yymsp[-2].minor.yy0.z; - yygotominor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-2].minor.yy0.z) + pParse->sLastToken.n; + yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); } break; - case 39: /* columnid ::= nm */ + case 27: /* typetoken ::= typename LP signed COMMA signed RP */ { - sqlite3AddColumn(pParse,&yymsp[0].minor.yy0); - yygotominor.yy0 = yymsp[0].minor.yy0; - pParse->constraintName.n = 0; + yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); } break; - case 40: /* nm ::= ID|INDEXED */ - case 41: /* nm ::= STRING */ yytestcase(yyruleno==41); - case 42: /* nm ::= JOIN_KW */ yytestcase(yyruleno==42); - case 45: /* typetoken ::= typename */ yytestcase(yyruleno==45); - case 48: /* typename ::= ID|STRING */ yytestcase(yyruleno==48); - case 130: /* as ::= AS nm */ yytestcase(yyruleno==130); - case 131: /* as ::= ID|STRING */ yytestcase(yyruleno==131); - case 142: /* dbnm ::= DOT nm */ yytestcase(yyruleno==142); - case 151: /* indexed_opt ::= INDEXED BY nm */ yytestcase(yyruleno==151); - case 257: /* nmnum ::= plus_num */ yytestcase(yyruleno==257); - case 258: /* nmnum ::= nm */ yytestcase(yyruleno==258); - case 259: /* nmnum ::= ON */ yytestcase(yyruleno==259); - case 260: /* nmnum ::= DELETE */ yytestcase(yyruleno==260); - case 261: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==261); - case 262: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==262); - case 263: /* plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==263); - case 264: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==264); - case 280: /* trnm ::= nm */ yytestcase(yyruleno==280); -{yygotominor.yy0 = yymsp[0].minor.yy0;} + case 28: /* 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 44: /* type ::= typetoken */ -{sqlite3AddColumnType(pParse,&yymsp[0].minor.yy0);} - break; - case 46: /* typetoken ::= typename LP signed RP */ -{ - yygotominor.yy0.z = yymsp[-3].minor.yy0.z; - yygotominor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); -} - break; - case 47: /* typetoken ::= typename LP signed COMMA signed RP */ -{ - yygotominor.yy0.z = yymsp[-5].minor.yy0.z; - yygotominor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); -} - break; - case 49: /* typename ::= typename ID|STRING */ -{yygotominor.yy0.z=yymsp[-1].minor.yy0.z; yygotominor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} - break; - case 54: /* ccons ::= CONSTRAINT nm */ - case 92: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==92); + case 29: /* ccons ::= CONSTRAINT nm */ + case 62: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==62); {pParse->constraintName = yymsp[0].minor.yy0;} break; - case 55: /* ccons ::= DEFAULT term */ - case 57: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==57); -{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy346);} + case 30: /* ccons ::= DEFAULT term */ + case 32: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==32); +{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy342);} break; - case 56: /* ccons ::= DEFAULT LP expr RP */ -{sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy346);} + case 31: /* ccons ::= DEFAULT LP expr RP */ +{sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy342);} break; - case 58: /* ccons ::= DEFAULT MINUS term */ + case 33: /* ccons ::= DEFAULT MINUS term */ { ExprSpan v; - v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy346.pExpr, 0, 0); + v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy342.pExpr, 0, 0); v.zStart = yymsp[-1].minor.yy0.z; - v.zEnd = yymsp[0].minor.yy346.zEnd; + v.zEnd = yymsp[0].minor.yy342.zEnd; sqlite3AddDefaultValue(pParse,&v); } break; - case 59: /* ccons ::= DEFAULT ID|INDEXED */ + case 34: /* ccons ::= DEFAULT ID|INDEXED */ { ExprSpan v; - spanExpr(&v, pParse, TK_STRING, &yymsp[0].minor.yy0); + spanExpr(&v, pParse, TK_STRING, yymsp[0].minor.yy0); sqlite3AddDefaultValue(pParse,&v); } break; - case 61: /* ccons ::= NOT NULL onconf */ -{sqlite3AddNotNull(pParse, yymsp[0].minor.yy328);} + case 35: /* ccons ::= NOT NULL onconf */ +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy392);} break; - case 62: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ -{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy328,yymsp[0].minor.yy328,yymsp[-2].minor.yy328);} + case 36: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy392,yymsp[0].minor.yy392,yymsp[-2].minor.yy392);} break; - case 63: /* ccons ::= UNIQUE onconf */ -{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy328,0,0,0,0);} + case 37: /* ccons ::= UNIQUE onconf */ +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy392,0,0,0,0);} break; - case 64: /* ccons ::= CHECK LP expr RP */ -{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy346.pExpr);} + case 38: /* ccons ::= CHECK LP expr RP */ +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy342.pExpr);} break; - case 65: /* ccons ::= REFERENCES nm eidlist_opt refargs */ -{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy14,yymsp[0].minor.yy328);} + case 39: /* ccons ::= REFERENCES nm eidlist_opt refargs */ +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy442,yymsp[0].minor.yy392);} break; - case 66: /* ccons ::= defer_subclause */ -{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy328);} + case 40: /* ccons ::= defer_subclause */ +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy392);} break; - case 67: /* ccons ::= COLLATE ID|STRING */ + case 41: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; - case 70: /* refargs ::= */ -{ yygotominor.yy328 = OE_None*0x0101; /* EV: R-19803-45884 */} + case 44: /* refargs ::= */ +{ yymsp[1].minor.yy392 = OE_None*0x0101; /* EV: R-19803-45884 */} break; - case 71: /* refargs ::= refargs refarg */ -{ yygotominor.yy328 = (yymsp[-1].minor.yy328 & ~yymsp[0].minor.yy429.mask) | yymsp[0].minor.yy429.value; } + case 45: /* refargs ::= refargs refarg */ +{ yymsp[-1].minor.yy392 = (yymsp[-1].minor.yy392 & ~yymsp[0].minor.yy207.mask) | yymsp[0].minor.yy207.value; } break; - case 72: /* refarg ::= MATCH nm */ - case 73: /* refarg ::= ON INSERT refact */ yytestcase(yyruleno==73); -{ yygotominor.yy429.value = 0; yygotominor.yy429.mask = 0x000000; } + case 46: /* refarg ::= MATCH nm */ +{ yymsp[-1].minor.yy207.value = 0; yymsp[-1].minor.yy207.mask = 0x000000; } break; - case 74: /* refarg ::= ON DELETE refact */ -{ yygotominor.yy429.value = yymsp[0].minor.yy328; yygotominor.yy429.mask = 0x0000ff; } + case 47: /* refarg ::= ON INSERT refact */ +{ yymsp[-2].minor.yy207.value = 0; yymsp[-2].minor.yy207.mask = 0x000000; } break; - case 75: /* refarg ::= ON UPDATE refact */ -{ yygotominor.yy429.value = yymsp[0].minor.yy328<<8; yygotominor.yy429.mask = 0x00ff00; } + case 48: /* refarg ::= ON DELETE refact */ +{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy392; yymsp[-2].minor.yy207.mask = 0x0000ff; } break; - case 76: /* refact ::= SET NULL */ -{ yygotominor.yy328 = OE_SetNull; /* EV: R-33326-45252 */} + case 49: /* refarg ::= ON UPDATE refact */ +{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy392<<8; yymsp[-2].minor.yy207.mask = 0x00ff00; } break; - case 77: /* refact ::= SET DEFAULT */ -{ yygotominor.yy328 = OE_SetDflt; /* EV: R-33326-45252 */} + case 50: /* refact ::= SET NULL */ +{ yymsp[-1].minor.yy392 = OE_SetNull; /* EV: R-33326-45252 */} break; - case 78: /* refact ::= CASCADE */ -{ yygotominor.yy328 = OE_Cascade; /* EV: R-33326-45252 */} + case 51: /* refact ::= SET DEFAULT */ +{ yymsp[-1].minor.yy392 = OE_SetDflt; /* EV: R-33326-45252 */} break; - case 79: /* refact ::= RESTRICT */ -{ yygotominor.yy328 = OE_Restrict; /* EV: R-33326-45252 */} + case 52: /* refact ::= CASCADE */ +{ yymsp[0].minor.yy392 = OE_Cascade; /* EV: R-33326-45252 */} break; - case 80: /* refact ::= NO ACTION */ -{ yygotominor.yy328 = OE_None; /* EV: R-33326-45252 */} + case 53: /* refact ::= RESTRICT */ +{ yymsp[0].minor.yy392 = OE_Restrict; /* EV: R-33326-45252 */} break; - case 82: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - case 98: /* defer_subclause_opt ::= defer_subclause */ yytestcase(yyruleno==98); - case 100: /* onconf ::= ON CONFLICT resolvetype */ yytestcase(yyruleno==100); - case 103: /* resolvetype ::= raisetype */ yytestcase(yyruleno==103); -{yygotominor.yy328 = yymsp[0].minor.yy328;} + case 54: /* refact ::= NO ACTION */ +{ yymsp[-1].minor.yy392 = OE_None; /* EV: R-33326-45252 */} break; - case 86: /* conslist_opt ::= */ -{yygotominor.yy0.n = 0; yygotominor.yy0.z = 0;} + case 55: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{yymsp[-2].minor.yy392 = 0;} break; - case 87: /* conslist_opt ::= COMMA conslist */ -{yygotominor.yy0 = yymsp[-1].minor.yy0;} + case 56: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 71: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==71); + case 142: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==142); +{yymsp[-1].minor.yy392 = yymsp[0].minor.yy392;} break; - case 90: /* tconscomma ::= COMMA */ + case 58: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ + case 75: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==75); + case 183: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==183); + case 186: /* in_op ::= NOT IN */ yytestcase(yyruleno==186); + case 210: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==210); +{yymsp[-1].minor.yy392 = 1;} + break; + case 59: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yymsp[-1].minor.yy392 = 0;} + break; + case 61: /* tconscomma ::= COMMA */ {pParse->constraintName.n = 0;} break; - case 93: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ -{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy14,yymsp[0].minor.yy328,yymsp[-2].minor.yy328,0);} + case 63: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy442,yymsp[0].minor.yy392,yymsp[-2].minor.yy392,0);} break; - case 94: /* tcons ::= UNIQUE LP sortlist RP onconf */ -{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy14,yymsp[0].minor.yy328,0,0,0,0);} + case 64: /* tcons ::= UNIQUE LP sortlist RP onconf */ +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy442,yymsp[0].minor.yy392,0,0,0,0);} break; - case 95: /* tcons ::= CHECK LP expr RP onconf */ -{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy346.pExpr);} + case 65: /* tcons ::= CHECK LP expr RP onconf */ +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy342.pExpr);} break; - case 96: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + case 66: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ { - sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy14, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[-1].minor.yy328); - sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy328); + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy442, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy442, yymsp[-1].minor.yy392); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy392); } break; - case 99: /* onconf ::= */ -{yygotominor.yy328 = OE_Default;} + case 68: /* onconf ::= */ + case 70: /* orconf ::= */ yytestcase(yyruleno==70); +{yymsp[1].minor.yy392 = OE_Default;} break; - case 101: /* orconf ::= */ -{yygotominor.yy186 = OE_Default;} + case 69: /* onconf ::= ON CONFLICT resolvetype */ +{yymsp[-2].minor.yy392 = yymsp[0].minor.yy392;} break; - case 102: /* orconf ::= OR resolvetype */ -{yygotominor.yy186 = (u8)yymsp[0].minor.yy328;} + case 72: /* resolvetype ::= IGNORE */ +{yymsp[0].minor.yy392 = OE_Ignore;} break; - case 104: /* resolvetype ::= IGNORE */ -{yygotominor.yy328 = OE_Ignore;} + case 73: /* resolvetype ::= REPLACE */ + case 143: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==143); +{yymsp[0].minor.yy392 = OE_Replace;} break; - case 105: /* resolvetype ::= REPLACE */ -{yygotominor.yy328 = OE_Replace;} - break; - case 106: /* cmd ::= DROP TABLE ifexists fullname */ + case 74: /* cmd ::= DROP TABLE ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy65, 0, yymsp[-1].minor.yy328); + sqlite3DropTable(pParse, yymsp[0].minor.yy347, 0, yymsp[-1].minor.yy392); } break; - case 109: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + case 77: /* 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.yy14, yymsp[0].minor.yy3, yymsp[-7].minor.yy328, yymsp[-5].minor.yy328); + sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy442, yymsp[0].minor.yy159, yymsp[-7].minor.yy392, yymsp[-5].minor.yy392); } break; - case 110: /* cmd ::= DROP VIEW ifexists fullname */ + case 78: /* cmd ::= DROP VIEW ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy65, 1, yymsp[-1].minor.yy328); + sqlite3DropTable(pParse, yymsp[0].minor.yy347, 1, yymsp[-1].minor.yy392); } break; - case 111: /* cmd ::= select */ + case 79: /* cmd ::= select */ { SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0}; - sqlite3Select(pParse, yymsp[0].minor.yy3, &dest); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy3); + sqlite3Select(pParse, yymsp[0].minor.yy159, &dest); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy159); } break; - case 112: /* select ::= with selectnowith */ + case 80: /* select ::= with selectnowith */ { - Select *p = yymsp[0].minor.yy3; + Select *p = yymsp[0].minor.yy159; if( p ){ - p->pWith = yymsp[-1].minor.yy59; + p->pWith = yymsp[-1].minor.yy331; parserDoubleLinkSelect(pParse, p); }else{ - sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy59); + sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy331); } - yygotominor.yy3 = p; + yymsp[-1].minor.yy159 = p; /*A-overwrites-W*/ } break; - case 113: /* selectnowith ::= oneselect */ - case 119: /* oneselect ::= values */ yytestcase(yyruleno==119); -{yygotominor.yy3 = yymsp[0].minor.yy3;} - break; - case 114: /* selectnowith ::= selectnowith multiselect_op oneselect */ + case 81: /* selectnowith ::= selectnowith multiselect_op oneselect */ { - Select *pRhs = yymsp[0].minor.yy3; - Select *pLhs = yymsp[-2].minor.yy3; + Select *pRhs = yymsp[0].minor.yy159; + Select *pLhs = yymsp[-2].minor.yy159; if( pRhs && pRhs->pPrior ){ SrcList *pFrom; Token x; @@ -128453,23 +133201,30 @@ static void yy_reduce( pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0,0); } if( pRhs ){ - pRhs->op = (u8)yymsp[-1].minor.yy328; + pRhs->op = (u8)yymsp[-1].minor.yy392; pRhs->pPrior = pLhs; if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue; pRhs->selFlags &= ~SF_MultiValue; - if( yymsp[-1].minor.yy328!=TK_ALL ) pParse->hasCompound = 1; + if( yymsp[-1].minor.yy392!=TK_ALL ) pParse->hasCompound = 1; }else{ sqlite3SelectDelete(pParse->db, pLhs); } - yygotominor.yy3 = pRhs; + yymsp[-2].minor.yy159 = pRhs; } break; - case 116: /* multiselect_op ::= UNION ALL */ -{yygotominor.yy328 = TK_ALL;} + case 82: /* multiselect_op ::= UNION */ + case 84: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==84); +{yymsp[0].minor.yy392 = yymsp[0].major; /*A-overwrites-OP*/} break; - case 118: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + case 83: /* multiselect_op ::= UNION ALL */ +{yymsp[-1].minor.yy392 = TK_ALL;} + break; + case 85: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { - yygotominor.yy3 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy14,yymsp[-5].minor.yy65,yymsp[-4].minor.yy132,yymsp[-3].minor.yy14,yymsp[-2].minor.yy132,yymsp[-1].minor.yy14,yymsp[-7].minor.yy381,yymsp[0].minor.yy476.pLimit,yymsp[0].minor.yy476.pOffset); +#if SELECTTRACE_ENABLED + Token s = yymsp[-8].minor.yy0; /*A-overwrites-S*/ +#endif + yymsp[-8].minor.yy159 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy442,yymsp[-5].minor.yy347,yymsp[-4].minor.yy122,yymsp[-3].minor.yy442,yymsp[-2].minor.yy122,yymsp[-1].minor.yy442,yymsp[-7].minor.yy392,yymsp[0].minor.yy64.pLimit,yymsp[0].minor.yy64.pOffset); #if SELECTTRACE_ENABLED /* Populate the Select.zSelName[] string that is used to help with ** query planner debugging, to differentiate between multiple Select @@ -128480,455 +133235,449 @@ static void yy_reduce( ** comment to be the zSelName value. Otherwise, the label is #N where ** is an integer that is incremented with each SELECT statement seen. */ - if( yygotominor.yy3!=0 ){ - const char *z = yymsp[-8].minor.yy0.z+6; + if( yymsp[-8].minor.yy159!=0 ){ + const char *z = s.z+6; int i; - sqlite3_snprintf(sizeof(yygotominor.yy3->zSelName), yygotominor.yy3->zSelName, "#%d", + sqlite3_snprintf(sizeof(yymsp[-8].minor.yy159->zSelName), yymsp[-8].minor.yy159->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(yygotominor.yy3->zSelName), yygotominor.yy3->zSelName, "%.*s", i, z); + sqlite3_snprintf(sizeof(yymsp[-8].minor.yy159->zSelName), yymsp[-8].minor.yy159->zSelName, "%.*s", i, z); } } #endif /* SELECTRACE_ENABLED */ } break; - case 120: /* values ::= VALUES LP nexprlist RP */ + case 86: /* values ::= VALUES LP nexprlist RP */ { - yygotominor.yy3 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values,0,0); + yymsp[-3].minor.yy159 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy442,0,0,0,0,0,SF_Values,0,0); } break; - case 121: /* values ::= values COMMA LP exprlist RP */ + case 87: /* values ::= values COMMA LP exprlist RP */ { - Select *pRight, *pLeft = yymsp[-4].minor.yy3; - pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values|SF_MultiValue,0,0); + Select *pRight, *pLeft = yymsp[-4].minor.yy159; + pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy442,0,0,0,0,0,SF_Values|SF_MultiValue,0,0); if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; if( pRight ){ pRight->op = TK_ALL; - pLeft = yymsp[-4].minor.yy3; pRight->pPrior = pLeft; - yygotominor.yy3 = pRight; + yymsp[-4].minor.yy159 = pRight; }else{ - yygotominor.yy3 = pLeft; + yymsp[-4].minor.yy159 = pLeft; } } break; - case 122: /* distinct ::= DISTINCT */ -{yygotominor.yy381 = SF_Distinct;} + case 88: /* distinct ::= DISTINCT */ +{yymsp[0].minor.yy392 = SF_Distinct;} break; - case 123: /* distinct ::= ALL */ -{yygotominor.yy381 = SF_All;} + case 89: /* distinct ::= ALL */ +{yymsp[0].minor.yy392 = SF_All;} break; - case 124: /* distinct ::= */ -{yygotominor.yy381 = 0;} + case 91: /* sclp ::= */ + case 119: /* orderby_opt ::= */ yytestcase(yyruleno==119); + case 126: /* groupby_opt ::= */ yytestcase(yyruleno==126); + case 199: /* exprlist ::= */ yytestcase(yyruleno==199); + case 205: /* eidlist_opt ::= */ yytestcase(yyruleno==205); +{yymsp[1].minor.yy442 = 0;} break; - case 125: /* sclp ::= selcollist COMMA */ - case 244: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==244); -{yygotominor.yy14 = yymsp[-1].minor.yy14;} - break; - case 126: /* sclp ::= */ - case 155: /* orderby_opt ::= */ yytestcase(yyruleno==155); - case 162: /* groupby_opt ::= */ yytestcase(yyruleno==162); - case 237: /* exprlist ::= */ yytestcase(yyruleno==237); - case 243: /* eidlist_opt ::= */ yytestcase(yyruleno==243); -{yygotominor.yy14 = 0;} - break; - case 127: /* selcollist ::= sclp expr as */ + case 92: /* selcollist ::= sclp expr as */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy14, yymsp[-1].minor.yy346.pExpr); - if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[0].minor.yy0, 1); - sqlite3ExprListSetSpan(pParse,yygotominor.yy14,&yymsp[-1].minor.yy346); + yymsp[-2].minor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy442, yymsp[-1].minor.yy342.pExpr); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-2].minor.yy442, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yymsp[-2].minor.yy442,&yymsp[-1].minor.yy342); } break; - case 128: /* selcollist ::= sclp STAR */ + case 93: /* selcollist ::= sclp STAR */ { - Expr *p = sqlite3Expr(pParse->db, TK_ALL, 0); - yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy14, p); + Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); + yymsp[-1].minor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy442, p); } break; - case 129: /* selcollist ::= sclp nm DOT STAR */ + case 94: /* selcollist ::= sclp nm DOT STAR */ { - Expr *pRight = sqlite3PExpr(pParse, TK_ALL, 0, 0, &yymsp[0].minor.yy0); + Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0, &yymsp[0].minor.yy0); Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); - yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14, pDot); + yymsp[-3].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy442, pDot); } break; - case 132: /* as ::= */ -{yygotominor.yy0.n = 0;} + case 95: /* as ::= AS nm */ + case 106: /* dbnm ::= DOT nm */ yytestcase(yyruleno==106); + case 219: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==219); + case 220: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==220); +{yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} break; - case 133: /* from ::= */ -{yygotominor.yy65 = sqlite3DbMallocZero(pParse->db, sizeof(*yygotominor.yy65));} + case 97: /* from ::= */ +{yymsp[1].minor.yy347 = sqlite3DbMallocZero(pParse->db, sizeof(*yymsp[1].minor.yy347));} break; - case 134: /* from ::= FROM seltablist */ + case 98: /* from ::= FROM seltablist */ { - yygotominor.yy65 = yymsp[0].minor.yy65; - sqlite3SrcListShiftJoinType(yygotominor.yy65); + yymsp[-1].minor.yy347 = yymsp[0].minor.yy347; + sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy347); } break; - case 135: /* stl_prefix ::= seltablist joinop */ + case 99: /* stl_prefix ::= seltablist joinop */ { - yygotominor.yy65 = yymsp[-1].minor.yy65; - if( ALWAYS(yygotominor.yy65 && yygotominor.yy65->nSrc>0) ) yygotominor.yy65->a[yygotominor.yy65->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy328; + if( ALWAYS(yymsp[-1].minor.yy347 && yymsp[-1].minor.yy347->nSrc>0) ) yymsp[-1].minor.yy347->a[yymsp[-1].minor.yy347->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy392; } break; - case 136: /* stl_prefix ::= */ -{yygotominor.yy65 = 0;} + case 100: /* stl_prefix ::= */ +{yymsp[1].minor.yy347 = 0;} break; - case 137: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ + case 101: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ { - yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); - sqlite3SrcListIndexedBy(pParse, yygotominor.yy65, &yymsp[-2].minor.yy0); + yymsp[-6].minor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); + sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy347, &yymsp[-2].minor.yy0); } break; - case 138: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ + case 102: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ { - yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy65,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); - sqlite3SrcListFuncArgs(pParse, yygotominor.yy65, yymsp[-4].minor.yy14); + yymsp[-8].minor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy347,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); + sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy347, yymsp[-4].minor.yy442); } break; - case 139: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ + case 103: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ { - yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy3,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); + yymsp[-6].minor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy159,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); } break; - case 140: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ + case 104: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ { - if( yymsp[-6].minor.yy65==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy132==0 && yymsp[0].minor.yy408==0 ){ - yygotominor.yy65 = yymsp[-4].minor.yy65; - }else if( yymsp[-4].minor.yy65->nSrc==1 ){ - yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); - if( yygotominor.yy65 ){ - struct SrcList_item *pNew = &yygotominor.yy65->a[yygotominor.yy65->nSrc-1]; - struct SrcList_item *pOld = yymsp[-4].minor.yy65->a; + if( yymsp[-6].minor.yy347==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy122==0 && yymsp[0].minor.yy180==0 ){ + yymsp[-6].minor.yy347 = yymsp[-4].minor.yy347; + }else if( yymsp[-4].minor.yy347->nSrc==1 ){ + yymsp[-6].minor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); + if( yymsp[-6].minor.yy347 ){ + struct SrcList_item *pNew = &yymsp[-6].minor.yy347->a[yymsp[-6].minor.yy347->nSrc-1]; + struct SrcList_item *pOld = yymsp[-4].minor.yy347->a; pNew->zName = pOld->zName; pNew->zDatabase = pOld->zDatabase; pNew->pSelect = pOld->pSelect; pOld->zName = pOld->zDatabase = 0; pOld->pSelect = 0; } - sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy65); + sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy347); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy65); - pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy65,0,0,0,0,SF_NestedFrom,0,0); - yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); + sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy347); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy347,0,0,0,0,SF_NestedFrom,0,0); + yymsp[-6].minor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); } } break; - case 141: /* dbnm ::= */ - case 150: /* indexed_opt ::= */ yytestcase(yyruleno==150); -{yygotominor.yy0.z=0; yygotominor.yy0.n=0;} + case 105: /* dbnm ::= */ + case 114: /* indexed_opt ::= */ yytestcase(yyruleno==114); +{yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} break; - case 143: /* fullname ::= nm dbnm */ -{yygotominor.yy65 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} + case 107: /* fullname ::= nm dbnm */ +{yymsp[-1].minor.yy347 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 144: /* joinop ::= COMMA|JOIN */ -{ yygotominor.yy328 = JT_INNER; } + case 108: /* joinop ::= COMMA|JOIN */ +{ yymsp[0].minor.yy392 = JT_INNER; } break; - case 145: /* joinop ::= JOIN_KW JOIN */ -{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); } + case 109: /* joinop ::= JOIN_KW JOIN */ +{yymsp[-1].minor.yy392 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} break; - case 146: /* joinop ::= JOIN_KW nm JOIN */ -{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); } + case 110: /* joinop ::= JOIN_KW nm JOIN */ +{yymsp[-2].minor.yy392 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} break; - case 147: /* joinop ::= JOIN_KW nm nm JOIN */ -{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); } + case 111: /* joinop ::= JOIN_KW nm nm JOIN */ +{yymsp[-3].minor.yy392 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} break; - case 148: /* on_opt ::= ON expr */ - case 165: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==165); - case 172: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==172); - case 232: /* case_else ::= ELSE expr */ yytestcase(yyruleno==232); - case 234: /* case_operand ::= expr */ yytestcase(yyruleno==234); -{yygotominor.yy132 = yymsp[0].minor.yy346.pExpr;} + 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 195: /* case_else ::= ELSE expr */ yytestcase(yyruleno==195); +{yymsp[-1].minor.yy122 = yymsp[0].minor.yy342.pExpr;} break; - case 149: /* on_opt ::= */ - case 164: /* having_opt ::= */ yytestcase(yyruleno==164); - case 171: /* where_opt ::= */ yytestcase(yyruleno==171); - case 233: /* case_else ::= */ yytestcase(yyruleno==233); - case 235: /* case_operand ::= */ yytestcase(yyruleno==235); -{yygotominor.yy132 = 0;} + case 113: /* on_opt ::= */ + case 128: /* having_opt ::= */ yytestcase(yyruleno==128); + case 135: /* where_opt ::= */ yytestcase(yyruleno==135); + case 196: /* case_else ::= */ yytestcase(yyruleno==196); + case 198: /* case_operand ::= */ yytestcase(yyruleno==198); +{yymsp[1].minor.yy122 = 0;} break; - case 152: /* indexed_opt ::= NOT INDEXED */ -{yygotominor.yy0.z=0; yygotominor.yy0.n=1;} + case 115: /* indexed_opt ::= INDEXED BY nm */ +{yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} break; - case 153: /* using_opt ::= USING LP idlist RP */ - case 181: /* idlist_opt ::= LP idlist RP */ yytestcase(yyruleno==181); -{yygotominor.yy408 = yymsp[-1].minor.yy408;} + case 116: /* indexed_opt ::= NOT INDEXED */ +{yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;} break; - case 154: /* using_opt ::= */ - case 180: /* idlist_opt ::= */ yytestcase(yyruleno==180); -{yygotominor.yy408 = 0;} + case 117: /* using_opt ::= USING LP idlist RP */ +{yymsp[-3].minor.yy180 = yymsp[-1].minor.yy180;} break; - case 156: /* orderby_opt ::= ORDER BY sortlist */ - case 163: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==163); - case 236: /* exprlist ::= nexprlist */ yytestcase(yyruleno==236); -{yygotominor.yy14 = yymsp[0].minor.yy14;} + case 118: /* using_opt ::= */ + case 144: /* idlist_opt ::= */ yytestcase(yyruleno==144); +{yymsp[1].minor.yy180 = 0;} break; - case 157: /* sortlist ::= sortlist COMMA expr sortorder */ + case 120: /* orderby_opt ::= ORDER BY sortlist */ + case 127: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==127); +{yymsp[-2].minor.yy442 = yymsp[0].minor.yy442;} + break; + case 121: /* sortlist ::= sortlist COMMA expr sortorder */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14,yymsp[-1].minor.yy346.pExpr); - sqlite3ExprListSetSortOrder(yygotominor.yy14,yymsp[0].minor.yy328); + yymsp[-3].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy442,yymsp[-1].minor.yy342.pExpr); + sqlite3ExprListSetSortOrder(yymsp[-3].minor.yy442,yymsp[0].minor.yy392); } break; - case 158: /* sortlist ::= expr sortorder */ + case 122: /* sortlist ::= expr sortorder */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy346.pExpr); - sqlite3ExprListSetSortOrder(yygotominor.yy14,yymsp[0].minor.yy328); + yymsp[-1].minor.yy442 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy342.pExpr); /*A-overwrites-Y*/ + sqlite3ExprListSetSortOrder(yymsp[-1].minor.yy442,yymsp[0].minor.yy392); } break; - case 159: /* sortorder ::= ASC */ -{yygotominor.yy328 = SQLITE_SO_ASC;} + case 123: /* sortorder ::= ASC */ +{yymsp[0].minor.yy392 = SQLITE_SO_ASC;} break; - case 160: /* sortorder ::= DESC */ -{yygotominor.yy328 = SQLITE_SO_DESC;} + case 124: /* sortorder ::= DESC */ +{yymsp[0].minor.yy392 = SQLITE_SO_DESC;} break; - case 161: /* sortorder ::= */ -{yygotominor.yy328 = SQLITE_SO_UNDEFINED;} + case 125: /* sortorder ::= */ +{yymsp[1].minor.yy392 = SQLITE_SO_UNDEFINED;} break; - case 166: /* limit_opt ::= */ -{yygotominor.yy476.pLimit = 0; yygotominor.yy476.pOffset = 0;} + case 130: /* limit_opt ::= */ +{yymsp[1].minor.yy64.pLimit = 0; yymsp[1].minor.yy64.pOffset = 0;} break; - case 167: /* limit_opt ::= LIMIT expr */ -{yygotominor.yy476.pLimit = yymsp[0].minor.yy346.pExpr; yygotominor.yy476.pOffset = 0;} + case 131: /* limit_opt ::= LIMIT expr */ +{yymsp[-1].minor.yy64.pLimit = yymsp[0].minor.yy342.pExpr; yymsp[-1].minor.yy64.pOffset = 0;} break; - case 168: /* limit_opt ::= LIMIT expr OFFSET expr */ -{yygotominor.yy476.pLimit = yymsp[-2].minor.yy346.pExpr; yygotominor.yy476.pOffset = yymsp[0].minor.yy346.pExpr;} + case 132: /* limit_opt ::= LIMIT expr OFFSET expr */ +{yymsp[-3].minor.yy64.pLimit = yymsp[-2].minor.yy342.pExpr; yymsp[-3].minor.yy64.pOffset = yymsp[0].minor.yy342.pExpr;} break; - case 169: /* limit_opt ::= LIMIT expr COMMA expr */ -{yygotominor.yy476.pOffset = yymsp[-2].minor.yy346.pExpr; yygotominor.yy476.pLimit = yymsp[0].minor.yy346.pExpr;} + case 133: /* limit_opt ::= LIMIT expr COMMA expr */ +{yymsp[-3].minor.yy64.pOffset = yymsp[-2].minor.yy342.pExpr; yymsp[-3].minor.yy64.pLimit = yymsp[0].minor.yy342.pExpr;} break; - case 170: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */ + case 134: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */ { - sqlite3WithPush(pParse, yymsp[-5].minor.yy59, 1); - sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy65, &yymsp[-1].minor.yy0); - sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy65,yymsp[0].minor.yy132); + sqlite3WithPush(pParse, yymsp[-5].minor.yy331, 1); + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy347, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy347,yymsp[0].minor.yy122); } break; - case 173: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ + case 137: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ { - sqlite3WithPush(pParse, yymsp[-7].minor.yy59, 1); - sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy65, &yymsp[-3].minor.yy0); - sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy14,"set list"); - sqlite3Update(pParse,yymsp[-4].minor.yy65,yymsp[-1].minor.yy14,yymsp[0].minor.yy132,yymsp[-5].minor.yy186); + sqlite3WithPush(pParse, yymsp[-7].minor.yy331, 1); + sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy347, &yymsp[-3].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy442,"set list"); + sqlite3Update(pParse,yymsp[-4].minor.yy347,yymsp[-1].minor.yy442,yymsp[0].minor.yy122,yymsp[-5].minor.yy392); } break; - case 174: /* setlist ::= setlist COMMA nm EQ expr */ + case 138: /* setlist ::= setlist COMMA nm EQ expr */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[0].minor.yy346.pExpr); - sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[-2].minor.yy0, 1); + yymsp[-4].minor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy442, yymsp[0].minor.yy342.pExpr); + sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy442, &yymsp[-2].minor.yy0, 1); } break; - case 175: /* setlist ::= nm EQ expr */ + case 139: /* setlist ::= nm EQ expr */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy346.pExpr); - sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[-2].minor.yy0, 1); + yylhsminor.yy442 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy342.pExpr); + sqlite3ExprListSetName(pParse, yylhsminor.yy442, &yymsp[-2].minor.yy0, 1); +} + yymsp[-2].minor.yy442 = yylhsminor.yy442; + break; + case 140: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ +{ + sqlite3WithPush(pParse, yymsp[-5].minor.yy331, 1); + sqlite3Insert(pParse, yymsp[-2].minor.yy347, yymsp[0].minor.yy159, yymsp[-1].minor.yy180, yymsp[-4].minor.yy392); } break; - case 176: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ + case 141: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ { - sqlite3WithPush(pParse, yymsp[-5].minor.yy59, 1); - sqlite3Insert(pParse, yymsp[-2].minor.yy65, yymsp[0].minor.yy3, yymsp[-1].minor.yy408, yymsp[-4].minor.yy186); + sqlite3WithPush(pParse, yymsp[-6].minor.yy331, 1); + sqlite3Insert(pParse, yymsp[-3].minor.yy347, 0, yymsp[-2].minor.yy180, yymsp[-5].minor.yy392); } break; - case 177: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ -{ - sqlite3WithPush(pParse, yymsp[-6].minor.yy59, 1); - sqlite3Insert(pParse, yymsp[-3].minor.yy65, 0, yymsp[-2].minor.yy408, yymsp[-5].minor.yy186); -} + case 145: /* idlist_opt ::= LP idlist RP */ +{yymsp[-2].minor.yy180 = yymsp[-1].minor.yy180;} break; - case 178: /* insert_cmd ::= INSERT orconf */ -{yygotominor.yy186 = yymsp[0].minor.yy186;} + case 146: /* idlist ::= idlist COMMA nm */ +{yymsp[-2].minor.yy180 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy180,&yymsp[0].minor.yy0);} break; - case 179: /* insert_cmd ::= REPLACE */ -{yygotominor.yy186 = OE_Replace;} + case 147: /* idlist ::= nm */ +{yymsp[0].minor.yy180 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} break; - case 182: /* idlist ::= idlist COMMA nm */ -{yygotominor.yy408 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy408,&yymsp[0].minor.yy0);} + case 148: /* expr ::= LP expr RP */ +{spanSet(&yymsp[-2].minor.yy342,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ yymsp[-2].minor.yy342.pExpr = yymsp[-1].minor.yy342.pExpr;} break; - case 183: /* idlist ::= nm */ -{yygotominor.yy408 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0);} + case 149: /* term ::= NULL */ + case 154: /* term ::= INTEGER|FLOAT|BLOB */ yytestcase(yyruleno==154); + case 155: /* term ::= STRING */ yytestcase(yyruleno==155); +{spanExpr(&yymsp[0].minor.yy342,pParse,yymsp[0].major,yymsp[0].minor.yy0);/*A-overwrites-X*/} break; - case 184: /* expr ::= term */ -{yygotominor.yy346 = yymsp[0].minor.yy346;} + case 150: /* expr ::= ID|INDEXED */ + case 151: /* expr ::= JOIN_KW */ yytestcase(yyruleno==151); +{spanExpr(&yymsp[0].minor.yy342,pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 185: /* expr ::= LP expr RP */ -{yygotominor.yy346.pExpr = yymsp[-1].minor.yy346.pExpr; spanSet(&yygotominor.yy346,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);} - break; - case 186: /* term ::= NULL */ - case 191: /* term ::= INTEGER|FLOAT|BLOB */ yytestcase(yyruleno==191); - case 192: /* term ::= STRING */ yytestcase(yyruleno==192); -{spanExpr(&yygotominor.yy346, pParse, yymsp[0].major, &yymsp[0].minor.yy0);} - break; - case 187: /* expr ::= ID|INDEXED */ - case 188: /* expr ::= JOIN_KW */ yytestcase(yyruleno==188); -{spanExpr(&yygotominor.yy346, pParse, TK_ID, &yymsp[0].minor.yy0);} - break; - case 189: /* expr ::= nm DOT nm */ + case 152: /* expr ::= nm DOT nm */ { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); - spanSet(&yygotominor.yy346,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + spanSet(&yymsp[-2].minor.yy342,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ + yymsp[-2].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); } break; - case 190: /* expr ::= nm DOT nm DOT nm */ + case 153: /* expr ::= nm DOT nm DOT nm */ { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-4].minor.yy0); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *temp3 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0); - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); - spanSet(&yygotominor.yy346,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); + spanSet(&yymsp[-4].minor.yy342,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); } break; - case 193: /* expr ::= VARIABLE */ + case 156: /* expr ::= VARIABLE */ { - if( yymsp[0].minor.yy0.n>=2 && yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1]) ){ + if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ + spanExpr(&yymsp[0].minor.yy342, pParse, TK_VARIABLE, yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy342.pExpr); + }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.yy342, &t, &t); if( pParse->nested==0 ){ - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &yymsp[0].minor.yy0); - yygotominor.yy346.pExpr = 0; + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); + yymsp[0].minor.yy342.pExpr = 0; }else{ - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &yymsp[0].minor.yy0); - if( yygotominor.yy346.pExpr ) sqlite3GetInt32(&yymsp[0].minor.yy0.z[1], &yygotominor.yy346.pExpr->iTable); + yymsp[0].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &t); + if( yymsp[0].minor.yy342.pExpr ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy342.pExpr->iTable); } - }else{ - spanExpr(&yygotominor.yy346, pParse, TK_VARIABLE, &yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yygotominor.yy346.pExpr); } - spanSet(&yygotominor.yy346, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); } break; - case 194: /* expr ::= expr COLLATE ID|STRING */ + case 157: /* expr ::= expr COLLATE ID|STRING */ { - yygotominor.yy346.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy346.pExpr, &yymsp[0].minor.yy0, 1); - yygotominor.yy346.zStart = yymsp[-2].minor.yy346.zStart; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + yymsp[-2].minor.yy342.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy342.pExpr, &yymsp[0].minor.yy0, 1); + yymsp[-2].minor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 195: /* expr ::= CAST LP expr AS typetoken RP */ + case 158: /* expr ::= CAST LP expr AS typetoken RP */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy346.pExpr, 0, &yymsp[-1].minor.yy0); - spanSet(&yygotominor.yy346,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); + spanSet(&yymsp[-5].minor.yy342,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ + yymsp[-5].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy342.pExpr, 0, &yymsp[-1].minor.yy0); } break; - case 196: /* expr ::= ID|INDEXED LP distinct exprlist RP */ + case 159: /* expr ::= ID|INDEXED LP distinct exprlist RP */ { - if( yymsp[-1].minor.yy14 && yymsp[-1].minor.yy14->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ + if( yymsp[-1].minor.yy442 && yymsp[-1].minor.yy442->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0); } - yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0); - spanSet(&yygotominor.yy346,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); - if( yymsp[-2].minor.yy381==SF_Distinct && yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->flags |= EP_Distinct; + yylhsminor.yy342.pExpr = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy442, &yymsp[-4].minor.yy0); + spanSet(&yylhsminor.yy342,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); + if( yymsp[-2].minor.yy392==SF_Distinct && yylhsminor.yy342.pExpr ){ + yylhsminor.yy342.pExpr->flags |= EP_Distinct; } } + yymsp[-4].minor.yy342 = yylhsminor.yy342; break; - case 197: /* expr ::= ID|INDEXED LP STAR RP */ + case 160: /* expr ::= ID|INDEXED LP STAR RP */ { - yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); - spanSet(&yygotominor.yy346,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); + yylhsminor.yy342.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); + spanSet(&yylhsminor.yy342,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); } + yymsp[-3].minor.yy342 = yylhsminor.yy342; break; - case 198: /* term ::= CTIME_KW */ + case 161: /* term ::= CTIME_KW */ { - yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0); - spanSet(&yygotominor.yy346, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); + yylhsminor.yy342.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0); + spanSet(&yylhsminor.yy342, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); } + yymsp[0].minor.yy342 = yylhsminor.yy342; break; - case 199: /* expr ::= expr AND expr */ - case 200: /* expr ::= expr OR expr */ yytestcase(yyruleno==200); - case 201: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==201); - case 202: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==202); - case 203: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==203); - case 204: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==204); - case 205: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==205); - case 206: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==206); -{spanBinaryExpr(&yygotominor.yy346,pParse,yymsp[-1].major,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy346);} + case 162: /* expr ::= expr AND expr */ + case 163: /* expr ::= expr OR expr */ yytestcase(yyruleno==163); + case 164: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==164); + case 165: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==165); + case 166: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==166); + case 167: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==167); + case 168: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==168); + case 169: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==169); +{spanBinaryExpr(pParse,yymsp[-1].major,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy342);} break; - case 207: /* likeop ::= LIKE_KW|MATCH */ -{yygotominor.yy96.eOperator = yymsp[0].minor.yy0; yygotominor.yy96.bNot = 0;} + case 170: /* likeop ::= LIKE_KW|MATCH */ +{yymsp[0].minor.yy318.eOperator = yymsp[0].minor.yy0; yymsp[0].minor.yy318.bNot = 0;/*A-overwrites-X*/} break; - case 208: /* likeop ::= NOT LIKE_KW|MATCH */ -{yygotominor.yy96.eOperator = yymsp[0].minor.yy0; yygotominor.yy96.bNot = 1;} + case 171: /* likeop ::= NOT LIKE_KW|MATCH */ +{yymsp[-1].minor.yy318.eOperator = yymsp[0].minor.yy0; yymsp[-1].minor.yy318.bNot = 1;} break; - case 209: /* expr ::= expr likeop expr */ + case 172: /* expr ::= expr likeop expr */ { ExprList *pList; - pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy346.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy346.pExpr); - yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy96.eOperator); - if( yymsp[-1].minor.yy96.bNot ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); - yygotominor.yy346.zStart = yymsp[-2].minor.yy346.zStart; - yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; - if( yygotominor.yy346.pExpr ) yygotominor.yy346.pExpr->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy342.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy342.pExpr); + yymsp[-2].minor.yy342.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy318.eOperator); + exprNot(pParse, yymsp[-1].minor.yy318.bNot, &yymsp[-2].minor.yy342); + yymsp[-2].minor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; + if( yymsp[-2].minor.yy342.pExpr ) yymsp[-2].minor.yy342.pExpr->flags |= EP_InfixFunc; } break; - case 210: /* expr ::= expr likeop expr ESCAPE expr */ + case 173: /* expr ::= expr likeop expr ESCAPE expr */ { ExprList *pList; - pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy346.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy346.pExpr); - yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy96.eOperator); - if( yymsp[-3].minor.yy96.bNot ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); - yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; - yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; - if( yygotominor.yy346.pExpr ) yygotominor.yy346.pExpr->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy342.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy342.pExpr); + yymsp[-4].minor.yy342.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy318.eOperator); + exprNot(pParse, yymsp[-3].minor.yy318.bNot, &yymsp[-4].minor.yy342); + yymsp[-4].minor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; + if( yymsp[-4].minor.yy342.pExpr ) yymsp[-4].minor.yy342.pExpr->flags |= EP_InfixFunc; } break; - case 211: /* expr ::= expr ISNULL|NOTNULL */ -{spanUnaryPostfix(&yygotominor.yy346,pParse,yymsp[0].major,&yymsp[-1].minor.yy346,&yymsp[0].minor.yy0);} + case 174: /* expr ::= expr ISNULL|NOTNULL */ +{spanUnaryPostfix(pParse,yymsp[0].major,&yymsp[-1].minor.yy342,&yymsp[0].minor.yy0);} break; - case 212: /* expr ::= expr NOT NULL */ -{spanUnaryPostfix(&yygotominor.yy346,pParse,TK_NOTNULL,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy0);} + case 175: /* expr ::= expr NOT NULL */ +{spanUnaryPostfix(pParse,TK_NOTNULL,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy0);} break; - case 213: /* expr ::= expr IS expr */ + case 176: /* expr ::= expr IS expr */ { - spanBinaryExpr(&yygotominor.yy346,pParse,TK_IS,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy346); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy346.pExpr, yygotominor.yy346.pExpr, TK_ISNULL); + spanBinaryExpr(pParse,TK_IS,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy342); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy342.pExpr, yymsp[-2].minor.yy342.pExpr, TK_ISNULL); } break; - case 214: /* expr ::= expr IS NOT expr */ + case 177: /* expr ::= expr IS NOT expr */ { - spanBinaryExpr(&yygotominor.yy346,pParse,TK_ISNOT,&yymsp[-3].minor.yy346,&yymsp[0].minor.yy346); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy346.pExpr, yygotominor.yy346.pExpr, TK_NOTNULL); + spanBinaryExpr(pParse,TK_ISNOT,&yymsp[-3].minor.yy342,&yymsp[0].minor.yy342); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy342.pExpr, yymsp[-3].minor.yy342.pExpr, TK_NOTNULL); } break; - case 215: /* expr ::= NOT expr */ - case 216: /* expr ::= BITNOT expr */ yytestcase(yyruleno==216); -{spanUnaryPrefix(&yygotominor.yy346,pParse,yymsp[-1].major,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + case 178: /* expr ::= NOT expr */ + case 179: /* expr ::= BITNOT expr */ yytestcase(yyruleno==179); +{spanUnaryPrefix(&yymsp[-1].minor.yy342,pParse,yymsp[-1].major,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 217: /* expr ::= MINUS expr */ -{spanUnaryPrefix(&yygotominor.yy346,pParse,TK_UMINUS,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + case 180: /* expr ::= MINUS expr */ +{spanUnaryPrefix(&yymsp[-1].minor.yy342,pParse,TK_UMINUS,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 218: /* expr ::= PLUS expr */ -{spanUnaryPrefix(&yygotominor.yy346,pParse,TK_UPLUS,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + case 181: /* expr ::= PLUS expr */ +{spanUnaryPrefix(&yymsp[-1].minor.yy342,pParse,TK_UPLUS,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 221: /* expr ::= expr between_op expr AND expr */ + case 182: /* between_op ::= BETWEEN */ + case 185: /* in_op ::= IN */ yytestcase(yyruleno==185); +{yymsp[0].minor.yy392 = 0;} + break; + case 184: /* expr ::= expr between_op expr AND expr */ { - ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy346.pExpr); - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy346.pExpr, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy342.pExpr); + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy342.pExpr, 0, 0); + if( yymsp[-4].minor.yy342.pExpr ){ + yymsp[-4].minor.yy342.pExpr->x.pList = pList; }else{ sqlite3ExprListDelete(pParse->db, pList); } - if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); - yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; - yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; + exprNot(pParse, yymsp[-3].minor.yy392, &yymsp[-4].minor.yy342); + yymsp[-4].minor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; } break; - case 224: /* expr ::= expr in_op LP exprlist RP */ + case 187: /* expr ::= expr in_op LP exprlist RP */ { - if( yymsp[-1].minor.yy14==0 ){ + if( yymsp[-1].minor.yy442==0 ){ /* Expressions of the form ** ** expr1 IN () @@ -128937,9 +133686,9 @@ static void yy_reduce( ** simplify to constants 0 (false) and 1 (true), respectively, ** regardless of the value of expr1. */ - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy328]); - sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy346.pExpr); - }else if( yymsp[-1].minor.yy14->nExpr==1 ){ + sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy342.pExpr); + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy392]); + }else if( yymsp[-1].minor.yy442->nExpr==1 ){ /* Expressions of the form: ** ** expr1 IN (?1) @@ -128956,421 +133705,413 @@ static void yy_reduce( ** 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.yy14->a[0].pExpr; - yymsp[-1].minor.yy14->a[0].pExpr = 0; - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + Expr *pRHS = yymsp[-1].minor.yy442->a[0].pExpr; + yymsp[-1].minor.yy442->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy442); /* 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; } - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy328 ? TK_NE : TK_EQ, yymsp[-4].minor.yy346.pExpr, pRHS, 0); + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy392 ? TK_NE : TK_EQ, yymsp[-4].minor.yy342.pExpr, pRHS, 0); }else{ - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy346.pExpr, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pList = yymsp[-1].minor.yy14; - sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy342.pExpr, 0, 0); + if( yymsp[-4].minor.yy342.pExpr ){ + yymsp[-4].minor.yy342.pExpr->x.pList = yymsp[-1].minor.yy442; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy342.pExpr); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy442); } - if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + exprNot(pParse, yymsp[-3].minor.yy392, &yymsp[-4].minor.yy342); } - yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + yymsp[-4].minor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 225: /* expr ::= LP select RP */ + case 188: /* expr ::= LP select RP */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pSelect = yymsp[-1].minor.yy3; - ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); - sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); - } - yygotominor.yy346.zStart = yymsp[-2].minor.yy0.z; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + spanSet(&yymsp[-2].minor.yy342,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ + yymsp[-2].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy342.pExpr, yymsp[-1].minor.yy159); } break; - case 226: /* expr ::= expr in_op LP select RP */ + case 189: /* expr ::= expr in_op LP select RP */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy346.pExpr, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pSelect = yymsp[-1].minor.yy3; - ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); - sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); - } - if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); - yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy342.pExpr, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy342.pExpr, yymsp[-1].minor.yy159); + exprNot(pParse, yymsp[-3].minor.yy392, &yymsp[-4].minor.yy342); + yymsp[-4].minor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 227: /* expr ::= expr in_op nm dbnm */ + case 190: /* expr ::= expr in_op nm dbnm */ { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0); - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-3].minor.yy346.pExpr, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); - ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); - sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); - }else{ - sqlite3SrcListDelete(pParse->db, pSrc); - } - if( yymsp[-2].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); - yygotominor.yy346.zStart = yymsp[-3].minor.yy346.zStart; - yygotominor.yy346.zEnd = yymsp[0].minor.yy0.z ? &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] : &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]; + Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); + yymsp[-3].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-3].minor.yy342.pExpr, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-3].minor.yy342.pExpr, pSelect); + exprNot(pParse, yymsp[-2].minor.yy392, &yymsp[-3].minor.yy342); + yymsp[-3].minor.yy342.zEnd = yymsp[0].minor.yy0.z ? &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] : &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]; } break; - case 228: /* expr ::= EXISTS LP select RP */ + case 191: /* expr ::= EXISTS LP select RP */ { - Expr *p = yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); - if( p ){ - p->x.pSelect = yymsp[-1].minor.yy3; - ExprSetProperty(p, EP_xIsSelect|EP_Subquery); - sqlite3ExprSetHeightAndFlags(pParse, p); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); - } - yygotominor.yy346.zStart = yymsp[-3].minor.yy0.z; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + Expr *p; + spanSet(&yymsp[-3].minor.yy342,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ + p = yymsp[-3].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); + sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy159); } break; - case 229: /* expr ::= CASE case_operand case_exprlist case_else END */ + case 192: /* expr ::= CASE case_operand case_exprlist case_else END */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy132, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->x.pList = yymsp[-1].minor.yy132 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[-1].minor.yy132) : yymsp[-2].minor.yy14; - sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + spanSet(&yymsp[-4].minor.yy342,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-C*/ + yymsp[-4].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy122, 0, 0); + if( yymsp[-4].minor.yy342.pExpr ){ + yymsp[-4].minor.yy342.pExpr->x.pList = yymsp[-1].minor.yy122 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy442,yymsp[-1].minor.yy122) : yymsp[-2].minor.yy442; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy342.pExpr); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy14); - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy132); + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy442); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy122); } - yygotominor.yy346.zStart = yymsp[-4].minor.yy0.z; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 230: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ + case 193: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[-2].minor.yy346.pExpr); - yygotominor.yy14 = sqlite3ExprListAppend(pParse,yygotominor.yy14, yymsp[0].minor.yy346.pExpr); + yymsp[-4].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy442, yymsp[-2].minor.yy342.pExpr); + yymsp[-4].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy442, yymsp[0].minor.yy342.pExpr); } break; - case 231: /* case_exprlist ::= WHEN expr THEN expr */ + case 194: /* case_exprlist ::= WHEN expr THEN expr */ { - yygotominor.yy14 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); - yygotominor.yy14 = sqlite3ExprListAppend(pParse,yygotominor.yy14, yymsp[0].minor.yy346.pExpr); + yymsp[-3].minor.yy442 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); + yymsp[-3].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy442, yymsp[0].minor.yy342.pExpr); } break; - case 238: /* nexprlist ::= nexprlist COMMA expr */ -{yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[0].minor.yy346.pExpr);} + case 197: /* case_operand ::= expr */ +{yymsp[0].minor.yy122 = yymsp[0].minor.yy342.pExpr; /*A-overwrites-X*/} break; - case 239: /* nexprlist ::= expr */ -{yygotominor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy346.pExpr);} + case 200: /* nexprlist ::= nexprlist COMMA expr */ +{yymsp[-2].minor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy442,yymsp[0].minor.yy342.pExpr);} break; - case 240: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + case 201: /* nexprlist ::= expr */ +{yymsp[0].minor.yy442 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy342.pExpr); /*A-overwrites-Y*/} + break; + case 202: /* 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.yy14, yymsp[-10].minor.yy328, - &yymsp[-11].minor.yy0, yymsp[0].minor.yy132, SQLITE_SO_ASC, yymsp[-8].minor.yy328); + sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy442, yymsp[-10].minor.yy392, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy122, SQLITE_SO_ASC, yymsp[-8].minor.yy392); } break; - case 241: /* uniqueflag ::= UNIQUE */ - case 292: /* raisetype ::= ABORT */ yytestcase(yyruleno==292); -{yygotominor.yy328 = OE_Abort;} + case 203: /* uniqueflag ::= UNIQUE */ + case 244: /* raisetype ::= ABORT */ yytestcase(yyruleno==244); +{yymsp[0].minor.yy392 = OE_Abort;} break; - case 242: /* uniqueflag ::= */ -{yygotominor.yy328 = OE_None;} + case 204: /* uniqueflag ::= */ +{yymsp[1].minor.yy392 = OE_None;} break; - case 245: /* eidlist ::= eidlist COMMA nm collate sortorder */ + case 206: /* eidlist_opt ::= LP eidlist RP */ +{yymsp[-2].minor.yy442 = yymsp[-1].minor.yy442;} + break; + case 207: /* eidlist ::= eidlist COMMA nm collate sortorder */ { - yygotominor.yy14 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy14, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy328, yymsp[0].minor.yy328); + yymsp[-4].minor.yy442 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy442, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy392, yymsp[0].minor.yy392); } break; - case 246: /* eidlist ::= nm collate sortorder */ + case 208: /* eidlist ::= nm collate sortorder */ { - yygotominor.yy14 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy328, yymsp[0].minor.yy328); + yymsp[-2].minor.yy442 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy392, yymsp[0].minor.yy392); /*A-overwrites-Y*/ } break; - case 249: /* cmd ::= DROP INDEX ifexists fullname */ -{sqlite3DropIndex(pParse, yymsp[0].minor.yy65, yymsp[-1].minor.yy328);} + case 211: /* cmd ::= DROP INDEX ifexists fullname */ +{sqlite3DropIndex(pParse, yymsp[0].minor.yy347, yymsp[-1].minor.yy392);} break; - case 250: /* cmd ::= VACUUM */ - case 251: /* cmd ::= VACUUM nm */ yytestcase(yyruleno==251); + case 212: /* cmd ::= VACUUM */ + case 213: /* cmd ::= VACUUM nm */ yytestcase(yyruleno==213); {sqlite3Vacuum(pParse);} break; - case 252: /* cmd ::= PRAGMA nm dbnm */ + case 214: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 253: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 215: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 254: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 216: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 255: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 217: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 256: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 218: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 265: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 221: /* 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.yy473, &all); + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy327, &all); } break; - case 266: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 222: /* 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.yy328, yymsp[-4].minor.yy378.a, yymsp[-4].minor.yy378.b, yymsp[-2].minor.yy65, yymsp[0].minor.yy132, yymsp[-10].minor.yy328, yymsp[-8].minor.yy328); - yygotominor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy392, yymsp[-4].minor.yy410.a, yymsp[-4].minor.yy410.b, yymsp[-2].minor.yy347, yymsp[0].minor.yy122, yymsp[-10].minor.yy392, yymsp[-8].minor.yy392); + yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ } break; - case 267: /* trigger_time ::= BEFORE */ - case 270: /* trigger_time ::= */ yytestcase(yyruleno==270); -{ yygotominor.yy328 = TK_BEFORE; } + case 223: /* trigger_time ::= BEFORE */ +{ yymsp[0].minor.yy392 = TK_BEFORE; } break; - case 268: /* trigger_time ::= AFTER */ -{ yygotominor.yy328 = TK_AFTER; } + case 224: /* trigger_time ::= AFTER */ +{ yymsp[0].minor.yy392 = TK_AFTER; } break; - case 269: /* trigger_time ::= INSTEAD OF */ -{ yygotominor.yy328 = TK_INSTEAD;} + case 225: /* trigger_time ::= INSTEAD OF */ +{ yymsp[-1].minor.yy392 = TK_INSTEAD;} break; - case 271: /* trigger_event ::= DELETE|INSERT */ - case 272: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==272); -{yygotominor.yy378.a = yymsp[0].major; yygotominor.yy378.b = 0;} + case 226: /* trigger_time ::= */ +{ yymsp[1].minor.yy392 = TK_BEFORE; } break; - case 273: /* trigger_event ::= UPDATE OF idlist */ -{yygotominor.yy378.a = TK_UPDATE; yygotominor.yy378.b = yymsp[0].minor.yy408;} + case 227: /* trigger_event ::= DELETE|INSERT */ + case 228: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==228); +{yymsp[0].minor.yy410.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy410.b = 0;} break; - case 276: /* when_clause ::= */ - case 297: /* key_opt ::= */ yytestcase(yyruleno==297); -{ yygotominor.yy132 = 0; } + case 229: /* trigger_event ::= UPDATE OF idlist */ +{yymsp[-2].minor.yy410.a = TK_UPDATE; yymsp[-2].minor.yy410.b = yymsp[0].minor.yy180;} break; - case 277: /* when_clause ::= WHEN expr */ - case 298: /* key_opt ::= KEY expr */ yytestcase(yyruleno==298); -{ yygotominor.yy132 = yymsp[0].minor.yy346.pExpr; } + case 230: /* when_clause ::= */ + case 249: /* key_opt ::= */ yytestcase(yyruleno==249); +{ yymsp[1].minor.yy122 = 0; } break; - case 278: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + case 231: /* when_clause ::= WHEN expr */ + case 250: /* key_opt ::= KEY expr */ yytestcase(yyruleno==250); +{ yymsp[-1].minor.yy122 = yymsp[0].minor.yy342.pExpr; } + break; + case 232: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { - assert( yymsp[-2].minor.yy473!=0 ); - yymsp[-2].minor.yy473->pLast->pNext = yymsp[-1].minor.yy473; - yymsp[-2].minor.yy473->pLast = yymsp[-1].minor.yy473; - yygotominor.yy473 = yymsp[-2].minor.yy473; + assert( yymsp[-2].minor.yy327!=0 ); + yymsp[-2].minor.yy327->pLast->pNext = yymsp[-1].minor.yy327; + yymsp[-2].minor.yy327->pLast = yymsp[-1].minor.yy327; } break; - case 279: /* trigger_cmd_list ::= trigger_cmd SEMI */ + case 233: /* trigger_cmd_list ::= trigger_cmd SEMI */ { - assert( yymsp[-1].minor.yy473!=0 ); - yymsp[-1].minor.yy473->pLast = yymsp[-1].minor.yy473; - yygotominor.yy473 = yymsp[-1].minor.yy473; + assert( yymsp[-1].minor.yy327!=0 ); + yymsp[-1].minor.yy327->pLast = yymsp[-1].minor.yy327; } break; - case 281: /* trnm ::= nm DOT nm */ + case 234: /* trnm ::= nm DOT nm */ { - yygotominor.yy0 = yymsp[0].minor.yy0; + yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; sqlite3ErrorMsg(pParse, "qualified table names are not allowed on INSERT, UPDATE, and DELETE " "statements within triggers"); } break; - case 283: /* tridxby ::= INDEXED BY nm */ + case 235: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 284: /* tridxby ::= NOT INDEXED */ + case 236: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 285: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ -{ yygotominor.yy473 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy14, yymsp[0].minor.yy132, yymsp[-5].minor.yy186); } + case 237: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ +{yymsp[-6].minor.yy327 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy442, yymsp[0].minor.yy122, yymsp[-5].minor.yy392);} break; - case 286: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ -{yygotominor.yy473 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy408, yymsp[0].minor.yy3, yymsp[-4].minor.yy186);} + case 238: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ +{yymsp[-4].minor.yy327 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy180, yymsp[0].minor.yy159, yymsp[-4].minor.yy392);/*A-overwrites-R*/} break; - case 287: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ -{yygotominor.yy473 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy132);} + case 239: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ +{yymsp[-4].minor.yy327 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy122);} break; - case 288: /* trigger_cmd ::= select */ -{yygotominor.yy473 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy3); } + case 240: /* trigger_cmd ::= select */ +{yymsp[0].minor.yy327 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy159); /*A-overwrites-X*/} break; - case 289: /* expr ::= RAISE LP IGNORE RP */ + case 241: /* expr ::= RAISE LP IGNORE RP */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); - if( yygotominor.yy346.pExpr ){ - yygotominor.yy346.pExpr->affinity = OE_Ignore; + spanSet(&yymsp[-3].minor.yy342,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ + yymsp[-3].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); + if( yymsp[-3].minor.yy342.pExpr ){ + yymsp[-3].minor.yy342.pExpr->affinity = OE_Ignore; } - yygotominor.yy346.zStart = yymsp[-3].minor.yy0.z; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 290: /* expr ::= RAISE LP raisetype COMMA nm RP */ + case 242: /* expr ::= RAISE LP raisetype COMMA nm RP */ { - yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0); - if( yygotominor.yy346.pExpr ) { - yygotominor.yy346.pExpr->affinity = (char)yymsp[-3].minor.yy328; + spanSet(&yymsp[-5].minor.yy342,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ + yymsp[-5].minor.yy342.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0); + if( yymsp[-5].minor.yy342.pExpr ) { + yymsp[-5].minor.yy342.pExpr->affinity = (char)yymsp[-3].minor.yy392; } - yygotominor.yy346.zStart = yymsp[-5].minor.yy0.z; - yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 291: /* raisetype ::= ROLLBACK */ -{yygotominor.yy328 = OE_Rollback;} + case 243: /* raisetype ::= ROLLBACK */ +{yymsp[0].minor.yy392 = OE_Rollback;} break; - case 293: /* raisetype ::= FAIL */ -{yygotominor.yy328 = OE_Fail;} + case 245: /* raisetype ::= FAIL */ +{yymsp[0].minor.yy392 = OE_Fail;} break; - case 294: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 246: /* cmd ::= DROP TRIGGER ifexists fullname */ { - sqlite3DropTrigger(pParse,yymsp[0].minor.yy65,yymsp[-1].minor.yy328); + sqlite3DropTrigger(pParse,yymsp[0].minor.yy347,yymsp[-1].minor.yy392); } break; - case 295: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 247: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { - sqlite3Attach(pParse, yymsp[-3].minor.yy346.pExpr, yymsp[-1].minor.yy346.pExpr, yymsp[0].minor.yy132); + sqlite3Attach(pParse, yymsp[-3].minor.yy342.pExpr, yymsp[-1].minor.yy342.pExpr, yymsp[0].minor.yy122); } break; - case 296: /* cmd ::= DETACH database_kw_opt expr */ + case 248: /* cmd ::= DETACH database_kw_opt expr */ { - sqlite3Detach(pParse, yymsp[0].minor.yy346.pExpr); + sqlite3Detach(pParse, yymsp[0].minor.yy342.pExpr); } break; - case 301: /* cmd ::= REINDEX */ + case 251: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 302: /* cmd ::= REINDEX nm dbnm */ + case 252: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 303: /* cmd ::= ANALYZE */ + case 253: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 304: /* cmd ::= ANALYZE nm dbnm */ + case 254: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 305: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 255: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { - sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy65,&yymsp[0].minor.yy0); + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy347,&yymsp[0].minor.yy0); } break; - case 306: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column */ + case 256: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ { - sqlite3AlterFinishAddColumn(pParse, &yymsp[0].minor.yy0); + yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; + sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); } break; - case 307: /* add_column_fullname ::= fullname */ + case 257: /* add_column_fullname ::= fullname */ { - pParse->db->lookaside.bEnabled = 0; - sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy65); + disableLookaside(pParse); + sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy347); } break; - case 310: /* cmd ::= create_vtab */ + case 258: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 311: /* cmd ::= create_vtab LP vtabarglist RP */ + case 259: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 312: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 260: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { - sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy328); + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy392); } break; - case 315: /* vtabarg ::= */ + case 261: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 317: /* vtabargtoken ::= ANY */ - case 318: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==318); - case 319: /* lp ::= LP */ yytestcase(yyruleno==319); + case 262: /* vtabargtoken ::= ANY */ + case 263: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==263); + case 264: /* lp ::= LP */ yytestcase(yyruleno==264); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 323: /* with ::= */ -{yygotominor.yy59 = 0;} + case 265: /* with ::= */ +{yymsp[1].minor.yy331 = 0;} break; - case 324: /* with ::= WITH wqlist */ - case 325: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==325); -{ yygotominor.yy59 = yymsp[0].minor.yy59; } + case 266: /* with ::= WITH wqlist */ +{ yymsp[-1].minor.yy331 = yymsp[0].minor.yy331; } break; - case 326: /* wqlist ::= nm eidlist_opt AS LP select RP */ + case 267: /* with ::= WITH RECURSIVE wqlist */ +{ yymsp[-2].minor.yy331 = yymsp[0].minor.yy331; } + break; + case 268: /* wqlist ::= nm eidlist_opt AS LP select RP */ { - yygotominor.yy59 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy3); + yymsp[-5].minor.yy331 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy442, yymsp[-1].minor.yy159); /*A-overwrites-X*/ } break; - case 327: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ + case 269: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ { - yygotominor.yy59 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy59, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy3); + yymsp[-7].minor.yy331 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy331, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy442, yymsp[-1].minor.yy159); } break; default: - /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); - /* (1) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==1); - /* (2) cmdlist ::= ecmd */ yytestcase(yyruleno==2); - /* (3) ecmd ::= SEMI */ yytestcase(yyruleno==3); - /* (4) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==4); - /* (10) trans_opt ::= */ yytestcase(yyruleno==10); - /* (11) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==11); - /* (12) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==12); - /* (20) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==20); - /* (21) savepoint_opt ::= */ yytestcase(yyruleno==21); - /* (25) cmd ::= create_table create_table_args */ yytestcase(yyruleno==25); - /* (36) columnlist ::= columnlist COMMA column */ yytestcase(yyruleno==36); - /* (37) columnlist ::= column */ yytestcase(yyruleno==37); - /* (43) type ::= */ yytestcase(yyruleno==43); - /* (50) signed ::= plus_num */ yytestcase(yyruleno==50); - /* (51) signed ::= minus_num */ yytestcase(yyruleno==51); - /* (52) carglist ::= carglist ccons */ yytestcase(yyruleno==52); - /* (53) carglist ::= */ yytestcase(yyruleno==53); - /* (60) ccons ::= NULL onconf */ yytestcase(yyruleno==60); - /* (88) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==88); - /* (89) conslist ::= tcons */ yytestcase(yyruleno==89); - /* (91) tconscomma ::= */ yytestcase(yyruleno==91); - /* (274) foreach_clause ::= */ yytestcase(yyruleno==274); - /* (275) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==275); - /* (282) tridxby ::= */ yytestcase(yyruleno==282); - /* (299) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==299); - /* (300) database_kw_opt ::= */ yytestcase(yyruleno==300); - /* (308) kwcolumn_opt ::= */ yytestcase(yyruleno==308); - /* (309) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==309); - /* (313) vtabarglist ::= vtabarg */ yytestcase(yyruleno==313); - /* (314) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==314); - /* (316) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==316); - /* (320) anylist ::= */ yytestcase(yyruleno==320); - /* (321) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==321); - /* (322) anylist ::= anylist ANY */ yytestcase(yyruleno==322); + /* (270) input ::= cmdlist */ yytestcase(yyruleno==270); + /* (271) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==271); + /* (272) cmdlist ::= ecmd */ yytestcase(yyruleno==272); + /* (273) ecmd ::= SEMI */ yytestcase(yyruleno==273); + /* (274) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==274); + /* (275) explain ::= */ yytestcase(yyruleno==275); + /* (276) trans_opt ::= */ yytestcase(yyruleno==276); + /* (277) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==277); + /* (278) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==278); + /* (279) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==279); + /* (280) savepoint_opt ::= */ yytestcase(yyruleno==280); + /* (281) cmd ::= create_table create_table_args */ yytestcase(yyruleno==281); + /* (282) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==282); + /* (283) columnlist ::= columnname carglist */ yytestcase(yyruleno==283); + /* (284) nm ::= ID|INDEXED */ yytestcase(yyruleno==284); + /* (285) nm ::= STRING */ yytestcase(yyruleno==285); + /* (286) nm ::= JOIN_KW */ yytestcase(yyruleno==286); + /* (287) typetoken ::= typename */ yytestcase(yyruleno==287); + /* (288) typename ::= ID|STRING */ yytestcase(yyruleno==288); + /* (289) signed ::= plus_num */ yytestcase(yyruleno==289); + /* (290) signed ::= minus_num */ yytestcase(yyruleno==290); + /* (291) carglist ::= carglist ccons */ yytestcase(yyruleno==291); + /* (292) carglist ::= */ yytestcase(yyruleno==292); + /* (293) ccons ::= NULL onconf */ yytestcase(yyruleno==293); + /* (294) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==294); + /* (295) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==295); + /* (296) conslist ::= tcons */ yytestcase(yyruleno==296); + /* (297) tconscomma ::= */ yytestcase(yyruleno==297); + /* (298) defer_subclause_opt ::= defer_subclause */ yytestcase(yyruleno==298); + /* (299) resolvetype ::= raisetype */ yytestcase(yyruleno==299); + /* (300) selectnowith ::= oneselect */ yytestcase(yyruleno==300); + /* (301) oneselect ::= values */ yytestcase(yyruleno==301); + /* (302) sclp ::= selcollist COMMA */ yytestcase(yyruleno==302); + /* (303) as ::= ID|STRING */ yytestcase(yyruleno==303); + /* (304) expr ::= term */ yytestcase(yyruleno==304); + /* (305) exprlist ::= nexprlist */ yytestcase(yyruleno==305); + /* (306) nmnum ::= plus_num */ yytestcase(yyruleno==306); + /* (307) nmnum ::= nm */ yytestcase(yyruleno==307); + /* (308) nmnum ::= ON */ yytestcase(yyruleno==308); + /* (309) nmnum ::= DELETE */ yytestcase(yyruleno==309); + /* (310) nmnum ::= DEFAULT */ yytestcase(yyruleno==310); + /* (311) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==311); + /* (312) foreach_clause ::= */ yytestcase(yyruleno==312); + /* (313) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==313); + /* (314) trnm ::= nm */ yytestcase(yyruleno==314); + /* (315) tridxby ::= */ yytestcase(yyruleno==315); + /* (316) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==316); + /* (317) database_kw_opt ::= */ yytestcase(yyruleno==317); + /* (318) kwcolumn_opt ::= */ yytestcase(yyruleno==318); + /* (319) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==319); + /* (320) vtabarglist ::= vtabarg */ yytestcase(yyruleno==320); + /* (321) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==321); + /* (322) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==322); + /* (323) anylist ::= */ yytestcase(yyruleno==323); + /* (324) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==324); + /* (325) anylist ::= anylist ANY */ yytestcase(yyruleno==325); break; +/********** End reduce actions ************************************************/ }; - assert( yyruleno>=0 && yyrulenoyyidx -= yysize; 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; - /* If the reduce action popped at least - ** one element off the stack, then we can push the new element back - ** onto the stack here, and skip the stack overflow test in yy_shift(). - ** That gives a significant speed improvement. */ - if( yysize ){ - yypParser->yyidx++; - yymsp -= yysize-1; - yymsp->stateno = (YYACTIONTYPE)yyact; - yymsp->major = (YYCODETYPE)yygoto; - yymsp->minor = yygotominor; - yyTraceShift(yypParser, yyact); - }else{ - yy_shift(yypParser,yyact,yygoto,&yygotominor); - } + yypParser->yyidx -= yysize - 1; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact); }else{ assert( yyact == YY_ACCEPT_ACTION ); + yypParser->yyidx -= yysize; yy_accept(yypParser); } } @@ -129391,6 +134132,8 @@ static void yy_parse_failed( while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); /* Here code is inserted which will be executed whenever the ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } #endif /* YYNOERRORRECOVERY */ @@ -129401,14 +134144,16 @@ static void yy_parse_failed( static void yy_syntax_error( yyParser *yypParser, /* The parser */ int yymajor, /* The major type of the error token */ - YYMINORTYPE yyminor /* The minor type of the error token */ + sqlite3ParserTOKENTYPE yyminor /* The minor type of the error token */ ){ sqlite3ParserARG_FETCH; -#define TOKEN (yyminor.yy0) +#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); +/************ End %syntax_error code ******************************************/ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } @@ -129427,6 +134172,8 @@ static void yy_accept( while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); /* Here code is inserted which will be executed whenever the ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } @@ -129456,7 +134203,7 @@ SQLITE_PRIVATE void sqlite3Parser( sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ ){ YYMINORTYPE yyminorunion; - int yyact; /* The parser action. */ + unsigned int yyact; /* The parser action. */ #if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) int yyendofinput; /* True if we are at the end of input */ #endif @@ -129470,18 +134217,23 @@ SQLITE_PRIVATE void sqlite3Parser( if( yypParser->yyidx<0 ){ #if YYSTACKDEPTH<=0 if( yypParser->yystksz <=0 ){ - /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ - yyminorunion = yyzerominor; - yyStackOverflow(yypParser, &yyminorunion); + yyStackOverflow(yypParser); return; } #endif yypParser->yyidx = 0; +#ifndef YYNOERRORRECOVERY yypParser->yyerrcnt = -1; +#endif yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInitialize. Empty stack. State 0\n", + yyTracePrompt); + } +#endif } - yyminorunion.yy0 = yyminor; #if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) yyendofinput = (yymajor==0); #endif @@ -129489,7 +134241,7 @@ SQLITE_PRIVATE void sqlite3Parser( #ifndef NDEBUG if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + fprintf(yyTraceFILE,"%sInput '%s'\n",yyTracePrompt,yyTokenName[yymajor]); } #endif @@ -129497,13 +134249,16 @@ SQLITE_PRIVATE void sqlite3Parser( yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); if( yyact <= YY_MAX_SHIFTREDUCE ){ if( yyact > YY_MAX_SHIFT ) yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; - yy_shift(yypParser,yyact,yymajor,&yyminorunion); + yy_shift(yypParser,yyact,yymajor,yyminor); +#ifndef YYNOERRORRECOVERY yypParser->yyerrcnt--; +#endif yymajor = YYNOCODE; }else if( yyact <= YY_MAX_REDUCE ){ yy_reduce(yypParser,yyact-YY_MIN_REDUCE); }else{ assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; #ifdef YYERRORSYMBOL int yymx; #endif @@ -129533,7 +134288,7 @@ SQLITE_PRIVATE void sqlite3Parser( ** */ if( yypParser->yyerrcnt<0 ){ - yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_syntax_error(yypParser,yymajor,yyminor); } yymx = yypParser->yystack[yypParser->yyidx].major; if( yymx==YYERRORSYMBOL || yyerrorhit ){ @@ -129543,10 +134298,10 @@ SQLITE_PRIVATE void sqlite3Parser( yyTracePrompt,yyTokenName[yymajor]); } #endif - yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( + while( yypParser->yyidx >= 0 && yymx != YYERRORSYMBOL && (yyact = yy_find_reduce_action( @@ -129560,9 +134315,7 @@ SQLITE_PRIVATE void sqlite3Parser( yy_parse_failed(yypParser); yymajor = YYNOCODE; }else if( yymx!=YYERRORSYMBOL ){ - YYMINORTYPE u2; - u2.YYERRSYMDT = 0; - yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); } } yypParser->yyerrcnt = 3; @@ -129575,7 +134328,7 @@ SQLITE_PRIVATE void sqlite3Parser( ** Applications can set this macro (for example inside %include) if ** they intend to abandon the parse upon the first syntax error seen. */ - yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_syntax_error(yypParser,yymajor, yyminor); yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); yymajor = YYNOCODE; @@ -129590,7 +134343,7 @@ SQLITE_PRIVATE void sqlite3Parser( ** three input tokens have been successfully shifted. */ if( yypParser->yyerrcnt<=0 ){ - yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_syntax_error(yypParser,yymajor, yyminor); } yypParser->yyerrcnt = 3; yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); @@ -129603,7 +134356,12 @@ SQLITE_PRIVATE void sqlite3Parser( }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); #ifndef NDEBUG if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sReturn\n",yyTracePrompt); + int i; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE,"%c%s", i==1 ? '[' : ' ', + yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"]\n"); } #endif return; @@ -129631,12 +134389,92 @@ SQLITE_PRIVATE void sqlite3Parser( /* #include "sqliteInt.h" */ /* #include */ +/* Character classes for tokenizing +** +** In the sqlite3GetToken() 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 +** all of them need to be used within the switch. +*/ +#define CC_X 0 /* The letter 'x', or start of BLOB literal */ +#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */ +#define CC_ID 2 /* unicode characters usable in IDs */ +#define CC_DIGIT 3 /* Digits */ +#define CC_DOLLAR 4 /* '$' */ +#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */ +#define CC_VARNUM 6 /* '?'. Numeric SQL variables */ +#define CC_SPACE 7 /* Space characters */ +#define CC_QUOTE 8 /* '"', '\'', or '`'. String literals, quoted ids */ +#define CC_QUOTE2 9 /* '['. [...] style quoted ids */ +#define CC_PIPE 10 /* '|'. Bitwise OR or concatenate */ +#define CC_MINUS 11 /* '-'. Minus or SQL-style comment */ +#define CC_LT 12 /* '<'. Part of < or <= or <> */ +#define CC_GT 13 /* '>'. Part of > or >= */ +#define CC_EQ 14 /* '='. Part of = or == */ +#define CC_BANG 15 /* '!'. Part of != */ +#define CC_SLASH 16 /* '/'. / or c-style comment */ +#define CC_LP 17 /* '(' */ +#define CC_RP 18 /* ')' */ +#define CC_SEMI 19 /* ';' */ +#define CC_PLUS 20 /* '+' */ +#define CC_STAR 21 /* '*' */ +#define CC_PERCENT 22 /* '%' */ +#define CC_COMMA 23 /* ',' */ +#define CC_AND 24 /* '&' */ +#define CC_TILDA 25 /* '~' */ +#define CC_DOT 26 /* '.' */ +#define CC_ILLEGAL 27 /* Illegal character */ + +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, +/* 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, +/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1, +/* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27, +/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +#endif +#ifdef SQLITE_EBCDIC +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ +/* 0x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 7, 7, 27, 27, +/* 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, +/* 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, +/* 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, +/* 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, +/* Ex */ 27, 27, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, +/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 27, 27, 27, 27, 27, +#endif +}; + /* -** The charMap() macro maps alphabetic characters into their +** The charMap() macro maps alphabetic characters (only) into their ** lower-case ASCII equivalent. On ASCII machines, this is just ** an upper-to-lower case map. On EBCDIC machines we also need -** to adjust the encoding. Only alphabetic characters and underscores -** need to be translated. +** to adjust the encoding. The mapping is only valid for alphabetics +** which are the only characters for which this feature is used. +** +** Used by keywordhash.h */ #ifdef SQLITE_ASCII # define charMap(X) sqlite3UpperToLower[(unsigned char)X] @@ -129670,7 +134508,7 @@ const unsigned char ebcdicToAscii[] = { ** returned. If the input is not a keyword, TK_ID is returned. ** ** The implementation of this routine was generated by a program, -** mkkeywordhash.h, located in the tool subdirectory of the distribution. +** mkkeywordhash.c, located in the tool subdirectory of the distribution. ** The output of the mkkeywordhash.c program is written into a file ** named keywordhash.h and then included into this source file by ** the #include below. @@ -129691,7 +134529,7 @@ const unsigned char ebcdicToAscii[] = { ** on platforms with limited memory. */ /* Hash score: 182 */ -static int keywordCode(const char *z, int n){ +static int keywordCode(const char *z, int n, int *pType){ /* zText[] encodes 834 bytes of keywords in 554 bytes */ /* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ /* ABLEFTHENDEFERRABLELSEXCEPTRANSACTIONATURALTERAISEXCLUSIVE */ @@ -129811,13 +134649,21 @@ static int keywordCode(const char *z, int n){ TK_JOIN_KW, TK_ROLLBACK, TK_ROW, TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_INITIALLY, TK_ALL, }; - int h, i; - if( n<2 ) return TK_ID; - h = ((charMap(z[0])*4) ^ - (charMap(z[n-1])*3) ^ - n) % 127; - for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){ - if( aLen[i]==n && sqlite3StrNICmp(&zText[aOffset[i]],z,n)==0 ){ + 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; + j = 0; + zKW = &zText[aOffset[i]]; +#ifdef SQLITE_ASCII + while( j': { + case CC_GT: { if( (c=z[1])=='=' ){ *tokenType = TK_GE; return 2; @@ -130091,16 +134942,16 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ return 1; } } - case '!': { + case CC_BANG: { if( z[1]!='=' ){ *tokenType = TK_ILLEGAL; - return 2; + return 1; }else{ *tokenType = TK_NE; return 2; } } - case '|': { + case CC_PIPE: { if( z[1]!='|' ){ *tokenType = TK_BITOR; return 1; @@ -130109,21 +134960,19 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ return 2; } } - case ',': { + case CC_COMMA: { *tokenType = TK_COMMA; return 1; } - case '&': { + case CC_AND: { *tokenType = TK_BITAND; return 1; } - case '~': { + case CC_TILDA: { *tokenType = TK_BITNOT; return 1; } - case '`': - case '\'': - case '"': { + case CC_QUOTE: { int delim = z[0]; testcase( delim=='`' ); testcase( delim=='\'' ); @@ -130148,7 +134997,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ return i; } } - case '.': { + case CC_DOT: { #ifndef SQLITE_OMIT_FLOATING_POINT if( !sqlite3Isdigit(z[1]) ) #endif @@ -130159,8 +135008,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ /* If the next character is a digit, this is a floating point ** number that begins with ".". Fall thru into the next case */ } - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': { + case CC_DIGIT: { testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); @@ -130195,22 +135043,18 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } return i; } - case '[': { + case CC_QUOTE2: { for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} *tokenType = c==']' ? TK_ID : TK_ILLEGAL; return i; } - case '?': { + case CC_VARNUM: { *tokenType = TK_VARIABLE; for(i=1; sqlite3Isdigit(z[i]); i++){} return i; } -#ifndef SQLITE_OMIT_TCL_VARIABLE - case '$': -#endif - case '@': /* For compatibility with MS SQL Server */ - case '#': - case ':': { + case CC_DOLLAR: + case CC_VARALPHA: { int n = 0; testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); @@ -130239,8 +135083,20 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ if( n==0 ) *tokenType = TK_ILLEGAL; return i; } + case CC_KYWD: { + for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} + if( IdChar(z[i]) ){ + /* This token started out using characters that can appear in keywords, + ** but z[i] is a character not allowed within keywords, so this must + ** be an identifier instead */ + i++; + break; + } + *tokenType = TK_ID; + return keywordCode((char*)z, i, tokenType); + } + case CC_X: { #ifndef SQLITE_OMIT_BLOB_LITERAL - case 'x': case 'X': { testcase( z[0]=='x' ); testcase( z[0]=='X' ); if( z[1]=='\'' ){ *tokenType = TK_BLOB; @@ -130252,20 +135108,22 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ if( z[i] ) i++; return i; } - /* Otherwise fall through to the next case */ - } #endif + /* If it is not a BLOB literal, then it must be an ID, since no + ** SQL keywords start with the letter 'x'. Fall through */ + } + case CC_ID: { + i = 1; + break; + } default: { - if( !IdChar(*z) ){ - break; - } - for(i=1; IdChar(z[i]); i++){} - *tokenType = keywordCode((char*)z, i); - return i; + *tokenType = TK_ILLEGAL; + return 1; } } - *tokenType = TK_ILLEGAL; - return 1; + while( IdChar(z[i]) ){ i++; } + *tokenType = TK_ID; + return i; } /* @@ -130281,7 +135139,6 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr void *pEngine; /* The LEMON-generated LALR(1) parser */ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ - u8 enableLookaside; /* Saved value of db->lookaside.bEnabled */ sqlite3 *db = pParse->db; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ @@ -130297,17 +135154,15 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr /* sqlite3ParserTrace(stdout, "parser: "); */ pEngine = sqlite3ParserAlloc(sqlite3Malloc); if( pEngine==0 ){ - db->mallocFailed = 1; - return SQLITE_NOMEM; + sqlite3OomFault(db); + return SQLITE_NOMEM_BKPT; } assert( pParse->pNewTable==0 ); assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); assert( pParse->nzVar==0 ); assert( pParse->azVar==0 ); - enableLookaside = db->lookaside.bEnabled; - if( db->lookaside.pStart ) db->lookaside.bEnabled = 1; - while( !db->mallocFailed && zSql[i]!=0 ){ + while( zSql[i]!=0 ){ assert( i>=0 ); pParse->sLastToken.z = &zSql[i]; pParse->sLastToken.n = sqlite3GetToken((unsigned char*)&zSql[i],&tokenType); @@ -130316,41 +135171,29 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr pParse->rc = SQLITE_TOOBIG; break; } - switch( tokenType ){ - case TK_SPACE: { - if( db->u1.isInterrupted ){ - sqlite3ErrorMsg(pParse, "interrupt"); - pParse->rc = SQLITE_INTERRUPT; - goto abort_parse; - } + if( tokenType>=TK_SPACE ){ + assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); + if( db->u1.isInterrupted ){ + pParse->rc = SQLITE_INTERRUPT; break; } - case TK_ILLEGAL: { + if( tokenType==TK_ILLEGAL ){ sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &pParse->sLastToken); - goto abort_parse; - } - case TK_SEMI: { - pParse->zTail = &zSql[i]; - /* Fall thru into the default case */ - } - default: { - sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse); - lastTokenParsed = tokenType; - if( pParse->rc!=SQLITE_OK ){ - goto abort_parse; - } break; } + }else{ + sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse); + lastTokenParsed = tokenType; + if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break; } } -abort_parse: assert( nErr==0 ); + pParse->zTail = &zSql[i]; if( pParse->rc==SQLITE_OK && db->mallocFailed==0 ){ assert( zSql[i]==0 ); if( lastTokenParsed!=TK_SEMI ){ sqlite3Parser(pEngine, TK_SEMI, pParse->sLastToken, pParse); - pParse->zTail = &zSql[i]; } if( pParse->rc==SQLITE_OK && db->mallocFailed==0 ){ sqlite3Parser(pEngine, 0, pParse->sLastToken, pParse); @@ -130358,15 +135201,14 @@ abort_parse: } #ifdef YYTRACKMAXSTACKDEPTH sqlite3_mutex_enter(sqlite3MallocMutex()); - sqlite3StatusSet(SQLITE_STATUS_PARSER_STACK, + sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK, sqlite3ParserStackPeak(pEngine) ); sqlite3_mutex_leave(sqlite3MallocMutex()); #endif /* YYDEBUG */ sqlite3ParserFree(pEngine, sqlite3_free); - db->lookaside.bEnabled = enableLookaside; if( db->mallocFailed ){ - pParse->rc = SQLITE_NOMEM; + 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)); @@ -130401,7 +135243,7 @@ abort_parse: sqlite3DeleteTable(db, pParse->pNewTable); } - if( pParse->bFreeWith ) sqlite3WithDelete(db, pParse->pWith); + 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); @@ -130704,7 +135546,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_complete16(const void *zSql){ if( zSql8 ){ rc = sqlite3_complete(zSql8); }else{ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } sqlite3ValueFree(pVal); return rc & 0xff; @@ -130994,7 +135836,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_initialize(void){ sqlite3GlobalConfig.pInitMutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); if( sqlite3GlobalConfig.bCoreMutex && !sqlite3GlobalConfig.pInitMutex ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } } } @@ -131025,10 +135867,15 @@ SQLITE_API int SQLITE_STDCALL sqlite3_initialize(void){ */ sqlite3_mutex_enter(sqlite3GlobalConfig.pInitMutex); if( sqlite3GlobalConfig.isInit==0 && sqlite3GlobalConfig.inProgress==0 ){ - FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); sqlite3GlobalConfig.inProgress = 1; - memset(pHash, 0, sizeof(sqlite3GlobalFunctions)); - sqlite3RegisterGlobalFunctions(); +#ifdef SQLITE_ENABLE_SQLLOG + { + extern void sqlite3_init_sqllog(void); + sqlite3_init_sqllog(); + } +#endif + memset(&sqlite3BuiltinFunctions, 0, sizeof(sqlite3BuiltinFunctions)); + sqlite3RegisterBuiltinFunctions(); if( sqlite3GlobalConfig.isPCacheInit==0 ){ rc = sqlite3PcacheInitialize(); } @@ -131246,9 +136093,10 @@ SQLITE_API int SQLITE_CDECL sqlite3_config(int op, ...){ break; } case SQLITE_CONFIG_PAGECACHE: { - /* EVIDENCE-OF: R-31408-40510 There are three arguments to - ** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory, the size - ** of each page buffer (sz), and the number of pages (N). */ + /* EVIDENCE-OF: R-18761-36601 There are three arguments to + ** 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); @@ -131434,6 +136282,11 @@ SQLITE_API int SQLITE_CDECL sqlite3_config(int op, ...){ break; } + case SQLITE_CONFIG_STMTJRNL_SPILL: { + sqlite3GlobalConfig.nStmtSpill = va_arg(ap, int); + break; + } + default: { rc = SQLITE_ERROR; break; @@ -131498,12 +136351,12 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ p = (LookasideSlot*)&((u8*)p)[sz]; } db->lookaside.pEnd = p; - db->lookaside.bEnabled = 1; + db->lookaside.bDisable = 0; db->lookaside.bMalloced = pBuf==0 ?1:0; }else{ db->lookaside.pStart = db; db->lookaside.pEnd = db; - db->lookaside.bEnabled = 0; + db->lookaside.bDisable = 1; db->lookaside.bMalloced = 0; } #endif /* SQLITE_OMIT_LOOKASIDE */ @@ -131547,6 +136400,36 @@ SQLITE_API int SQLITE_STDCALL sqlite3_db_release_memory(sqlite3 *db){ return SQLITE_OK; } +/* +** Flush any dirty pages in the pager-cache for any attached database +** to disk. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_db_cacheflush(sqlite3 *db){ + int i; + int rc = SQLITE_OK; + int bSeenBusy = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + for(i=0; rc==SQLITE_OK && inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsInTrans(pBt) ){ + Pager *pPager = sqlite3BtreePager(pBt); + rc = sqlite3PagerFlush(pPager); + if( rc==SQLITE_BUSY ){ + bSeenBusy = 1; + rc = SQLITE_OK; + } + } + } + sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); + return ((rc==SQLITE_OK && bSeenBusy) ? SQLITE_BUSY : rc); +} + /* ** Configuration settings for an individual database connection */ @@ -131567,8 +136450,10 @@ SQLITE_API int SQLITE_CDECL sqlite3_db_config(sqlite3 *db, int op, ...){ int op; /* The opcode */ u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ } aFlagOp[] = { - { SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys }, - { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, + { SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys }, + { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, + { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, + { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -131728,7 +136613,7 @@ SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){ ** with SQLITE_ANY as the encoding. */ static void functionDestroy(sqlite3 *db, FuncDef *p){ - FuncDestructor *pDestructor = p->pDestructor; + FuncDestructor *pDestructor = p->u.pDestructor; if( pDestructor ){ pDestructor->nRef--; if( pDestructor->nRef==0 ){ @@ -131910,18 +136795,17 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ */ sqlite3ConnectionClosed(db); - for(j=0; jaFunc.a); j++){ - FuncDef *pNext, *pHash, *p; - for(p=db->aFunc.a[j]; p; p=pHash){ - pHash = p->pHash; - while( p ){ - functionDestroy(db, p); - pNext = p->pNext; - sqlite3DbFree(db, p); - p = pNext; - } - } + for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){ + FuncDef *pNext, *p; + p = sqliteHashData(i); + do{ + functionDestroy(db, p); + pNext = p->pNext; + sqlite3DbFree(db, p); + p = pNext; + }while( p ); } + sqlite3HashClear(&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. */ @@ -132345,7 +137229,7 @@ SQLITE_PRIVATE int sqlite3CreateFunc( int nArg, int enc, void *pUserData, - void (*xFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xSFunc)(sqlite3_context*,int,sqlite3_value **), void (*xStep)(sqlite3_context*,int,sqlite3_value **), void (*xFinal)(sqlite3_context*), FuncDestructor *pDestructor @@ -132356,9 +137240,9 @@ SQLITE_PRIVATE int sqlite3CreateFunc( assert( sqlite3_mutex_held(db->mutex) ); if( zFunctionName==0 || - (xFunc && (xFinal || xStep)) || - (!xFunc && (xFinal && !xStep)) || - (!xFunc && (!xFinal && xStep)) || + (xSFunc && (xFinal || xStep)) || + (!xSFunc && (xFinal && !xStep)) || + (!xSFunc && (!xFinal && xStep)) || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) || (255<(nName = sqlite3Strlen30( zFunctionName))) ){ return SQLITE_MISUSE_BKPT; @@ -132381,10 +137265,10 @@ SQLITE_PRIVATE int sqlite3CreateFunc( }else if( enc==SQLITE_ANY ){ int rc; rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8|extraFlags, - pUserData, xFunc, xStep, xFinal, pDestructor); + pUserData, xSFunc, xStep, xFinal, pDestructor); if( rc==SQLITE_OK ){ rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE|extraFlags, - pUserData, xFunc, xStep, xFinal, pDestructor); + pUserData, xSFunc, xStep, xFinal, pDestructor); } if( rc!=SQLITE_OK ){ return rc; @@ -132400,7 +137284,7 @@ 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, nName, nArg, (u8)enc, 0); + p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0); if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==enc && p->nArg==nArg ){ if( db->nVdbeActive ){ sqlite3ErrorWithMsg(db, SQLITE_BUSY, @@ -132412,10 +137296,10 @@ SQLITE_PRIVATE int sqlite3CreateFunc( } } - p = sqlite3FindFunction(db, zFunctionName, nName, nArg, (u8)enc, 1); + p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1); assert(p || db->mallocFailed); if( !p ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } /* If an older version of the function with a configured destructor is @@ -132425,11 +137309,10 @@ SQLITE_PRIVATE int sqlite3CreateFunc( if( pDestructor ){ pDestructor->nRef++; } - p->pDestructor = pDestructor; + p->u.pDestructor = pDestructor; p->funcFlags = (p->funcFlags & SQLITE_FUNC_ENCMASK) | extraFlags; testcase( p->funcFlags & SQLITE_DETERMINISTIC ); - p->xFunc = xFunc; - p->xStep = xStep; + p->xSFunc = xSFunc ? xSFunc : xStep; p->xFinalize = xFinal; p->pUserData = pUserData; p->nArg = (u16)nArg; @@ -132445,11 +137328,11 @@ SQLITE_API int SQLITE_STDCALL sqlite3_create_function( int nArg, int enc, void *p, - void (*xFunc)(sqlite3_context*,int,sqlite3_value **), + 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, xFunc, xStep, + return sqlite3_create_function_v2(db, zFunc, nArg, enc, p, xSFunc, xStep, xFinal, 0); } @@ -132459,7 +137342,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_create_function_v2( int nArg, int enc, void *p, - void (*xFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xSFunc)(sqlite3_context*,int,sqlite3_value **), void (*xStep)(sqlite3_context*,int,sqlite3_value **), void (*xFinal)(sqlite3_context*), void (*xDestroy)(void *) @@ -132482,7 +137365,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_create_function_v2( pArg->xDestroy = xDestroy; pArg->pUserData = p; } - rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xFunc, xStep, xFinal, pArg); + rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xSFunc, xStep, xFinal, pArg); if( pArg && pArg->nRef==0 ){ assert( rc!=SQLITE_OK ); xDestroy(p); @@ -132502,7 +137385,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_create_function16( int nArg, int eTextRep, void *p, - void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xSFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ){ @@ -132515,7 +137398,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_create_function16( sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE); - rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal,0); + rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xSFunc,xStep,xFinal,0); sqlite3DbFree(db, zFunc8); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); @@ -132541,7 +137424,6 @@ SQLITE_API int SQLITE_STDCALL sqlite3_overload_function( const char *zName, int nArg ){ - int nName = sqlite3Strlen30(zName); int rc = SQLITE_OK; #ifdef SQLITE_ENABLE_API_ARMOR @@ -132550,7 +137432,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_overload_function( } #endif sqlite3_mutex_enter(db->mutex); - if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ + if( sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)==0 ){ rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, 0, sqlite3InvalidFunction, 0, 0, 0); } @@ -132690,6 +137572,27 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook( return pRet; } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Register a callback to be invoked each time a row is updated, +** inserted or deleted using this database connection. +*/ +SQLITE_API void *SQLITE_STDCALL sqlite3_preupdate_hook( + sqlite3 *db, /* Attach the hook to this database */ + void(*xCallback)( /* Callback function */ + void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64), + void *pArg /* First callback argument */ +){ + void *pRet; + sqlite3_mutex_enter(db->mutex); + pRet = db->pPreUpdateArg; + db->xPreUpdateCallback = xCallback; + db->pPreUpdateArg = pArg; + sqlite3_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(). @@ -132920,14 +137823,14 @@ SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3 *db){ SQLITE_API const char *SQLITE_STDCALL sqlite3_errmsg(sqlite3 *db){ const char *z; if( !db ){ - return sqlite3ErrStr(SQLITE_NOMEM); + return sqlite3ErrStr(SQLITE_NOMEM_BKPT); } if( !sqlite3SafetyCheckSickOrOk(db) ){ return sqlite3ErrStr(SQLITE_MISUSE_BKPT); } sqlite3_mutex_enter(db->mutex); if( db->mallocFailed ){ - z = sqlite3ErrStr(SQLITE_NOMEM); + z = sqlite3ErrStr(SQLITE_NOMEM_BKPT); }else{ testcase( db->pErr==0 ); z = (char*)sqlite3_value_text(db->pErr); @@ -132979,7 +137882,7 @@ SQLITE_API const void *SQLITE_STDCALL sqlite3_errmsg16(sqlite3 *db){ ** be cleared before returning. Do this directly, instead of via ** sqlite3ApiExit(), to avoid setting the database handle error message. */ - db->mallocFailed = 0; + sqlite3OomClear(db); } sqlite3_mutex_leave(db->mutex); return z; @@ -132995,7 +137898,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_errcode(sqlite3 *db){ return SQLITE_MISUSE_BKPT; } if( !db || db->mallocFailed ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } return db->errCode & db->errMask; } @@ -133004,10 +137907,13 @@ SQLITE_API int SQLITE_STDCALL sqlite3_extended_errcode(sqlite3 *db){ return SQLITE_MISUSE_BKPT; } if( !db || db->mallocFailed ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; } return db->errCode; } +SQLITE_API int SQLITE_STDCALL sqlite3_system_errno(sqlite3 *db){ + return db ? db->iSysErrno : 0; +} /* ** Return a string that describes the kind of error specified in the @@ -133084,7 +137990,7 @@ static int createCollation( } pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 1); - if( pColl==0 ) return SQLITE_NOMEM; + if( pColl==0 ) return SQLITE_NOMEM_BKPT; pColl->xCmp = xCompare; pColl->pUser = pCtx; pColl->xDel = xDel; @@ -133132,8 +138038,8 @@ static const int aHardLimit[] = { #if SQLITE_MAX_VDBE_OP<40 # error SQLITE_MAX_VDBE_OP must be at least 40 #endif -#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>1000 -# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 1000 +#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127 +# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127 #endif #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 # error SQLITE_MAX_ATTACHED must be between 0 and 125 @@ -133263,7 +138169,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( for(iIn=0; iInaCollSeq); @@ -133599,9 +138508,9 @@ static int openDatabase( ** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating ** functions: */ - createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0); - createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0); - createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0); + 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, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0); if( db->mallocFailed ){ @@ -133610,14 +138519,14 @@ static int openDatabase( /* EVIDENCE-OF: R-08308-17224 The default collating function for all ** strings is BINARY. */ - db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0); + db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0); assert( db->pDfltColl!=0 ); /* Parse the filename/URI argument. */ db->openFlags = flags; rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); if( rc!=SQLITE_OK ){ - if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; + if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg); sqlite3_free(zErrMsg); goto opendb_out; @@ -133628,7 +138537,7 @@ static int openDatabase( flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM_BKPT; } sqlite3Error(db, rc); goto opendb_out; @@ -133639,13 +138548,13 @@ static int openDatabase( sqlite3BtreeLeave(db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); - /* The default safety_level for the main database is 'full'; for the temp - ** database it is 'NONE'. This matches the pager layer defaults. + /* The default safety_level for the main database is FULL; for the temp + ** database it is OFF. This matches the pager layer defaults. */ db->aDb[0].zName = "main"; - db->aDb[0].safety_level = 3; + db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; db->aDb[1].zName = "temp"; - db->aDb[1].safety_level = 1; + db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; db->magic = SQLITE_MAGIC_OPEN; if( db->mallocFailed ){ @@ -133657,7 +138566,7 @@ static int openDatabase( ** is accessed. */ sqlite3Error(db, SQLITE_OK); - sqlite3RegisterBuiltinFunctions(db); + sqlite3RegisterPerConnectionBuiltinFunctions(db); /* Load automatic extensions - extensions that have been registered ** using the sqlite3_automatic_extension() API. @@ -133740,7 +138649,6 @@ static int openDatabase( sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT); opendb_out: - sqlite3_free(zOpen); if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); @@ -133762,6 +138670,22 @@ opendb_out: sqlite3GlobalConfig.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; izType; + zDataType = sqlite3ColumnType(pCol,0); zCollSeq = pCol->zColl; notnull = pCol->notNull!=0; primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0; @@ -134095,7 +139024,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( primarykey = 1; } if( !zCollSeq ){ - zCollSeq = "BINARY"; + zCollSeq = sqlite3StrBINARY; } error_out: @@ -134176,6 +139105,12 @@ SQLITE_API int SQLITE_STDCALL sqlite3_file_control(sqlite3 *db, const char *zDbN if( op==SQLITE_FCNTL_FILE_POINTER ){ *(sqlite3_file**)pArg = fd; rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_VFS_POINTER ){ + *(sqlite3_vfs**)pArg = sqlite3PagerVfs(pPager); + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_JOURNAL_POINTER ){ + *(sqlite3_file**)pArg = sqlite3PagerJrnlFile(pPager); + rc = SQLITE_OK; }else if( fd->pMethods ){ rc = sqlite3OsFileControl(fd, op, pArg); }else{ @@ -134316,7 +139251,7 @@ SQLITE_API int SQLITE_CDECL sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_ASSERT: { volatile int x = 0; - assert( (x = va_arg(ap,int))!=0 ); + assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 ); rc = x; break; } @@ -134619,6 +139554,88 @@ SQLITE_API int SQLITE_STDCALL sqlite3_db_readonly(sqlite3 *db, const char *zDbNa return pBt ? sqlite3BtreeIsReadonly(pBt) : -1; } +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Obtain a snapshot handle for the snapshot of database zDb currently +** being read by handle db. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_snapshot_get( + sqlite3 *db, + const char *zDb, + sqlite3_snapshot **ppSnapshot +){ + int rc = SQLITE_ERROR; +#ifndef SQLITE_OMIT_WAL + int iDb; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_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); + } + } + } + + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + +/* +** Open a read-transaction on the snapshot idendified by pSnapshot. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_snapshot_open( + sqlite3 *db, + const char *zDb, + sqlite3_snapshot *pSnapshot +){ + int rc = SQLITE_ERROR; +#ifndef SQLITE_OMIT_WAL + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->autoCommit==0 ){ + int iDb; + iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( 0==sqlite3BtreeIsInReadTrans(pBt) ){ + rc = sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), pSnapshot); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0); + } + } + } + } + + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + +/* +** Free a snapshot handle obtained from sqlite3_snapshot_get(). +*/ +SQLITE_API void SQLITE_STDCALL sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){ + sqlite3_free(pSnapshot); +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + /************** End of main.c ************************************************/ /************** Begin file notify.c ******************************************/ /* @@ -135268,6 +140285,12 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ # define NDEBUG 1 #endif +/* FTS3/FTS4 require virtual tables */ +#ifdef SQLITE_OMIT_VIRTUALTABLE +# undef SQLITE_ENABLE_FTS3 +# undef SQLITE_ENABLE_FTS4 +#endif + /* ** FTS4 is really an extension for FTS3. It is enabled using the ** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all @@ -144726,6 +149749,18 @@ SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule( /* #include */ /* #include */ +/* +** Return true if the two-argument version of fts3_tokenizer() +** has been activated via a prior call to sqlite3_db_config(db, +** SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); +*/ +static int fts3TokenizerEnabled(sqlite3_context *context){ + sqlite3 *db = sqlite3_context_db_handle(context); + int isEnabled = 0; + sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled); + return isEnabled; +} + /* ** Implementation of the SQL scalar function for accessing the underlying ** hash table. This function may be called as follows: @@ -144746,7 +149781,7 @@ SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule( ** is a blob containing the pointer stored as the hash data corresponding ** to string (after the hash-table is updated, if applicable). */ -static void scalarFunc( +static void fts3TokenizerFunc( sqlite3_context *context, int argc, sqlite3_value **argv @@ -144764,16 +149799,20 @@ static void scalarFunc( nName = sqlite3_value_bytes(argv[0])+1; if( argc==2 ){ - void *pOld; - int n = sqlite3_value_bytes(argv[1]); - if( zName==0 || n!=sizeof(pPtr) ){ - sqlite3_result_error(context, "argument type mismatch", -1); - return; - } - pPtr = *(void **)sqlite3_value_blob(argv[1]); - pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr); - if( pOld==pPtr ){ - sqlite3_result_error(context, "out of memory", -1); + if( fts3TokenizerEnabled(context) ){ + void *pOld; + int n = sqlite3_value_bytes(argv[1]); + if( zName==0 || n!=sizeof(pPtr) ){ + sqlite3_result_error(context, "argument type mismatch", -1); + return; + } + pPtr = *(void **)sqlite3_value_blob(argv[1]); + pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr); + if( pOld==pPtr ){ + sqlite3_result_error(context, "out of memory", -1); + } + }else{ + sqlite3_result_error(context, "fts3tokenize disabled", -1); return; } }else{ @@ -144787,7 +149826,6 @@ static void scalarFunc( return; } } - sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT); } @@ -145047,6 +150085,7 @@ int registerTokenizer( return sqlite3_finalize(pStmt); } + static int queryTokenizer( sqlite3 *db, @@ -145117,11 +150156,13 @@ static void intTestFunc( assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") ); /* Test the storage function */ - rc = registerTokenizer(db, "nosuchtokenizer", p1); - assert( rc==SQLITE_OK ); - rc = queryTokenizer(db, "nosuchtokenizer", &p2); - assert( rc==SQLITE_OK ); - assert( p2==p1 ); + if( fts3TokenizerEnabled(context) ){ + rc = registerTokenizer(db, "nosuchtokenizer", p1); + assert( rc==SQLITE_OK ); + rc = queryTokenizer(db, "nosuchtokenizer", &p2); + assert( rc==SQLITE_OK ); + assert( p2==p1 ); + } sqlite3_result_text(context, "ok", -1, SQLITE_STATIC); } @@ -145137,7 +150178,7 @@ static void intTestFunc( ** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); ** ** This function adds a scalar function (see header comment above -** scalarFunc() in this file for details) and, if ENABLE_TABLE is +** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is ** defined at compilation time, a temporary virtual table (see header ** comment above struct HashTableVtab) to the database schema. Both ** provide read/write access to the contents of *pHash. @@ -145166,10 +150207,10 @@ SQLITE_PRIVATE int sqlite3Fts3InitHashTable( #endif if( SQLITE_OK==rc ){ - rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0); + rc = sqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0); } if( SQLITE_OK==rc ){ - rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0); + rc = sqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0); } #ifdef SQLITE_TEST if( SQLITE_OK==rc ){ @@ -146221,7 +151262,8 @@ static int fts3SqlStmt( ** of the oldest level in the db that contains at least ? segments. Or, ** if no level in the FTS index contains more than ? segments, the statement ** returns zero rows. */ -/* 28 */ "SELECT level FROM %Q.'%q_segdir' GROUP BY level HAVING count(*)>=?" +/* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' " + " GROUP BY level HAVING cnt>=?" " ORDER BY (level %% 1024) ASC LIMIT 1", /* Estimate the upper limit on the number of leaf nodes in a new segment @@ -149082,7 +154124,7 @@ static int fts3SegmentMerge( ** segment. The level of the new segment is equal to the numerically ** greatest segment level currently present in the database for this ** index. The idx of the new segment is always 0. */ - if( csr.nSegment==1 ){ + if( csr.nSegment==1 && 0==fts3SegReaderIsPending(csr.apSegment[0]) ){ rc = SQLITE_DONE; goto finished; } @@ -150724,10 +155766,11 @@ 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, nMin); + sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin)); if( sqlite3_step(pFindLevel)==SQLITE_ROW ){ iAbsLevel = sqlite3_column_int64(pFindLevel, 0); - nSeg = nMin; + nSeg = sqlite3_column_int(pFindLevel, 1); + assert( nSeg>=2 ); }else{ nSeg = -1; } @@ -155781,7 +160824,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ return SQLITE_NOMEM; } - nRow = pRtree->nRowEst / (iIdx + 1); + nRow = pRtree->nRowEst >> (iIdx/2); pIdxInfo->estimatedCost = (double)6.0 * (double)nRow; setEstimatedRows(pIdxInfo, nRow); @@ -157615,6 +162658,38 @@ static void xFree(void *p){ sqlite3_free(p); } +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. It is copied here from SQLite source +** code file utf8.c. +*/ +static const unsigned char icuUtf8Trans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; + +#define SQLITE_ICU_READ_UTF8(zIn, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = icuUtf8Trans1[c-0xc0]; \ + while( (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + } + +#define SQLITE_ICU_SKIP_UTF8(zIn) \ + assert( *zIn ); \ + if( *(zIn++)>=0xc0 ){ \ + while( (*zIn & 0xc0)==0x80 ){zIn++;} \ + } + + /* ** Compare two UTF-8 strings for equality where the first string is ** a "LIKE" expression. Return true (1) if they are the same and @@ -157628,16 +162703,14 @@ static int icuLikeCompare( static const int MATCH_ONE = (UChar32)'_'; static const int MATCH_ALL = (UChar32)'%'; - int iPattern = 0; /* Current byte index in zPattern */ - int iString = 0; /* Current byte index in zString */ - int prevEscape = 0; /* True if the previous character was uEsc */ - while( zPattern[iPattern]!=0 ){ + while( 1 ){ /* Read (and consume) the next character from the input pattern. */ UChar32 uPattern; - U8_NEXT_UNSAFE(zPattern, iPattern, uPattern); + SQLITE_ICU_READ_UTF8(zPattern, uPattern); + if( uPattern==0 ) break; /* There are now 4 possibilities: ** @@ -157654,28 +162727,28 @@ static int icuLikeCompare( ** MATCH_ALL. For each MATCH_ONE, skip one character in the ** test string. */ - while( (c=zPattern[iPattern]) == MATCH_ALL || c == MATCH_ONE ){ + while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){ if( c==MATCH_ONE ){ - if( zString[iString]==0 ) return 0; - U8_FWD_1_UNSAFE(zString, iString); + if( *zString==0 ) return 0; + SQLITE_ICU_SKIP_UTF8(zString); } - iPattern++; + zPattern++; } - if( zPattern[iPattern]==0 ) return 1; + if( *zPattern==0 ) return 1; - while( zString[iString] ){ - if( icuLikeCompare(&zPattern[iPattern], &zString[iString], uEsc) ){ + while( *zString ){ + if( icuLikeCompare(zPattern, zString, uEsc) ){ return 1; } - U8_FWD_1_UNSAFE(zString, iString); + SQLITE_ICU_SKIP_UTF8(zString); } return 0; }else if( !prevEscape && uPattern==MATCH_ONE ){ /* Case 2. */ - if( zString[iString]==0 ) return 0; - U8_FWD_1_UNSAFE(zString, iString); + if( *zString==0 ) return 0; + SQLITE_ICU_SKIP_UTF8(zString); }else if( !prevEscape && uPattern==uEsc){ /* Case 3. */ @@ -157684,7 +162757,7 @@ static int icuLikeCompare( }else{ /* Case 4. */ UChar32 uString; - U8_NEXT_UNSAFE(zString, iString, uString); + SQLITE_ICU_READ_UTF8(zString, uString); uString = u_foldCase(uString, U_FOLD_CASE_DEFAULT); uPattern = u_foldCase(uPattern, U_FOLD_CASE_DEFAULT); if( uString!=uPattern ){ @@ -157694,7 +162767,7 @@ static int icuLikeCompare( } } - return zString[iString]==0; + return *zString==0; } /* @@ -157879,15 +162952,17 @@ 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){ - const UChar *zInput; - UChar *zOutput; - int nInput; - int nOutput; - - UErrorCode status = U_ZERO_ERROR; + const UChar *zInput; /* Pointer to input string */ + UChar *zOutput = 0; /* Pointer to output buffer */ + int nInput; /* Size of utf-16 input string in bytes */ + int nOut; /* Size of output buffer in bytes */ + int cnt; + int bToUpper; /* True for toupper(), false for tolower() */ + UErrorCode status; const char *zLocale = 0; assert(nArg==1 || nArg==2); + bToUpper = (sqlite3_user_data(p)!=0); if( nArg==2 ){ zLocale = (const char *)sqlite3_value_text(apArg[1]); } @@ -157896,26 +162971,38 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ if( !zInput ){ return; } - nInput = sqlite3_value_bytes16(apArg[0]); - - nOutput = nInput * 2 + 2; - zOutput = sqlite3_malloc(nOutput); - if( !zOutput ){ + nOut = nInput = sqlite3_value_bytes16(apArg[0]); + if( nOut==0 ){ + sqlite3_result_text16(p, "", 0, SQLITE_STATIC); return; } - if( sqlite3_user_data(p) ){ - u_strToUpper(zOutput, nOutput/2, zInput, nInput/2, zLocale, &status); - }else{ - u_strToLower(zOutput, nOutput/2, zInput, nInput/2, zLocale, &status); - } + for(cnt=0; cnt<2; cnt++){ + UChar *zNew = sqlite3_realloc(zOutput, nOut); + if( zNew==0 ){ + sqlite3_free(zOutput); + sqlite3_result_error_nomem(p); + return; + } + zOutput = zNew; + status = U_ZERO_ERROR; + if( bToUpper ){ + nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + }else{ + nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + } - if( !U_SUCCESS(status) ){ - icuFunctionError(p, "u_strToLower()/u_strToUpper", status); + if( U_SUCCESS(status) ){ + sqlite3_result_text16(p, zOutput, nOut, xFree); + }else if( status==U_BUFFER_OVERFLOW_ERROR ){ + assert( cnt==0 ); + continue; + }else{ + icuFunctionError(p, bToUpper ? "u_strToUpper" : "u_strToLower", status); + } return; } - - sqlite3_result_text16(p, zOutput, -1, xFree); + assert( 0 ); /* Unreachable */ } /* @@ -158732,6 +163819,38 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( const char *zState ); +/* +** Open an RBU handle to perform an RBU vacuum on database file zTarget. +** An RBU vacuum is similar to SQLite's built-in VACUUM command, except +** that it can be suspended and resumed like an RBU update. +** +** The second argument to this function, which may not be NULL, 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 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 +** 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. +** +** 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 +** of the state tables within the state database are zeroed. This way, +** the next call to sqlite3rbu_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 +** a description of the complications associated with using RBU with +** zipvfs databases. +*/ +SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_vacuum( + const char *zTarget, + const char *zState +); + /* ** Internally, each RBU connection uses a separate SQLite database ** connection to access the target and rbu update databases. This @@ -158759,6 +163878,9 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( ** 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. +** +** Database handles returned by this function remain valid until the next +** call to any sqlite3rbu_xxx() function other than sqlite3rbu_db(). */ SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3rbu_db(sqlite3rbu*, int bRbu); @@ -158815,6 +163937,48 @@ SQLITE_API int SQLITE_STDCALL sqlite3rbu_close(sqlite3rbu *pRbu, char **pzErrmsg */ SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3rbu_progress(sqlite3rbu *pRbu); +/* +** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100) +** progress indications for the two stages of an RBU update. This API may +** be useful for driving GUI progress indicators and similar. +** +** An RBU update is divided into two stages: +** +** * Stage 1, in which changes are accumulated in an oal/wal file, and +** * Stage 2, in which the contents of the wal file are copied into the +** main database. +** +** The update is visible to non-RBU clients during stage 2. During stage 1 +** non-RBU reader clients may see the original database. +** +** If this API is called during stage 2 of the update, output variable +** (*pnOne) is set to 10000 to indicate that stage 1 has finished and (*pnTwo) +** to a value between 0 and 10000 to indicate the permyriadage progress of +** stage 2. A value of 5000 indicates that stage 2 is half finished, +** 9000 indicates that it is 90% finished, and so on. +** +** If this API is called during stage 1 of the update, output variable +** (*pnTwo) is set to 0 to indicate that stage 2 has not yet started. The +** value to which (*pnOne) is set depends on whether or not the RBU +** database contains an "rbu_count" table. The rbu_count table, if it +** exists, must contain the same columns as the following: +** +** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; +** +** There must be one row in the table for each source (data_xxx) table within +** the RBU database. The 'tbl' column should contain the name of the source +** table. The 'cnt' column should contain the number of rows within the +** source table. +** +** If the rbu_count table is present and populated correctly and this +** API is called during stage 1, the *pnOne output variable is set to the +** permyriadage progress of the same stage. If the rbu_count table does +** not exist, then (*pnOne) is set to -1 during stage 1. If the rbu_count +** table exists but is not correctly populated, the value of the *pnOne +** output variable during stage 1 is undefined. +*/ +SQLITE_API void SQLITE_STDCALL sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int *pnTwo); + /* ** Create an RBU VFS named zName that accesses the underlying file-system ** via existing VFS zParent. Or, if the zParent parameter is passed NULL, @@ -158936,14 +164100,15 @@ SQLITE_API void SQLITE_STDCALL sqlite3rbu_destroy_vfs(const char *zName); ** RBU_STATE_OALSZ: ** Valid if STAGE==1. The size in bytes of the *-oal file. */ -#define RBU_STATE_STAGE 1 -#define RBU_STATE_TBL 2 -#define RBU_STATE_IDX 3 -#define RBU_STATE_ROW 4 -#define RBU_STATE_PROGRESS 5 -#define RBU_STATE_CKPT 6 -#define RBU_STATE_COOKIE 7 -#define RBU_STATE_OALSZ 8 +#define RBU_STATE_STAGE 1 +#define RBU_STATE_TBL 2 +#define RBU_STATE_IDX 3 +#define RBU_STATE_ROW 4 +#define RBU_STATE_PROGRESS 5 +#define RBU_STATE_CKPT 6 +#define RBU_STATE_COOKIE 7 +#define RBU_STATE_OALSZ 8 +#define RBU_STATE_PHASEONESTEP 9 #define RBU_STAGE_OAL 1 #define RBU_STAGE_MOVE 2 @@ -158964,6 +164129,7 @@ typedef struct RbuUpdateStmt RbuUpdateStmt; #if !defined(SQLITE_AMALGAMATION) typedef unsigned int u32; +typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; #endif @@ -158977,6 +164143,8 @@ typedef sqlite3_int64 i64; #define WAL_LOCK_CKPT 1 #define WAL_LOCK_READ0 3 +#define SQLITE_FCNTL_RBUCNT 5149216 + /* ** A structure to store values read from the rbu_state table in memory. */ @@ -158989,6 +164157,7 @@ struct RbuState { i64 nProgress; u32 iCookie; i64 iOalSz; + i64 nPhaseOneStep; }; struct RbuUpdateStmt { @@ -159033,6 +164202,7 @@ struct RbuObjIter { int iTnum; /* Root page of current object */ int iPkTnum; /* If eType==EXTERNAL, root of PK index */ int bUnique; /* Current index is unique */ + int nIndex; /* Number of aux. indexes on table zTbl */ /* Statements created by rbuObjIterPrepareAll() */ int nCol; /* Number of columns in current object */ @@ -159069,10 +164239,11 @@ struct RbuObjIter { */ #define RBU_INSERT 1 /* Insert on a main table b-tree */ #define RBU_DELETE 2 /* Delete a row from a main table b-tree */ -#define RBU_IDX_DELETE 3 /* Delete a row from an aux. index b-tree */ -#define RBU_IDX_INSERT 4 /* Insert on an aux. index b-tree */ -#define RBU_UPDATE 5 /* Update a row in a main table b-tree */ +#define RBU_REPLACE 3 /* Delete and then insert a row */ +#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */ +#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */ +#define RBU_UPDATE 6 /* Update a row in a main table b-tree */ /* ** A single step of an incremental checkpoint - frame iWalFrame of the wal @@ -159085,6 +164256,43 @@ struct RbuFrame { /* ** RBU handle. +** +** 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() +** API to calculate the permyriadage progress of populating the *-oal file +** using the formula: +** +** permyriadage = (10000 * nProgress) / nPhaseOneStep +** +** nPhaseOneStep is initialized to the sum of: +** +** nRow * (nIndex + 1) +** +** for all source tables in the RBU database, where nRow is the number +** of rows in the source table and nIndex the number of indexes on the +** corresponding target database table. +** +** This estimate is accurate if the RBU update consists entirely of +** INSERT operations. However, it is inaccurate if: +** +** * the RBU update contains any UPDATE operations. If the PK specified +** for an UPDATE operation does not exist in the target table, then +** no b-tree operations are required on index b-trees. Or if the +** specified PK does exist, then (nIndex*2) such operations are +** required (one delete and one insert on each index b-tree). +** +** * the RBU update contains any DELETE operations for which the specified +** PK does not exist. In this case no operations are required on index +** b-trees. +** +** * the RBU update contains REPLACE operations. These are similar to +** UPDATE operations. +** +** nPhaseOneStep is updated to account for the conditions above during the +** 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 { int eStage; /* Value of RBU_STATE_STAGE field */ @@ -159102,6 +164310,7 @@ struct sqlite3rbu { const char *zVfsName; /* Name of automatically created rbu vfs */ rbu_file *pTargetFd; /* File handle open on target db */ i64 iOalSz; + i64 nPhaseOneStep; /* The following state variables are used as part of the incremental ** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding @@ -159114,6 +164323,10 @@ struct sqlite3rbu { int pgsz; u8 *aBuf; i64 iWalCksum; + + /* Used in RBU vacuum mode only */ + int nRbu; /* Number of RBU VFS in the stack */ + rbu_file *pRbuFd; /* Fd for main db of dbRbu */ }; /* @@ -159139,6 +164352,7 @@ struct rbu_file { int openFlags; /* Flags this file was opened with */ u32 iCookie; /* Cookie value for main db files */ u8 iWriteVer; /* "write-version" value for main db files */ + u8 bNolock; /* True to fail EXCLUSIVE locks */ int nShm; /* Number of entries in apShm[] array */ char **apShm; /* Array of mmap'd *-shm regions */ @@ -159149,6 +164363,11 @@ struct rbu_file { rbu_file *pMainNext; /* Next MAIN_DB file */ }; +/* +** True for an RBU vacuum handle, or false otherwise. +*/ +#define rbuIsVacuum(p) ((p)->zTarget==0) + /************************************************************************* ** The following three functions, found below: @@ -159597,8 +164816,11 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ /* ** The implementation of the rbu_target_name() SQL function. This function -** accepts one argument - the name of a table in the RBU database. If the -** table name matches the pattern: +** accepts one or two arguments. The first argument is the name of a table - +** the name of a table in the RBU database. The second, if it is present, is 1 +** for a view or 0 for a table. +** +** For a non-vacuum RBU handle, if the table name matches the pattern: ** ** data[0-9]_ ** @@ -159609,21 +164831,33 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ ** "data_t1" -> "t1" ** "data0123_t2" -> "t2" ** "dataAB_t3" -> NULL +** +** For an rbu vacuum handle, a copy of the first argument is returned if +** the second argument is either missing or 0 (not a view). */ static void rbuTargetNameFunc( - sqlite3_context *context, + sqlite3_context *pCtx, int argc, sqlite3_value **argv ){ + sqlite3rbu *p = sqlite3_user_data(pCtx); const char *zIn; - assert( argc==1 ); + assert( argc==1 || argc==2 ); zIn = (const char*)sqlite3_value_text(argv[0]); - if( zIn && 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(context, &zIn[i+1], -1, SQLITE_STATIC); + if( zIn ){ + if( rbuIsVacuum(p) ){ + if( argc==1 || 0==sqlite3_value_int(argv[1]) ){ + sqlite3_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); + } + } } } } @@ -159641,7 +164875,8 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ memset(pIter, 0, sizeof(RbuObjIter)); rc = prepareAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, - "SELECT rbu_target_name(name) AS target, name FROM sqlite_master " + "SELECT rbu_target_name(name, type='view') AS target, name " + "FROM sqlite_master " "WHERE type IN ('table', 'view') AND target IS NOT NULL " "ORDER BY name" ); @@ -159724,7 +164959,7 @@ static void *rbuMalloc(sqlite3rbu *p, int nByte){ void *pRet = 0; if( p->rc==SQLITE_OK ){ assert( nByte>0 ); - pRet = sqlite3_malloc(nByte); + pRet = sqlite3_malloc64(nByte); if( pRet==0 ){ p->rc = SQLITE_NOMEM; }else{ @@ -159770,8 +165005,8 @@ static char *rbuStrndup(const char *zStr, int *pRc){ assert( *pRc==SQLITE_OK ); if( zStr ){ - int nCopy = strlen(zStr) + 1; - zRet = (char*)sqlite3_malloc(nCopy); + size_t nCopy = strlen(zStr) + 1; + zRet = (char*)sqlite3_malloc64(nCopy); if( zRet ){ memcpy(zRet, zStr, nCopy); }else{ @@ -159932,6 +165167,7 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ ); } + 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; @@ -159945,6 +165181,12 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ } rbuFinalize(p, pXInfo); bIndex = 1; + pIter->nIndex++; + } + + if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ + /* "PRAGMA index_list" includes the main PK b-tree */ + pIter->nIndex--; } rbuFinalize(p, pList); @@ -160010,6 +165252,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ pStmt = 0; if( p->rc==SQLITE_OK + && rbuIsVacuum(p)==0 && bRbuRowid!=(pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) ){ p->rc = SQLITE_ERROR; @@ -160058,6 +165301,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ rbuFinalize(p, pStmt); rbuObjIterCacheIndexedCols(p, pIter); assert( pIter->eType!=RBU_PK_VTAB || pIter->abIndexed==0 ); + assert( pIter->eType!=RBU_PK_VTAB || pIter->nIndex==0 ); } return p->rc; @@ -160148,6 +165392,8 @@ static char *rbuObjIterGetIndexCols( for(i=0; pIter->abTblPk[i]==0; i++); assert( inTblCol ); zCol = pIter->azTblCol[i]; + }else if( rbuIsVacuum(p) ){ + zCol = "_rowid_"; }else{ zCol = "rbu_rowid"; } @@ -160611,6 +165857,14 @@ static void rbuTmpInsertFunc( int rc = SQLITE_OK; int i; + assert( sqlite3_value_int(apVal[0])!=0 + || p->objiter.eType==RBU_PK_EXTERNAL + || p->objiter.eType==RBU_PK_NONE + ); + if( sqlite3_value_int(apVal[0])!=0 ){ + p->nPhaseOneStep += p->objiter.nIndex; + } + for(i=0; rc==SQLITE_OK && iobjiter.pTmpInsert, i+1, apVal[i]); } @@ -160680,7 +165934,7 @@ static int rbuObjIterPrepareAll( } /* And to delete index entries */ - if( p->rc==SQLITE_OK ){ + 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) @@ -160690,6 +165944,15 @@ static int rbuObjIterPrepareAll( /* Create the SELECT statement to read keys in sorted order */ 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", + zCollist, + pIter->zDataTbl, + zCollist, zLimit + ); + }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", @@ -160698,13 +165961,13 @@ static int rbuObjIterPrepareAll( ); }else{ zSql = sqlite3_mprintf( + "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " + "UNION ALL " "SELECT %s, rbu_control FROM '%q' " "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 " - "UNION ALL " - "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " "ORDER BY %s%s", - zCollist, pIter->zDataTbl, zCollist, p->zStateDb, pIter->zDataTbl, + zCollist, pIter->zDataTbl, zCollist, zLimit ); } @@ -160716,7 +165979,9 @@ static int rbuObjIterPrepareAll( sqlite3_free(zWhere); sqlite3_free(zBind); }else{ - int bRbuRowid = (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE); + int bRbuRowid = (pIter->eType==RBU_PK_VTAB) + ||(pIter->eType==RBU_PK_NONE) + ||(pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)); const char *zTbl = pIter->zTbl; /* Table this step applies to */ const char *zWrite; /* Imposter table name */ @@ -160743,8 +166008,10 @@ static int rbuObjIterPrepareAll( ); } - /* Create the DELETE statement to write to the target PK b-tree */ - if( p->rc==SQLITE_OK ){ + /* Create the DELETE statement to write to the target PK b-tree. + ** Because it only performs INSERT operations, this is not required for + ** an rbu vacuum handle. */ + if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pDelete, pz, sqlite3_mprintf( "DELETE FROM \"%s%w\" WHERE %s", zWrite, zTbl, zWhere @@ -160752,7 +166019,7 @@ static int rbuObjIterPrepareAll( ); } - if( pIter->abIndexed ){ + if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ const char *zRbuRowid = ""; if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ zRbuRowid = ", rbu_rowid"; @@ -160770,17 +166037,17 @@ static int rbuObjIterPrepareAll( rbuMPrintfExec(p, p->dbMain, "CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" " "BEGIN " - " SELECT rbu_tmp_insert(2, %s);" + " SELECT rbu_tmp_insert(3, %s);" "END;" "CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" " "BEGIN " - " SELECT rbu_tmp_insert(2, %s);" + " SELECT rbu_tmp_insert(3, %s);" "END;" "CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" " "BEGIN " - " SELECT rbu_tmp_insert(3, %s);" + " SELECT rbu_tmp_insert(4, %s);" "END;", zWrite, zTbl, zOldlist, zWrite, zTbl, zOldlist, @@ -160802,10 +166069,16 @@ static int rbuObjIterPrepareAll( /* Create the SELECT statement to read keys from data_xxx */ if( p->rc==SQLITE_OK ){ + const char *zRbuRowid = ""; + if( bRbuRowid ){ + zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid"; + } p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, sqlite3_mprintf( - "SELECT %s, rbu_control%s FROM '%q'%s", - zCollist, (bRbuRowid ? ", rbu_rowid" : ""), + "SELECT %s,%s rbu_control%s FROM '%q'%s", + zCollist, + (rbuIsVacuum(p) ? "0 AS " : ""), + zRbuRowid, pIter->zDataTbl, zLimit ) ); @@ -160900,11 +166173,15 @@ static int rbuGetUpdateStmt( return p->rc; } -static sqlite3 *rbuOpenDbhandle(sqlite3rbu *p, const char *zName){ +static sqlite3 *rbuOpenDbhandle( + sqlite3rbu *p, + const char *zName, + int bUseVfs +){ sqlite3 *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, p->zVfsName); + p->rc = sqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0); if( p->rc ){ p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); sqlite3_close(db); @@ -160914,6 +166191,95 @@ static sqlite3 *rbuOpenDbhandle(sqlite3rbu *p, const char *zName){ return db; } +/* +** Free an RbuState object allocated by rbuLoadState(). +*/ +static void rbuFreeState(RbuState *p){ + if( p ){ + sqlite3_free(p->zTbl); + sqlite3_free(p->zIdx); + sqlite3_free(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(). +** +** If an error occurs, leave an error code and message in the rbu handle +** and return NULL. +*/ +static RbuState *rbuLoadState(sqlite3rbu *p){ + RbuState *pRet = 0; + sqlite3_stmt *pStmt = 0; + int rc; + int rc2; + + pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); + if( pRet==0 ) return 0; + + rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + sqlite3_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) ){ + case RBU_STATE_STAGE: + pRet->eStage = sqlite3_column_int(pStmt, 1); + if( pRet->eStage!=RBU_STAGE_OAL + && pRet->eStage!=RBU_STAGE_MOVE + && pRet->eStage!=RBU_STAGE_CKPT + ){ + p->rc = SQLITE_CORRUPT; + } + break; + + case RBU_STATE_TBL: + pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); + break; + + case RBU_STATE_IDX: + pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); + break; + + case RBU_STATE_ROW: + pRet->nRow = sqlite3_column_int(pStmt, 1); + break; + + case RBU_STATE_PROGRESS: + pRet->nProgress = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_CKPT: + pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_COOKIE: + pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_OALSZ: + pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); + break; + + case RBU_STATE_PHASEONESTEP: + pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1); + break; + + default: + rc = SQLITE_CORRUPT; + break; + } + } + rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + + p->rc = rc; + return pRet; +} + + /* ** 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. @@ -160921,10 +166287,14 @@ static sqlite3 *rbuOpenDbhandle(sqlite3rbu *p, const char *zName){ static void rbuOpenDatabase(sqlite3rbu *p){ assert( p->rc==SQLITE_OK ); assert( p->dbMain==0 && p->dbRbu==0 ); + assert( rbuIsVacuum(p) || p->zTarget!=0 ); - p->eStage = 0; - p->dbMain = rbuOpenDbhandle(p, p->zTarget); - p->dbRbu = rbuOpenDbhandle(p, p->zRbu); + /* Open the RBU database */ + 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); + } /* If using separate RBU and state databases, attach the state database to ** the RBU db handle now. */ @@ -160935,6 +166305,96 @@ static void rbuOpenDatabase(sqlite3rbu *p){ memcpy(p->zStateDb, "main", 4); } +#if 0 + if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ + p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0); + } +#endif + + /* If it has not already been created, create the rbu_state table */ + rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); + +#if 0 + if( rbuIsVacuum(p) ){ + if( p->rc==SQLITE_OK ){ + int rc2; + int bOk = 0; + sqlite3_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) + ){ + bOk = 1; + } + rc2 = sqlite3_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"); + } + + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); + } + } + } +#endif + + if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ + int bOpen = 0; + int rc; + p->nRbu = 0; + p->pRbuFd = 0; + rc = sqlite3_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); + rbuFreeState(pState); + } + } + if( bOpen ) p->dbMain = rbuOpenDbhandle(p, p->zRbu, p->nRbu<=1); + } + + p->eStage = 0; + if( p->rc==SQLITE_OK && p->dbMain==0 ){ + if( !rbuIsVacuum(p) ){ + p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + }else if( p->pRbuFd->pWalFd ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); + }else{ + char *zTarget; + char *zExtra = 0; + if( strlen(p->zRbu)>=5 && 0==memcmp("file:", p->zRbu, 5) ){ + zExtra = &p->zRbu[5]; + while( *zExtra ){ + if( *zExtra++=='?' ) break; + } + if( *zExtra=='\0' ) zExtra = 0; + } + + zTarget = sqlite3_mprintf("file:%s-vacuum?rbu_memory=1%s%s", + sqlite3_db_filename(p->dbRbu, "main"), + (zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra) + ); + + if( zTarget==0 ){ + p->rc = SQLITE_NOMEM; + return; + } + p->dbMain = rbuOpenDbhandle(p, zTarget, p->nRbu<=1); + sqlite3_free(zTarget); + } + } + if( p->rc==SQLITE_OK ){ p->rc = sqlite3_create_function(p->dbMain, "rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0 @@ -160949,7 +166409,7 @@ static void rbuOpenDatabase(sqlite3rbu *p){ if( p->rc==SQLITE_OK ){ p->rc = sqlite3_create_function(p->dbRbu, - "rbu_target_name", 1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 + "rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 ); } @@ -161119,7 +166579,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_realloc(pRbu->aFrame, nNew * sizeof(RbuFrame)); + aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame)); if( aNew==0 ) return SQLITE_NOMEM; pRbu->aFrame = aNew; pRbu->nFrameAlloc = nNew; @@ -161184,7 +166644,7 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ if( nChar==0 ){ return 0; } - zWideFilename = sqlite3_malloc( nChar*sizeof(zWideFilename[0]) ); + zWideFilename = sqlite3_malloc64( nChar*sizeof(zWideFilename[0]) ); if( zWideFilename==0 ){ return 0; } @@ -161208,9 +166668,15 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ */ static void rbuMoveOalFile(sqlite3rbu *p){ const char *zBase = sqlite3_db_filename(p->dbMain, "main"); + const char *zMove = zBase; + char *zOal; + char *zWal; - char *zWal = sqlite3_mprintf("%s-wal", zBase); - char *zOal = sqlite3_mprintf("%s-oal", zBase); + if( rbuIsVacuum(p) ){ + zMove = sqlite3_db_filename(p->dbRbu, "main"); + } + zOal = sqlite3_mprintf("%s-oal", zMove); + zWal = sqlite3_mprintf("%s-wal", zMove); assert( p->eStage==RBU_STAGE_MOVE ); assert( p->rc==SQLITE_OK && p->zErrmsg==0 ); @@ -161231,8 +166697,8 @@ static void rbuMoveOalFile(sqlite3rbu *p){ /* Re-open the databases. */ rbuObjIterFinalize(&p->objiter); - sqlite3_close(p->dbMain); sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); p->dbMain = 0; p->dbRbu = 0; @@ -161298,14 +166764,12 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){ switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){ case SQLITE_INTEGER: { int iVal = sqlite3_column_int(p->objiter.pSelect, iCol); - if( iVal==0 ){ - res = RBU_INSERT; - }else if( iVal==1 ){ - res = RBU_DELETE; - }else if( iVal==2 ){ - res = RBU_IDX_DELETE; - }else if( iVal==3 ){ - res = RBU_IDX_INSERT; + switch( iVal ){ + case 0: res = RBU_INSERT; break; + case 1: res = RBU_DELETE; break; + case 2: res = RBU_REPLACE; break; + case 3: res = RBU_IDX_DELETE; break; + case 4: res = RBU_IDX_INSERT; break; } break; } @@ -161344,6 +166808,83 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ # define assertColumnName(x,y,z) #endif +/* +** 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. +*/ +static void rbuStepOneOp(sqlite3rbu *p, int eType){ + RbuObjIter *pIter = &p->objiter; + sqlite3_value *pVal; + sqlite3_stmt *pWriter; + int i; + + assert( p->rc==SQLITE_OK ); + assert( eType!=RBU_DELETE || pIter->zIdx==0 ); + assert( eType==RBU_DELETE || eType==RBU_IDX_DELETE + || eType==RBU_INSERT || eType==RBU_IDX_INSERT + ); + + /* If this is a delete, decrement nPhaseOneStep by nIndex. If the DELETE + ** statement below does actually delete a row, nPhaseOneStep will be + ** incremented by the same amount when SQL function rbu_tmp_insert() + ** is invoked by the trigger. */ + if( eType==RBU_DELETE ){ + p->nPhaseOneStep -= p->objiter.nIndex; + } + + if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){ + pWriter = pIter->pDelete; + }else{ + pWriter = pIter->pInsert; + } + + for(i=0; inCol; i++){ + /* If this is an INSERT into a table b-tree and the table has an + ** explicit INTEGER PRIMARY KEY, check that this is not an attempt + ** 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 + ){ + p->rc = SQLITE_MISMATCH; + p->zErrmsg = sqlite3_mprintf("datatype mismatch"); + return; + } + + if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){ + continue; + } + + pVal = sqlite3_column_value(pIter->pSelect, i); + p->rc = sqlite3_bind_value(pWriter, i+1, pVal); + if( p->rc ) return; + } + if( pIter->zIdx==0 ){ + if( pIter->eType==RBU_PK_VTAB + || pIter->eType==RBU_PK_NONE + || (pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)) + ){ + /* For a virtual table, or a table with no primary key, the + ** SELECT statement is: + ** + ** SELECT , rbu_control, rbu_rowid FROM .... + ** + ** Hence column_value(pIter->nCol+1). + */ + 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); + } + } + if( p->rc==SQLITE_OK ){ + sqlite3_step(pWriter); + p->rc = resetAndCollectError(pWriter, &p->zErrmsg); + } +} + /* ** This function does the work for an sqlite3rbu_step() call. ** @@ -161358,78 +166899,36 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ static int rbuStep(sqlite3rbu *p){ RbuObjIter *pIter = &p->objiter; const char *zMask = 0; - int i; int eType = rbuStepType(p, &zMask); if( eType ){ + assert( eType==RBU_INSERT || eType==RBU_DELETE + || eType==RBU_REPLACE || eType==RBU_IDX_DELETE + || eType==RBU_IDX_INSERT || eType==RBU_UPDATE + ); assert( eType!=RBU_UPDATE || pIter->zIdx==0 ); - if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){ + if( pIter->zIdx==0 && (eType==RBU_IDX_DELETE || eType==RBU_IDX_INSERT) ){ rbuBadControlError(p); } - else if( - eType==RBU_INSERT - || eType==RBU_DELETE - || eType==RBU_IDX_DELETE - || eType==RBU_IDX_INSERT - ){ - sqlite3_value *pVal; - sqlite3_stmt *pWriter; - - assert( eType!=RBU_UPDATE ); - assert( eType!=RBU_DELETE || pIter->zIdx==0 ); - - if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){ - pWriter = pIter->pDelete; - }else{ - pWriter = pIter->pInsert; + else if( eType==RBU_REPLACE ){ + if( pIter->zIdx==0 ){ + p->nPhaseOneStep += p->objiter.nIndex; + rbuStepOneOp(p, RBU_DELETE); } - - for(i=0; inCol; i++){ - /* If this is an INSERT into a table b-tree and the table has an - ** explicit INTEGER PRIMARY KEY, check that this is not an attempt - ** 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 - ){ - p->rc = SQLITE_MISMATCH; - p->zErrmsg = sqlite3_mprintf("datatype mismatch"); - goto step_out; - } - - if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){ - continue; - } - - pVal = sqlite3_column_value(pIter->pSelect, i); - p->rc = sqlite3_bind_value(pWriter, i+1, pVal); - if( p->rc ) goto step_out; - } - if( pIter->zIdx==0 - && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) - ){ - /* For a virtual table, or a table with no primary key, the - ** SELECT statement is: - ** - ** SELECT , rbu_control, rbu_rowid FROM .... - ** - ** Hence column_value(pIter->nCol+1). - */ - assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid"); - pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); - p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal); - } - if( p->rc==SQLITE_OK ){ - sqlite3_step(pWriter); - p->rc = resetAndCollectError(pWriter, &p->zErrmsg); - } - }else{ + if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT); + } + else if( eType!=RBU_UPDATE ){ + rbuStepOneOp(p, eType); + } + else{ sqlite3_value *pVal; sqlite3_stmt *pUpdate = 0; assert( eType==RBU_UPDATE ); + p->nPhaseOneStep -= p->objiter.nIndex; rbuGetUpdateStmt(p, pIter, zMask, &pUpdate); if( pUpdate ){ + int i; for(i=0; p->rc==SQLITE_OK && inCol; i++){ char c = zMask[pIter->aiSrcOrder[i]]; pVal = sqlite3_column_value(pIter->pSelect, i); @@ -161452,20 +166951,23 @@ static int rbuStep(sqlite3rbu *p){ } } } - - step_out: return p->rc; } /* ** Increment the schema cookie of the main database opened by p->dbMain. +** +** Or, if this is an RBU vacuum, set the schema cookie of the main db +** opened by p->dbMain to one more than the schema cookie of the main +** db opened by p->dbRbu. */ static void rbuIncrSchemaCookie(sqlite3rbu *p){ if( p->rc==SQLITE_OK ){ + sqlite3 *dbread = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain); int iCookie = 1000000; sqlite3_stmt *pStmt; - p->rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, + p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg, "PRAGMA schema_version" ); if( p->rc==SQLITE_OK ){ @@ -161493,6 +166995,7 @@ static void rbuIncrSchemaCookie(sqlite3rbu *p){ static void rbuSaveState(sqlite3rbu *p, int eStage){ if( p->rc==SQLITE_OK || p->rc==SQLITE_DONE ){ sqlite3_stmt *pInsert = 0; + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); int rc; assert( p->zErrmsg==0 ); @@ -161506,6 +167009,7 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ "(%d, %d), " "(%d, %lld), " "(%d, %lld), " + "(%d, %lld), " "(%d, %lld) ", p->zStateDb, RBU_STATE_STAGE, eStage, @@ -161514,8 +167018,9 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ RBU_STATE_ROW, p->nStep, RBU_STATE_PROGRESS, p->nProgress, RBU_STATE_CKPT, p->iWalCksum, - RBU_STATE_COOKIE, (i64)p->pTargetFd->iCookie, - RBU_STATE_OALSZ, p->iOalSz + RBU_STATE_COOKIE, (i64)pFd->iCookie, + RBU_STATE_OALSZ, p->iOalSz, + RBU_STATE_PHASEONESTEP, p->nPhaseOneStep ) ); assert( pInsert==0 || rc==SQLITE_OK ); @@ -161529,6 +167034,92 @@ 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: +** +** "PRAGMA main.$zPragma" +** +** where $zPragma is the string passed as the second argument, then +** on sqlite3rbu.dbMain: +** +** "PRAGMA main.$zPragma = $val" +** +** where $val is the value returned by the first PRAGMA invocation. +** +** In short, it copies the value of the specified PRAGMA setting from +** dbRbu to dbMain. +*/ +static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){ + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pPragma = 0; + p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.%s", zPragma) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPragma) ){ + p->rc = rbuMPrintfExec(p, p->dbMain, "PRAGMA main.%s = %d", + zPragma, sqlite3_column_int(pPragma, 0) + ); + } + rbuFinalize(p, pPragma); + } +} + +/* +** The RBU handle passed as the only argument has just been opened and +** 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; + + assert( rbuIsVacuum(p) ); + p->rc = sqlite3_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" + " AND name!='sqlite_sequence' " + " ORDER BY type DESC" + ); + } + + 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); + } + rbuFinalize(p, pSql); + if( p->rc!=SQLITE_OK ) return; + + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, + "SELECT * FROM sqlite_master WHERE rootpage=0 OR rootpage IS NULL" + ); + } + + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbMain, &pInsert, &p->zErrmsg, + "INSERT INTO sqlite_master VALUES(?,?,?,?,?)" + ); + } + + while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){ + int i; + for(i=0; i<5; i++){ + sqlite3_bind_value(pInsert, i+1, sqlite3_column_value(pSql, i)); + } + sqlite3_step(pInsert); + p->rc = sqlite3_reset(pInsert); + } + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=0",0,0,&p->zErrmsg); + } + + rbuFinalize(p, pSql); + rbuFinalize(p, pInsert); +} + /* ** Step the RBU object. */ @@ -161537,13 +167128,22 @@ SQLITE_API int SQLITE_STDCALL sqlite3rbu_step(sqlite3rbu *p){ switch( p->eStage ){ case RBU_STAGE_OAL: { RbuObjIter *pIter = &p->objiter; + + /* If this is an RBU vacuum operation and the state table was empty + ** when this handle was opened, create the target database schema. */ + if( rbuIsVacuum(p) && p->nProgress==0 && p->rc==SQLITE_OK ){ + rbuCreateTargetSchema(p); + rbuCopyPragma(p, "user_version"); + rbuCopyPragma(p, "application_id"); + } + while( p->rc==SQLITE_OK && pIter->zTbl ){ if( pIter->bCleanup ){ /* Clean up the rbu_tmp_xxx table for the previous table. It ** cannot be dropped as there are currently active SQL statements. ** But the contents can be deleted. */ - if( pIter->abIndexed ){ + if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ rbuMPrintfExec(p, p->dbRbu, "DELETE FROM %s.'rbu_tmp_%q'", p->zStateDb, pIter->zDataTbl ); @@ -161630,90 +167230,6 @@ SQLITE_API int SQLITE_STDCALL sqlite3rbu_step(sqlite3rbu *p){ } } -/* -** Free an RbuState object allocated by rbuLoadState(). -*/ -static void rbuFreeState(RbuState *p){ - if( p ){ - sqlite3_free(p->zTbl); - sqlite3_free(p->zIdx); - sqlite3_free(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(). -** -** If an error occurs, leave an error code and message in the rbu handle -** and return NULL. -*/ -static RbuState *rbuLoadState(sqlite3rbu *p){ - RbuState *pRet = 0; - sqlite3_stmt *pStmt = 0; - int rc; - int rc2; - - pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); - if( pRet==0 ) return 0; - - rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, - sqlite3_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) ){ - case RBU_STATE_STAGE: - pRet->eStage = sqlite3_column_int(pStmt, 1); - if( pRet->eStage!=RBU_STAGE_OAL - && pRet->eStage!=RBU_STAGE_MOVE - && pRet->eStage!=RBU_STAGE_CKPT - ){ - p->rc = SQLITE_CORRUPT; - } - break; - - case RBU_STATE_TBL: - pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); - break; - - case RBU_STATE_IDX: - pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); - break; - - case RBU_STATE_ROW: - pRet->nRow = sqlite3_column_int(pStmt, 1); - break; - - case RBU_STATE_PROGRESS: - pRet->nProgress = sqlite3_column_int64(pStmt, 1); - break; - - case RBU_STATE_CKPT: - pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); - break; - - case RBU_STATE_COOKIE: - pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); - break; - - case RBU_STATE_OALSZ: - pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); - break; - - default: - rc = SQLITE_CORRUPT; - break; - } - } - rc2 = sqlite3_finalize(pStmt); - if( rc==SQLITE_OK ) rc = rc2; - - p->rc = rc; - return pRet; -} - /* ** Compare strings z1 and z2, returning 0 if they are identical, or non-zero ** otherwise. Either or both argument may be NULL. Two NULL values are @@ -161810,19 +167326,112 @@ static void rbuDeleteVfs(sqlite3rbu *p){ } /* -** Open and return a new RBU handle. +** This user-defined SQL function is invoked with a single argument - the +** name of a table expected to appear in the target database. It returns +** the number of auxilliary indexes on the table. */ -SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( +static void rbuIndexCntFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3rbu *p = (sqlite3rbu*)sqlite3_user_data(pCtx); + sqlite3_stmt *pStmt = 0; + char *zErrmsg = 0; + int rc; + + 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])) + ); + if( rc!=SQLITE_OK ){ + sqlite3_result_error(pCtx, zErrmsg, -1); + }else{ + int nIndex = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + nIndex = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + sqlite3_result_int(pCtx, nIndex); + }else{ + sqlite3_result_error(pCtx, sqlite3_errmsg(p->dbMain), -1); + } + } + + sqlite3_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 +** is assumed to contain the same columns as: +** +** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; +** +** There should be one row in the table for each data_xxx table in the +** 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 +** 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){ + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pStmt = 0; + int bExists = 0; /* True if rbu_count exists */ + + p->nPhaseOneStep = -1; + + p->rc = sqlite3_create_function(p->dbRbu, + "rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0 + ); + + /* Check for the rbu_count table. If it does not exist, or if an error + ** occurs, nPhaseOneStep will be left set to -1. */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + "SELECT 1 FROM sqlite_master WHERE tbl_name = 'rbu_count'" + ); + } + if( p->rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + bExists = 1; + } + p->rc = sqlite3_finalize(pStmt); + } + + if( p->rc==SQLITE_OK && bExists ){ + p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + "SELECT sum(cnt * (1 + rbu_index_cnt(rbu_target_name(tbl))))" + "FROM rbu_count" + ); + if( p->rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + p->nPhaseOneStep = sqlite3_column_int64(pStmt, 0); + } + p->rc = sqlite3_finalize(pStmt); + } + } + } +} + + +static sqlite3rbu *openRbuHandle( const char *zTarget, const char *zRbu, const char *zState ){ sqlite3rbu *p; - int nTarget = strlen(zTarget); - int nRbu = strlen(zRbu); - int nState = zState ? strlen(zState) : 0; + size_t nTarget = zTarget ? strlen(zTarget) : 0; + size_t nRbu = strlen(zRbu); + size_t nState = zState ? strlen(zState) : 0; + size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1+ nState+1; - p = (sqlite3rbu*)sqlite3_malloc(sizeof(sqlite3rbu)+nTarget+1+nRbu+1+nState+1); + p = (sqlite3rbu*)sqlite3_malloc64(nByte); if( p ){ RbuState *pState = 0; @@ -161830,22 +167439,24 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( memset(p, 0, sizeof(sqlite3rbu)); rbuCreateVfs(p); - /* Open the target database */ + /* Open the target, RBU and state databases */ if( p->rc==SQLITE_OK ){ - p->zTarget = (char*)&p[1]; - memcpy(p->zTarget, zTarget, nTarget+1); - p->zRbu = &p->zTarget[nTarget+1]; + char *pCsr = (char*)&p[1]; + if( zTarget ){ + p->zTarget = pCsr; + memcpy(p->zTarget, zTarget, nTarget+1); + pCsr += nTarget+1; + } + p->zRbu = pCsr; memcpy(p->zRbu, zRbu, nRbu+1); + pCsr += nRbu+1; if( zState ){ - p->zState = &p->zRbu[nRbu+1]; + p->zState = pCsr; memcpy(p->zState, zState, nState+1); } rbuOpenDatabase(p); } - /* If it has not already been created, create the rbu_state table */ - rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); - if( p->rc==SQLITE_OK ){ pState = rbuLoadState(p); assert( pState || p->rc!=SQLITE_OK ); @@ -161853,9 +167464,11 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( if( pState->eStage==0 ){ rbuDeleteOalFile(p); + rbuInitPhaseOneSteps(p); p->eStage = RBU_STAGE_OAL; }else{ p->eStage = pState->eStage; + p->nPhaseOneStep = pState->nPhaseOneStep; } p->nProgress = pState->nProgress; p->iOalSz = pState->iOalSz; @@ -161873,27 +167486,39 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( } } - if( p->rc==SQLITE_OK + if( p->rc==SQLITE_OK && (p->eStage==RBU_STAGE_OAL || p->eStage==RBU_STAGE_MOVE) - && pState->eStage!=0 && p->pTargetFd->iCookie!=pState->iCookie - ){ - /* At this point (pTargetFd->iCookie) contains the value of the - ** change-counter cookie (the thing that gets incremented when a - ** 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 update"); + && pState->eStage!=0 + ){ + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); + if( pFd->iCookie!=pState->iCookie ){ + /* At this point (pTargetFd->iCookie) contains the value of the + ** change-counter cookie (the thing that gets incremented when a + ** 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", + (rbuIsVacuum(p) ? "vacuum" : "update") + ); + } } if( p->rc==SQLITE_OK ){ if( p->eStage==RBU_STAGE_OAL ){ sqlite3 *db = p->dbMain; + if( pState->eStage==0 && rbuIsVacuum(p) ){ + rbuCopyPragma(p, "page_size"); + rbuCopyPragma(p, "auto_vacuum"); + } + /* Open transactions both databases. The *-oal file is opened or ** created at this point. */ - p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_exec(p->dbRbu, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); + p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); + } + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, &p->zErrmsg); } /* Check if the main database is a zipvfs db. If it is, set the upper @@ -161938,6 +167563,28 @@ SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( return p; } +/* +** Open and return a new RBU handle. +*/ +SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_open( + const char *zTarget, + const char *zRbu, + const char *zState +){ + /* TODO: Check that zTarget and zRbu are non-NULL */ + return openRbuHandle(zTarget, zRbu, zState); +} + +/* +** Open a handle to begin or resume an RBU VACUUM operation. +*/ +SQLITE_API sqlite3rbu *SQLITE_STDCALL sqlite3rbu_vacuum( + const char *zTarget, + const char *zState +){ + /* TODO: Check that both arguments are non-NULL */ + return openRbuHandle(0, zTarget, zState); +} /* ** Return the database handle used by pRbu. @@ -161958,8 +167605,8 @@ SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3rbu_db(sqlite3rbu *pRbu, int bRbu){ */ static void rbuEditErrmsg(sqlite3rbu *p){ if( p->rc==SQLITE_CONSTRAINT && p->zErrmsg ){ - int i; - int nErrmsg = strlen(p->zErrmsg); + unsigned int i; + size_t nErrmsg = strlen(p->zErrmsg); for(i=0; i<(nErrmsg-8); i++){ if( memcmp(&p->zErrmsg[i], "rbu_imp_", 8)==0 ){ int nDel = 8; @@ -161992,9 +167639,19 @@ SQLITE_API int SQLITE_STDCALL sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ /* Close any open statement handles. */ rbuObjIterFinalize(&p->objiter); + /* 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() + ** 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); + if( p->rc==SQLITE_DONE && rc2!=SQLITE_OK ) p->rc = rc2; + } + /* Close the open database handle and VFS object. */ - sqlite3_close(p->dbMain); sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); rbuDeleteVfs(p); sqlite3_free(p->aBuf); sqlite3_free(p->aFrame); @@ -162019,6 +167676,42 @@ SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3rbu_progress(sqlite3rbu *pRbu){ return pRbu->nProgress; } +/* +** Return permyriadage progress indications for the two main stages of +** an RBU update. +*/ +SQLITE_API void SQLITE_STDCALL sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){ + const int MAX_PROGRESS = 10000; + switch( p->eStage ){ + case RBU_STAGE_OAL: + if( p->nPhaseOneStep>0 ){ + *pnOne = (int)(MAX_PROGRESS * (i64)p->nProgress/(i64)p->nPhaseOneStep); + }else{ + *pnOne = -1; + } + *pnTwo = 0; + break; + + case RBU_STAGE_MOVE: + *pnOne = MAX_PROGRESS; + *pnTwo = 0; + break; + + case RBU_STAGE_CKPT: + *pnOne = MAX_PROGRESS; + *pnTwo = (int)(MAX_PROGRESS * (i64)p->nStep / (i64)p->nFrame); + break; + + case RBU_STAGE_DONE: + *pnOne = MAX_PROGRESS; + *pnTwo = MAX_PROGRESS; + break; + + default: + assert( 0 ); + } +} + SQLITE_API int SQLITE_STDCALL sqlite3rbu_savestate(sqlite3rbu *p){ int rc = p->rc; @@ -162160,6 +167853,22 @@ static u32 rbuGetU32(u8 *aBuf){ + ((u32)aBuf[3]); } +/* +** Write an unsigned 32-bit value in big-endian format to the supplied +** buffer. +*/ +static void rbuPutU32(u8 *aBuf, u32 iVal){ + aBuf[0] = (iVal >> 24) & 0xFF; + aBuf[1] = (iVal >> 16) & 0xFF; + aBuf[2] = (iVal >> 8) & 0xFF; + aBuf[3] = (iVal >> 0) & 0xFF; +} + +static void rbuPutU16(u8 *aBuf, u16 iVal){ + aBuf[0] = (iVal >> 8) & 0xFF; + aBuf[1] = (iVal >> 0) & 0xFF; +} + /* ** Read data from an rbuVfs-file. */ @@ -162185,6 +167894,35 @@ static int rbuVfsRead( memset(zBuf, 0, iAmt); }else{ rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); +#if 1 + /* If this is being called to read the first page of the target + ** database as part of an rbu vacuum operation, synthesize the + ** contents of the first page if it does not yet exist. Otherwise, + ** SQLite will not check for a *-wal file. */ + if( pRbu && rbuIsVacuum(pRbu) + && rc==SQLITE_IOERR_SHORT_READ && iOfst==0 + && (p->openFlags & SQLITE_OPEN_MAIN_DB) + && pRbu->rc==SQLITE_OK + ){ + sqlite3_file *pFd = (sqlite3_file*)pRbu->pRbuFd; + rc = pFd->pMethods->xRead(pFd, zBuf, iAmt, iOfst); + if( rc==SQLITE_OK ){ + u8 *aBuf = (u8*)zBuf; + u32 iRoot = rbuGetU32(&aBuf[52]) ? 1 : 0; + rbuPutU32(&aBuf[52], iRoot); /* largest root page number */ + rbuPutU32(&aBuf[36], 0); /* number of free pages */ + rbuPutU32(&aBuf[32], 0); /* first page on free list trunk */ + rbuPutU32(&aBuf[28], 1); /* size of db file in pages */ + rbuPutU32(&aBuf[24], pRbu->pRbuFd->iCookie+1); /* Change counter */ + + if( iAmt>100 ){ + memset(&aBuf[100], 0, iAmt-100); + rbuPutU16(&aBuf[105], iAmt & 0xFFFF); + aBuf[100] = 0x0D; + } + } + } +#endif } if( rc==SQLITE_OK && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){ /* These look like magic numbers. But they are stable, as they are part @@ -162259,7 +167997,20 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){ */ static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ rbu_file *p = (rbu_file *)pFile; - return p->pReal->pMethods->xFileSize(p->pReal, pSize); + int rc; + rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); + + /* If this is an RBU vacuum operation and this is the target database, + ** pretend that it has at least one page. Otherwise, SQLite will not + ** check for the existance of a *-wal file. rbuVfsRead() contains + ** similar logic. */ + if( rc==SQLITE_OK && *pSize==0 + && p->pRbu && rbuIsVacuum(p->pRbu) + && (p->openFlags & SQLITE_OPEN_MAIN_DB) + ){ + *pSize = 1024; + } + return rc; } /* @@ -162271,7 +168022,9 @@ static int rbuVfsLock(sqlite3_file *pFile, int eLock){ int rc = SQLITE_OK; assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); - if( pRbu && eLock==SQLITE_LOCK_EXCLUSIVE && pRbu->eStage!=RBU_STAGE_DONE ){ + if( eLock==SQLITE_LOCK_EXCLUSIVE + && (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(). */ rc = SQLITE_BUSY; @@ -162334,6 +168087,12 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){ } return rc; } + else if( op==SQLITE_FCNTL_RBUCNT ){ + sqlite3rbu *pRbu = (sqlite3rbu*)pArg; + pRbu->nRbu++; + pRbu->pRbuFd = p; + p->bNolock = 1; + } rc = xControl(p->pReal, op, pArg); if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ @@ -162423,7 +168182,7 @@ static int rbuVfsShmMap( if( eStage==RBU_STAGE_OAL || eStage==RBU_STAGE_MOVE ){ if( iRegion<=p->nShm ){ int nByte = (iRegion+1) * sizeof(char*); - char **apNew = (char**)sqlite3_realloc(p->apShm, nByte); + char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte); if( apNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -162434,7 +168193,7 @@ static int rbuVfsShmMap( } if( rc==SQLITE_OK && p->apShm[iRegion]==0 ){ - char *pNew = (char*)sqlite3_malloc(szRegion); + char *pNew = (char*)sqlite3_malloc64(szRegion); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -162492,11 +168251,38 @@ static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){ 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); + 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. */ @@ -162532,6 +168318,7 @@ static int rbuVfsOpen( 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]; @@ -162544,23 +168331,7 @@ 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. */ - int n = 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); - pFd->zWal = z; + pFd->zWal = rbuMainToWal(zName, flags); } else if( flags & SQLITE_OPEN_WAL ){ rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName); @@ -162570,10 +168341,17 @@ static int rbuVfsOpen( ** code ensures that the string passed to xOpen() is terminated by a ** pair of '\0' bytes in case the VFS attempts to extract a URI ** parameter from it. */ - int nCopy = strlen(zName); - char *zCopy = sqlite3_malloc(nCopy+2); + const char *zBase = zName; + size_t nCopy; + char *zCopy; + if( rbuIsVacuum(pDb->pRbu) ){ + zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); + zBase = rbuMainToWal(zBase, SQLITE_OPEN_URI); + } + nCopy = strlen(zBase); + zCopy = sqlite3_malloc64(nCopy+2); if( zCopy ){ - memcpy(zCopy, zName, nCopy); + memcpy(zCopy, zBase, nCopy); zCopy[nCopy-3] = 'o'; zCopy[nCopy] = '\0'; zCopy[nCopy+1] = '\0'; @@ -162588,8 +168366,17 @@ static int rbuVfsOpen( } } + if( oflags & SQLITE_OPEN_MAIN_DB + && sqlite3_uri_boolean(zName, "rbu_memory", 0) + ){ + assert( oflags & SQLITE_OPEN_MAIN_DB ); + oflags = SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; + zOpen = 0; + } + if( rc==SQLITE_OK ){ - rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, flags, pOutFlags); + rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags); } if( pFd->pReal->pMethods ){ /* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods @@ -162800,13 +168587,13 @@ SQLITE_API int SQLITE_STDCALL sqlite3rbu_create_vfs(const char *zName, const cha }; rbu_vfs *pNew = 0; /* Newly allocated VFS */ - int nName; int rc = SQLITE_OK; + size_t nName; + size_t nByte; - int nByte; nName = strlen(zName); nByte = sizeof(rbu_vfs) + nName + 1; - pNew = (rbu_vfs*)sqlite3_malloc(nByte); + pNew = (rbu_vfs*)sqlite3_malloc64(nByte); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -163000,7 +168787,9 @@ static int statConnect( int iDb; if( argc>=4 ){ - iDb = sqlite3FindDbName(db, argv[3]); + Token nm; + sqlite3TokenInit(&nm, (char*)argv[3]); + iDb = sqlite3FindDb(db, &nm); if( iDb<0 ){ *pzErr = sqlite3_mprintf("no such database: %s", argv[3]); return SQLITE_ERROR; @@ -163011,7 +168800,7 @@ static int statConnect( rc = sqlite3_declare_vtab(db, VTAB_SCHEMA); if( rc==SQLITE_OK ){ pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable)); - if( pTab==0 ) rc = SQLITE_NOMEM; + if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; } assert( rc==SQLITE_OK || pTab==0 ); @@ -163092,7 +168881,7 @@ static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ pCsr = (StatCursor *)sqlite3_malloc64(sizeof(StatCursor)); if( pCsr==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; }else{ memset(pCsr, 0, sizeof(StatCursor)); pCsr->base.pVtab = pVTab; @@ -163198,7 +168987,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ nUsable = szPage - sqlite3BtreeGetReserveNoMutex(pBt); sqlite3BtreeLeave(pBt); p->aCell = sqlite3_malloc64((p->nCell+1) * sizeof(StatCell)); - if( p->aCell==0 ) return SQLITE_NOMEM; + if( p->aCell==0 ) return SQLITE_NOMEM_BKPT; memset(p->aCell, 0, (p->nCell+1) * sizeof(StatCell)); for(i=0; inCell; i++){ @@ -163231,13 +169020,13 @@ static int statDecodePage(Btree *pBt, StatPage *p){ pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); pCell->nOvfl = nOvfl; pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl); - if( pCell->aOvfl==0 ) return SQLITE_NOMEM; + if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT; pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]); for(j=1; jaOvfl[j-1]; DbPage *pPg = 0; - rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg); + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg, 0); if( rc!=SQLITE_OK ){ assert( pPg==0 ); return rc; @@ -163305,12 +169094,12 @@ statNextRestart: pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } - rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg); + rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0); pCsr->aPage[0].iPgno = iRoot; pCsr->aPage[0].iCell = 0; pCsr->aPage[0].zPath = z = sqlite3_mprintf("/"); pCsr->iPage = 0; - if( z==0 ) rc = SQLITE_NOMEM; + if( z==0 ) rc = SQLITE_NOMEM_BKPT; }else{ pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); @@ -163345,7 +169134,7 @@ statNextRestart: } pCell->iOvfl++; statSizeAndOffset(pCsr); - return z==0 ? SQLITE_NOMEM : SQLITE_OK; + return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; } if( p->iRightChildPg ) break; p->iCell++; @@ -163365,11 +169154,11 @@ statNextRestart: }else{ p[1].iPgno = p->aCell[p->iCell].iChildPg; } - rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg); + rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0); p[1].iCell = 0; p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell); p->iCell++; - if( z==0 ) rc = SQLITE_NOMEM; + if( z==0 ) rc = SQLITE_NOMEM_BKPT; } @@ -163403,7 +169192,7 @@ statNextRestart: pCsr->nUnused = p->nUnused; pCsr->nMxPayload = p->nMxPayload; pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath); - if( z==0 ) rc = SQLITE_NOMEM; + if( z==0 ) rc = SQLITE_NOMEM_BKPT; nPayload = 0; for(i=0; inCell; i++){ nPayload += p->aCell[i].nLocal; @@ -163437,7 +169226,7 @@ static int statFilter( 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; + return pCursor->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM_BKPT; } }else{ pCsr->iDb = pTab->iDb; @@ -163453,7 +169242,7 @@ static int statFilter( " FROM \"%w\".%s WHERE rootpage!=0" " ORDER BY name", pTab->db->aDb[pCsr->iDb].zName, zMaster); if( zSql==0 ){ - return SQLITE_NOMEM; + return SQLITE_NOMEM_BKPT; }else{ rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0); sqlite3_free(zSql); @@ -163551,6 +169340,4650 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; } #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ /************** End of dbstat.c **********************************************/ +/************** Begin file sqlite3session.c **********************************/ + +#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) +/* #include "sqlite3session.h" */ +/* #include */ +/* #include */ + +#ifndef SQLITE_AMALGAMATION +/* # include "sqliteInt.h" */ +/* # include "vdbeInt.h" */ +#endif + +typedef struct SessionTable SessionTable; +typedef struct SessionChange SessionChange; +typedef struct SessionBuffer SessionBuffer; +typedef struct SessionInput SessionInput; + +/* +** Minimum chunk size used by streaming versions of functions. +*/ +#ifndef SESSIONS_STRM_CHUNK_SIZE +# ifdef SQLITE_TEST +# define SESSIONS_STRM_CHUNK_SIZE 64 +# else +# define SESSIONS_STRM_CHUNK_SIZE 1024 +# endif +#endif + +typedef struct SessionHook SessionHook; +struct SessionHook { + void *pCtx; + int (*xOld)(void*,int,sqlite3_value**); + int (*xNew)(void*,int,sqlite3_value**); + int (*xCount)(void*); + int (*xDepth)(void*); +}; + +/* +** Session handle structure. +*/ +struct sqlite3_session { + sqlite3 *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 */ + int bAutoAttach; /* True to auto-attach tables */ + 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. */ + SessionTable *pTable; /* List of attached tables */ + SessionHook hook; /* APIs to grab new and old data with */ +}; + +/* +** Instances of this structure are used to build strings or binary records. +*/ +struct SessionBuffer { + u8 *aBuf; /* Pointer to changeset buffer */ + int nBuf; /* Size of buffer aBuf */ + int nAlloc; /* Size of allocation containing aBuf */ +}; + +/* +** 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()). +*/ +struct SessionInput { + int bNoDiscard; /* If true, discard no data */ + int iCurrent; /* Offset in aData[] of current change */ + int iNext; /* Offset in aData[] of next change */ + u8 *aData; /* Pointer to buffer containing changeset */ + int nData; /* Number of bytes in aData */ + + SessionBuffer buf; /* Current read buffer */ + int (*xInput)(void*, void*, int*); /* Input stream call (or NULL) */ + void *pIn; /* First argument to xInput */ + int bEof; /* Set to true after xInput finished */ +}; + +/* +** Structure for changeset iterators. +*/ +struct sqlite3_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 rc; /* Iterator error code */ + sqlite3_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 */ +}; + +/* +** 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. +** +** 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 +** table. +** +** The data associated with each hash-table entry is a structure containing +** a subset of the initial values that the modified row contained at the +** start of the session. Or no initial values if the row was inserted. +*/ +struct SessionTable { + SessionTable *pNext; + char *zName; /* Local name of table */ + int nCol; /* Number of columns in table zName */ + const char **azCol; /* Column names */ + u8 *abPK; /* Array of primary key flags */ + int nEntry; /* Total number of entries in hash table */ + int nChange; /* Size of apChange[] array */ + SessionChange **apChange; /* Hash table buckets */ +}; + +/* +** RECORD FORMAT: +** +** The following record format is similar to (but not compatible with) that +** used in SQLite database files. This format is used as part of the +** change-set binary format, and so must be architecture independent. +** +** Unlike the SQLite database record format, each field is self-contained - +** there is no separation of header and data. Each field begins with a +** single byte describing its type, as follows: +** +** 0x00: Undefined value. +** 0x01: Integer value. +** 0x02: Real value. +** 0x03: Text value. +** 0x04: Blob value. +** 0x05: SQL NULL value. +** +** Note that the above match the definitions of SQLITE_INTEGER, SQLITE_TEXT +** and so on in sqlite3.h. For undefined and NULL values, the field consists +** only of the single type byte. For other types of values, the type byte +** is followed by: +** +** Text values: +** A varint containing the number of bytes in the value (encoded using +** UTF-8). Followed by a buffer containing the UTF-8 representation +** of the text value. There is no nul terminator. +** +** Blob values: +** A varint containing the number of bytes in the value, followed by +** a buffer containing the value itself. +** +** Integer values: +** An 8-byte big-endian integer value. +** +** Real values: +** An 8-byte big-endian IEEE 754-2008 real value. +** +** Varint values are encoded in the same way as varints in the SQLite +** record format. +** +** CHANGESET FORMAT: +** +** A changeset is a collection of DELETE, UPDATE and INSERT operations on +** one or more tables. Operations on a single table are grouped together, +** but may occur in any order (i.e. deletes, updates and inserts are all +** mixed together). +** +** Each group of changes begins with a table header: +** +** 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 changes to the table. +** +** 1 byte: Either SQLITE_INSERT (0x12), UPDATE (0x17) or DELETE (0x09). +** 1 byte: The "indirect-change" flag. +** old.* record: (delete and update only) +** new.* record: (insert and update only) +** +** The "old.*" and "new.*" records, if present, are N field records in the +** format described above under "RECORD FORMAT", where N is the number of +** columns in the table. The i'th field of each record is associated with +** the i'th column of the table, counting from left to right in the order +** in which columns were declared in the CREATE TABLE statement. +** +** The new.* record that is part of each INSERT change contains the values +** that make up the new row. Similarly, the old.* record that is part of each +** DELETE change contains the values that made up the row that was deleted +** from the database. In the changeset format, the records that are part +** of INSERT or DELETE changes never contain any undefined (type byte 0x00) +** fields. +** +** Within the old.* record associated with an UPDATE change, all fields +** associated with table columns that are not PRIMARY KEY columns and are +** not modified by the UPDATE change are set to "undefined". Other fields +** are set to the values that made up the row before the UPDATE that the +** change records took place. Within the new.* record, fields associated +** with table columns modified by the UPDATE change contain the new +** values. Fields associated with table columns that are not modified +** are set to "undefined". +** +** PATCHSET FORMAT: +** +** A patchset is also a collection of changes. It is similar to a changeset, +** but leaves undefined those fields that are not useful if no conflict +** resolution is required when applying the changeset. +** +** Each group of changes begins with a table header: +** +** 1 byte: Constant 0x50 (capital 'P') +** 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 changes to the table. +** +** 1 byte: Either SQLITE_INSERT (0x12), UPDATE (0x17) or DELETE (0x09). +** 1 byte: The "indirect-change" flag. +** single record: (PK fields for DELETE, PK and modified fields for UPDATE, +** full record for INSERT). +** +** As in the changeset format, each field of the single record that is part +** of a patchset change is associated with the correspondingly positioned +** table column, counting from left to right within the CREATE TABLE +** 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. +** +** 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". +** PRIMARY KEY fields contain the values identifying the table row to update, +** and fields associated with modified columns contain the new column values. +** +** 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". +*/ + +/* +** For each row modified during a session, there exists a single instance of +** this structure stored in a SessionTable.aChange[] hash table. +*/ +struct SessionChange { + int op; /* One of UPDATE, DELETE, INSERT */ + int bIndirect; /* True if this change is "indirect" */ + int nRecord; /* Number of bytes in buffer aRecord[] */ + u8 *aRecord; /* Buffer containing old.* record */ + SessionChange *pNext; /* For hash-table collisions */ +}; + +/* +** Write a varint with value iVal into the buffer at aBuf. Return the +** number of bytes written. +*/ +static int sessionVarintPut(u8 *aBuf, int iVal){ + return putVarint32(aBuf, iVal); +} + +/* +** Return the number of bytes required to store value iVal as a varint. +*/ +static int sessionVarintLen(int iVal){ + return sqlite3VarintLen(iVal); +} + +/* +** Read a varint value from aBuf[] into *piVal. Return the number of +** bytes read. +*/ +static int sessionVarintGet(u8 *aBuf, int *piVal){ + return getVarint32(aBuf, *piVal); +} + +/* Load an unaligned and unsigned 32-bit integer */ +#define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) + +/* +** Read a 64-bit big-endian integer value from buffer aRec[]. Return +** the value read. +*/ +static sqlite3_int64 sessionGetI64(u8 *aRec){ + u64 x = SESSION_UINT32(aRec); + u32 y = SESSION_UINT32(aRec+4); + x = (x<<32) + y; + return (sqlite3_int64)x; +} + +/* +** Write a 64-bit big-endian integer value to the buffer aBuf[]. +*/ +static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){ + aBuf[0] = (i>>56) & 0xFF; + aBuf[1] = (i>>48) & 0xFF; + aBuf[2] = (i>>40) & 0xFF; + aBuf[3] = (i>>32) & 0xFF; + aBuf[4] = (i>>24) & 0xFF; + aBuf[5] = (i>>16) & 0xFF; + aBuf[6] = (i>> 8) & 0xFF; + aBuf[7] = (i>> 0) & 0xFF; +} + +/* +** This function is used to serialize the contents of value pValue (see +** comment titled "RECORD FORMAT" above). +** +** If it is non-NULL, the serialized form of the value is written to +** buffer aBuf. *pnWrite is set to the number of bytes written before +** returning. Or, if aBuf is NULL, the only thing this function does is +** 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)) +** 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 */ +){ + int nByte; /* Size of serialized value in bytes */ + + if( pValue ){ + int eType; /* Value type (SQLITE_NULL, TEXT etc.) */ + + eType = sqlite3_value_type(pValue); + if( aBuf ) aBuf[0] = eType; + + switch( eType ){ + case SQLITE_NULL: + nByte = 1; + break; + + case SQLITE_INTEGER: + case SQLITE_FLOAT: + if( aBuf ){ + /* TODO: SQLite does something special to deal with mixed-endian + ** floating point values (e.g. ARM7). This code probably should + ** too. */ + u64 i; + if( eType==SQLITE_INTEGER ){ + i = (u64)sqlite3_value_int64(pValue); + }else{ + double r; + assert( sizeof(double)==8 && sizeof(u64)==8 ); + r = sqlite3_value_double(pValue); + memcpy(&i, &r, 8); + } + sessionPutI64(&aBuf[1], i); + } + nByte = 9; + break; + + default: { + u8 *z; + int n; + int nVarint; + + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==SQLITE_TEXT ){ + z = (u8 *)sqlite3_value_text(pValue); + }else{ + z = (u8 *)sqlite3_value_blob(pValue); + } + n = sqlite3_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 + ); + } + + nByte = 1 + nVarint + n; + break; + } + } + }else{ + nByte = 1; + if( aBuf ) aBuf[0] = '\0'; + } + + if( pnWrite ) *pnWrite += nByte; + return SQLITE_OK; +} + + +/* +** This macro is used to calculate hash key values for data structures. In +** order to use this macro, the entire data structure must be represented +** as a series of unsigned integers. In order to calculate a hash-key value +** for a data structure represented as three such integers, the macro may +** then be used as follows: +** +** int hash_key_value; +** hash_key_value = HASH_APPEND(0, ); +** hash_key_value = HASH_APPEND(hash_key_value, ); +** hash_key_value = HASH_APPEND(hash_key_value, ); +** +** In practice, the data structures this macro is used for are the primary +** key values of modified rows. +*/ +#define HASH_APPEND(hash, add) ((hash) << 3) ^ (hash) ^ (unsigned int)(add) + +/* +** Append the hash of the 64-bit integer passed as the second argument to the +** hash-key value passed as the first. Return the new hash-key value. +*/ +static unsigned int sessionHashAppendI64(unsigned int h, i64 i){ + h = HASH_APPEND(h, i & 0xFFFFFFFF); + return HASH_APPEND(h, (i>>32)&0xFFFFFFFF); +} + +/* +** Append the hash of the blob passed via the second and third arguments to +** the hash-key value passed as the first. Return the new hash-key value. +*/ +static unsigned int sessionHashAppendBlob(unsigned int h, int n, const u8 *z){ + int i; + for(i=0; inCol==pSession->hook.xCount(pSession->hook.pCtx) ); + for(i=0; inCol; i++){ + if( pTab->abPK[i] ){ + int rc; + int eType; + sqlite3_value *pVal; + + if( bNew ){ + rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal); + }else{ + rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal); + } + if( rc!=SQLITE_OK ) return rc; + + eType = sqlite3_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); + }else{ + double rVal = sqlite3_value_double(pVal); + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&iVal, &rVal, 8); + } + h = sessionHashAppendI64(h, iVal); + }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + const u8 *z; + int n; + if( eType==SQLITE_TEXT ){ + z = (const u8 *)sqlite3_value_text(pVal); + }else{ + z = (const u8 *)sqlite3_value_blob(pVal); + } + n = sqlite3_value_bytes(pVal); + if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; + h = sessionHashAppendBlob(h, n, z); + }else{ + assert( eType==SQLITE_NULL ); + *pbNullPK = 1; + } + } + } + + *piHash = (h % pTab->nChange); + return SQLITE_OK; +} + +/* +** The buffer that the argument points to contains a serialized SQL value. +** Return the number of bytes of space occupied by the value (including +** the type byte). +*/ +static int sessionSerialLen(u8 *a){ + int e = *a; + int n; + if( e==0 ) return 1; + if( e==SQLITE_NULL ) return 1; + if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; + return sessionVarintGet(&a[1], &n) + 1 + n; +} + +/* +** Based on the primary key values stored in change aRecord, calculate a +** hash key. Assume the has table has nBucket buckets. The hash keys +** calculated by this function are compatible with those calculated by +** sessionPreupdateHash(). +** +** The bPkOnly argument is non-zero if the record at aRecord[] is from +** a patchset DELETE. In this case the non-PK fields are omitted entirely. +*/ +static unsigned int sessionChangeHash( + SessionTable *pTab, /* Table handle */ + int bPkOnly, /* Record consists of PK fields only */ + u8 *aRecord, /* Change record */ + int nBucket /* Assume this many buckets in hash table */ +){ + unsigned int h = 0; /* Value to return */ + int i; /* Used to iterate through columns */ + u8 *a = aRecord; /* Used to iterate through change record */ + + for(i=0; inCol; i++){ + int eType = *a; + int isPK = pTab->abPK[i]; + if( bPkOnly && isPK==0 ) continue; + + /* It is not possible for eType to be SQLITE_NULL here. The session + ** module does not record changes for rows with NULL values stored in + ** primary key columns. */ + assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT + || eType==SQLITE_TEXT || eType==SQLITE_BLOB + || eType==SQLITE_NULL || eType==0 + ); + assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) ); + + if( isPK ){ + a++; + h = sessionHashAppendType(h, eType); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + h = sessionHashAppendI64(h, sessionGetI64(a)); + a += 8; + }else{ + int n; + a += sessionVarintGet(a, &n); + h = sessionHashAppendBlob(h, n, a); + a += n; + } + }else{ + a += sessionSerialLen(a); + } + } + return (h % nBucket); +} + +/* +** Arguments aLeft and aRight are pointers to change records for table pTab. +** This function returns true if the two records apply to the same row (i.e. +** have the same values stored in the primary key columns), or false +** otherwise. +*/ +static int sessionChangeEqual( + SessionTable *pTab, /* Table used for PK definition */ + int bLeftPkOnly, /* True if aLeft[] contains PK fields only */ + u8 *aLeft, /* Change record */ + int bRightPkOnly, /* True if aRight[] contains PK fields only */ + u8 *aRight /* Change record */ +){ + u8 *a1 = aLeft; /* Cursor to iterate through aLeft */ + u8 *a2 = aRight; /* Cursor to iterate through aRight */ + int iCol; /* Used to iterate through table columns */ + + for(iCol=0; iColnCol; iCol++){ + if( pTab->abPK[iCol] ){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + + if( pTab->abPK[iCol] && (n1!=n2 || memcmp(a1, a2, n1)) ){ + return 0; + } + a1 += n1; + a2 += n2; + }else{ + if( bLeftPkOnly==0 ) a1 += sessionSerialLen(a1); + if( bRightPkOnly==0 ) a2 += sessionSerialLen(a2); + } + } + + return 1; +} + +/* +** Arguments aLeft and aRight both point to buffers containing change +** records with nCol columns. This function "merges" the two records into +** a single records which is written to the buffer at *paOut. *paOut is +** then set to point to one byte after the last byte written before +** returning. +** +** The merging of records is done as follows: For each column, if the +** aRight record contains a value for the column, copy the value from +** their. Otherwise, if aLeft contains a value, copy it. If neither +** record contains a value for a given column, then neither does the +** output record. +*/ +static void sessionMergeRecord( + u8 **paOut, + int nCol, + u8 *aLeft, + u8 *aRight +){ + u8 *a1 = aLeft; /* Cursor used to iterate through aLeft */ + u8 *a2 = aRight; /* Cursor used to iterate through aRight */ + u8 *aOut = *paOut; /* Output cursor */ + int iCol; /* Used to iterate from 0 to nCol */ + + for(iCol=0; iColnCol; i++){ + int nOld; + u8 *aOld; + int nNew; + u8 *aNew; + + aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); + aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); + if( pTab->abPK[i] || nOld!=nNew || memcmp(aOld, aNew, nNew) ){ + if( pTab->abPK[i]==0 ) bRequired = 1; + memcpy(aOut, aOld, nOld); + aOut += nOld; + }else{ + *(aOut++) = '\0'; + } + } + + if( !bRequired ) return 0; + } + + /* Write the new.* vector */ + aOld1 = aOldRecord1; + aOld2 = aOldRecord2; + aNew1 = aNewRecord1; + aNew2 = aNewRecord2; + for(i=0; inCol; i++){ + int nOld; + u8 *aOld; + int nNew; + u8 *aNew; + + aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); + aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); + if( bPatchset==0 + && (pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew))) + ){ + *(aOut++) = '\0'; + }else{ + memcpy(aOut, aNew, nNew); + aOut += nNew; + } + } + + *paOut = aOut; + return 1; +} + +/* +** This function is only called from within a pre-update-hook callback. +** It determines if the current pre-update-hook change affects the same row +** as the change stored in argument pChange. If so, it returns true. Otherwise +** if the pre-update-hook does not affect the same row as pChange, it returns +** false. +*/ +static int sessionPreupdateEqual( + sqlite3_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 */ +){ + int iCol; /* Used to iterate through columns */ + u8 *a = pChange->aRecord; /* Cursor used to scan change record */ + + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); + for(iCol=0; iColnCol; iCol++){ + if( !pTab->abPK[iCol] ){ + a += sessionSerialLen(a); + }else{ + sqlite3_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 */ + + /* The following calls to preupdate_new() and preupdate_old() can not + ** fail. This is because they cache their return values, and by the + ** time control flows to here they have already been called once from + ** within sessionPreupdateHash(). The first two asserts below verify + ** this (that the method has already been called). */ + if( op==SQLITE_INSERT ){ + /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */ + rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal); + }else{ + /* assert( db->pPreUpdate->pUnpacked ); */ + rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); + } + assert( rc==SQLITE_OK ); + if( sqlite3_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 + || eType==SQLITE_BLOB || eType==SQLITE_TEXT + ); + + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal = sessionGetI64(a); + a += 8; + if( eType==SQLITE_INTEGER ){ + if( sqlite3_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; + } + }else{ + int n; + const u8 *z; + a += sessionVarintGet(a, &n); + if( sqlite3_value_bytes(pVal)!=n ) return 0; + if( eType==SQLITE_TEXT ){ + z = sqlite3_value_text(pVal); + }else{ + z = sqlite3_value_blob(pVal); + } + if( memcmp(a, z, n) ) return 0; + a += n; + break; + } + } + } + + return 1; +} + +/* +** If required, grow the hash table used to store changes on table pTab +** (part of the session pSession). If a fatal OOM error occurs, set the +** session object to failed and return SQLITE_ERROR. Otherwise, return +** SQLITE_OK. +** +** It is possible that a non-fatal OOM error occurs in this function. In +** that case the hash-table does not grow, but SQLITE_OK is returned anyway. +** Growing the hash table in this case is a performance optimization only, +** it is not required for correct operation. +*/ +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; + + apNew = (SessionChange **)sqlite3_malloc(sizeof(SessionChange *) * nNew); + if( apNew==0 ){ + if( pTab->nChange==0 ){ + return SQLITE_ERROR; + } + return SQLITE_OK; + } + memset(apNew, 0, sizeof(SessionChange *) * nNew); + + for(i=0; inChange; i++){ + SessionChange *p; + SessionChange *pNext; + for(p=pTab->apChange[i]; p; p=pNext){ + int bPkOnly = (p->op==SQLITE_DELETE && bPatchset); + int iHash = sessionChangeHash(pTab, bPkOnly, p->aRecord, nNew); + pNext = p->pNext; + p->pNext = apNew[iHash]; + apNew[iHash] = p; + } + } + + sqlite3_free(pTab->apChange); + pTab->nChange = nNew; + pTab->apChange = apNew; + } + + return SQLITE_OK; +} + +/* +** 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. +** +** 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 +** nul-terminated copy of the table name. *pazCol (if not NULL) is set to +** point to an array of pointers to column names. And *pabPK (again, if not +** NULL) is set to point to an array of booleans - true if the corresponding +** column is part of the primary key. +** +** For example, if the table is declared as: +** +** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); +** +** Then the four output variables are populated as follows: +** +** *pnCol = 4 +** *pzTab = "tbl1" +** *pazCol = {"w", "x", "y", "z"} +** *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. +*/ +static int sessionTableInfo( + sqlite3 *db, /* Database connection */ + const char *zDb, /* Name of attached database (e.g. "main") */ + const char *zThis, /* Table name */ + int *pnCol, /* OUT: number of columns */ + const char **pzTab, /* OUT: Copy of zThis */ + const char ***pazCol, /* OUT: Array of column names for table */ + u8 **pabPK /* OUT: Array of booleans - true for PK col */ +){ + char *zPragma; + sqlite3_stmt *pStmt; + int rc; + int nByte; + int nDbCol = 0; + int nThis; + int i; + u8 *pAlloc = 0; + char **azCol = 0; + u8 *abPK = 0; + + assert( pazCol && pabPK ); + + nThis = sqlite3Strlen30(zThis); + zPragma = sqlite3_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); + if( rc!=SQLITE_OK ) return rc; + + nByte = nThis + 1; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + nByte += sqlite3_column_bytes(pStmt, 1); + nDbCol++; + } + rc = sqlite3_reset(pStmt); + + if( rc==SQLITE_OK ){ + nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); + pAlloc = sqlite3_malloc(nByte); + if( pAlloc==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + azCol = (char **)pAlloc; + pAlloc = (u8 *)&azCol[nDbCol]; + abPK = (u8 *)pAlloc; + pAlloc = &abPK[nDbCol]; + if( pzTab ){ + memcpy(pAlloc, zThis, nThis+1); + *pzTab = (char *)pAlloc; + pAlloc += nThis+1; + } + + i = 0; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nName = sqlite3_column_bytes(pStmt, 1); + const unsigned char *zName = sqlite3_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); + i++; + } + rc = sqlite3_reset(pStmt); + + } + + /* If successful, populate the output variables. Otherwise, zero them and + ** free any allocation made. An error code will be returned in this case. + */ + if( rc==SQLITE_OK ){ + *pazCol = (const char **)azCol; + *pabPK = abPK; + *pnCol = nDbCol; + }else{ + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pzTab ) *pzTab = 0; + sqlite3_free(azCol); + } + sqlite3_finalize(pStmt); + return rc; +} + +/* +** This function is only called from within a pre-update handler for a +** write to table pTab, part of session pSession. If this is the first +** 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 +** 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 +** 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){ + if( pTab->nCol==0 ){ + u8 *abPK; + assert( pTab->azCol==0 || pTab->abPK==0 ); + pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, + pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK + ); + if( pSession->rc==SQLITE_OK ){ + int i; + for(i=0; inCol; i++){ + if( abPK[i] ){ + pTab->abPK = abPK; + break; + } + } + } + } + return (pSession->rc || pTab->abPK==0); +} + +/* +** 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. +** +** Unless one is already present or an error occurs, an entry is added +** to the changed-rows hash table associated with table pTab. +*/ +static void sessionPreupdateOneChange( + int op, /* One of SQLITE_UPDATE, INSERT, DELETE */ + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab /* Table that change applies to */ +){ + int iHash; + int bNull = 0; + int rc = SQLITE_OK; + + if( pSession->rc ) return; + + /* Load table details if required */ + if( sessionInitTable(pSession, pTab) ) return; + + /* Check the number of columns in this xPreUpdate call matches the + ** number of columns in the table. */ + if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){ + pSession->rc = SQLITE_SCHEMA; + return; + } + + /* Grow the hash table if required */ + if( sessionGrowHash(0, pTab) ){ + pSession->rc = SQLITE_NOMEM; + return; + } + + /* 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. */ + rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull); + if( rc!=SQLITE_OK ) goto error_out; + + if( bNull==0 ){ + /* Search the hash table for an existing record for this row. */ + SessionChange *pC; + for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){ + if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break; + } + + if( pC==0 ){ + /* Create a new change object containing all the old values (if + ** 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 */ + int i; /* Used to iterate through columns */ + + assert( rc==SQLITE_OK ); + pTab->nEntry++; + + /* Figure out how large an allocation is required */ + nByte = sizeof(SessionChange); + for(i=0; inCol; i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p); + assert( trc==SQLITE_OK ); + }else if( pTab->abPK[i] ){ + TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p); + assert( trc==SQLITE_OK ); + } + + /* This may fail if SQLite value p contains a utf-16 string that must + ** be converted to utf-8 and an OOM error occurs while doing so. */ + rc = sessionSerializeValue(0, p, &nByte); + if( rc!=SQLITE_OK ) goto error_out; + } + + /* Allocate the change object */ + pChange = (SessionChange *)sqlite3_malloc(nByte); + if( !pChange ){ + rc = SQLITE_NOMEM; + goto error_out; + }else{ + memset(pChange, 0, sizeof(SessionChange)); + pChange->aRecord = (u8 *)&pChange[1]; + } + + /* Populate the change object. None of the preupdate_old(), + ** preupdate_new() or SerializeValue() calls below may fail as all + ** required values and encodings have already been cached in memory. + ** It is not possible for an OOM to occur in this block. */ + nByte = 0; + for(i=0; inCol; i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + pSession->hook.xOld(pSession->hook.pCtx, i, &p); + }else if( pTab->abPK[i] ){ + pSession->hook.xNew(pSession->hook.pCtx, i, &p); + } + sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); + } + + /* Add the change to the hash-table */ + if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ + pChange->bIndirect = 1; + } + pChange->nRecord = nByte; + pChange->op = op; + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + + }else if( pC->bIndirect ){ + /* If the existing change is considered "indirect", but this current + ** change is "direct", mark the change object as direct. */ + if( pSession->hook.xDepth(pSession->hook.pCtx)==0 + && pSession->bIndirect==0 + ){ + pC->bIndirect = 0; + } + } + } + + /* If an error has occurred, mark the session object as failed. */ + error_out: + if( rc!=SQLITE_OK ){ + pSession->rc = rc; + } +} + +static int sessionFindTable( + sqlite3_session *pSession, + const char *zName, + SessionTable **ppTab +){ + int rc = SQLITE_OK; + int nName = sqlite3Strlen30(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( pRet==0 && pSession->bAutoAttach ){ + /* If there is a table-filter configured, invoke it. If it returns 0, + ** do not automatically add the new table. */ + if( pSession->xTableFilter==0 + || pSession->xTableFilter(pSession->pFilterCtx, zName) + ){ + rc = sqlite3session_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( rc==SQLITE_OK || pRet==0 ); + *ppTab = pRet; + return rc; +} + +/* +** The 'pre-update' hook registered by this module with SQLite databases. +*/ +static void xPreUpdate( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *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) */ +){ + sqlite3_session *pSession; + int nDb = sqlite3Strlen30(zDb); + + assert( sqlite3_mutex_held(db->mutex) ); + + for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ + SessionTable *pTab; + + /* If this session is attached to a different database ("main", "temp" + ** etc.), or if it is not currently enabled, there is nothing to do. Skip + ** 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; + + pSession->rc = sessionFindTable(pSession, zName, &pTab); + if( pTab ){ + assert( pSession->rc==SQLITE_OK ); + sessionPreupdateOneChange(op, pSession, pTab); + if( op==SQLITE_UPDATE ){ + sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab); + } + } + } +} + +/* +** 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 sessionPreupdateNew(void *pCtx, int iVal, sqlite3_value **ppVal){ + return sqlite3_preupdate_new((sqlite3*)pCtx, iVal, ppVal); +} +static int sessionPreupdateCount(void *pCtx){ + return sqlite3_preupdate_count((sqlite3*)pCtx); +} +static int sessionPreupdateDepth(void *pCtx){ + return sqlite3_preupdate_depth((sqlite3*)pCtx); +} + +/* +** Install the pre-update hooks on the session object passed as the only +** argument. +*/ +static void sessionPreupdateHooks( + sqlite3_session *pSession +){ + pSession->hook.pCtx = (void*)pSession->db; + pSession->hook.xOld = sessionPreupdateOld; + pSession->hook.xNew = sessionPreupdateNew; + pSession->hook.xCount = sessionPreupdateCount; + pSession->hook.xDepth = sessionPreupdateDepth; +} + +typedef struct SessionDiffCtx SessionDiffCtx; +struct SessionDiffCtx { + sqlite3_stmt *pStmt; + int nOldOff; +}; + +/* +** The diff hook implementations. +*/ +static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){ + SessionDiffCtx *p = (SessionDiffCtx*)pCtx; + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff); + return SQLITE_OK; +} +static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){ + SessionDiffCtx *p = (SessionDiffCtx*)pCtx; + *ppVal = sqlite3_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); +} +static int sessionDiffDepth(void *pCtx){ + return 0; +} + +/* +** Install the diff hooks on the session object passed as the only +** argument. +*/ +static void sessionDiffHooks( + sqlite3_session *pSession, + SessionDiffCtx *pDiffCtx +){ + pSession->hook.pCtx = (void*)pDiffCtx; + pSession->hook.xOld = sessionDiffOld; + pSession->hook.xNew = sessionDiffNew; + pSession->hook.xCount = sessionDiffCount; + pSession->hook.xDepth = sessionDiffDepth; +} + +static char *sessionExprComparePK( + int nCol, + const char *zDb1, const char *zDb2, + const char *zTab, + const char **azCol, u8 *abPK +){ + int i; + const char *zSep = ""; + char *zRet = 0; + + for(i=0; inCol, zDb1, zDb2, pTab->zName,zExpr); + + if( zStmt==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt; + rc = sqlite3_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) ){ + sessionPreupdateOneChange(op, pSession, pTab); + } + rc = sqlite3_finalize(pStmt); + } + sqlite3_free(zStmt); + } + + return rc; +} + +static int sessionDiffFindModified( + sqlite3_session *pSession, + SessionTable *pTab, + const char *zFrom, + const char *zExpr +){ + int rc = SQLITE_OK; + + char *zExpr2 = sessionExprCompareOther(pTab->nCol, + pSession->zDb, zFrom, pTab->zName, pTab->azCol, pTab->abPK + ); + if( zExpr2==0 ){ + rc = SQLITE_NOMEM; + }else{ + char *zStmt = sqlite3_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); + + if( rc==SQLITE_OK ){ + SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; + pDiffCtx->pStmt = pStmt; + pDiffCtx->nOldOff = pTab->nCol; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab); + } + rc = sqlite3_finalize(pStmt); + } + sqlite3_free(zStmt); + } + } + + return rc; +} + +SQLITE_API int SQLITE_STDCALL sqlite3session_diff( + sqlite3_session *pSession, + const char *zFrom, + const char *zTbl, + char **pzErrMsg +){ + const char *zDb = pSession->zDb; + int rc = pSession->rc; + SessionDiffCtx d; + + memset(&d, 0, sizeof(d)); + sessionDiffHooks(pSession, &d); + + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( pzErrMsg ) *pzErrMsg = 0; + if( rc==SQLITE_OK ){ + char *zExpr = 0; + sqlite3 *db = pSession->db; + SessionTable *pTo; /* Table zTbl */ + + /* Locate and if necessary initialize the target table object */ + rc = sessionFindTable(pSession, zTbl, &pTo); + if( pTo==0 ) goto diff_out; + if( sessionInitTable(pSession, pTo) ){ + rc = pSession->rc; + goto diff_out; + } + + /* Check the table schemas match */ + if( rc==SQLITE_OK ){ + int bHasPk = 0; + int bMismatch = 0; + int nCol; /* Columns in zFrom.zTbl */ + u8 *abPK; + const char **azCol = 0; + rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK); + if( rc==SQLITE_OK ){ + if( pTo->nCol!=nCol ){ + bMismatch = 1; + }else{ + int i; + for(i=0; iabPK[i]!=abPK[i] ) bMismatch = 1; + if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1; + if( abPK[i] ) bHasPk = 1; + } + } + + } + sqlite3_free((char*)azCol); + if( bMismatch ){ + *pzErrMsg = sqlite3_mprintf("table schemas do not match"); + rc = SQLITE_SCHEMA; + } + if( bHasPk==0 ){ + /* Ignore tables with no primary keys */ + goto diff_out; + } + } + + if( rc==SQLITE_OK ){ + zExpr = sessionExprComparePK(pTo->nCol, + zDb, zFrom, pTo->zName, pTo->azCol, pTo->abPK + ); + } + + /* Find new rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindNew(SQLITE_INSERT, pSession, pTo, zDb, zFrom, zExpr); + } + + /* Find old rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindNew(SQLITE_DELETE, pSession, pTo, zFrom, zDb, zExpr); + } + + /* Find modified rows */ + if( rc==SQLITE_OK ){ + rc = sessionDiffFindModified(pSession, pTo, zFrom, zExpr); + } + + sqlite3_free(zExpr); + } + + diff_out: + sessionPreupdateHooks(pSession); + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return rc; +} + +/* +** Create a session object. This session object will record changes to +** database zDb attached to connection db. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_create( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_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 */ + + /* 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); + if( !pNew ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(sqlite3_session)); + pNew->db = db; + pNew->zDb = (char *)&pNew[1]; + pNew->bEnable = 1; + memcpy(pNew->zDb, zDb, nDb+1); + sessionPreupdateHooks(pNew); + + /* 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); + pNew->pNext = pOld; + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + + *ppSession = pNew; + return SQLITE_OK; +} + +/* +** Free the list of table objects passed as the first argument. The contents +** of the changed-rows hash tables are also deleted. +*/ +static void sessionDeleteTable(SessionTable *pList){ + SessionTable *pNext; + SessionTable *pTab; + + for(pTab=pList; pTab; pTab=pNext){ + int i; + pNext = pTab->pNext; + for(i=0; inChange; i++){ + SessionChange *p; + SessionChange *pNextChange; + for(p=pTab->apChange[i]; p; p=pNextChange){ + pNextChange = p->pNext; + sqlite3_free(p); + } + } + sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */ + sqlite3_free(pTab->apChange); + sqlite3_free(pTab); + } +} + +/* +** Delete a session object previously allocated using sqlite3session_create(). +*/ +SQLITE_API void SQLITE_STDCALL sqlite3session_delete(sqlite3_session *pSession){ + sqlite3 *db = pSession->db; + sqlite3_session *pHead; + sqlite3_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); + for(pp=&pHead; ALWAYS((*pp)!=0); pp=&((*pp)->pNext)){ + if( (*pp)==pSession ){ + *pp = (*pp)->pNext; + if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead); + break; + } + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + + /* Delete all attached table objects. And the contents of their + ** associated hash-tables. */ + sessionDeleteTable(pSession->pTable); + + /* Free the session object itself. */ + sqlite3_free(pSession); +} + +/* +** Set a table filter on a Session Object. +*/ +SQLITE_API void SQLITE_STDCALL sqlite3session_table_filter( + sqlite3_session *pSession, + int(*xFilter)(void*, const char*), + void *pCtx /* First argument passed to xFilter */ +){ + pSession->bAutoAttach = 1; + pSession->pFilterCtx = pCtx; + pSession->xTableFilter = xFilter; +} + +/* +** Attach a table to a session. All subsequent changes made to the table +** while the session object is enabled will be recorded. +** +** Only tables that have a PRIMARY KEY defined may be attached. It does +** not matter if the PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) +** or not. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_attach( + sqlite3_session *pSession, /* Session object */ + const char *zName /* Table name */ +){ + int rc = SQLITE_OK; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + + if( !zName ){ + pSession->bAutoAttach = 1; + }else{ + SessionTable *pTab; /* New table object (if required) */ + int nName; /* Number of bytes in string zName */ + + /* First search for an existing entry. If one is found, this call is + ** a no-op. Return early. */ + nName = sqlite3Strlen30(zName); + for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break; + } + + if( !pTab ){ + /* Allocate new SessionTable object. */ + pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1); + if( !pTab ){ + rc = SQLITE_NOMEM; + }else{ + /* Populate the new SessionTable object and link it into the list. + ** The new object must be linked onto the end of the list, not + ** simply added to the start of it in order to ensure that tables + ** appear in the correct order when a changeset or patchset is + ** eventually generated. */ + SessionTable **ppTab; + memset(pTab, 0, sizeof(SessionTable)); + pTab->zName = (char *)&pTab[1]; + memcpy(pTab->zName, zName, nName+1); + for(ppTab=&pSession->pTable; *ppTab; ppTab=&(*ppTab)->pNext); + *ppTab = pTab; + } + } + } + + sqlite3_mutex_leave(sqlite3_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 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->nBufnAlloc ? p->nAlloc : 128; + do { + nNew = nNew*2; + }while( nNew<(p->nBuf+nByte) ); + + aNew = (u8 *)sqlite3_realloc(p->aBuf, nNew); + if( 0==aNew ){ + *pRc = SQLITE_NOMEM; + }else{ + p->aBuf = aNew; + p->nAlloc = nNew; + } + } + return (*pRc!=SQLITE_OK); +} + +/* +** Append the value passed as the second argument to the buffer passed +** as the first. +** +** This function is a no-op if *pRc is non-zero when it is called. +** 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){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + int nByte = 0; + rc = sessionSerializeValue(0, pVal, &nByte); + sessionBufferGrow(p, nByte, &rc); + if( rc==SQLITE_OK ){ + rc = sessionSerializeValue(&p->aBuf[p->nBuf], pVal, 0); + p->nBuf += nByte; + }else{ + *pRc = rc; + } + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single byte to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendByte(SessionBuffer *p, u8 v, int *pRc){ + if( 0==sessionBufferGrow(p, 1, pRc) ){ + p->aBuf[p->nBuf++] = v; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single varint to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendVarint(SessionBuffer *p, int v, int *pRc){ + if( 0==sessionBufferGrow(p, 9, pRc) ){ + p->nBuf += sessionVarintPut(&p->aBuf[p->nBuf], v); + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a blob of data to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendBlob( + SessionBuffer *p, + const u8 *aBlob, + int nBlob, + int *pRc +){ + if( 0==sessionBufferGrow(p, nBlob, pRc) ){ + memcpy(&p->aBuf[p->nBuf], aBlob, nBlob); + p->nBuf += nBlob; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a string to the buffer. All bytes in the string +** up to (but not including) the nul-terminator are written to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendStr( + SessionBuffer *p, + const char *zStr, + int *pRc +){ + int nStr = sqlite3Strlen30(zStr); + if( 0==sessionBufferGrow(p, nStr, pRc) ){ + memcpy(&p->aBuf[p->nBuf], zStr, nStr); + p->nBuf += nStr; + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append the string representation of integer iVal +** to the buffer. No nul-terminator is written. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendInteger( + SessionBuffer *p, /* Buffer to append to */ + int iVal, /* Value to write the string rep. of */ + int *pRc /* IN/OUT: Error code */ +){ + char aBuf[24]; + sqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal); + sessionAppendStr(p, aBuf, pRc); +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append the string zStr enclosed in quotes (") and +** with any embedded quote characters escaped to the buffer. No +** nul-terminator byte is written. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendIdent( + SessionBuffer *p, /* Buffer to a append to */ + const char *zStr, /* String to quote, escape and append */ + int *pRc /* IN/OUT: Error code */ +){ + int nStr = sqlite3Strlen30(zStr)*2 + 2 + 1; + if( 0==sessionBufferGrow(p, nStr, pRc) ){ + char *zOut = (char *)&p->aBuf[p->nBuf]; + const char *zIn = zStr; + *zOut++ = '"'; + while( *zIn ){ + if( *zIn=='"' ) *zOut++ = '"'; + *zOut++ = *(zIn++); + } + *zOut++ = '"'; + p->nBuf = (int)((u8 *)zOut - p->aBuf); + } +} + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwse, it appends the serialized version of the value stored +** in column iCol of the row that SQL statement pStmt currently points +** to to the buffer. +*/ +static void sessionAppendCol( + SessionBuffer *p, /* Buffer to append to */ + sqlite3_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); + sessionAppendByte(p, (u8)eType, pRc); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + sqlite3_int64 i; + u8 aBuf[8]; + if( eType==SQLITE_INTEGER ){ + i = sqlite3_column_int64(pStmt, iCol); + }else{ + double r = sqlite3_column_double(pStmt, iCol); + memcpy(&i, &r, 8); + } + sessionPutI64(aBuf, i); + sessionAppendBlob(p, aBuf, 8, pRc); + } + if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){ + u8 *z; + int nByte; + if( eType==SQLITE_BLOB ){ + z = (u8 *)sqlite3_column_blob(pStmt, iCol); + }else{ + z = (u8 *)sqlite3_column_text(pStmt, iCol); + } + nByte = sqlite3_column_bytes(pStmt, iCol); + if( z || (eType==SQLITE_BLOB && nByte==0) ){ + sessionAppendVarint(p, nByte, pRc); + sessionAppendBlob(p, z, nByte, pRc); + }else{ + *pRc = SQLITE_NOMEM; + } + } + } +} + +/* +** +** This function appends an update change to the buffer (see the comments +** under "CHANGESET FORMAT" at the top of the file). An update change +** consists of: +** +** 1 byte: SQLITE_UPDATE (0x17) +** n bytes: old.* record (see RECORD FORMAT) +** m bytes: new.* record (see RECORD FORMAT) +** +** The SessionChange object passed as the third argument contains the +** values that were stored in the row when the session began (the old.* +** values). The statement handle passed as the second argument points +** at the current version of the row (the new.* values). +** +** If all of the old.* values are equal to their corresponding new.* value +** (i.e. nothing has changed), then no data at all is appended to the buffer. +** +** Otherwise, the old.* record contains all primary key values and the +** original values of any fields that have been modified. The new.* record +** contains the new values of only those fields that have been modified. +*/ +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 */ + SessionChange *p, /* Object containing old values */ + u8 *abPK /* Boolean array - true for PK columns */ +){ + int rc = SQLITE_OK; + SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */ + int bNoop = 1; /* Set to zero if any values are modified */ + int nRewind = pBuf->nBuf; /* Set to zero if any values are modified */ + int i; /* Used to iterate through columns */ + u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ + + sessionAppendByte(pBuf, SQLITE_UPDATE, &rc); + sessionAppendByte(pBuf, p->bIndirect, &rc); + for(i=0; inBuf = nRewind; + }else{ + sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc); + } + sqlite3_free(buf2.aBuf); + + return rc; +} + +/* +** Append a DELETE change to the buffer passed as the first argument. Use +** the changeset format if argument bPatchset is zero, or the patchset +** format otherwise. +*/ +static int sessionAppendDelete( + SessionBuffer *pBuf, /* Buffer to append to */ + int bPatchset, /* True for "patchset", 0 for "changeset" */ + SessionChange *p, /* Object containing old values */ + int nCol, /* Number of columns in table */ + u8 *abPK /* Boolean array - true for PK columns */ +){ + int rc = SQLITE_OK; + + sessionAppendByte(pBuf, SQLITE_DELETE, &rc); + sessionAppendByte(pBuf, p->bIndirect, &rc); + + if( bPatchset==0 ){ + sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc); + }else{ + int i; + u8 *a = p->aRecord; + for(i=0; iaRecord)==p->nRecord ); + } + + return rc; +} + +/* +** Formulate and prepare a SELECT statement to retrieve a row from table +** zTab in database zDb based on its primary key. i.e. +** +** SELECT * FROM zDb.zTab WHERE pk1 = ? AND pk2 = ? AND ... +*/ +static int sessionSelectStmt( + sqlite3 *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 */ +){ + int rc = SQLITE_OK; + 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; iaRecord; + + for(i=0; inCol, pRc); + sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc); + sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc); +} + +/* +** Generate either a changeset (if argument bPatchset is zero) or a patchset +** (if it is non-zero) based on the current contents of the session object +** passed as the first argument. +** +** If no error occurs, SQLITE_OK is returned and the new changeset/patchset +** stored in output variables *pnChangeset and *ppChangeset. Or, if an error +** occurs, an SQLite error code is returned and both output variables set +** to 0. +*/ +static int sessionGenerateChangeset( + sqlite3_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 */ + SessionTable *pTab; /* Used to iterate through attached tables */ + SessionBuffer buf = {0,0,0}; /* Buffer in which to accumlate changeset */ + int rc; /* Return code */ + + 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), + ** this call will be a no-op. */ + if( xOutput==0 ){ + *pnChangeset = 0; + *ppChangeset = 0; + } + + if( pSession->rc ) return pSession->rc; + rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + + for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ + if( pTab->nEntry ){ + const char *zName = pTab->zName; + int nCol; /* Number of columns in table */ + 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 */ + int nRewind = buf.nBuf; /* Initial size of write buffer */ + int nNoop; /* Size of buffer after writing tbl header */ + + /* Check the table schema is still Ok. */ + rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK); + if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ + rc = SQLITE_SCHEMA; + } + + /* Write a table header */ + sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); + + /* Build and compile a statement to execute: */ + if( rc==SQLITE_OK ){ + rc = sessionSelectStmt( + db, pSession->zDb, zName, nCol, azCol, abPK, &pSel); + } + + nNoop = buf.nBuf; + for(i=0; inChange && rc==SQLITE_OK; i++){ + SessionChange *p; /* Used to iterate through changes */ + + 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( p->op==SQLITE_INSERT ){ + int iCol; + sessionAppendByte(&buf, SQLITE_INSERT, &rc); + sessionAppendByte(&buf, p->bIndirect, &rc); + for(iCol=0; iColop!=SQLITE_INSERT ){ + rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_reset(pSel); + } + + /* 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 + ){ + rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf); + nNoop = -1; + buf.nBuf = 0; + } + + } + } + + sqlite3_finalize(pSel); + if( buf.nBuf==nNoop ){ + buf.nBuf = nRewind; + } + sqlite3_free((char*)azCol); /* cast works around VC++ bug */ + } + } + + if( rc==SQLITE_OK ){ + if( xOutput==0 ){ + *pnChangeset = buf.nBuf; + *ppChangeset = buf.aBuf; + buf.aBuf = 0; + }else if( buf.nBuf>0 ){ + rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf); + } + } + + sqlite3_free(buf.aBuf); + sqlite3_exec(db, "RELEASE changeset", 0, 0, 0); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return rc; +} + +/* +** Obtain a changeset object containing all changes recorded by the +** session object passed as the first argument. +** +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_changeset( + sqlite3_session *pSession, /* Session object */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +){ + return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); +} + +/* +** Streaming version of sqlite3session_changeset(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0); +} + +/* +** Streaming version of sqlite3session_patchset(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0); +} + +/* +** Obtain a patchset object containing all changes recorded by the +** session object passed as the first argument. +** +** It is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_patchset( + sqlite3_session *pSession, /* Session object */ + int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ + void **ppPatchset /* OUT: Buffer containing changeset */ +){ + return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset); +} + +/* +** Enable or disable the session object passed as the first argument. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_enable(sqlite3_session *pSession, int bEnable){ + int ret; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( bEnable>=0 ){ + pSession->bEnable = bEnable; + } + ret = pSession->bEnable; + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return ret; +} + +/* +** Enable or disable the session object passed as the first argument. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){ + int ret; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( bIndirect>=0 ){ + pSession->bIndirect = bIndirect; + } + ret = pSession->bIndirect; + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return ret; +} + +/* +** Return true if there have been no changes to monitored tables recorded +** by the session object passed as the only argument. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3session_isempty(sqlite3_session *pSession){ + int ret = 0; + SessionTable *pTab; + + sqlite3_mutex_enter(sqlite3_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)); + + return (ret==0); +} + +/* +** Do the work for either sqlite3changeset_start() or start_strm(). +*/ +static int sessionChangesetStart( + sqlite3_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 */ +){ + sqlite3_changeset_iter *pRet; /* Iterator to return */ + int nByte; /* Number of bytes to allocate for iterator */ + + assert( xInput==0 || (pChangeset==0 && nChangeset==0) ); + + /* Zero the output variable in case an error occurs. */ + *pp = 0; + + /* Allocate and initialize the iterator structure. */ + nByte = sizeof(sqlite3_changeset_iter); + pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte); + if( !pRet ) return SQLITE_NOMEM; + memset(pRet, 0, sizeof(sqlite3_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); + + /* Populate the output variable and return success. */ + *pp = pRet; + return SQLITE_OK; +} + +/* +** Create an iterator used to iterate through the contents of a changeset. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_start( + sqlite3_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); +} + +/* +** Streaming version of sqlite3changeset_start(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_start_strm( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +){ + return sessionChangesetStart(pp, xInput, pIn, 0, 0); +} + +/* +** If the SessionInput object passed as the only argument is a streaming +** 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 ){ + int nMove = pIn->buf.nBuf - pIn->iNext; + assert( nMove>=0 ); + if( nMove>0 ){ + memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iNext], nMove); + } + pIn->buf.nBuf -= pIn->iNext; + pIn->iNext = 0; + pIn->nData = pIn->buf.nBuf; + } +} + +/* +** Ensure that there are at least nByte bytes available in the buffer. Or, +** if there are not nByte bytes remaining in the input, that all available +** data is in the buffer. +** +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +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; + + if( pIn->bNoDiscard==0 ) sessionDiscardData(pIn); + if( SQLITE_OK==sessionBufferGrow(&pIn->buf, nNew, &rc) ){ + rc = pIn->xInput(pIn->pIn, &pIn->buf.aBuf[pIn->buf.nBuf], &nNew); + if( nNew==0 ){ + pIn->bEof = 1; + }else{ + pIn->buf.nBuf += nNew; + } + } + + pIn->aData = pIn->buf.aBuf; + pIn->nData = pIn->buf.nBuf; + } + } + return rc; +} + +/* +** When this function is called, *ppRec points to the start of a record +** that contains nCol values. This function advances the pointer *ppRec +** until it points to the byte immediately following that record. +*/ +static void sessionSkipRecord( + u8 **ppRec, /* IN/OUT: Record pointer */ + int nCol /* Number of values in record */ +){ + u8 *aRec = *ppRec; + int i; + for(i=0; iaData[pIn->iNext++]; + } + + assert( apOut[i]==0 ); + if( eType ){ + apOut[i] = sqlite3ValueNew(0); + if( !apOut[i] ) rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + u8 *aVal = &pIn->aData[pIn->iNext]; + if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int nByte; + 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); + } + pIn->iNext += nByte; + } + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + sqlite3_int64 v = sessionGetI64(aVal); + if( eType==SQLITE_INTEGER ){ + sqlite3VdbeMemSetInt64(apOut[i], v); + }else{ + double d; + memcpy(&d, &v, 8); + sqlite3VdbeMemSetDouble(apOut[i], d); + } + pIn->iNext += 8; + } + } + } + + return rc; +} + +/* +** The input pointer currently points to the second byte of a table-header. +** Specifically, to the following: +** +** + number of columns in table (varint) +** + array of PK flags (1 byte per column), +** + table name (nul terminated). +** +** This function ensures that all of the above is present in the input +** buffer (i.e. that it can be accessed without any calls to xInput()). +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. +** The input pointer is not moved. +*/ +static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ + int rc = SQLITE_OK; + int nCol = 0; + int nRead = 0; + + rc = sessionInputBuffer(pIn, 9); + if( rc==SQLITE_OK ){ + nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol); + rc = sessionInputBuffer(pIn, nRead+nCol+100); + nRead += nCol; + } + + while( rc==SQLITE_OK ){ + while( (pIn->iNext + nRead)nData && pIn->aData[pIn->iNext + nRead] ){ + nRead++; + } + if( (pIn->iNext + nRead)nData ) break; + rc = sessionInputBuffer(pIn, nRead + 100); + } + *pnByte = nRead+1; + return rc; +} + +/* +** The input pointer currently points to the first byte of the first field +** of a record consisting of nCol columns. This function ensures the entire +** record is buffered. It does not move the input pointer. +** +** If successful, SQLITE_OK is returned and *pnByte is set to the size of +** the record in bytes. Otherwise, an SQLite error code is returned. The +** final value of *pnByte is undefined in this case. +*/ +static int sessionChangesetBufferRecord( + SessionInput *pIn, /* Input data */ + int nCol, /* Number of columns in record */ + int *pnByte /* OUT: Size of record in bytes */ +){ + int rc = SQLITE_OK; + int nByte = 0; + int i; + for(i=0; rc==SQLITE_OK && iaData[pIn->iNext + nByte++]; + if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int n; + nByte += sessionVarintGet(&pIn->aData[pIn->iNext+nByte], &n); + nByte += n; + rc = sessionInputBuffer(pIn, nByte); + }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + nByte += 8; + } + } + } + *pnByte = nByte; + return rc; +} + +/* +** The input pointer currently points to the second byte of a table-header. +** Specifically, to the following: +** +** + number of columns in table (varint) +** + array of PK flags (1 byte per column), +** + table name (nul terminated). +** +** This function decodes the table-header and populates the p->nCol, +** p->zTab and p->abPK[] variables accordingly. The p->apValue[] array is +** also allocated or resized according to the new value of p->nCol. The +** input pointer is left pointing to the byte following the table header. +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code +** is returned and the final values of the various fields enumerated above +** are undefined. +*/ +static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ + int rc; + int nCopy; + assert( p->rc==SQLITE_OK ); + + rc = sessionChangesetBufferTblhdr(&p->in, &nCopy); + if( rc==SQLITE_OK ){ + 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( rc==SQLITE_OK ){ + int iPK = sizeof(sqlite3_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->abPK = (u8*)&p->apValue[p->nCol*2]; + p->zTab = (char*)&p->abPK[p->nCol]; + return (p->rc = rc); +} + +/* +** 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. +** +** 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 +** the record to *pnRec. +** +** Either way, this function returns SQLITE_ROW if the iterator is +** successfully advanced to the next change in the changeset, an SQLite +** error code if an error occurs, or SQLITE_DONE if there are no further +** changes in the changeset. +*/ +static int sessionChangesetNext( + sqlite3_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 i; + u8 op; + + assert( (paRec==0 && pnRec==0) || (paRec && pnRec) ); + + /* If the iterator is in the error-state, return immediately. */ + if( p->rc!=SQLITE_OK ) return p->rc; + + /* Free the current contents of p->apValue[], if any. */ + if( p->apValue ){ + for(i=0; inCol*2; i++){ + sqlite3ValueFree(p->apValue[i]); + } + memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); + } + + /* Make sure the buffer contains at least 10 bytes of input data, or all + ** remaining data if there are less than 10 bytes available. This is + ** sufficient either for the 'T' or 'P' byte and the varint that follows + ** it, or for the two single byte values otherwise. */ + p->rc = sessionInputBuffer(&p->in, 2); + if( p->rc!=SQLITE_OK ) return p->rc; + + /* If the iterator is already at the end of the changeset, return DONE. */ + if( p->in.iNext>=p->in.nData ){ + return SQLITE_DONE; + } + + sessionDiscardData(&p->in); + p->in.iCurrent = p->in.iNext; + + op = p->in.aData[p->in.iNext++]; + if( op=='T' || op=='P' ){ + 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; + op = p->in.aData[p->in.iNext++]; + } + + p->op = op; + p->bIndirect = p->in.aData[p->in.iNext++]; + if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ + return (p->rc = SQLITE_CORRUPT_BKPT); + } + + if( paRec ){ + int nVal; /* Number of values to buffer */ + if( p->bPatchset==0 && op==SQLITE_UPDATE ){ + nVal = p->nCol * 2; + }else if( p->bPatchset && op==SQLITE_DELETE ){ + nVal = 0; + for(i=0; inCol; i++) if( p->abPK[i] ) nVal++; + }else{ + nVal = p->nCol; + } + p->rc = sessionChangesetBufferRecord(&p->in, nVal, pnRec); + if( p->rc!=SQLITE_OK ) return p->rc; + *paRec = &p->in.aData[p->in.iNext]; + p->in.iNext += *pnRec; + }else{ + + /* 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); + 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]); + if( p->rc!=SQLITE_OK ) return p->rc; + } + + if( p->bPatchset && 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; inCol; i++){ + assert( p->apValue[i]==0 ); + assert( p->abPK[i]==0 || p->apValue[i+p->nCol] ); + if( p->abPK[i] ){ + p->apValue[i] = p->apValue[i+p->nCol]; + p->apValue[i+p->nCol] = 0; + } + } + } + } + + return SQLITE_ROW; +} + +/* +** Advance an iterator created by sqlite3changeset_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 SQLITE_STDCALL sqlite3changeset_next(sqlite3_changeset_iter *p){ + return sessionChangesetNext(p, 0, 0); +} + +/* +** The following function extracts information on the current change +** from a changeset iterator. It may only be called after changeset_next() +** has returned SQLITE_ROW. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_op( + sqlite3_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 */ + int *pbIndirect /* OUT: True if change is indirect */ +){ + *pOp = pIter->op; + *pnCol = pIter->nCol; + *pzTab = pIter->zTab; + if( pbIndirect ) *pbIndirect = pIter->bIndirect; + return SQLITE_OK; +} + +/* +** Return information regarding the PRIMARY KEY and number of columns in +** the database table affected by the change that pIter currently points +** to. This function may only be called after changeset_next() returns +** SQLITE_ROW. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_pk( + sqlite3_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 */ +){ + *pabPK = pIter->abPK; + if( pnCol ) *pnCol = pIter->nCol; + return SQLITE_OK; +} + +/* +** This function may only be called while the iterator is pointing to an +** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()). +** Otherwise, SQLITE_MISUSE is returned. +** +** It sets *ppValue to point to an sqlite3_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. +** +** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is +** not modified. Otherwise, SQLITE_OK. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_old( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of old.* value to retrieve */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +){ + if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_DELETE ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=pIter->nCol ){ + return SQLITE_RANGE; + } + *ppValue = pIter->apValue[iVal]; + return SQLITE_OK; +} + +/* +** This function may only be called while the iterator is pointing to an +** SQLITE_UPDATE or SQLITE_INSERT change (see sqlite3changeset_op()). +** Otherwise, SQLITE_MISUSE is returned. +** +** It sets *ppValue to point to an sqlite3_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. +** +** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is +** not modified. Otherwise, SQLITE_OK. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_new( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of new.* value to retrieve */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +){ + if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_INSERT ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=pIter->nCol ){ + return SQLITE_RANGE; + } + *ppValue = pIter->apValue[pIter->nCol+iVal]; + return SQLITE_OK; +} + +/* +** The following two macros are used internally. They are similar to the +** sqlite3changeset_new() and sqlite3changeset_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)] +#define sessionChangesetOld(pIter, iVal) (pIter)->apValue[(iVal)] + +/* +** This function may only be called with a changeset iterator that has been +** 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 +** 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 SQLITE_STDCALL sqlite3changeset_conflict( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Index of conflict record value to fetch */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +){ + if( !pIter->pConflict ){ + return SQLITE_MISUSE; + } + if( iVal<0 || iVal>=sqlite3_column_count(pIter->pConflict) ){ + return SQLITE_RANGE; + } + *ppValue = sqlite3_column_value(pIter->pConflict, iVal); + return SQLITE_OK; +} + +/* +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +){ + if( pIter->pConflict || pIter->apValue ){ + return SQLITE_MISUSE; + } + *pnOut = pIter->nCol; + return SQLITE_OK; +} + + +/* +** Finalize an iterator allocated with sqlite3changeset_start(). +** +** This function may not be called on iterators passed to a conflict handler +** callback by changeset_apply(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_finalize(sqlite3_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; inCol*2; i++) sqlite3ValueFree(p->apValue[i]); + } + sqlite3_free(p->tblhdr.aBuf); + sqlite3_free(p->in.buf.aBuf); + sqlite3_free(p); + } + return rc; +} + +static int sessionChangesetInvert( + SessionInput *pInput, /* Input changeset */ + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, + int *pnInverted, /* OUT: Number of bytes in output changeset */ + void **ppInverted /* OUT: Inverse of pChangeset */ +){ + int rc = SQLITE_OK; /* Return value */ + 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 */ + SessionBuffer sPK = {0, 0, 0}; /* PK array for current table */ + + /* Initialize the output buffer */ + memset(&sOut, 0, sizeof(SessionBuffer)); + + /* Zero the output variables in case an error occurs. */ + if( ppInverted ){ + *ppInverted = 0; + *pnInverted = 0; + } + + while( 1 ){ + u8 eType; + + /* Test for EOF. */ + if( (rc = sessionInputBuffer(pInput, 2)) ) goto finished_invert; + if( pInput->iNext>=pInput->nData ) break; + eType = pInput->aData[pInput->iNext]; + + switch( eType ){ + case 'T': { + /* A 'table' record consists of: + ** + ** * A constant 'T' character, + ** * Number of columns in said table (a varint), + ** * An array of nCol bytes (sPK), + ** * A nul-terminated table name. + */ + int nByte; + int nVar; + pInput->iNext++; + if( (rc = sessionChangesetBufferTblhdr(pInput, &nByte)) ){ + goto finished_invert; + } + nVar = sessionVarintGet(&pInput->aData[pInput->iNext], &nCol); + sPK.nBuf = 0; + sessionAppendBlob(&sPK, &pInput->aData[pInput->iNext+nVar], nCol, &rc); + sessionAppendByte(&sOut, eType, &rc); + sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc); + if( rc ) goto finished_invert; + + pInput->iNext += nByte; + sqlite3_free(apVal); + apVal = 0; + abPK = sPK.aBuf; + break; + } + + case SQLITE_INSERT: + case SQLITE_DELETE: { + int nByte; + int bIndirect = pInput->aData[pInput->iNext+1]; + int eType2 = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE); + pInput->iNext += 2; + assert( rc==SQLITE_OK ); + rc = sessionChangesetBufferRecord(pInput, nCol, &nByte); + sessionAppendByte(&sOut, eType2, &rc); + sessionAppendByte(&sOut, bIndirect, &rc); + sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc); + pInput->iNext += nByte; + if( rc ) goto finished_invert; + break; + } + + case SQLITE_UPDATE: { + int iCol; + + if( 0==apVal ){ + apVal = (sqlite3_value **)sqlite3_malloc(sizeof(apVal[0])*nCol*2); + if( 0==apVal ){ + rc = SQLITE_NOMEM; + goto finished_invert; + } + memset(apVal, 0, sizeof(apVal[0])*nCol*2); + } + + /* Write the header for the new UPDATE change. Same as the original. */ + sessionAppendByte(&sOut, eType, &rc); + sessionAppendByte(&sOut, pInput->aData[pInput->iNext+1], &rc); + + /* Read the old.* and new.* records for the update change. */ + pInput->iNext += 2; + rc = sessionReadRecord(pInput, nCol, 0, &apVal[0]); + if( rc==SQLITE_OK ){ + rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol]); + } + + /* Write the new old.* record. Consists of the PK columns from the + ** original old.* record, and the other values from the original + ** new.* record. */ + for(iCol=0; iCol=SESSIONS_STRM_CHUNK_SIZE ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + sOut.nBuf = 0; + if( rc!=SQLITE_OK ) goto finished_invert; + } + } + + assert( rc==SQLITE_OK ); + if( pnInverted ){ + *pnInverted = sOut.nBuf; + *ppInverted = sOut.aBuf; + sOut.aBuf = 0; + }else if( sOut.nBuf>0 ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + } + + finished_invert: + sqlite3_free(sOut.aBuf); + sqlite3_free(apVal); + sqlite3_free(sPK.aBuf); + return rc; +} + + +/* +** Invert a changeset object. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_invert( + int nChangeset, /* Number of bytes in input */ + const void *pChangeset, /* Input changeset */ + int *pnInverted, /* OUT: Number of bytes in output changeset */ + void **ppInverted /* OUT: Inverse of pChangeset */ +){ + SessionInput sInput; + + /* Set up the input stream */ + memset(&sInput, 0, sizeof(SessionInput)); + sInput.nData = nChangeset; + sInput.aData = (u8*)pChangeset; + + return sessionChangesetInvert(&sInput, 0, 0, pnInverted, ppInverted); +} + +/* +** Streaming version of sqlite3changeset_invert(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_invert_strm( + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + SessionInput sInput; + int rc; + + /* Set up the input stream */ + memset(&sInput, 0, sizeof(SessionInput)); + sInput.xInput = xInput; + sInput.pIn = pIn; + + rc = sessionChangesetInvert(&sInput, xOutput, pOut, 0, 0); + sqlite3_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 */ + 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 bDeferConstraints; /* True to defer constraints */ + SessionBuffer constraints; /* Deferred constraints are stored here */ +}; + +/* +** Formulate a statement to DELETE a row from database db. Assuming a table +** structure like this: +** +** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); +** +** The DELETE statement looks like this: +** +** DELETE FROM x WHERE a = :1 AND c = :3 AND (:5 OR b IS :2 AND d IS :4) +** +** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require +** matching b and d values, or 1 otherwise. The second case comes up if the +** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE. +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pDelete is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionDeleteRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int i; + const char *zSep = ""; + int rc = SQLITE_OK; + SessionBuffer buf = {0, 0, 0}; + int nPk = 0; + + sessionAppendStr(&buf, "DELETE FROM ", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " WHERE ", &rc); + + for(i=0; inCol; i++){ + if( p->abPK[i] ){ + nPk++; + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = " AND "; + } + } + + if( nPknCol ){ + sessionAppendStr(&buf, " AND (?", &rc); + sessionAppendInteger(&buf, p->nCol+1, &rc); + sessionAppendStr(&buf, " OR ", &rc); + + zSep = ""; + for(i=0; inCol; i++){ + if( !p->abPK[i] ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = "AND "; + } + } + sessionAppendStr(&buf, ")", &rc); + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); + } + sqlite3_free(buf.aBuf); + + return rc; +} + +/* +** Formulate and prepare a statement to UPDATE a row from database db. +** Assuming a table structure like this: +** +** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); +** +** The UPDATE statement looks like this: +** +** UPDATE x SET +** a = CASE WHEN ?2 THEN ?3 ELSE a END, +** b = CASE WHEN ?5 THEN ?6 ELSE b END, +** c = CASE WHEN ?8 THEN ?9 ELSE c END, +** d = CASE WHEN ?11 THEN ?12 ELSE d END +** WHERE a = ?1 AND c = ?7 AND (?13 OR +** (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND +** ) +** +** For each column in the table, there are three variables to bind: +** +** ?(i*3+1) The old.* value of the column, if any. +** ?(i*3+2) A boolean flag indicating that the value is being modified. +** ?(i*3+3) The new.* value of the column, if any. +** +** Also, a boolean flag that, if set to true, causes the statement to update +** a row even if the non-PK values do not match. This is required if the +** conflict-handler is invoked with CHANGESET_DATA and returns +** CHANGESET_REPLACE. This is variable "?(nCol*3+1)". +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pUpdate is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionUpdateRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int rc = SQLITE_OK; + int i; + const char *zSep = ""; + SessionBuffer buf = {0, 0, 0}; + + /* Append "UPDATE tbl SET " */ + sessionAppendStr(&buf, "UPDATE ", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " SET ", &rc); + + /* Append the assignments */ + for(i=0; inCol; i++){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = CASE WHEN ?", &rc); + sessionAppendInteger(&buf, i*3+2, &rc); + sessionAppendStr(&buf, " THEN ?", &rc); + sessionAppendInteger(&buf, i*3+3, &rc); + sessionAppendStr(&buf, " ELSE ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " END", &rc); + zSep = ", "; + } + + /* Append the PK part of the WHERE clause */ + sessionAppendStr(&buf, " WHERE ", &rc); + for(i=0; inCol; i++){ + if( p->abPK[i] ){ + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, i*3+1, &rc); + sessionAppendStr(&buf, " AND ", &rc); + } + } + + /* Append the non-PK part of the WHERE clause */ + sessionAppendStr(&buf, " (?", &rc); + sessionAppendInteger(&buf, p->nCol*3+1, &rc); + sessionAppendStr(&buf, " OR 1", &rc); + for(i=0; inCol; i++){ + if( !p->abPK[i] ){ + sessionAppendStr(&buf, " AND (?", &rc); + sessionAppendInteger(&buf, i*3+2, &rc); + sessionAppendStr(&buf, "=0 OR ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, i*3+1, &rc); + sessionAppendStr(&buf, ")", &rc); + } + } + sessionAppendStr(&buf, ")", &rc); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); + } + sqlite3_free(buf.aBuf); + + return rc; +} + +/* +** Formulate and prepare an SQL statement to query table zTab by primary +** key. Assuming the following table structure: +** +** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); +** +** The SELECT statement looks like this: +** +** SELECT * FROM x WHERE a = ?1 AND c = ?3 +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pSelect is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionSelectRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + return sessionSelectStmt( + db, "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect); +} + +/* +** Formulate and prepare an INSERT statement to add a record to table zTab. +** For example: +** +** INSERT INTO main."zTab" VALUES(?1, ?2, ?3 ...); +** +** If successful, SQLITE_OK is returned and SessionApplyCtx.pInsert is left +** pointing to the prepared version of the SQL statement. +*/ +static int sessionInsertRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int rc = SQLITE_OK; + int i; + SessionBuffer buf = {0, 0, 0}; + + sessionAppendStr(&buf, "INSERT INTO main.", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " VALUES(?", &rc); + for(i=1; inCol; i++){ + sessionAppendStr(&buf, ", ?", &rc); + } + sessionAppendStr(&buf, ")", &rc); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); + } + sqlite3_free(buf.aBuf); + return rc; +} + +/* +** A wrapper around sqlite3_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 */ + int i, /* Parameter number to bind to */ + sqlite3_value *pVal /* Value to bind */ +){ + int eType = sqlite3_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 + ** 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 + ** a conflict-handler) has zeroed the pVal->z pointer. Return NOMEM. */ + return SQLITE_NOMEM; + } + return sqlite3_bind_value(pStmt, i, pVal); +} + +/* +** Iterator pIter must point to an SQLITE_INSERT entry. This function +** transfers new.* values from the current iterator entry to statement +** pStmt. The table being inserted into has nCol columns. +** +** New.* value $i from the iterator is bound to variable ($i+1) of +** statement pStmt. If parameter abPK is NULL, all values from 0 to (nCol-1) +** are transfered to the statement. Otherwise, if abPK is not NULL, it points +** to an array nCol elements in size. In this case only those values for +** which abPK[$i] is true are read from the iterator and bound to the +** statement. +** +** 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 **), + int nCol, /* Number of columns */ + u8 *abPK, /* If not NULL, bind only if true */ + sqlite3_stmt *pStmt /* Bind values to this statement */ +){ + int i; + int rc = SQLITE_OK; + + /* Neither sqlite3changeset_old or sqlite3changeset_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 ); + + for(i=0; rc==SQLITE_OK && idb, pIter, p->abPK, p->pSelect); + }else{ + rc = SQLITE_OK; + } + + if( rc==SQLITE_ROW ){ + /* There exists another row with the new.* primary key. */ + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); + pIter->pConflict = 0; + rc = sqlite3_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 + ** to the SessionApplyCtx.constraints buffer. */ + 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; + }else{ + /* No other row with the new.* primary key. */ + res = xConflict(pCtx, eType+1, pIter); + if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; + } + } + + if( rc==SQLITE_OK ){ + switch( res ){ + case SQLITE_CHANGESET_REPLACE: + assert( pbReplace ); + *pbReplace = 1; + break; + + case SQLITE_CHANGESET_OMIT: + break; + + case SQLITE_CHANGESET_ABORT: + rc = SQLITE_ABORT; + break; + + default: + rc = SQLITE_MISUSE; + break; + } + } + + return rc; +} + +/* +** Attempt to apply the change that the iterator passed as the first argument +** currently points to to the database. If a conflict is encountered, invoke +** the conflict handler callback. +** +** If argument pbRetry is NULL, then ignore any CHANGESET_DATA conflict. If +** one is encountered, update or delete the row with the matching primary key +** instead. Or, if pbRetry is not NULL and a CHANGESET_DATA conflict occurs, +** invoke the conflict handler. If it returns CHANGESET_REPLACE, set *pbRetry +** to true before returning. In this case the caller will invoke this function +** again, this time with pbRetry set to NULL. +** +** If argument pbReplace is NULL and a CHANGESET_CONFLICT conflict is +** encountered invoke the conflict handler with CHANGESET_CONSTRAINT instead. +** Or, if pbReplace is not NULL, invoke it with CHANGESET_CONFLICT. If such +** an invocation returns SQLITE_CHANGESET_REPLACE, set *pbReplace to true +** before retrying. In this case the caller attempts to remove the conflicting +** row before invoking this function again, this time with pbReplace set +** to NULL. +** +** If any conflict handler returns SQLITE_CHANGESET_ABORT, this function +** returns SQLITE_ABORT. Otherwise, if no error occurs, SQLITE_OK is +** returned. +*/ +static int sessionApplyOneOp( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + SessionApplyCtx *p, /* changeset_apply() context */ + int(*xConflict)(void *, int, sqlite3_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. */ +){ + const char *zDummy; + int op; + int nCol; + int rc = SQLITE_OK; + + assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect ); + assert( p->azCol && p->abPK ); + assert( !pbReplace || *pbReplace==0 ); + + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); + + if( op==SQLITE_DELETE ){ + + /* Bind values to the DELETE statement. If conflict handling is required, + ** bind values for all columns and set bound variable (nCol+1) to true. + ** Or, if conflict handling is not required, bind just the PK column + ** values and, if it exists, set (nCol+1) to false. Conflict handling + ** is not required if: + ** + ** * this is a patchset, or + ** * (pbRetry==0), or + ** * all columns of the table are PK columns (in this case there is + ** 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)); + } + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pDelete); + rc = sqlite3_reset(p->pDelete); + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 + ); + } + + }else if( op==SQLITE_UPDATE ){ + int i; + + /* Bind values to the UPDATE statement. */ + for(i=0; rc==SQLITE_OK && ipUpdate, i*3+2, !!pNew); + if( pOld ){ + rc = sessionBindValue(p->pUpdate, i*3+1, pOld); + } + if( rc==SQLITE_OK && pNew ){ + rc = sessionBindValue(p->pUpdate, i*3+3, pNew); + } + } + if( rc==SQLITE_OK ){ + sqlite3_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); + + if( rc==SQLITE_OK && sqlite3_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. */ + + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + + }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ + /* This is always a CONSTRAINT conflict. */ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 + ); + } + + }else{ + assert( op==SQLITE_INSERT ); + rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pInsert); + rc = sqlite3_reset(p->pInsert); + if( (rc&0xff)==SQLITE_CONSTRAINT ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace + ); + } + } + + return rc; +} + +/* +** Attempt to apply the change that the iterator passed as the first argument +** currently points to to the database. If a conflict is encountered, invoke +** the conflict handler callback. +** +** The difference between this function and sessionApplyOne() is that this +** function handles the case where the conflict-handler is invoked and +** returns SQLITE_CHANGESET_REPLACE - indicating that the change should be +** 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 */ + SessionApplyCtx *pApply, /* Apply context */ + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int bReplace = 0; + int bRetry = 0; + 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 ){ + rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0); + } + } + + return rc; +} + +/* +** Retry the changes accumulated in the pApply->constraints buffer. +*/ +static int sessionRetryConstraints( + sqlite3 *db, + int bPatchset, + const char *zTab, + SessionApplyCtx *pApply, + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int rc = SQLITE_OK; + + while( pApply->constraints.nBuf ){ + sqlite3_changeset_iter *pIter2 = 0; + SessionBuffer cons = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + + rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf); + if( rc==SQLITE_OK ){ + int nByte = 2*pApply->nCol*sizeof(sqlite3_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; + if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ + rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); + } + + rc2 = sqlite3changeset_finalize(pIter2); + if( rc==SQLITE_OK ) rc = rc2; + } + assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); + + sqlite3_free(cons.aBuf); + if( rc!=SQLITE_OK ) break; + if( pApply->constraints.nBuf>=cons.nBuf ){ + /* No progress was made on the last round. */ + pApply->bDeferConstraints = 0; + } + } + + return rc; +} + +/* +** Argument pIter is a changeset iterator that has been initialized, but +** not yet passed to sqlite3changeset_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 */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + 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 */ + ), + void *pCtx /* First argument passed to xConflict */ +){ + int schemaMismatch = 0; + int rc; /* Return code */ + const char *zTab = 0; /* Name of current table */ + int nTab = 0; /* Result of sqlite3Strlen30(zTab) */ + SessionApplyCtx sApply; /* changeset_apply() context object */ + int bPatchset; + + assert( xConflict!=0 ); + + 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); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0); + } + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ + int nCol; + int op; + const char *zNew; + + sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); + + if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ + u8 *abPK; + + rc = sessionRetryConstraints( + db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx + ); + 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)); + sApply.db = db; + sApply.bDeferConstraints = 1; + + /* 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); + if( zTab==0 ){ + rc = SQLITE_NOMEM; + break; + } + nTab = (int)strlen(zTab); + sApply.azCol = (const char **)zTab; + }else{ + sqlite3changeset_pk(pIter, &abPK, 0); + rc = sessionTableInfo( + db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK + ); + if( rc!=SQLITE_OK ) break; + + if( sApply.nCol==0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): no such table: %s", zTab + ); + } + else if( sApply.nCol!=nCol ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): table %s has %d columns, expected %d", + zTab, sApply.nCol, nCol + ); + } + else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_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; + } + nTab = sqlite3Strlen30(zTab); + } + } + + /* If there is a schema mismatch on the current table, proceed to the + ** next change. A log message has already been issued. */ + if( schemaMismatch ) continue; + + rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx); + } + + bPatchset = pIter->bPatchset; + if( rc==SQLITE_OK ){ + rc = sqlite3changeset_finalize(pIter); + }else{ + sqlite3changeset_finalize(pIter); + } + + if( rc==SQLITE_OK ){ + rc = sessionRetryConstraints(db, bPatchset, zTab, &sApply, xConflict, pCtx); + } + + if( rc==SQLITE_OK ){ + int nFk, notUsed; + sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, ¬Used, 0); + if( nFk!=0 ){ + int res = SQLITE_CHANGESET_ABORT; + sqlite3_changeset_iter sIter; + memset(&sIter, 0, sizeof(sIter)); + sIter.nCol = nFk; + res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter); + if( res!=SQLITE_CHANGESET_OMIT ){ + rc = SQLITE_CONSTRAINT; + } + } + } + sqlite3_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); + } + + 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)); + return rc; +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main database +** attached to handle "db". Invoke the supplied conflict handler callback +** to resolve any conflicts encountered while applying the change. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_apply( + sqlite3 *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 fifth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_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; +} + +/* +** Apply the changeset passed via xInput/pIn to the main database +** attached to handle "db". Invoke the supplied conflict handler callback +** to resolve any conflicts encountered while applying the change. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_apply_strm( + sqlite3 *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 */ + sqlite3_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_strm(&pIter, xInput, pIn); + if( rc==SQLITE_OK ){ + rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx); + } + return rc; +} + +/* +** sqlite3_changegroup handle. +*/ +struct sqlite3_changegroup { + int rc; /* Error code */ + int bPatch; /* True to accumulate patchsets */ + SessionTable *pList; /* List of tables in current patch */ +}; + +/* +** 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 +** allocated and a pointer to it stored in *ppNew. +*/ +static int sessionChangeMerge( + SessionTable *pTab, /* Table structure */ + int bPatchset, /* True for patchsets */ + SessionChange *pExist, /* Existing change */ + int op2, /* Second change operation */ + int bIndirect, /* True if second change is indirect */ + u8 *aRec, /* Second change record */ + int nRec, /* Number of bytes in aRec */ + SessionChange **ppNew /* OUT: Merged change */ +){ + SessionChange *pNew = 0; + + if( !pExist ){ + pNew = (SessionChange *)sqlite3_malloc(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); + }else{ + int op1 = pExist->op; + + /* + ** op1=INSERT, op2=INSERT -> Unsupported. Discard op2. + ** op1=INSERT, op2=UPDATE -> INSERT. + ** op1=INSERT, op2=DELETE -> (none) + ** + ** op1=UPDATE, op2=INSERT -> Unsupported. Discard op2. + ** op1=UPDATE, op2=UPDATE -> UPDATE. + ** op1=UPDATE, op2=DELETE -> DELETE. + ** + ** op1=DELETE, op2=INSERT -> UPDATE. + ** op1=DELETE, op2=UPDATE -> Unsupported. Discard op2. + ** op1=DELETE, op2=DELETE -> Unsupported. Discard op2. + */ + if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT) + || (op1==SQLITE_UPDATE && op2==SQLITE_INSERT) + || (op1==SQLITE_DELETE && op2==SQLITE_UPDATE) + || (op1==SQLITE_DELETE && op2==SQLITE_DELETE) + ){ + pNew = pExist; + }else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){ + sqlite3_free(pExist); + assert( pNew==0 ); + }else{ + u8 *aExist = pExist->aRecord; + int 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); + if( !pNew ){ + sqlite3_free(pExist); + return SQLITE_NOMEM; + } + memset(pNew, 0, sizeof(SessionChange)); + pNew->bIndirect = (bIndirect && pExist->bIndirect); + aCsr = pNew->aRecord = (u8 *)&pNew[1]; + + if( op1==SQLITE_INSERT ){ /* INSERT + UPDATE */ + u8 *a1 = aRec; + assert( op2==SQLITE_UPDATE ); + pNew->op = SQLITE_INSERT; + if( bPatchset==0 ) sessionSkipRecord(&a1, pTab->nCol); + sessionMergeRecord(&aCsr, pTab->nCol, aExist, a1); + }else if( op1==SQLITE_DELETE ){ /* DELETE + INSERT */ + assert( op2==SQLITE_INSERT ); + pNew->op = SQLITE_UPDATE; + if( bPatchset ){ + memcpy(aCsr, aRec, nRec); + aCsr += nRec; + }else{ + if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){ + sqlite3_free(pNew); + pNew = 0; + } + } + }else if( op2==SQLITE_UPDATE ){ /* UPDATE + UPDATE */ + u8 *a1 = aExist; + u8 *a2 = aRec; + assert( op1==SQLITE_UPDATE ); + if( bPatchset==0 ){ + sessionSkipRecord(&a1, pTab->nCol); + sessionSkipRecord(&a2, pTab->nCol); + } + pNew->op = SQLITE_UPDATE; + if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){ + sqlite3_free(pNew); + pNew = 0; + } + }else{ /* UPDATE + DELETE */ + assert( op1==SQLITE_UPDATE && op2==SQLITE_DELETE ); + pNew->op = SQLITE_DELETE; + if( bPatchset ){ + memcpy(aCsr, aRec, nRec); + aCsr += nRec; + }else{ + sessionMergeRecord(&aCsr, pTab->nCol, aRec, aExist); + } + } + + if( pNew ){ + pNew->nRecord = (int)(aCsr - pNew->aRecord); + } + sqlite3_free(pExist); + } + } + + *ppNew = pNew; + return SQLITE_OK; +} + +/* +** Add all changes in the changeset traversed by the iterator passed as +** 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 */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + SessionTable *pTab = 0; + + + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){ + const char *zNew; + int nCol; + int op; + int iHash; + int bIndirect; + SessionChange *pChange; + SessionChange *pExist = 0; + SessionChange **pp; + + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + break; + } + + sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); + if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ + /* Search the list for a matching table */ + int nNew = (int)strlen(zNew); + u8 *abPK; + + sqlite3changeset_pk(pIter, &abPK, 0); + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; + } + if( !pTab ){ + SessionTable **ppTab; + + pTab = sqlite3_malloc(sizeof(SessionTable) + nCol + nNew+1); + if( !pTab ){ + rc = SQLITE_NOMEM; + break; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + memcpy(pTab->abPK, abPK, nCol); + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zNew, nNew+1); + + /* 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 + ** the right order. */ + for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); + *ppTab = pTab; + }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){ + rc = SQLITE_SCHEMA; + break; + } + } + + if( sessionGrowHash(pIter->bPatchset, pTab) ){ + rc = SQLITE_NOMEM; + break; + } + iHash = sessionChangeHash( + pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange + ); + + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. + */ + for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ + int bPkOnly1 = 0; + int bPkOnly2 = 0; + if( pIter->bPatchset ){ + bPkOnly1 = (*pp)->op==SQLITE_DELETE; + bPkOnly2 = op==SQLITE_DELETE; + } + if( sessionChangeEqual(pTab, bPkOnly1, (*pp)->aRecord, bPkOnly2, aRec) ){ + pExist = *pp; + *pp = (*pp)->pNext; + pTab->nEntry--; + break; + } + } + + rc = sessionChangeMerge(pTab, + pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange + ); + if( rc ) break; + if( pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + } + + if( rc==SQLITE_OK ) rc = pIter->rc; + return rc; +} + +/* +** Serialize a changeset (or patchset) based on all changesets (or patchsets) +** added to the changegroup object passed as the first argument. +** +** If xOutput is not NULL, then the changeset/patchset is returned to the +** user via one or more calls to xOutput, as with the other streaming +** interfaces. +** +** Or, if xOutput is NULL, then (*ppOut) is populated with a pointer to a +** 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. +** +** 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, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut, + int *pnOut, + void **ppOut +){ + int rc = SQLITE_OK; + SessionBuffer buf = {0, 0, 0}; + SessionTable *pTab; + assert( xOutput==0 || (ppOut==0 && pnOut==0) ); + + /* Create the serialized output changeset based on the contents of the + ** hash tables attached to the SessionTable objects in list p->pList. + */ + for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ + int i; + if( pTab->nEntry==0 ) continue; + + sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc); + for(i=0; inChange; i++){ + SessionChange *p; + for(p=pTab->apChange[i]; p; p=p->pNext){ + 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 ){ + if( xOutput ){ + if( buf.nBuf>0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf); + }else{ + *ppOut = buf.aBuf; + *pnOut = buf.nBuf; + buf.aBuf = 0; + } + } + sqlite3_free(buf.aBuf); + + return rc; +} + +/* +** Allocate a new, empty, sqlite3_changegroup. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changegroup_new(sqlite3_changegroup **pp){ + int rc = SQLITE_OK; /* Return code */ + sqlite3_changegroup *p; /* New object */ + p = (sqlite3_changegroup*)sqlite3_malloc(sizeof(sqlite3_changegroup)); + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(p, 0, sizeof(sqlite3_changegroup)); + } + *pp = p; + return rc; +} + +/* +** Add the changeset currently stored in buffer pData, size nData bytes, +** to changeset-group p. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){ + sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + + rc = sqlite3changeset_start(&pIter, nData, pData); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, pGrp); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Obtain a buffer containing a changeset representing the concatenation +** of all changesets added to the group so far. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changegroup_output( + sqlite3_changegroup *pGrp, + int *pnData, + void **ppData +){ + return sessionChangegroupOutput(pGrp, 0, 0, pnData, ppData); +} + +/* +** Streaming versions of changegroup_add(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changegroup_add_strm( + sqlite3_changegroup *pGrp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +){ + sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + + rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, pGrp); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Streaming versions of changegroup_output(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changegroup_output_strm( + sqlite3_changegroup *pGrp, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + return sessionChangegroupOutput(pGrp, xOutput, pOut, 0, 0); +} + +/* +** Delete a changegroup object. +*/ +SQLITE_API void SQLITE_STDCALL sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ + if( pGrp ){ + sessionDeleteTable(pGrp->pList); + sqlite3_free(pGrp); + } +} + +/* +** Combine two changesets together. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_concat( + int nLeft, /* Number of bytes in lhs input */ + void *pLeft, /* Lhs input changeset */ + int nRight /* Number of bytes in rhs input */, + void *pRight, /* Rhs input changeset */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: changeset (left right) */ +){ + sqlite3_changegroup *pGrp; + int rc; + + rc = sqlite3changegroup_new(&pGrp); + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add(pGrp, nLeft, pLeft); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add(pGrp, nRight, pRight); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); + } + sqlite3changegroup_delete(pGrp); + + return rc; +} + +/* +** Streaming version of sqlite3changeset_concat(). +*/ +SQLITE_API int SQLITE_STDCALL sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + sqlite3_changegroup *pGrp; + int rc; + + rc = sqlite3changegroup_new(&pGrp); + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add_strm(pGrp, xInputA, pInA); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_add_strm(pGrp, xInputB, pInB); + } + if( rc==SQLITE_OK ){ + rc = sqlite3changegroup_output_strm(pGrp, xOutput, pOut); + } + sqlite3changegroup_delete(pGrp); + + return rc; +} + +#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ + +/************** End of sqlite3session.c **************************************/ /************** Begin file json1.c *******************************************/ /* ** 2015-08-12 @@ -163585,7 +174018,11 @@ SQLITE_EXTENSION_INIT1 /* #include */ /* #include */ -#define UNUSED_PARAM(X) (void)(X) +/* 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)) @@ -163830,10 +174267,33 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return; p->zBuf[p->nUsed++] = '"'; for(i=0; inUsed+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; } @@ -163874,7 +174334,7 @@ static void jsonAppendValue( default: { if( p->bErr==0 ){ sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); - p->bErr = 1; + p->bErr = 2; jsonReset(p); } break; @@ -164735,7 +175195,7 @@ static void jsonTest1Func( #endif /* SQLITE_DEBUG */ /**************************************************************************** -** SQL function implementations +** Scalar SQL function implementations ****************************************************************************/ /* @@ -165068,6 +175528,104 @@ static void jsonValidFunc( 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); +} + +/* +** json_group_obj(NAME,VALUE) +** +** Return a JSON object composed of all names and values in the aggregate. +*/ +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==0 ) 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 @@ -165566,6 +176124,15 @@ SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){ { "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; @@ -165581,6 +176148,11 @@ SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){ (void*)&aFunc[i].flag, aFunc[i].xFunc, 0, 0); } + for(i=0; ixPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); -** iOff>=0; +** iCol>=0; ** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) ** ){ ** // An instance of phrase iPhrase at offset iOff of column iCol @@ -165825,13 +176416,51 @@ struct Fts5PhraseIter { ** ** The Fts5PhraseIter structure is defined above. Applications should not ** modify this structure directly - it should only be used as shown above -** with the xPhraseFirst() and xPhraseNext() API methods. +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** ** xPhraseNext() ** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 1 */ + int iVersion; /* Currently always set to 3 */ void *(*xUserData)(Fts5Context*); @@ -165861,8 +176490,11 @@ struct Fts5ExtensionApi { int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); void *(*xGetAuxdata)(Fts5Context*, int bClear); - void (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); }; /* @@ -166156,6 +176788,7 @@ struct fts5_api { #ifndef _FTS5INT_H #define _FTS5INT_H +/* #include "fts5.h" */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 @@ -166167,10 +176800,11 @@ SQLITE_EXTENSION_INIT1 typedef unsigned char u8; typedef unsigned int u32; typedef unsigned short u16; +typedef short i16; typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; -#define ArraySize(x) (sizeof(x) / sizeof(x[0])) +#define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0]))) #define testcase(x) #define ALWAYS(x) 1 @@ -166187,6 +176821,10 @@ typedef sqlite3_uint64 u64; #endif +/* Truncate very long tokens to this many bytes. Hard limit is +** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset +** field that occurs at the start of each leaf page (see fts5_index.c). */ +#define FTS5_MAX_TOKEN_SIZE 32768 /* ** Maximum number of prefix indexes on single FTS5 table. This must be @@ -166221,6 +176859,16 @@ SQLITE_API extern int sqlite3_fts5_may_be_corrupt; # define assert_nc(x) assert(x) #endif +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAM +# define UNUSED_PARAM(X) (void)(X) +#endif + +#ifndef UNUSED_PARAM2 +# define UNUSED_PARAM2(X, Y) (void)(X), (void)(Y) +#endif + typedef struct Fts5Global Fts5Global; typedef struct Fts5Colset Fts5Colset; @@ -166292,6 +176940,7 @@ struct Fts5Config { char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ + int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; Fts5Tokenizer *pTok; fts5_tokenizer *pTokApi; @@ -166301,6 +176950,8 @@ struct Fts5Config { int pgsz; /* Approximate page size used in %_data */ int nAutomerge; /* 'automerge' setting */ int nCrisisMerge; /* Maximum allowed segments per level */ + int nUsermerge; /* 'usermerge' setting */ + int nHashSize; /* Bytes of memory for in-memory hash */ char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ @@ -166319,6 +176970,9 @@ struct Fts5Config { #define FTS5_CONTENT_NONE 1 #define FTS5_CONTENT_EXTERNAL 2 +#define FTS5_DETAIL_FULL 0 +#define FTS5_DETAIL_NONE 1 +#define FTS5_DETAIL_COLUMNS 2 @@ -166365,25 +177019,27 @@ struct Fts5Buffer { int nSpace; }; -static int sqlite3Fts5BufferGrow(int*, Fts5Buffer*, int); +static int sqlite3Fts5BufferSize(int*, Fts5Buffer*, u32); static void sqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64); -static void sqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, int, const u8*); +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 void sqlite3Fts5BufferAppend32(int*, Fts5Buffer*, int); static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); #define fts5BufferZero(x) sqlite3Fts5BufferZero(x) -#define fts5BufferGrow(a,b,c) sqlite3Fts5BufferGrow(a,b,c) #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 fts5BufferAppend32(a,b,c) sqlite3Fts5BufferAppend32(a,b,c) + +#define fts5BufferGrow(pRc,pBuf,nn) ( \ + (u32)((pBuf)->n) + (u32)(nn) <= (u32)((pBuf)->nSpace) ? 0 : \ + sqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \ +) /* Write and decode big-endian 32-bit integer values */ static void sqlite3Fts5Put32(u8*, int); @@ -166416,6 +177072,7 @@ struct Fts5PoslistWriter { i64 iPrev; }; static int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64); +static void sqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64); static int sqlite3Fts5PoslistNext64( const u8 *a, int n, /* Buffer containing poslist */ @@ -166430,6 +177087,13 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn); /* Character set tests (like isspace(), isalpha() etc.) */ static int sqlite3Fts5IsBareword(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*); + /* ** End of interface to code in fts5_buffer.c. **************************************************************************/ @@ -166442,6 +177106,15 @@ static int sqlite3Fts5IsBareword(char t); typedef struct Fts5Index Fts5Index; typedef struct Fts5IndexIter Fts5IndexIter; +struct Fts5IndexIter { + i64 iRowid; + const u8 *pData; + int nData; + u8 bEof; +}; + +#define sqlite3Fts5IterEof(x) ((x)->bEof) + /* ** Values used as part of the flags argument passed to IndexQuery(). */ @@ -166450,6 +177123,12 @@ typedef struct Fts5IndexIter Fts5IndexIter; #define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ #define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ +/* The following are used internally by the fts5_index.c module. They are +** defined here only to make it easier to avoid clashes with the flags +** above. */ +#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 +#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 + /* ** Create/destroy an Fts5Index object. */ @@ -166457,14 +177136,27 @@ static int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, c static int sqlite3Fts5IndexClose(Fts5Index *p); /* -** for( -** sqlite3Fts5IndexQuery(p, "token", 5, 0, 0, &pIter); -** 0==sqlite3Fts5IterEof(pIter); -** sqlite3Fts5IterNext(pIter) -** ){ -** i64 iRowid = sqlite3Fts5IterRowid(pIter); -** } +** Return a simple checksum value based on the arguments. */ +static u64 sqlite3Fts5IndexEntryCksum( + i64 iRowid, + int iCol, + int iPos, + int iIdx, + const char *pTerm, + int nTerm +); + +/* +** Argument p points to a buffer containing utf-8 text that is n bytes in +** 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( + const char *p, + int nByte, + int nChar +); /* ** Open a new iterator to iterate though all rowids that match the @@ -166482,12 +177174,8 @@ static int sqlite3Fts5IndexQuery( ** The various operations on open token or token prefix iterators opened ** using sqlite3Fts5IndexQuery(). */ -static int sqlite3Fts5IterEof(Fts5IndexIter*); static int sqlite3Fts5IterNext(Fts5IndexIter*); static int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch); -static i64 sqlite3Fts5IterRowid(Fts5IndexIter*); -static int sqlite3Fts5IterPoslist(Fts5IndexIter*,Fts5Colset*, const u8**, int*, i64*); -static int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf); /* ** Close an iterator opened by sqlite3Fts5IndexQuery(). @@ -166551,7 +177239,6 @@ static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int); /* ** Functions called by the storage module as part of integrity-check. */ -static u64 sqlite3Fts5IndexCksum(Fts5Config*,i64,int,int,const char*,int); static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum); /* @@ -166571,6 +177258,7 @@ static int sqlite3Fts5IndexReads(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 sqlite3Fts5IndexLoadConfig(Fts5Index *p); @@ -166616,7 +177304,7 @@ static int sqlite3Fts5GetTokenizer( char **pzErr ); -static Fts5Index *sqlite3Fts5IndexFromCsrid(Fts5Global*, i64, int*); +static Fts5Index *sqlite3Fts5IndexFromCsrid(Fts5Global*, i64, Fts5Config **); /* ** End of interface to code in fts5.c. @@ -166630,7 +177318,7 @@ typedef struct Fts5Hash Fts5Hash; /* ** Create a hash table, free a hash table. */ -static int sqlite3Fts5HashNew(Fts5Hash**, int *pnSize); +static int sqlite3Fts5HashNew(Fts5Config*, Fts5Hash**, int *pnSize); static void sqlite3Fts5HashFree(Fts5Hash*); static int sqlite3Fts5HashWrite( @@ -166689,7 +177377,7 @@ static int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName); static int sqlite3Fts5DropAll(Fts5Config*); static int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); -static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64); +static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**); static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); @@ -166709,12 +177397,11 @@ static int sqlite3Fts5StorageConfigValue( Fts5Storage *p, const char*, sqlite3_value*, int ); -static int sqlite3Fts5StorageSpecialDelete(Fts5Storage *p, i64 iDel, sqlite3_value**); - 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); /* ** End of interface to code in fts5_storage.c. @@ -166767,7 +177454,17 @@ static int sqlite3Fts5ExprPhraseCount(Fts5Expr*); static int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase); static int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **); -static int sqlite3Fts5ExprClonePhrase(Fts5Config*, Fts5Expr*, int, Fts5Expr**); +typedef struct Fts5PoslistPopulator Fts5PoslistPopulator; +static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr*, int); +static int sqlite3Fts5ExprPopulatePoslists( + Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int, const char*, int +); +static void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64); +static void sqlite3Fts5ExprClearEof(Fts5Expr*); + +static int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**); + +static int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *); /******************************************* ** The fts5_expr.c API above this point is used by the other hand-written @@ -166784,6 +177481,12 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( Fts5ExprNearset *pNear ); +static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( + Fts5Parse *pParse, + Fts5ExprNode *pLeft, + Fts5ExprNode *pRight +); + static Fts5ExprPhrase *sqlite3Fts5ParseTerm( Fts5Parse *pParse, Fts5ExprPhrase *pPhrase, @@ -166873,19 +177576,35 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); #define FTS5_PLUS 12 #define FTS5_STAR 13 -/* Driver template for the LEMON parser generator. -** The author disclaims copyright to this source code. +/* +** 2000-05-29 ** -** This version of "lempar.c" is modified, slightly, for use by SQLite. -** The only modifications are the addition of a couple of NEVER() -** macros to disable tests that are needed in the case of a general -** LALR(1) grammar but which are always false in the -** specific grammar used by SQLite. +** 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. +** +************************************************************************* +** Driver template for the LEMON parser generator. +** +** The "lemon" program processes an LALR(1) input grammar file, then uses +** this template to construct a parser. The "lemon" program inserts text +** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** interstitial "-" characters) contained in this template is changed into +** the value of the %name directive from the grammar. Otherwise, the content +** of this template is copied straight through into the generate parser +** source file. +** +** The following is the concatenation of all %include directives from the +** input grammar file: */ -/* First off, code is included that follows the "include" declaration -** in the input grammar file. */ /* #include */ +/************ Begin %include sections from the grammar ************************/ +/* #include "fts5Int.h" */ +/* #include "fts5parse.h" */ /* ** Disable all error recovery processing in the parser push-down @@ -166898,44 +177617,54 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); */ #define fts5yytestcase(X) testcase(X) -/* Next is all token values, in a form suitable for use by makeheaders. -** This section will be null unless lemon is run with the -m switch. +/* +** Indicate that sqlite3ParserFree() will never be called with a null +** pointer. */ -/* -** These constants (all generated automatically by the parser generator) -** specify the various kinds of tokens (terminals) that the parser -** understands. -** -** Each symbol here is a terminal symbol in the grammar. +#define fts5YYPARSEFREENOTNULL 1 + +/* +** Alternative datatype for the argument to the malloc() routine passed +** into sqlite3ParserAlloc(). The default is size_t. */ -/* Make sure the INTERFACE macro is defined. -*/ -#ifndef INTERFACE -# define INTERFACE 1 -#endif -/* The next thing included is series of defines which control +#define fts5YYMALLOCARGTYPE u64 + +/**************** 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 +** "lemon" is run with the "-m" command-line option. +***************** Begin makeheaders token definitions *************************/ +/**************** End makeheaders token definitions ***************************/ + +/* The next sections is a series of control #defines. ** various aspects of the generated parser. -** fts5YYCODETYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 terminals -** and nonterminals. "int" is used otherwise. -** fts5YYNOCODE is a number of type fts5YYCODETYPE which corresponds -** to no legal terminal or nonterminal number. This -** number is used to fill in empty slots of the hash -** table. +** fts5YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** fts5YYNOCODE is a number of type fts5YYCODETYPE that is not used for +** any terminal or nonterminal symbol. ** fts5YYFALLBACK If defined, this indicates that one or more tokens -** have fall-back values which should be used if the -** original value of the token will not parse. -** fts5YYACTIONTYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 rules and -** states combined. "int" is used otherwise. -** sqlite3Fts5ParserFTS5TOKENTYPE is the data type used for minor tokens given -** directly to the parser from the tokenizer. -** fts5YYMINORTYPE is the data type used for all minor tokens. +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** 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 +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** 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 -** for base tokens is called "fts5yy0". +** 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 @@ -166954,6 +177683,10 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); ** fts5YY_ACCEPT_ACTION The fts5yy_action[] code for accept ** fts5YY_NO_ACTION The fts5yy_action[] code for no-op */ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ #define fts5YYCODETYPE unsigned char #define fts5YYNOCODE 27 #define fts5YYACTIONTYPE unsigned char @@ -166984,10 +177717,7 @@ typedef union { #define fts5YY_ERROR_ACTION 88 #define fts5YY_ACCEPT_ACTION 89 #define fts5YY_NO_ACTION 90 - -/* The fts5yyzerominor constant is used to initialize instances of -** fts5YYMINORTYPE objects to zero. */ -static const fts5YYMINORTYPE fts5yyzerominor = { 0 }; +/************* End control #defines *******************************************/ /* Define the fts5yytestcase() macro to be a no-op if is not already defined ** otherwise. @@ -167052,7 +177782,8 @@ static const fts5YYMINORTYPE fts5yyzerominor = { 0 }; ** fts5yy_reduce_ofst[] For each state, the offset into fts5yy_action for ** shifting non-terminals after a reduce. ** fts5yy_default[] Default action for each state. -*/ +** +*********** Begin parsing tables **********************************************/ #define fts5YY_ACTTAB_COUNT (78) static const fts5YYACTIONTYPE fts5yy_action[] = { /* 0 */ 89, 15, 46, 5, 48, 24, 12, 19, 23, 14, @@ -167096,9 +177827,10 @@ static const fts5YYACTIONTYPE fts5yy_default[] = { /* 10 */ 87, 88, 87, 87, 88, 88, 88, 66, 80, 88, /* 20 */ 81, 88, 88, 78, 88, 65, }; +/********** End of lemon-generated parsing tables *****************************/ -/* The next table maps tokens into fallback tokens. If a construct -** like the following: +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: ** ** %fallback ID X Y Z. ** @@ -167106,6 +177838,10 @@ static const fts5YYACTIONTYPE fts5yy_default[] = { ** and Z. Whenever one of the tokens X, Y, or Z is input to the parser ** but it does not parse, the type of the token is changed to ID and ** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. */ #ifdef fts5YYFALLBACK static const fts5YYCODETYPE fts5yyFallback[] = { @@ -167144,7 +177880,9 @@ struct fts5yyParser { #ifdef fts5YYTRACKMAXSTACKDEPTH int fts5yyidxMax; /* Maximum value of fts5yyidx */ #endif +#ifndef fts5YYNOERRORRECOVERY int fts5yyerrcnt; /* Shifts left before out of the error */ +#endif sqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */ #if fts5YYSTACKDEPTH<=0 int fts5yystksz; /* Current side of the stack */ @@ -167256,6 +177994,15 @@ static void 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 +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef fts5YYMALLOCARGTYPE +# define fts5YYMALLOCARGTYPE size_t +#endif + /* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like @@ -167268,9 +178015,9 @@ static void fts5yyGrowStack(fts5yyParser *p){ ** A pointer to a parser. This pointer is used in subsequent calls ** to sqlite3Fts5Parser and sqlite3Fts5ParserFree. */ -static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64)){ +static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE)){ fts5yyParser *pParser; - pParser = (fts5yyParser*)(*mallocProc)( (u64)sizeof(fts5yyParser) ); + pParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof(fts5yyParser) ); if( pParser ){ pParser->fts5yyidx = -1; #ifdef fts5YYTRACKMAXSTACKDEPTH @@ -167285,10 +178032,12 @@ static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64)){ return pParser; } -/* The following function deletes the value associated with a -** symbol. The symbol can be either a terminal or nonterminal. -** "fts5yymajor" is the symbol code, and "fts5yypminor" is a pointer to -** the value. +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "fts5yymajor" is the symbol code, and "fts5yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. */ static void fts5yy_destructor( fts5yyParser *fts5yypParser, /* The parser */ @@ -167304,9 +178053,10 @@ static void fts5yy_destructor( ** being destroyed before it is finished parsing. ** ** Note: during a reduce, the only symbols destroyed are those - ** which appear on the RHS of the rule, but which are not used + ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ +/********* Begin destructor definitions ***************************************/ case 15: /* input */ { (void)pParse; @@ -167336,6 +178086,7 @@ static void fts5yy_destructor( sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy11)); } break; +/********* End destructor definitions *****************************************/ default: break; /* If no destructor action specified: do nothing */ } } @@ -167345,49 +178096,37 @@ static void fts5yy_destructor( ** ** If there is a destructor routine associated with the token which ** is popped from the stack, then call it. -** -** Return the major token number for the symbol popped. */ -static int fts5yy_pop_parser_stack(fts5yyParser *pParser){ - fts5YYCODETYPE fts5yymajor; - fts5yyStackEntry *fts5yytos = &pParser->fts5yystack[pParser->fts5yyidx]; - - /* There is no mechanism by which the parser stack can be popped below - ** empty in SQLite. */ +static void fts5yy_pop_parser_stack(fts5yyParser *pParser){ + fts5yyStackEntry *fts5yytos; assert( pParser->fts5yyidx>=0 ); + fts5yytos = &pParser->fts5yystack[pParser->fts5yyidx--]; #ifndef NDEBUG - if( fts5yyTraceFILE && pParser->fts5yyidx>=0 ){ + if( fts5yyTraceFILE ){ fprintf(fts5yyTraceFILE,"%sPopping %s\n", fts5yyTracePrompt, fts5yyTokenName[fts5yytos->major]); } #endif - fts5yymajor = fts5yytos->major; - fts5yy_destructor(pParser, fts5yymajor, &fts5yytos->minor); - pParser->fts5yyidx--; - return fts5yymajor; + fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor); } /* -** Deallocate and destroy a parser. Destructors are all called for +** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. ** -** Inputs: -**
      -**
    • A pointer to the parser. This should be a pointer -** obtained from sqlite3Fts5ParserAlloc. -**
    • A pointer to a function used to reclaim memory obtained -** from malloc. -**
    +** If the fts5YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. */ static void sqlite3Fts5ParserFree( void *p, /* The parser to be deleted */ void (*freeProc)(void*) /* Function used to reclaim memory */ ){ fts5yyParser *pParser = (fts5yyParser*)p; - /* In SQLite, we never try to destroy a parser that was not successfully - ** created in the first place. */ - if( NEVER(pParser==0) ) return; +#ifndef fts5YYPARSEFREENEVERNULL + if( pParser==0 ) return; +#endif while( pParser->fts5yyidx>=0 ) fts5yy_pop_parser_stack(pParser); #if fts5YYSTACKDEPTH<=0 free(pParser->fts5yystack); @@ -167408,12 +178147,8 @@ static int sqlite3Fts5ParserStackPeak(void *p){ /* ** Find the appropriate action for a parser given the terminal ** look-ahead token iLookAhead. -** -** If the look-ahead token is fts5YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return fts5YY_NO_ACTION. */ -static int fts5yy_find_shift_action( +static unsigned int fts5yy_find_shift_action( fts5yyParser *pParser, /* The parser */ fts5YYCODETYPE iLookAhead /* The look-ahead token */ ){ @@ -167422,61 +178157,62 @@ static int fts5yy_find_shift_action( if( stateno>=fts5YY_MIN_REDUCE ) return stateno; assert( stateno <= fts5YY_SHIFT_COUNT ); - i = fts5yy_shift_ofst[stateno]; - if( i==fts5YY_SHIFT_USE_DFLT ) return fts5yy_default[stateno]; - assert( iLookAhead!=fts5YYNOCODE ); - i += iLookAhead; - if( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){ - if( iLookAhead>0 ){ + do{ + i = fts5yy_shift_ofst[stateno]; + if( i==fts5YY_SHIFT_USE_DFLT ) return fts5yy_default[stateno]; + assert( iLookAhead!=fts5YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ #ifdef fts5YYFALLBACK - fts5YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", - fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]); - } -#endif - return fts5yy_find_shift_action(pParser, iFallback); - } -#endif -#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 %s\n", - fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[fts5YYWILDCARD]); + fprintf(fts5yyTraceFILE, "%sFALLBACK %s => %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]); + } +#endif + assert( fts5yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#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 %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], + fts5yyTokenName[fts5YYWILDCARD]); + } +#endif /* NDEBUG */ + return fts5yy_action[j]; } -#endif /* NDEBUG */ - return fts5yy_action[j]; } - } #endif /* fts5YYWILDCARD */ + } + return fts5yy_default[stateno]; + }else{ + return fts5yy_action[i]; } - return fts5yy_default[stateno]; - }else{ - return fts5yy_action[i]; - } + }while(1); } /* ** Find the appropriate action for a parser given the non-terminal ** look-ahead token iLookAhead. -** -** If the look-ahead token is fts5YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return fts5YY_NO_ACTION. */ static int fts5yy_find_reduce_action( int stateno, /* Current state number */ @@ -167508,7 +178244,7 @@ static int fts5yy_find_reduce_action( /* ** The following routine is called if the stack overflows. */ -static void fts5yyStackOverflow(fts5yyParser *fts5yypParser, fts5YYMINORTYPE *fts5yypMinor){ +static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){ sqlite3Fts5ParserARG_FETCH; fts5yypParser->fts5yyidx--; #ifndef NDEBUG @@ -167519,8 +178255,10 @@ static void fts5yyStackOverflow(fts5yyParser *fts5yypParser, fts5YYMINORTYPE *ft while( fts5yypParser->fts5yyidx>=0 ) fts5yy_pop_parser_stack(fts5yypParser); /* Here code is inserted which will execute if the parser ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ - assert( 0 ); + sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow"); +/******** End %stack_overflow code ********************************************/ sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument var */ } @@ -167530,15 +178268,13 @@ static void fts5yyStackOverflow(fts5yyParser *fts5yypParser, fts5YYMINORTYPE *ft #ifndef NDEBUG static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState){ if( fts5yyTraceFILE ){ - int i; if( fts5yyNewStatefts5yyidx; i++) - fprintf(fts5yyTraceFILE," %s",fts5yyTokenName[fts5yypParser->fts5yystack[i].major]); - fprintf(fts5yyTraceFILE,"\n"); + fprintf(fts5yyTraceFILE,"%sShift '%s', go to state %d\n", + fts5yyTracePrompt,fts5yyTokenName[fts5yypParser->fts5yystack[fts5yypParser->fts5yyidx].major], + fts5yyNewState); }else{ - fprintf(fts5yyTraceFILE,"%sShift *\n",fts5yyTracePrompt); + fprintf(fts5yyTraceFILE,"%sShift '%s'\n", + fts5yyTracePrompt,fts5yyTokenName[fts5yypParser->fts5yystack[fts5yypParser->fts5yyidx].major]); } } } @@ -167547,13 +178283,13 @@ static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState){ #endif /* -** Perform a shift action. Return the number of errors. +** Perform a shift action. */ 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 */ - fts5YYMINORTYPE *fts5yypMinor /* Pointer to the minor token to shift in */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyMinor /* The minor token to shift in */ ){ fts5yyStackEntry *fts5yytos; fts5yypParser->fts5yyidx++; @@ -167564,14 +178300,14 @@ static void fts5yy_shift( #endif #if fts5YYSTACKDEPTH>0 if( fts5yypParser->fts5yyidx>=fts5YYSTACKDEPTH ){ - fts5yyStackOverflow(fts5yypParser, fts5yypMinor); + fts5yyStackOverflow(fts5yypParser); return; } #else if( fts5yypParser->fts5yyidx>=fts5yypParser->fts5yystksz ){ fts5yyGrowStack(fts5yypParser); if( fts5yypParser->fts5yyidx>=fts5yypParser->fts5yystksz ){ - fts5yyStackOverflow(fts5yypParser, fts5yypMinor); + fts5yyStackOverflow(fts5yypParser); return; } } @@ -167579,7 +178315,7 @@ static void fts5yy_shift( fts5yytos = &fts5yypParser->fts5yystack[fts5yypParser->fts5yyidx]; fts5yytos->stateno = (fts5YYACTIONTYPE)fts5yyNewState; fts5yytos->major = (fts5YYCODETYPE)fts5yyMajor; - fts5yytos->minor = *fts5yypMinor; + fts5yytos->minor.fts5yy0 = fts5yyMinor; fts5yyTraceShift(fts5yypParser, fts5yyNewState); } @@ -167624,41 +178360,46 @@ static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */ */ static void fts5yy_reduce( fts5yyParser *fts5yypParser, /* The parser */ - int fts5yyruleno /* Number of the rule by which to reduce */ + unsigned int fts5yyruleno /* Number of the rule by which to reduce */ ){ int fts5yygoto; /* The next state */ int fts5yyact; /* The next action */ - fts5YYMINORTYPE fts5yygotominor; /* The LHS of the rule reduced */ fts5yyStackEntry *fts5yymsp; /* The top of the parser's stack */ int fts5yysize; /* Amount to pop the stack */ sqlite3Fts5ParserARG_FETCH; fts5yymsp = &fts5yypParser->fts5yystack[fts5yypParser->fts5yyidx]; #ifndef NDEBUG - if( fts5yyTraceFILE && fts5yyruleno>=0 - && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){ + if( fts5yyTraceFILE && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){ fts5yysize = fts5yyRuleInfo[fts5yyruleno].nrhs; - fprintf(fts5yyTraceFILE, "%sReduce [%s] -> state %d.\n", fts5yyTracePrompt, + fprintf(fts5yyTraceFILE, "%sReduce [%s], go to state %d.\n", fts5yyTracePrompt, fts5yyRuleName[fts5yyruleno], fts5yymsp[-fts5yysize].stateno); } #endif /* NDEBUG */ - /* Silence complaints from purify about fts5yygotominor being uninitialized - ** in some cases when it is copied into the stack after the following - ** switch. fts5yygotominor is uninitialized when a rule reduces that does - ** not set the value of its left-hand side nonterminal. Leaving the - ** value of the nonterminal uninitialized is utterly harmless as long - ** as the value is never used. So really the only thing this code - ** accomplishes is to quieten purify. - ** - ** 2007-01-16: The wireshark project (www.wireshark.org) reports that - ** without this code, their parser segfaults. I'm not sure what there - ** parser is doing to make this happen. This is the second bug report - ** from wireshark this week. Clearly they are stressing Lemon in ways - ** that it has not been previously stressed... (SQLite ticket #2172) - */ - /*memset(&fts5yygotominor, 0, sizeof(fts5yygotominor));*/ - fts5yygotominor = fts5yyzerominor; - + /* 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 ){ +#ifdef fts5YYTRACKMAXSTACKDEPTH + if( fts5yypParser->fts5yyidx>fts5yypParser->fts5yyidxMax ){ + fts5yypParser->fts5yyidxMax = fts5yypParser->fts5yyidx; + } +#endif +#if fts5YYSTACKDEPTH>0 + if( fts5yypParser->fts5yyidx>=fts5YYSTACKDEPTH-1 ){ + fts5yyStackOverflow(fts5yypParser); + return; + } +#else + if( fts5yypParser->fts5yyidx>=fts5yypParser->fts5yystksz-1 ){ + fts5yyGrowStack(fts5yypParser); + if( fts5yypParser->fts5yyidx>=fts5yypParser->fts5yystksz-1 ){ + fts5yyStackOverflow(fts5yypParser); + return; + } + } +#endif + } switch( fts5yyruleno ){ /* Beginning here are the reduction cases. A typical example @@ -167669,132 +178410,142 @@ static void fts5yy_reduce( ** #line ** break; */ +/********** Begin reduce actions **********************************************/ + fts5YYMINORTYPE fts5yylhsminor; case 0: /* input ::= expr */ { sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy18); } break; case 1: /* expr ::= expr AND expr */ { - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); } + fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 2: /* expr ::= expr OR expr */ { - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); } + fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 3: /* expr ::= expr NOT expr */ { - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); } + fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 4: /* expr ::= LP expr RP */ -{fts5yygotominor.fts5yy18 = fts5yymsp[-1].minor.fts5yy18;} +{fts5yymsp[-2].minor.fts5yy18 = fts5yymsp[-1].minor.fts5yy18;} break; case 5: /* expr ::= exprlist */ case 6: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==6); -{fts5yygotominor.fts5yy18 = fts5yymsp[0].minor.fts5yy18;} +{fts5yylhsminor.fts5yy18 = fts5yymsp[0].minor.fts5yy18;} + fts5yymsp[0].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 7: /* exprlist ::= exprlist cnearset */ { - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-1].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18); } + fts5yymsp[-1].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 8: /* cnearset ::= nearset */ { - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); } + fts5yymsp[0].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 9: /* cnearset ::= colset COLON nearset */ { sqlite3Fts5ParseSetColset(pParse, fts5yymsp[0].minor.fts5yy26, fts5yymsp[-2].minor.fts5yy3); - fts5yygotominor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); + fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); } + fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; break; case 10: /* colset ::= LCP colsetlist RCP */ -{ fts5yygotominor.fts5yy3 = fts5yymsp[-1].minor.fts5yy3; } +{ fts5yymsp[-2].minor.fts5yy3 = fts5yymsp[-1].minor.fts5yy3; } break; case 11: /* colset ::= STRING */ { - fts5yygotominor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } + fts5yymsp[0].minor.fts5yy3 = fts5yylhsminor.fts5yy3; break; case 12: /* colsetlist ::= colsetlist STRING */ { - fts5yygotominor.fts5yy3 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy3, &fts5yymsp[0].minor.fts5yy0); } + fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy3, &fts5yymsp[0].minor.fts5yy0); } + fts5yymsp[-1].minor.fts5yy3 = fts5yylhsminor.fts5yy3; break; case 13: /* colsetlist ::= STRING */ { - fts5yygotominor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } + fts5yymsp[0].minor.fts5yy3 = fts5yylhsminor.fts5yy3; break; case 14: /* nearset ::= phrase */ -{ fts5yygotominor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); } +{ fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); } + fts5yymsp[0].minor.fts5yy26 = fts5yylhsminor.fts5yy26; break; case 15: /* nearset ::= STRING LP nearphrases neardist_opt RP */ { sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0); sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy26, &fts5yymsp[-1].minor.fts5yy0); - fts5yygotominor.fts5yy26 = fts5yymsp[-2].minor.fts5yy26; + fts5yylhsminor.fts5yy26 = fts5yymsp[-2].minor.fts5yy26; } + fts5yymsp[-4].minor.fts5yy26 = fts5yylhsminor.fts5yy26; break; case 16: /* nearphrases ::= phrase */ { - fts5yygotominor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); + fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); } + fts5yymsp[0].minor.fts5yy26 = fts5yylhsminor.fts5yy26; break; case 17: /* nearphrases ::= nearphrases phrase */ { - fts5yygotominor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy26, fts5yymsp[0].minor.fts5yy11); + fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy26, fts5yymsp[0].minor.fts5yy11); } + fts5yymsp[-1].minor.fts5yy26 = fts5yylhsminor.fts5yy26; break; case 18: /* neardist_opt ::= */ -{ fts5yygotominor.fts5yy0.p = 0; fts5yygotominor.fts5yy0.n = 0; } +{ fts5yymsp[1].minor.fts5yy0.p = 0; fts5yymsp[1].minor.fts5yy0.n = 0; } break; case 19: /* neardist_opt ::= COMMA STRING */ -{ fts5yygotominor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } +{ fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } break; case 20: /* phrase ::= phrase PLUS STRING star_opt */ { - fts5yygotominor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy11, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy11, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); } + fts5yymsp[-3].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; case 21: /* phrase ::= STRING star_opt */ { - fts5yygotominor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); } + fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; case 22: /* star_opt ::= STAR */ -{ fts5yygotominor.fts5yy20 = 1; } +{ fts5yymsp[0].minor.fts5yy20 = 1; } break; case 23: /* star_opt ::= */ -{ fts5yygotominor.fts5yy20 = 0; } +{ fts5yymsp[1].minor.fts5yy20 = 0; } break; default: break; +/********** End reduce actions ************************************************/ }; - assert( fts5yyruleno>=0 && fts5yyrulenofts5yyidx -= fts5yysize; 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; - /* If the reduce action popped at least - ** one element off the stack, then we can push the new element back - ** onto the stack here, and skip the stack overflow test in fts5yy_shift(). - ** That gives a significant speed improvement. */ - if( fts5yysize ){ - fts5yypParser->fts5yyidx++; - fts5yymsp -= fts5yysize-1; - fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact; - fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto; - fts5yymsp->minor = fts5yygotominor; - fts5yyTraceShift(fts5yypParser, fts5yyact); - }else{ - fts5yy_shift(fts5yypParser,fts5yyact,fts5yygoto,&fts5yygotominor); - } + fts5yypParser->fts5yyidx -= fts5yysize - 1; + fts5yymsp -= fts5yysize-1; + fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact; + fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto; + fts5yyTraceShift(fts5yypParser, fts5yyact); }else{ assert( fts5yyact == fts5YY_ACCEPT_ACTION ); + fts5yypParser->fts5yyidx -= fts5yysize; fts5yy_accept(fts5yypParser); } } @@ -167815,6 +178566,8 @@ static void fts5yy_parse_failed( while( fts5yypParser->fts5yyidx>=0 ) fts5yy_pop_parser_stack(fts5yypParser); /* Here code is inserted which will be executed whenever the ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } #endif /* fts5YYNOERRORRECOVERY */ @@ -167825,14 +178578,17 @@ static void fts5yy_parse_failed( static void fts5yy_syntax_error( fts5yyParser *fts5yypParser, /* The parser */ int fts5yymajor, /* The major type of the error token */ - fts5YYMINORTYPE fts5yyminor /* The minor type of the error token */ + sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The minor type of the error token */ ){ sqlite3Fts5ParserARG_FETCH; -#define FTS5TOKEN (fts5yyminor.fts5yy0) +#define FTS5TOKEN fts5yyminor +/************ Begin %syntax_error code ****************************************/ + UNUSED_PARAM(fts5yymajor); /* Silence a compiler warning */ sqlite3Fts5ParseError( pParse, "fts5: syntax error near \"%.*s\"",FTS5TOKEN.n,FTS5TOKEN.p ); +/************ End %syntax_error code ******************************************/ sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } @@ -167851,6 +178607,8 @@ static void fts5yy_accept( while( fts5yypParser->fts5yyidx>=0 ) fts5yy_pop_parser_stack(fts5yypParser); /* Here code is inserted which will be executed whenever the ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ } @@ -167880,7 +178638,7 @@ static void sqlite3Fts5Parser( sqlite3Fts5ParserARG_PDECL /* Optional %extra_argument parameter */ ){ fts5YYMINORTYPE fts5yyminorunion; - int fts5yyact; /* The parser action. */ + unsigned int fts5yyact; /* The parser action. */ #if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY) int fts5yyendofinput; /* True if we are at the end of input */ #endif @@ -167894,18 +178652,23 @@ static void sqlite3Fts5Parser( if( fts5yypParser->fts5yyidx<0 ){ #if fts5YYSTACKDEPTH<=0 if( fts5yypParser->fts5yystksz <=0 ){ - /*memset(&fts5yyminorunion, 0, sizeof(fts5yyminorunion));*/ - fts5yyminorunion = fts5yyzerominor; - fts5yyStackOverflow(fts5yypParser, &fts5yyminorunion); + fts5yyStackOverflow(fts5yypParser); return; } #endif fts5yypParser->fts5yyidx = 0; +#ifndef fts5YYNOERRORRECOVERY fts5yypParser->fts5yyerrcnt = -1; +#endif fts5yypParser->fts5yystack[0].stateno = 0; fts5yypParser->fts5yystack[0].major = 0; +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sInitialize. Empty stack. State 0\n", + fts5yyTracePrompt); + } +#endif } - fts5yyminorunion.fts5yy0 = fts5yyminor; #if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY) fts5yyendofinput = (fts5yymajor==0); #endif @@ -167913,7 +178676,7 @@ static void sqlite3Fts5Parser( #ifndef NDEBUG if( fts5yyTraceFILE ){ - fprintf(fts5yyTraceFILE,"%sInput %s\n",fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]); + fprintf(fts5yyTraceFILE,"%sInput '%s'\n",fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]); } #endif @@ -167921,13 +178684,16 @@ static void sqlite3Fts5Parser( fts5yyact = fts5yy_find_shift_action(fts5yypParser,(fts5YYCODETYPE)fts5yymajor); if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){ if( fts5yyact > fts5YY_MAX_SHIFT ) fts5yyact += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE; - fts5yy_shift(fts5yypParser,fts5yyact,fts5yymajor,&fts5yyminorunion); + fts5yy_shift(fts5yypParser,fts5yyact,fts5yymajor,fts5yyminor); +#ifndef fts5YYNOERRORRECOVERY fts5yypParser->fts5yyerrcnt--; +#endif fts5yymajor = fts5YYNOCODE; }else if( fts5yyact <= fts5YY_MAX_REDUCE ){ fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE); }else{ assert( fts5yyact == fts5YY_ERROR_ACTION ); + fts5yyminorunion.fts5yy0 = fts5yyminor; #ifdef fts5YYERRORSYMBOL int fts5yymx; #endif @@ -167957,7 +178723,7 @@ static void sqlite3Fts5Parser( ** */ if( fts5yypParser->fts5yyerrcnt<0 ){ - fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminorunion); + fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminor); } fts5yymx = fts5yypParser->fts5yystack[fts5yypParser->fts5yyidx].major; if( fts5yymx==fts5YYERRORSYMBOL || fts5yyerrorhit ){ @@ -167967,10 +178733,10 @@ static void sqlite3Fts5Parser( fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]); } #endif - fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); + fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion); fts5yymajor = fts5YYNOCODE; }else{ - while( + while( fts5yypParser->fts5yyidx >= 0 && fts5yymx != fts5YYERRORSYMBOL && (fts5yyact = fts5yy_find_reduce_action( @@ -167984,9 +178750,7 @@ static void sqlite3Fts5Parser( fts5yy_parse_failed(fts5yypParser); fts5yymajor = fts5YYNOCODE; }else if( fts5yymx!=fts5YYERRORSYMBOL ){ - fts5YYMINORTYPE u2; - u2.fts5YYERRSYMDT = 0; - fts5yy_shift(fts5yypParser,fts5yyact,fts5YYERRORSYMBOL,&u2); + fts5yy_shift(fts5yypParser,fts5yyact,fts5YYERRORSYMBOL,fts5yyminor); } } fts5yypParser->fts5yyerrcnt = 3; @@ -167999,7 +178763,7 @@ static void sqlite3Fts5Parser( ** Applications can set this macro (for example inside %include) if ** they intend to abandon the parse upon the first syntax error seen. */ - fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminorunion); + fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor); fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); fts5yymajor = fts5YYNOCODE; @@ -168014,7 +178778,7 @@ static void sqlite3Fts5Parser( ** three input tokens have been successfully shifted. */ if( fts5yypParser->fts5yyerrcnt<=0 ){ - fts5yy_syntax_error(fts5yypParser,fts5yymajor,fts5yyminorunion); + fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor); } fts5yypParser->fts5yyerrcnt = 3; fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); @@ -168027,7 +178791,12 @@ static void sqlite3Fts5Parser( }while( fts5yymajor!=fts5YYNOCODE && fts5yypParser->fts5yyidx>=0 ); #ifndef NDEBUG if( fts5yyTraceFILE ){ - fprintf(fts5yyTraceFILE,"%sReturn\n",fts5yyTracePrompt); + int i; + fprintf(fts5yyTraceFILE,"%sReturn. Stack=",fts5yyTracePrompt); + for(i=1; i<=fts5yypParser->fts5yyidx; i++) + fprintf(fts5yyTraceFILE,"%c%s", i==1 ? '[' : ' ', + fts5yyTokenName[fts5yypParser->fts5yystack[i].major]); + fprintf(fts5yyTraceFILE,"]\n"); } #endif return; @@ -168047,6 +178816,7 @@ static void sqlite3Fts5Parser( */ +/* #include "fts5Int.h" */ #include /* amalgamator: keep */ /* @@ -168171,7 +178941,7 @@ static void fts5HighlightAppend( const char *z, int n ){ if( *pRc==SQLITE_OK ){ - if( n<0 ) n = strlen(z); + if( n<0 ) n = (int)strlen(z); p->zOut = sqlite3_mprintf("%z%.*s", p->zOut, n, z); if( p->zOut==0 ) *pRc = SQLITE_NOMEM; } @@ -168192,6 +178962,8 @@ static int fts5HighlightCb( int rc = SQLITE_OK; int iPos; + UNUSED_PARAM2(pToken, nToken); + if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK; iPos = p->iPos++; @@ -168425,6 +179197,7 @@ static int fts5CountCb( void *pUserData /* Pointer to sqlite3_int64 variable */ ){ sqlite3_int64 *pn = (sqlite3_int64*)pUserData; + UNUSED_PARAM2(pApi, pFts); (*pn)++; return SQLITE_OK; } @@ -168578,7 +179351,7 @@ static int sqlite3Fts5AuxInit(fts5_api *pApi){ int rc = SQLITE_OK; /* Return code */ int i; /* To iterate through builtin functions */ - for(i=0; rc==SQLITE_OK && ixCreateFunction(pApi, aBuiltin[i].zFunc, aBuiltin[i].pUserData, @@ -168607,17 +179380,13 @@ static int sqlite3Fts5AuxInit(fts5_api *pApi){ +/* #include "fts5Int.h" */ -static int sqlite3Fts5BufferGrow(int *pRc, Fts5Buffer *pBuf, int nByte){ - - if( (pBuf->n + nByte) > pBuf->nSpace ){ +static int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){ + if( (u32)pBuf->nSpacenSpace ? pBuf->nSpace : 64; u8 *pNew; - int nNew = pBuf->nSpace ? pBuf->nSpace*2 : 64; - - /* A no-op if an error has already occurred */ - if( *pRc ) return 1; - - while( nNew<(pBuf->n + nByte) ){ + while( nNewp, nNew); @@ -168632,12 +179401,13 @@ static int sqlite3Fts5BufferGrow(int *pRc, Fts5Buffer *pBuf, int nByte){ return 0; } + /* ** 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){ - if( sqlite3Fts5BufferGrow(pRc, pBuf, 9) ) return; + if( fts5BufferGrow(pRc, pBuf, 9) ) return; pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iVal); } @@ -168652,12 +179422,6 @@ static int sqlite3Fts5Get32(const u8 *aBuf){ return (aBuf[0] << 24) + (aBuf[1] << 16) + (aBuf[2] << 8) + aBuf[3]; } -static void sqlite3Fts5BufferAppend32(int *pRc, Fts5Buffer *pBuf, int iVal){ - if( sqlite3Fts5BufferGrow(pRc, pBuf, 4) ) return; - sqlite3Fts5Put32(&pBuf->p[pBuf->n], iVal); - pBuf->n += 4; -} - /* ** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set ** the error code in p. If an error has already occurred when this function @@ -168666,11 +179430,11 @@ static void sqlite3Fts5BufferAppend32(int *pRc, Fts5Buffer *pBuf, int iVal){ static void sqlite3Fts5BufferAppendBlob( int *pRc, Fts5Buffer *pBuf, - int nData, + u32 nData, const u8 *pData ){ - assert( *pRc || nData>=0 ); - if( sqlite3Fts5BufferGrow(pRc, pBuf, nData) ) return; + assert_nc( *pRc || nData>=0 ); + if( fts5BufferGrow(pRc, pBuf, nData) ) return; memcpy(&pBuf->p[pBuf->n], pData, nData); pBuf->n += nData; } @@ -168685,7 +179449,7 @@ static void sqlite3Fts5BufferAppendString( Fts5Buffer *pBuf, const char *zStr ){ - int nStr = strlen(zStr); + int nStr = (int)strlen(zStr); sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1, (const u8*)zStr); pBuf->n--; } @@ -168813,23 +179577,36 @@ static int sqlite3Fts5PoslistReaderInit( return pIter->bEof; } +/* +** Append position iPos to the position list being accumulated in buffer +** pBuf, which must be already be large enough to hold the new data. +** The previous position written to this list is *piPrev. *piPrev is set +** to iPos before returning. +*/ +static void sqlite3Fts5PoslistSafeAppend( + Fts5Buffer *pBuf, + i64 *piPrev, + i64 iPos +){ + 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)); + *piPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); + *piPrev = iPos; +} + static int sqlite3Fts5PoslistWriterAppend( Fts5Buffer *pBuf, Fts5PoslistWriter *pWriter, i64 iPos ){ - static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; - int rc = SQLITE_OK; - if( 0==sqlite3Fts5BufferGrow(&rc, pBuf, 5+5+5) ){ - if( (iPos & colmask) != (pWriter->iPrev & colmask) ){ - pBuf->p[pBuf->n++] = 1; - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); - pWriter->iPrev = (iPos & colmask); - } - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-pWriter->iPrev)+2); - pWriter->iPrev = iPos; - } - return rc; + 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); + return SQLITE_OK; } static void *sqlite3Fts5MallocZero(int *pRc, int nByte){ @@ -168857,7 +179634,7 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ char *zRet = 0; if( *pRc==SQLITE_OK ){ if( nIn<0 ){ - nIn = strlen(pIn); + nIn = (int)strlen(pIn); } zRet = (char*)sqlite3_malloc(nIn+1); if( zRet ){ @@ -168897,6 +179674,89 @@ static int sqlite3Fts5IsBareword(char t){ } +/************************************************************************* +*/ +typedef struct Fts5TermsetEntry Fts5TermsetEntry; +struct Fts5TermsetEntry { + char *pTerm; + int nTerm; + int iIdx; /* Index (main or aPrefix[] entry) */ + Fts5TermsetEntry *pNext; +}; + +struct Fts5Termset { + Fts5TermsetEntry *apHash[512]; +}; + +static int sqlite3Fts5TermsetNew(Fts5Termset **pp){ + int rc = SQLITE_OK; + *pp = sqlite3Fts5MallocZero(&rc, sizeof(Fts5Termset)); + return rc; +} + +static int sqlite3Fts5TermsetAdd( + Fts5Termset *p, + int iIdx, + const char *pTerm, int nTerm, + int *pbPresent +){ + int rc = SQLITE_OK; + *pbPresent = 0; + if( p ){ + int i; + u32 hash = 13; + Fts5TermsetEntry *pEntry; + + /* Calculate a hash value for this term. This is the same hash checksum + ** used by the fts5_hash.c module. This is not important for correct + ** operation of the module, but is necessary to ensure that some tests + ** designed to produce hash table collisions really do work. */ + for(i=nTerm-1; i>=0; i--){ + hash = (hash << 3) ^ hash ^ pTerm[i]; + } + hash = (hash << 3) ^ hash ^ iIdx; + hash = hash % ArraySize(p->apHash); + + for(pEntry=p->apHash[hash]; pEntry; pEntry=pEntry->pNext){ + if( pEntry->iIdx==iIdx + && pEntry->nTerm==nTerm + && memcmp(pEntry->pTerm, pTerm, nTerm)==0 + ){ + *pbPresent = 1; + break; + } + } + + if( pEntry==0 ){ + pEntry = sqlite3Fts5MallocZero(&rc, sizeof(Fts5TermsetEntry) + nTerm); + if( pEntry ){ + pEntry->pTerm = (char*)&pEntry[1]; + pEntry->nTerm = nTerm; + pEntry->iIdx = iIdx; + memcpy(pEntry->pTerm, pTerm, nTerm); + pEntry->pNext = p->apHash[hash]; + p->apHash[hash] = pEntry; + } + } + } + + return rc; +} + +static void sqlite3Fts5TermsetFree(Fts5Termset *p){ + if( p ){ + u32 i; + for(i=0; iapHash); i++){ + Fts5TermsetEntry *pEntry = p->apHash[i]; + while( pEntry ){ + Fts5TermsetEntry *pDel = pEntry; + pEntry = pEntry->pNext; + sqlite3_free(pDel); + } + } + sqlite3_free(p); + } +} /* ** 2014 Jun 09 @@ -168914,11 +179774,13 @@ static int sqlite3Fts5IsBareword(char t){ */ - +/* #include "fts5Int.h" */ #define FTS5_DEFAULT_PAGE_SIZE 4050 #define FTS5_DEFAULT_AUTOMERGE 4 +#define FTS5_DEFAULT_USERMERGE 4 #define FTS5_DEFAULT_CRISISMERGE 16 +#define FTS5_DEFAULT_HASHSIZE (1024*1024) /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (128*1024) @@ -169093,6 +179955,33 @@ static void sqlite3Fts5Dequote(char *z){ } } + +struct Fts5Enum { + const char *zName; + int eVal; +}; +typedef struct Fts5Enum Fts5Enum; + +static int fts5ConfigSetEnum( + const Fts5Enum *aEnum, + const char *zEnum, + int *peVal +){ + int nEnum = (int)strlen(zEnum); + int i; + int iVal = -1; + + for(i=0; aEnum[i].zName; i++){ + if( sqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){ + if( iVal>=0 ) return SQLITE_ERROR; + iVal = aEnum[i].eVal; + } + } + + *peVal = iVal; + return iVal<0 ? SQLITE_ERROR : SQLITE_OK; +} + /* ** Parse a "special" CREATE VIRTUAL TABLE directive and update ** configuration object pConfig as appropriate. @@ -169110,44 +179999,63 @@ static int fts5ConfigParseSpecial( char **pzErr /* OUT: Error message */ ){ int rc = SQLITE_OK; - int nCmd = strlen(zCmd); + int nCmd = (int)strlen(zCmd); if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){ const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES; const char *p; - if( pConfig->aPrefix ){ - *pzErr = sqlite3_mprintf("multiple prefix=... directives"); - rc = SQLITE_ERROR; - }else{ + int bFirst = 1; + if( pConfig->aPrefix==0 ){ pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte); + if( rc ) return rc; } + p = zArg; - while( rc==SQLITE_OK && p[0] ){ + while( 1 ){ int nPre = 0; + while( p[0]==' ' ) p++; + if( bFirst==0 && p[0]==',' ){ + p++; + while( p[0]==' ' ) p++; + }else if( p[0]=='\0' ){ + break; + } + if( p[0]<'0' || p[0]>'9' ){ + *pzErr = sqlite3_mprintf("malformed prefix=... directive"); + rc = SQLITE_ERROR; + break; + } + + if( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){ + *pzErr = sqlite3_mprintf( + "too many prefix indexes (max %d)", FTS5_MAX_PREFIX_INDEXES + ); + rc = SQLITE_ERROR; + break; + } + while( p[0]>='0' && p[0]<='9' && nPre<1000 ){ nPre = nPre*10 + (p[0] - '0'); p++; } - while( p[0]==' ' ) p++; - if( p[0]==',' ){ - p++; - }else if( p[0] ){ - *pzErr = sqlite3_mprintf("malformed prefix=... directive"); - rc = SQLITE_ERROR; - } - if( rc==SQLITE_OK && (nPre==0 || nPre>=1000) ){ - *pzErr = sqlite3_mprintf("prefix length out of range: %d", nPre); + + if( nPre<=0 || nPre>=1000 ){ + *pzErr = sqlite3_mprintf("prefix length out of range (max 999)"); rc = SQLITE_ERROR; + break; } + pConfig->aPrefix[pConfig->nPrefix] = nPre; pConfig->nPrefix++; + bFirst = 0; } + assert( pConfig->nPrefix<=FTS5_MAX_PREFIX_INDEXES ); return rc; } if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){ const char *p = (const char*)zArg; - int nArg = strlen(zArg) + 1; + int nArg = (int)strlen(zArg) + 1; char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2); char *pSpace = pDel; @@ -169224,6 +180132,20 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){ + const Fts5Enum aDetail[] = { + { "none", FTS5_DETAIL_NONE }, + { "full", FTS5_DETAIL_FULL }, + { "columns", FTS5_DETAIL_COLUMNS }, + { 0, 0 } + }; + + if( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){ + *pzErr = sqlite3_mprintf("malformed detail=... directive"); + } + return rc; + } + *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); return SQLITE_ERROR; } @@ -169263,7 +180185,7 @@ static const char *fts5ConfigGobbleWord( ){ const char *zRet = 0; - int nIn = strlen(zIn); + int nIn = (int)strlen(zIn); char *zOut = sqlite3_malloc(nIn+1); assert( *pRc==SQLITE_OK ); @@ -169280,7 +180202,9 @@ static const char *fts5ConfigGobbleWord( *pbQuoted = 1; }else{ zRet = fts5ConfigSkipBareword(zIn); - zOut[zRet-zIn] = '\0'; + if( zRet ){ + zOut[zRet-zIn] = '\0'; + } } } @@ -169379,6 +180303,7 @@ static int sqlite3Fts5ConfigParse( pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); pRet->bColumnsize = 1; + pRet->eDetail = FTS5_DETAIL_FULL; #ifdef SQLITE_DEBUG pRet->bPrefixIndex = 1; #endif @@ -169605,33 +180530,37 @@ static int sqlite3Fts5ConfigParseRank( *pzRank = 0; *pzRankArgs = 0; - p = fts5ConfigSkipWhitespace(p); - pRank = p; - p = fts5ConfigSkipBareword(p); - - if( p ){ - zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); - if( zRank ) memcpy(zRank, pRank, p-pRank); - }else{ + if( p==0 ){ rc = SQLITE_ERROR; - } + }else{ + p = fts5ConfigSkipWhitespace(p); + pRank = p; + p = fts5ConfigSkipBareword(p); - if( rc==SQLITE_OK ){ - p = fts5ConfigSkipWhitespace(p); - if( *p!='(' ) rc = SQLITE_ERROR; - p++; - } - if( rc==SQLITE_OK ){ - const char *pArgs; - p = fts5ConfigSkipWhitespace(p); - pArgs = p; - if( *p!=')' ){ - p = fts5ConfigSkipArgs(p); - if( p==0 ){ - rc = SQLITE_ERROR; - }else{ - zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); - if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); + if( p ){ + zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); + if( zRank ) memcpy(zRank, pRank, p-pRank); + }else{ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + p = fts5ConfigSkipWhitespace(p); + if( *p!='(' ) rc = SQLITE_ERROR; + p++; + } + if( rc==SQLITE_OK ){ + const char *pArgs; + p = fts5ConfigSkipWhitespace(p); + pArgs = p; + if( *p!=')' ){ + p = fts5ConfigSkipArgs(p); + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); + if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); + } } } } @@ -169666,6 +180595,18 @@ static int sqlite3Fts5ConfigSetValue( } } + else if( 0==sqlite3_stricmp(zKey, "hashsize") ){ + int nHashSize = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nHashSize = sqlite3_value_int(pVal); + } + if( nHashSize<=0 ){ + *pbBadkey = 1; + }else{ + pConfig->nHashSize = nHashSize; + } + } + else if( 0==sqlite3_stricmp(zKey, "automerge") ){ int nAutomerge = -1; if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ @@ -169679,6 +180620,18 @@ static int sqlite3Fts5ConfigSetValue( } } + else if( 0==sqlite3_stricmp(zKey, "usermerge") ){ + int nUsermerge = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nUsermerge = sqlite3_value_int(pVal); + } + if( nUsermerge<2 || nUsermerge>16 ){ + *pbBadkey = 1; + }else{ + pConfig->nUsermerge = nUsermerge; + } + } + else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){ int nCrisisMerge = -1; if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ @@ -169725,7 +180678,9 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ /* Set default values */ pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; + pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; + pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ @@ -169765,7 +180720,6 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ return rc; } - /* ** 2014 May 31 ** @@ -169782,6 +180736,8 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ +/* #include "fts5Int.h" */ +/* #include "fts5parse.h" */ /* ** All token types in the generated fts5parse.h file are greater than 0. @@ -169806,6 +180762,7 @@ static void sqlite3Fts5ParserTrace(FILE*, char*); struct Fts5Expr { Fts5Index *pIndex; + Fts5Config *pConfig; Fts5ExprNode *pRoot; int bDesc; /* Iterate in descending rowid order */ int nPhrase; /* Number of phrases in expression */ @@ -169827,6 +180784,9 @@ struct Fts5ExprNode { int bEof; /* True at EOF */ int bNomatch; /* True if entry is not a match */ + /* Next method for this node. */ + int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64); + i64 iRowid; /* Current rowid */ Fts5ExprNearset *pNear; /* For FTS5_STRING - cluster of phrases */ @@ -169838,6 +180798,12 @@ struct Fts5ExprNode { #define Fts5NodeIsString(p) ((p)->eType==FTS5_TERM || (p)->eType==FTS5_STRING) +/* +** Invoke the xNext method of an Fts5ExprNode object. This macro should be +** used as if it has the same signature as the xNext() methods themselves. +*/ +#define fts5ExprNodeNext(a,b,c,d) (b)->xNext((a), (b), (c), (d)) + /* ** An instance of the following structure represents a single search term ** or term prefix. @@ -169999,12 +180965,23 @@ static int sqlite3Fts5ExprNew( sParse.rc = SQLITE_NOMEM; sqlite3Fts5ParseNodeFree(sParse.pExpr); }else{ - pNew->pRoot = sParse.pExpr; + if( !sParse.pExpr ){ + const int nByte = sizeof(Fts5ExprNode); + pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte); + if( pNew->pRoot ){ + pNew->pRoot->bEof = 1; + } + }else{ + pNew->pRoot = sParse.pExpr; + } pNew->pIndex = 0; + pNew->pConfig = pConfig; pNew->apExprPhrase = sParse.apPhrase; pNew->nPhrase = sParse.nPhrase; sParse.apPhrase = 0; } + }else{ + sqlite3Fts5ParseNodeFree(sParse.pExpr); } sqlite3_free(sParse.apPhrase); @@ -170050,7 +181027,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ assert( bDesc==0 || bDesc==1 ); for(p=pTerm; p; p=p->pSynonym){ if( 0==sqlite3Fts5IterEof(p->pIter) ){ - i64 iRowid = sqlite3Fts5IterRowid(p->pIter); + i64 iRowid = p->pIter->iRowid; if( bRetValid==0 || (bDesc!=(iRowidpSynonym ); for(p=pTerm; p; p=p->pSynonym){ Fts5IndexIter *pIter = p->pIter; - if( sqlite3Fts5IterEof(pIter)==0 && sqlite3Fts5IterRowid(pIter)==iRowid ){ - const u8 *a; - int n; - i64 dummy; - rc = sqlite3Fts5IterPoslist(pIter, pColset, &a, &n, &dummy); - if( rc!=SQLITE_OK ) goto synonym_poslist_out; + if( sqlite3Fts5IterEof(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); @@ -170100,20 +181072,19 @@ static int fts5ExprSynonymPoslist( if( aIter!=aStatic ) sqlite3_free(aIter); aIter = aNew; } - sqlite3Fts5PoslistReaderInit(a, n, &aIter[nIter]); + sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &aIter[nIter]); assert( aIter[nIter].bEof==0 ); nIter++; } } - assert( *pbDel==0 ); if( nIter==1 ){ *pa = (u8*)aIter[0].a; *pn = aIter[0].n; }else{ Fts5PoslistWriter writer = {0}; - Fts5Buffer buf = {0,0,0}; i64 iPrev = -1; + fts5BufferZero(pBuf); while( 1 ){ int i; i64 iMin = FTS5_LARGEST_INT64; @@ -170128,15 +181099,12 @@ static int fts5ExprSynonymPoslist( } } if( iMin==FTS5_LARGEST_INT64 || rc!=SQLITE_OK ) break; - rc = sqlite3Fts5PoslistWriterAppend(&buf, &writer, iMin); + rc = sqlite3Fts5PoslistWriterAppend(pBuf, &writer, iMin); iPrev = iMin; } - if( rc ){ - sqlite3_free(buf.p); - }else{ - *pa = buf.p; - *pn = buf.n; - *pbDel = 1; + if( rc==SQLITE_OK ){ + *pa = pBuf->p; + *pn = pBuf->n; } } @@ -170159,7 +181127,6 @@ static int fts5ExprSynonymPoslist( */ static int fts5ExprPhraseIsMatch( Fts5ExprNode *pNode, /* Node pPhrase belongs to */ - Fts5Colset *pColset, /* Restrict matches to these columns */ Fts5ExprPhrase *pPhrase, /* Phrase object to initialize */ int *pbMatch /* OUT: Set to true if really a match */ ){ @@ -170173,7 +181140,7 @@ static int fts5ExprPhraseIsMatch( /* If the aStatic[] array is not large enough, allocate a large array ** using sqlite3_malloc(). This approach could be improved upon. */ - if( pPhrase->nTerm>(sizeof(aStatic) / sizeof(aStatic[0])) ){ + if( pPhrase->nTerm>ArraySize(aStatic) ){ int nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm; aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte); if( !aIter ) return SQLITE_NOMEM; @@ -170183,20 +181150,23 @@ static int fts5ExprPhraseIsMatch( /* Initialize a term iterator for each term in the phrase */ for(i=0; inTerm; i++){ Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; - i64 dummy; int n = 0; int bFlag = 0; - const u8 *a = 0; + u8 *a = 0; if( pTerm->pSynonym ){ - rc = fts5ExprSynonymPoslist( - pTerm, pColset, pNode->iRowid, &bFlag, (u8**)&a, &n - ); + Fts5Buffer buf = {0, 0, 0}; + rc = fts5ExprSynonymList(pTerm, pNode->iRowid, &buf, &a, &n); + if( rc ){ + sqlite3_free(a); + goto ismatch_out; + } + if( a==buf.p ) bFlag = 1; }else{ - rc = sqlite3Fts5IterPoslist(pTerm->pIter, pColset, &a, &n, &dummy); + a = (u8*)pTerm->pIter->pData; + n = pTerm->pIter->nData; } - if( rc!=SQLITE_OK ) goto ismatch_out; sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]); - aIter[i].bFlag = bFlag; + aIter[i].bFlag = (u8)bFlag; if( aIter[i].bEof ) goto ismatch_out; } @@ -170266,12 +181236,6 @@ static int fts5LookaheadReaderInit( return fts5LookaheadReaderNext(p); } -#if 0 -static int fts5LookaheadReaderEof(Fts5LookaheadReader *p){ - return (p->iPos==FTS5_LOOKAHEAD_EOF); -} -#endif - typedef struct Fts5NearTrimmer Fts5NearTrimmer; struct Fts5NearTrimmer { Fts5LookaheadReader reader; /* Input iterator */ @@ -170309,7 +181273,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ /* If the aStatic[] array is not large enough, allocate a large array ** using sqlite3_malloc(). This approach could be improved upon. */ - if( pNear->nPhrase>(sizeof(aStatic) / sizeof(aStatic[0])) ){ + if( pNear->nPhrase>ArraySize(aStatic) ){ int nByte = sizeof(Fts5NearTrimmer) * pNear->nPhrase; a = (Fts5NearTrimmer*)sqlite3Fts5MallocZero(&rc, nByte); }else{ @@ -170386,6 +181350,315 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ } } +/* +** Advance iterator pIter until it points to a value equal to or laster +** than the initial value of *piLast. If this means the iterator points +** to a value laster than *piLast, update *piLast to the new lastest value. +** +** If the iterator reaches EOF, set *pbEof to true before returning. If +** an error occurs, set *pRc to an error code. If either *pbEof or *pRc +** are set, return a non-zero value. Otherwise, return zero. +*/ +static int fts5ExprAdvanceto( + Fts5IndexIter *pIter, /* Iterator to advance */ + int bDesc, /* True if iterator is "rowid DESC" */ + i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ + int *pRc, /* OUT: Error code */ + int *pbEof /* OUT: Set to true if EOF */ +){ + i64 iLast = *piLast; + i64 iRowid; + + iRowid = pIter->iRowid; + if( (bDesc==0 && iLast>iRowid) || (bDesc && iLastiRowid; + assert( (bDesc==0 && iRowid>=iLast) || (bDesc==1 && iRowid<=iLast) ); + } + *piLast = iRowid; + + return 0; +} + +static int fts5ExprSynonymAdvanceto( + Fts5ExprTerm *pTerm, /* Term iterator to advance */ + int bDesc, /* True if iterator is "rowid DESC" */ + i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ + int *pRc /* OUT: Error code */ +){ + int rc = SQLITE_OK; + i64 iLast = *piLast; + Fts5ExprTerm *p; + int bEof = 0; + + for(p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){ + if( sqlite3Fts5IterEof(p->pIter)==0 ){ + i64 iRowid = p->pIter->iRowid; + if( (bDesc==0 && iLast>iRowid) || (bDesc && iLastpIter, iLast); + } + } + } + + if( rc!=SQLITE_OK ){ + *pRc = rc; + bEof = 1; + }else{ + *piLast = fts5ExprSynonymRowid(pTerm, bDesc, &bEof); + } + return bEof; +} + + +static int fts5ExprNearTest( + int *pRc, + Fts5Expr *pExpr, /* Expression that pNear is a part of */ + Fts5ExprNode *pNode /* The "NEAR" node (FTS5_STRING) */ +){ + Fts5ExprNearset *pNear = pNode->pNear; + int rc = *pRc; + + if( pExpr->pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5ExprTerm *pTerm; + Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + pPhrase->poslist.n = 0; + for(pTerm=&pPhrase->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ + Fts5IndexIter *pIter = pTerm->pIter; + if( sqlite3Fts5IterEof(pIter)==0 ){ + if( pIter->iRowid==pNode->iRowid && pIter->nData>0 ){ + pPhrase->poslist.n = 1; + } + } + } + return pPhrase->poslist.n; + }else{ + int i; + + /* Check that each phrase in the nearset matches the current row. + ** Populate the pPhrase->poslist buffers at the same time. If any + ** phrase is not a match, break out of the loop early. */ + for(i=0; rc==SQLITE_OK && inPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset ){ + int bMatch = 0; + rc = fts5ExprPhraseIsMatch(pNode, pPhrase, &bMatch); + if( bMatch==0 ) break; + }else{ + Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter; + fts5BufferSet(&rc, &pPhrase->poslist, pIter->nData, pIter->pData); + } + } + + *pRc = rc; + if( i==pNear->nPhrase && (i==1 || fts5ExprNearIsMatch(pRc, pNear)) ){ + return 1; + } + return 0; + } +} + + +/* +** 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. +*/ +static int fts5ExprNearInitAll( + Fts5Expr *pExpr, + Fts5ExprNode *pNode +){ + Fts5ExprNearset *pNear = pNode->pNear; + int i, j; + int rc = SQLITE_OK; + + assert( pNode->bNomatch==0 ); + for(i=0; rc==SQLITE_OK && inPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + for(j=0; jnTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + Fts5ExprTerm *p; + int bEof = 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( bEof ){ + pNode->bEof = 1; + return rc; + } + } + } + + return rc; +} + +/* +** If pExpr is an ASC iterator, this function returns a value with the +** same sign as: +** +** (iLhs - iRhs) +** +** Otherwise, if this is a DESC iterator, the opposite is returned: +** +** (iRhs - iLhs) +*/ +static int fts5RowidCmp( + Fts5Expr *pExpr, + i64 iLhs, + i64 iRhs +){ + assert( pExpr->bDesc==0 || pExpr->bDesc==1 ); + if( pExpr->bDesc==0 ){ + if( iLhs iRhs); + }else{ + if( iLhs>iRhs ) return -1; + return (iLhs < iRhs); + } +} + +static void fts5ExprSetEof(Fts5ExprNode *pNode){ + int i; + pNode->bEof = 1; + pNode->bNomatch = 0; + for(i=0; inChild; i++){ + fts5ExprSetEof(pNode->apChild[i]); + } +} + +static void fts5ExprNodeZeroPoslist(Fts5ExprNode *pNode){ + if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){ + Fts5ExprNearset *pNear = pNode->pNear; + int i; + for(i=0; inPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + pPhrase->poslist.n = 0; + } + }else{ + int i; + for(i=0; inChild; i++){ + fts5ExprNodeZeroPoslist(pNode->apChild[i]); + } + } +} + + + +/* +** Compare the values currently indicated by the two nodes as follows: +** +** res = (*p1) - (*p2) +** +** Nodes that point to values that come later in the iteration order are +** considered to be larger. Nodes at EOF are the largest of all. +** +** This means that if the iteration order is ASC, then numerically larger +** rowids are considered larger. Or if it is the default DESC, numerically +** smaller rowids are larger. +*/ +static int fts5NodeCompare( + Fts5Expr *pExpr, + Fts5ExprNode *p1, + Fts5ExprNode *p2 +){ + if( p2->bEof ) return -1; + if( p1->bEof ) return +1; + return fts5RowidCmp(pExpr, p1->iRowid, p2->iRowid); +} + +/* +** All individual term iterators in pNear are guaranteed to be valid when +** this function is called. This function checks if all term iterators +** point to the same rowid, and if not, advances them until they do. +** If an EOF is reached before this happens, *pbEof is set to true before +** returning. +** +** SQLITE_OK is returned if an error occurs, or an SQLite error code +** otherwise. It is not considered an error code if an iterator reaches +** EOF. +*/ +static int fts5ExprNodeTest_STRING( + Fts5Expr *pExpr, /* Expression pPhrase belongs to */ + Fts5ExprNode *pNode +){ + Fts5ExprNearset *pNear = pNode->pNear; + Fts5ExprPhrase *pLeft = pNear->apPhrase[0]; + int rc = SQLITE_OK; + i64 iLast; /* Lastest rowid any iterator points to */ + int i, j; /* Phrase and token index, respectively */ + int bMatch; /* True if all terms are at the same rowid */ + const int bDesc = pExpr->bDesc; + + /* Check that this node should not be FTS5_TERM */ + assert( pNear->nPhrase>1 + || pNear->apPhrase[0]->nTerm>1 + || pNear->apPhrase[0]->aTerm[0].pSynonym + ); + + /* Initialize iLast, the "lastest" rowid any iterator points to. If the + ** iterator skips through rowids in the default ascending order, this means + ** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it + ** means the minimum rowid. */ + if( pLeft->aTerm[0].pSynonym ){ + iLast = fts5ExprSynonymRowid(&pLeft->aTerm[0], bDesc, 0); + }else{ + iLast = pLeft->aTerm[0].pIter->iRowid; + } + + do { + bMatch = 1; + for(i=0; inPhrase; i++){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; + for(j=0; jnTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + if( pTerm->pSynonym ){ + i64 iRowid = fts5ExprSynonymRowid(pTerm, bDesc, 0); + if( iRowid==iLast ) continue; + bMatch = 0; + if( fts5ExprSynonymAdvanceto(pTerm, bDesc, &iLast, &rc) ){ + pNode->bNomatch = 0; + pNode->bEof = 1; + return rc; + } + }else{ + Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; + if( pIter->iRowid==iLast ) continue; + bMatch = 0; + if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ + return rc; + } + } + } + } + }while( bMatch==0 ); + + pNode->iRowid = iLast; + pNode->bNomatch = ((0==fts5ExprNearTest(&rc, pExpr, pNode)) && rc==SQLITE_OK); + assert( pNode->bEof==0 || pNode->bNomatch==0 ); + + return rc; +} + /* ** Advance the first term iterator in the first phrase of pNear. Set output ** variable *pbEof to true if it reaches EOF or if an error occurs. @@ -170393,7 +181666,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ ** Return SQLITE_OK if successful, or an SQLite error code if an error ** occurs. */ -static int fts5ExprNearAdvanceFirst( +static int fts5ExprNodeNext_STRING( Fts5Expr *pExpr, /* Expression pPhrase belongs to */ Fts5ExprNode *pNode, /* FTS5_STRING or FTS5_TERM node */ int bFromValid, @@ -170402,6 +181675,7 @@ static int fts5ExprNearAdvanceFirst( Fts5ExprTerm *pTerm = &pNode->pNear->apPhrase[0]->aTerm[0]; int rc = SQLITE_OK; + pNode->bNomatch = 0; if( pTerm->pSynonym ){ int bEof = 1; Fts5ExprTerm *p; @@ -170413,7 +181687,7 @@ static int fts5ExprNearAdvanceFirst( ** is valid - each iterator that points to a rowid before iFrom. */ for(p=pTerm; p; p=p->pSynonym){ if( sqlite3Fts5IterEof(p->pIter)==0 ){ - i64 ii = sqlite3Fts5IterRowid(p->pIter); + i64 ii = p->pIter->iRowid; if( ii==iRowid || (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc) ){ @@ -170448,108 +181722,16 @@ static int fts5ExprNearAdvanceFirst( pNode->bEof = (rc || sqlite3Fts5IterEof(pIter)); } + if( pNode->bEof==0 ){ + assert( rc==SQLITE_OK ); + rc = fts5ExprNodeTest_STRING(pExpr, pNode); + } + return rc; } -/* -** Advance iterator pIter until it points to a value equal to or laster -** than the initial value of *piLast. If this means the iterator points -** to a value laster than *piLast, update *piLast to the new lastest value. -** -** If the iterator reaches EOF, set *pbEof to true before returning. If -** an error occurs, set *pRc to an error code. If either *pbEof or *pRc -** are set, return a non-zero value. Otherwise, return zero. -*/ -static int fts5ExprAdvanceto( - Fts5IndexIter *pIter, /* Iterator to advance */ - int bDesc, /* True if iterator is "rowid DESC" */ - i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ - int *pRc, /* OUT: Error code */ - int *pbEof /* OUT: Set to true if EOF */ -){ - i64 iLast = *piLast; - i64 iRowid; - iRowid = sqlite3Fts5IterRowid(pIter); - if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast=iLast) || (bDesc==1 && iRowid<=iLast) ); - } - *piLast = iRowid; - - return 0; -} - -static int fts5ExprSynonymAdvanceto( - Fts5ExprTerm *pTerm, /* Term iterator to advance */ - int bDesc, /* True if iterator is "rowid DESC" */ - i64 *piLast, /* IN/OUT: Lastest rowid seen so far */ - int *pRc /* OUT: Error code */ -){ - int rc = SQLITE_OK; - i64 iLast = *piLast; - Fts5ExprTerm *p; - int bEof = 0; - - for(p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){ - if( sqlite3Fts5IterEof(p->pIter)==0 ){ - i64 iRowid = sqlite3Fts5IterRowid(p->pIter); - if( (bDesc==0 && iLast>iRowid) || (bDesc && iLastpIter, iLast); - } - } - } - - if( rc!=SQLITE_OK ){ - *pRc = rc; - bEof = 1; - }else{ - *piLast = fts5ExprSynonymRowid(pTerm, bDesc, &bEof); - } - return bEof; -} - - -static int fts5ExprNearTest( - int *pRc, - Fts5Expr *pExpr, /* Expression that pNear is a part of */ - Fts5ExprNode *pNode /* The "NEAR" node (FTS5_STRING) */ -){ - Fts5ExprNearset *pNear = pNode->pNear; - int rc = *pRc; - int i; - - /* Check that each phrase in the nearset matches the current row. - ** Populate the pPhrase->poslist buffers at the same time. If any - ** phrase is not a match, break out of the loop early. */ - for(i=0; rc==SQLITE_OK && inPhrase; i++){ - Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset ){ - int bMatch = 0; - rc = fts5ExprPhraseIsMatch(pNode, pNear->pColset, pPhrase, &bMatch); - if( bMatch==0 ) break; - }else{ - rc = sqlite3Fts5IterPoslistBuffer( - pPhrase->aTerm[0].pIter, &pPhrase->poslist - ); - } - } - - *pRc = rc; - if( i==pNear->nPhrase && (i==1 || fts5ExprNearIsMatch(pRc, pNear)) ){ - return 1; - } - - return 0; -} - -static int fts5ExprTokenTest( +static int fts5ExprNodeTest_TERM( Fts5Expr *pExpr, /* Expression that pNear is a part of */ Fts5ExprNode *pNode /* The "NEAR" node (FTS5_TERM) */ ){ @@ -170558,203 +181740,98 @@ static int fts5ExprTokenTest( ** fts5_index.c iterator object. This is much faster than synthesizing ** a new poslist the way we have to for more complicated phrase or NEAR ** expressions. */ - Fts5ExprNearset *pNear = pNode->pNear; - Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0]; Fts5IndexIter *pIter = pPhrase->aTerm[0].pIter; - Fts5Colset *pColset = pNear->pColset; - int rc; assert( pNode->eType==FTS5_TERM ); - assert( pNear->nPhrase==1 && pPhrase->nTerm==1 ); + assert( pNode->pNear->nPhrase==1 && pPhrase->nTerm==1 ); assert( pPhrase->aTerm[0].pSynonym==0 ); - rc = sqlite3Fts5IterPoslist(pIter, pColset, - (const u8**)&pPhrase->poslist.p, &pPhrase->poslist.n, &pNode->iRowid - ); + pPhrase->poslist.n = pIter->nData; + if( pExpr->pConfig->eDetail==FTS5_DETAIL_FULL ){ + pPhrase->poslist.p = (u8*)pIter->pData; + } + pNode->iRowid = pIter->iRowid; pNode->bNomatch = (pPhrase->poslist.n==0); - return rc; + return SQLITE_OK; } /* -** All individual term iterators in pNear are guaranteed to be valid when -** this function is called. This function checks if all term iterators -** point to the same rowid, and if not, advances them until they do. -** If an EOF is reached before this happens, *pbEof is set to true before -** returning. -** -** SQLITE_OK is returned if an error occurs, or an SQLite error code -** otherwise. It is not considered an error code if an iterator reaches -** EOF. +** xNext() method for a node of type FTS5_TERM. */ -static int fts5ExprNearNextMatch( - Fts5Expr *pExpr, /* Expression pPhrase belongs to */ - Fts5ExprNode *pNode +static int fts5ExprNodeNext_TERM( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom ){ - Fts5ExprNearset *pNear = pNode->pNear; - Fts5ExprPhrase *pLeft = pNear->apPhrase[0]; - int rc = SQLITE_OK; - i64 iLast; /* Lastest rowid any iterator points to */ - int i, j; /* Phrase and token index, respectively */ - int bMatch; /* True if all terms are at the same rowid */ - const int bDesc = pExpr->bDesc; + int rc; + Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter; - /* Check that this node should not be FTS5_TERM */ - assert( pNear->nPhrase>1 - || pNear->apPhrase[0]->nTerm>1 - || pNear->apPhrase[0]->aTerm[0].pSynonym - ); - - /* Initialize iLast, the "lastest" rowid any iterator points to. If the - ** iterator skips through rowids in the default ascending order, this means - ** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it - ** means the minimum rowid. */ - if( pLeft->aTerm[0].pSynonym ){ - iLast = fts5ExprSynonymRowid(&pLeft->aTerm[0], bDesc, 0); + assert( pNode->bEof==0 ); + if( bFromValid ){ + rc = sqlite3Fts5IterNextFrom(pIter, iFrom); }else{ - iLast = sqlite3Fts5IterRowid(pLeft->aTerm[0].pIter); + rc = sqlite3Fts5IterNext(pIter); } - - do { - bMatch = 1; - for(i=0; inPhrase; i++){ - Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - for(j=0; jnTerm; j++){ - Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; - if( pTerm->pSynonym ){ - i64 iRowid = fts5ExprSynonymRowid(pTerm, bDesc, 0); - if( iRowid==iLast ) continue; - bMatch = 0; - if( fts5ExprSynonymAdvanceto(pTerm, bDesc, &iLast, &rc) ){ - pNode->bEof = 1; - return rc; - } - }else{ - Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - i64 iRowid = sqlite3Fts5IterRowid(pIter); - if( iRowid==iLast ) continue; - bMatch = 0; - if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ - return rc; - } - } - } - } - }while( bMatch==0 ); - - pNode->iRowid = iLast; - pNode->bNomatch = (0==fts5ExprNearTest(&rc, pExpr, pNode)); - - return rc; -} - -/* -** 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. -*/ -static int fts5ExprNearInitAll( - Fts5Expr *pExpr, - Fts5ExprNode *pNode -){ - Fts5ExprNearset *pNear = pNode->pNear; - int i, j; - int rc = SQLITE_OK; - - for(i=0; rc==SQLITE_OK && inPhrase; i++){ - Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - for(j=0; jnTerm; j++){ - Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; - Fts5ExprTerm *p; - int bEof = 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, 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( bEof ){ - pNode->bEof = 1; - return rc; - } - } - } - - return rc; -} - -/* fts5ExprNodeNext() calls fts5ExprNodeNextMatch(). And vice-versa. */ -static int fts5ExprNodeNextMatch(Fts5Expr*, Fts5ExprNode*); - - -/* -** If pExpr is an ASC iterator, this function returns a value with the -** same sign as: -** -** (iLhs - iRhs) -** -** Otherwise, if this is a DESC iterator, the opposite is returned: -** -** (iRhs - iLhs) -*/ -static int fts5RowidCmp( - Fts5Expr *pExpr, - i64 iLhs, - i64 iRhs -){ - assert( pExpr->bDesc==0 || pExpr->bDesc==1 ); - if( pExpr->bDesc==0 ){ - if( iLhs iRhs); + if( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){ + rc = fts5ExprNodeTest_TERM(pExpr, pNode); }else{ - if( iLhs>iRhs ) return -1; - return (iLhs < iRhs); + pNode->bEof = 1; + pNode->bNomatch = 0; } + return rc; } -static void fts5ExprSetEof(Fts5ExprNode *pNode){ +static void fts5ExprNodeTest_OR( + Fts5Expr *pExpr, /* Expression of which pNode is a part */ + Fts5ExprNode *pNode /* Expression node to test */ +){ + Fts5ExprNode *pNext = pNode->apChild[0]; int i; - pNode->bEof = 1; + + for(i=1; inChild; i++){ + Fts5ExprNode *pChild = pNode->apChild[i]; + int cmp = fts5NodeCompare(pExpr, pNext, pChild); + if( cmp>0 || (cmp==0 && pChild->bNomatch==0) ){ + pNext = pChild; + } + } + pNode->iRowid = pNext->iRowid; + pNode->bEof = pNext->bEof; + pNode->bNomatch = pNext->bNomatch; +} + +static int fts5ExprNodeNext_OR( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int i; + i64 iLast = pNode->iRowid; + for(i=0; inChild; i++){ - fts5ExprSetEof(pNode->apChild[i]); - } -} - -static void fts5ExprNodeZeroPoslist(Fts5ExprNode *pNode){ - if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){ - Fts5ExprNearset *pNear = pNode->pNear; - int i; - for(i=0; inPhrase; i++){ - Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - pPhrase->poslist.n = 0; - } - }else{ - int i; - for(i=0; inChild; i++){ - fts5ExprNodeZeroPoslist(pNode->apChild[i]); + Fts5ExprNode *p1 = pNode->apChild[i]; + assert( p1->bEof || fts5RowidCmp(pExpr, p1->iRowid, iLast)>=0 ); + if( p1->bEof==0 ){ + if( (p1->iRowid==iLast) + || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0) + ){ + int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom); + if( rc!=SQLITE_OK ) return rc; + } } } + + fts5ExprNodeTest_OR(pExpr, pNode); + return SQLITE_OK; } - -static int fts5ExprNodeNext(Fts5Expr*, Fts5ExprNode*, int, i64); - /* ** Argument pNode is an FTS5_AND node. */ -static int fts5ExprAndNextRowid( +static int fts5ExprNodeTest_AND( Fts5Expr *pExpr, /* Expression pPhrase belongs to */ Fts5ExprNode *pAnd /* FTS5_AND node to advance */ ){ @@ -170769,15 +181846,11 @@ static int fts5ExprAndNextRowid( bMatch = 1; for(iChild=0; iChildnChild; iChild++){ Fts5ExprNode *pChild = pAnd->apChild[iChild]; - if( 0 && pChild->eType==FTS5_STRING ){ - /* TODO */ - }else{ - int cmp = fts5RowidCmp(pExpr, iLast, pChild->iRowid); - if( cmp>0 ){ - /* Advance pChild until it points to iLast or laster */ - rc = fts5ExprNodeNext(pExpr, pChild, 1, iLast); - if( rc!=SQLITE_OK ) return rc; - } + int cmp = fts5RowidCmp(pExpr, iLast, pChild->iRowid); + 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 the child node is now at EOF, so is the parent AND node. Otherwise, @@ -170807,126 +181880,66 @@ static int fts5ExprAndNextRowid( return SQLITE_OK; } - -/* -** Compare the values currently indicated by the two nodes as follows: -** -** res = (*p1) - (*p2) -** -** Nodes that point to values that come later in the iteration order are -** considered to be larger. Nodes at EOF are the largest of all. -** -** This means that if the iteration order is ASC, then numerically larger -** rowids are considered larger. Or if it is the default DESC, numerically -** smaller rowids are larger. -*/ -static int fts5NodeCompare( - Fts5Expr *pExpr, - Fts5ExprNode *p1, - Fts5ExprNode *p2 -){ - if( p2->bEof ) return -1; - if( p1->bEof ) return +1; - return fts5RowidCmp(pExpr, p1->iRowid, p2->iRowid); -} - -/* -** Advance node iterator pNode, part of expression pExpr. If argument -** bFromValid is zero, then pNode is advanced exactly once. Or, if argument -** bFromValid is non-zero, then pNode is advanced until it is at or past -** rowid value iFrom. Whether "past" means "less than" or "greater than" -** depends on whether this is an ASC or DESC iterator. -*/ -static int fts5ExprNodeNext( +static int fts5ExprNodeNext_AND( Fts5Expr *pExpr, Fts5ExprNode *pNode, int bFromValid, i64 iFrom ){ - int rc = SQLITE_OK; - - if( pNode->bEof==0 ){ - switch( pNode->eType ){ - case FTS5_STRING: { - rc = fts5ExprNearAdvanceFirst(pExpr, pNode, bFromValid, iFrom); - break; - }; - - case FTS5_TERM: { - Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter; - if( bFromValid ){ - rc = sqlite3Fts5IterNextFrom(pIter, iFrom); - }else{ - rc = sqlite3Fts5IterNext(pIter); - } - if( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){ - assert( rc==SQLITE_OK ); - rc = fts5ExprTokenTest(pExpr, pNode); - }else{ - pNode->bEof = 1; - } - return rc; - }; - - case FTS5_AND: { - Fts5ExprNode *pLeft = pNode->apChild[0]; - rc = fts5ExprNodeNext(pExpr, pLeft, bFromValid, iFrom); - break; - } - - case FTS5_OR: { - int i; - i64 iLast = pNode->iRowid; - - for(i=0; rc==SQLITE_OK && inChild; i++){ - Fts5ExprNode *p1 = pNode->apChild[i]; - assert( p1->bEof || fts5RowidCmp(pExpr, p1->iRowid, iLast)>=0 ); - if( p1->bEof==0 ){ - if( (p1->iRowid==iLast) - || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0) - ){ - rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom); - } - } - } - - break; - } - - default: assert( pNode->eType==FTS5_NOT ); { - assert( pNode->nChild==2 ); - rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); - break; - } - } - - if( rc==SQLITE_OK ){ - rc = fts5ExprNodeNextMatch(pExpr, pNode); - } + int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest_AND(pExpr, pNode); } - - /* Assert that if bFromValid was true, either: - ** - ** a) an error occurred, or - ** b) the node is now at EOF, or - ** c) the node is now at or past rowid iFrom. - */ - assert( bFromValid==0 - || rc!=SQLITE_OK /* a */ - || pNode->bEof /* b */ - || pNode->iRowid==iFrom || pExpr->bDesc==(pNode->iRowidapChild[0]; + Fts5ExprNode *p2 = pNode->apChild[1]; + assert( pNode->nChild==2 ); + + while( rc==SQLITE_OK && p1->bEof==0 ){ + int cmp = fts5NodeCompare(pExpr, p1, p2); + if( cmp>0 ){ + rc = fts5ExprNodeNext(pExpr, p2, 1, p1->iRowid); + cmp = fts5NodeCompare(pExpr, p1, p2); + } + assert( rc!=SQLITE_OK || cmp<=0 ); + if( cmp || p2->bNomatch ) break; + rc = fts5ExprNodeNext(pExpr, p1, 0, 0); + } + pNode->bEof = p1->bEof; + pNode->bNomatch = p1->bNomatch; + pNode->iRowid = p1->iRowid; + if( p1->bEof ){ + fts5ExprNodeZeroPoslist(p2); + } + return rc; +} + +static int fts5ExprNodeNext_NOT( + Fts5Expr *pExpr, + Fts5ExprNode *pNode, + int bFromValid, + i64 iFrom +){ + int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); + if( rc==SQLITE_OK ){ + rc = fts5ExprNodeTest_NOT(pExpr, pNode); + } + return rc; +} /* ** If pNode currently points to a match, this function returns SQLITE_OK ** without modifying it. Otherwise, pNode is advanced until it does point ** to a match or EOF is reached. */ -static int fts5ExprNodeNextMatch( +static int fts5ExprNodeTest( Fts5Expr *pExpr, /* Expression of which pNode is a part */ Fts5ExprNode *pNode /* Expression node to test */ ){ @@ -170935,55 +181948,27 @@ static int fts5ExprNodeNextMatch( switch( pNode->eType ){ case FTS5_STRING: { - /* Advance the iterators until they all point to the same rowid */ - rc = fts5ExprNearNextMatch(pExpr, pNode); + rc = fts5ExprNodeTest_STRING(pExpr, pNode); break; } case FTS5_TERM: { - rc = fts5ExprTokenTest(pExpr, pNode); + rc = fts5ExprNodeTest_TERM(pExpr, pNode); break; } case FTS5_AND: { - rc = fts5ExprAndNextRowid(pExpr, pNode); + rc = fts5ExprNodeTest_AND(pExpr, pNode); break; } case FTS5_OR: { - Fts5ExprNode *pNext = pNode->apChild[0]; - int i; - - for(i=1; inChild; i++){ - Fts5ExprNode *pChild = pNode->apChild[i]; - int cmp = fts5NodeCompare(pExpr, pNext, pChild); - if( cmp>0 || (cmp==0 && pChild->bNomatch==0) ){ - pNext = pChild; - } - } - pNode->iRowid = pNext->iRowid; - pNode->bEof = pNext->bEof; - pNode->bNomatch = pNext->bNomatch; + fts5ExprNodeTest_OR(pExpr, pNode); break; } default: assert( pNode->eType==FTS5_NOT ); { - Fts5ExprNode *p1 = pNode->apChild[0]; - Fts5ExprNode *p2 = pNode->apChild[1]; - assert( pNode->nChild==2 ); - - while( rc==SQLITE_OK && p1->bEof==0 ){ - int cmp = fts5NodeCompare(pExpr, p1, p2); - if( cmp>0 ){ - rc = fts5ExprNodeNext(pExpr, p2, 1, p1->iRowid); - cmp = fts5NodeCompare(pExpr, p1, p2); - } - assert( rc!=SQLITE_OK || cmp<=0 ); - if( cmp || p2->bNomatch ) break; - rc = fts5ExprNodeNext(pExpr, p1, 0, 0); - } - pNode->bEof = p1->bEof; - pNode->iRowid = p1->iRowid; + rc = fts5ExprNodeTest_NOT(pExpr, pNode); break; } } @@ -171002,20 +181987,42 @@ static int fts5ExprNodeNextMatch( static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ int rc = SQLITE_OK; pNode->bEof = 0; + pNode->bNomatch = 0; if( Fts5NodeIsString(pNode) ){ /* Initialize all term iterators in the NEAR object. */ rc = fts5ExprNearInitAll(pExpr, pNode); + }else if( pNode->xNext==0 ){ + pNode->bEof = 1; }else{ int i; + int nEof = 0; for(i=0; inChild && rc==SQLITE_OK; i++){ + Fts5ExprNode *pChild = pNode->apChild[i]; rc = fts5ExprNodeFirst(pExpr, pNode->apChild[i]); + assert( pChild->bEof==0 || pChild->bEof==1 ); + nEof += pChild->bEof; } pNode->iRowid = pNode->apChild[0]->iRowid; + + switch( pNode->eType ){ + case FTS5_AND: + if( nEof>0 ) fts5ExprSetEof(pNode); + break; + + case FTS5_OR: + if( pNode->nChild==nEof ) fts5ExprSetEof(pNode); + break; + + default: + assert( pNode->eType==FTS5_NOT ); + pNode->bEof = pNode->apChild[0]->bEof; + break; + } } if( rc==SQLITE_OK ){ - rc = fts5ExprNodeNextMatch(pExpr, pNode); + rc = fts5ExprNodeTest(pExpr, pNode); } return rc; } @@ -171038,22 +182045,22 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ */ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ Fts5ExprNode *pRoot = p->pRoot; - int rc = SQLITE_OK; - if( pRoot ){ - p->pIndex = pIdx; - p->bDesc = bDesc; - rc = fts5ExprNodeFirst(p, pRoot); + int rc; /* Return code */ - /* 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 ){ - rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); - } + p->pIndex = pIdx; + p->bDesc = bDesc; + rc = fts5ExprNodeFirst(p, pRoot); - /* If the iterator is not at a real match, skip forward until it is. */ - while( pRoot->bNomatch && rc==SQLITE_OK && pRoot->bEof==0 ){ - rc = fts5ExprNodeNext(p, pRoot, 0, 0); - } + /* 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 ){ + rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); + } + + /* If the iterator is not at a real match, skip forward until it is. */ + while( pRoot->bNomatch ){ + assert( pRoot->bEof==0 && rc==SQLITE_OK ); + rc = fts5ExprNodeNext(p, pRoot, 0, 0); } return rc; } @@ -171067,9 +182074,11 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){ int rc; Fts5ExprNode *pRoot = p->pRoot; + assert( pRoot->bEof==0 && pRoot->bNomatch==0 ); do { rc = fts5ExprNodeNext(p, pRoot, 0, 0); - }while( pRoot->bNomatch && pRoot->bEof==0 && rc==SQLITE_OK ); + assert( pRoot->bNomatch==0 || (rc==SQLITE_OK && pRoot->bEof==0) ); + }while( pRoot->bNomatch ); if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ pRoot->bEof = 1; } @@ -171077,7 +182086,7 @@ static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){ } static int sqlite3Fts5ExprEof(Fts5Expr *p){ - return (p->pRoot==0 || p->pRoot->bEof); + return p->pRoot->bEof; } static i64 sqlite3Fts5ExprRowid(Fts5Expr *p){ @@ -171102,10 +182111,10 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; sqlite3_free(pTerm->zTerm); sqlite3Fts5IterClose(pTerm->pIter); - for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){ pNext = pSyn->pSynonym; sqlite3Fts5IterClose(pSyn->pIter); + fts5BufferFree((Fts5Buffer*)&pSyn[1]); sqlite3_free(pSyn); } } @@ -171160,6 +182169,21 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( sqlite3Fts5ParseNearsetFree(pNear); sqlite3Fts5ParsePhraseFree(pPhrase); }else{ + if( pRet->nPhrase>0 ){ + Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1]; + assert( pLast==pParse->apPhrase[pParse->nPhrase-2] ); + if( pPhrase->nTerm==0 ){ + fts5ExprPhraseFree(pPhrase); + pRet->nPhrase--; + pParse->nPhrase--; + pPhrase = pLast; + }else if( pLast->nTerm==0 ){ + fts5ExprPhraseFree(pLast); + pParse->apPhrase[pParse->nPhrase-2] = pPhrase; + pParse->nPhrase--; + pRet->nPhrase--; + } + } pRet->apPhrase[pRet->nPhrase++] = pPhrase; } return pRet; @@ -171187,19 +182211,21 @@ static int fts5ParseTokenize( TokenCtx *pCtx = (TokenCtx*)pContext; Fts5ExprPhrase *pPhrase = pCtx->pPhrase; + UNUSED_PARAM2(iUnused1, iUnused2); + /* If an error has already occurred, this is a no-op */ if( pCtx->rc!=SQLITE_OK ) return pCtx->rc; + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; - assert( pPhrase==0 || pPhrase->nTerm>0 ); - if( pPhrase && (tflags & FTS5_TOKEN_COLOCATED) ){ + if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){ Fts5ExprTerm *pSyn; - int nByte = sizeof(Fts5ExprTerm) + nToken+1; + int nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1; pSyn = (Fts5ExprTerm*)sqlite3_malloc(nByte); if( pSyn==0 ){ rc = SQLITE_NOMEM; }else{ memset(pSyn, 0, nByte); - pSyn->zTerm = (char*)&pSyn[1]; + pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); memcpy(pSyn->zTerm, pToken, nToken); pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; @@ -171284,7 +182310,7 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_QUERY : 0); int n; sqlite3Fts5Dequote(z); - n = strlen(z); + n = (int)strlen(z); rc = sqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize); } sqlite3_free(z); @@ -171292,7 +182318,7 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( pParse->rc = rc; fts5ExprPhraseFree(sCtx.pPhrase); sCtx.pPhrase = 0; - }else if( sCtx.pPhrase ){ + }else{ if( pAppend==0 ){ if( (pParse->nPhrase % 8)==0 ){ @@ -171309,9 +182335,14 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( pParse->nPhrase++; } + 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)); + }else if( sCtx.pPhrase->nTerm ){ + sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix; + } pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase; - assert( sCtx.pPhrase->nTerm>0 ); - sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix; } return sCtx.pPhrase; @@ -171322,7 +182353,6 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( ** expression passed as the second argument. */ static int sqlite3Fts5ExprClonePhrase( - Fts5Config *pConfig, Fts5Expr *pExpr, int iPhrase, Fts5Expr **ppNew @@ -171330,14 +182360,10 @@ static int sqlite3Fts5ExprClonePhrase( int rc = SQLITE_OK; /* Return code */ Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ int i; /* Used to iterate through phrase terms */ - Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ - TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ - pOrig = pExpr->apExprPhrase[iPhrase]; - pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); if( rc==SQLITE_OK ){ pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, @@ -171351,13 +182377,25 @@ static int sqlite3Fts5ExprClonePhrase( pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)); } + if( rc==SQLITE_OK ){ + Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; + if( pColsetOrig ){ + int nByte = sizeof(Fts5Colset) + pColsetOrig->nCol * sizeof(int); + Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte); + if( pColset ){ + memcpy(pColset, pColsetOrig, nByte); + } + pNew->pRoot->pNear->pColset = pColset; + } + } for(i=0; rc==SQLITE_OK && inTerm; i++){ int tflags = 0; Fts5ExprTerm *p; for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ const char *zTerm = p->zTerm; - rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, strlen(zTerm), 0, 0); + rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), + 0, 0); tflags = FTS5_TOKEN_COLOCATED; } if( rc==SQLITE_OK ){ @@ -171368,6 +182406,7 @@ static int sqlite3Fts5ExprClonePhrase( if( rc==SQLITE_OK ){ /* All the allocations succeeded. Put the expression object together. */ pNew->pIndex = pExpr->pIndex; + pNew->pConfig = pExpr->pConfig; pNew->nPhrase = 1; pNew->apExprPhrase[0] = sCtx.pPhrase; pNew->pRoot->pNear->apPhrase[0] = sCtx.pPhrase; @@ -171376,8 +182415,10 @@ static int sqlite3Fts5ExprClonePhrase( if( pOrig->nTerm==1 && pOrig->aTerm[0].pSynonym==0 ){ pNew->pRoot->eType = FTS5_TERM; + pNew->pRoot->xNext = fts5ExprNodeNext_TERM; }else{ pNew->pRoot->eType = FTS5_STRING; + pNew->pRoot->xNext = fts5ExprNodeNext_STRING; } }else{ sqlite3Fts5ExprFree(pNew); @@ -171408,23 +182449,25 @@ static void sqlite3Fts5ParseSetDistance( Fts5ExprNearset *pNear, Fts5Token *p ){ - int nNear = 0; - int i; - if( p->n ){ - for(i=0; in; i++){ - char c = (char)p->p[i]; - if( c<'0' || c>'9' ){ - sqlite3Fts5ParseError( - pParse, "expected integer, got \"%.*s\"", p->n, p->p - ); - return; + if( pNear ){ + int nNear = 0; + int i; + if( p->n ){ + for(i=0; in; i++){ + char c = (char)p->p[i]; + if( c<'0' || c>'9' ){ + sqlite3Fts5ParseError( + pParse, "expected integer, got \"%.*s\"", p->n, p->p + ); + return; + } + nNear = nNear * 10 + (p->p[i] - '0'); } - nNear = nNear * 10 + (p->p[i] - '0'); + }else{ + nNear = FTS5_DEFAULT_NEARDIST; } - }else{ - nNear = FTS5_DEFAULT_NEARDIST; + pNear->nNear = nNear; } - pNear->nNear = nNear; } /* @@ -171509,6 +182552,15 @@ static void sqlite3Fts5ParseSetColset( Fts5ExprNearset *pNear, Fts5Colset *pColset ){ + if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ + pParse->rc = SQLITE_ERROR; + pParse->zErr = sqlite3_mprintf( + "fts5: column queries are not supported (detail=none)" + ); + sqlite3_free(pColset); + return; + } + if( pNear ){ pNear->pColset = pColset; }else{ @@ -171516,6 +182568,38 @@ static void sqlite3Fts5ParseSetColset( } } +static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ + switch( pNode->eType ){ + case FTS5_STRING: { + Fts5ExprNearset *pNear = pNode->pNear; + if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 + && pNear->apPhrase[0]->aTerm[0].pSynonym==0 + ){ + pNode->eType = FTS5_TERM; + pNode->xNext = fts5ExprNodeNext_TERM; + }else{ + pNode->xNext = fts5ExprNodeNext_STRING; + } + break; + }; + + case FTS5_OR: { + pNode->xNext = fts5ExprNodeNext_OR; + break; + }; + + case FTS5_AND: { + pNode->xNext = fts5ExprNodeNext_AND; + break; + }; + + default: assert( pNode->eType==FTS5_NOT ); { + pNode->xNext = fts5ExprNodeNext_NOT; + break; + }; + } +} + static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){ int nByte = sizeof(Fts5ExprNode*) * pSub->nChild; @@ -171565,17 +182649,31 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( if( pRet ){ pRet->eType = eType; pRet->pNear = pNear; + fts5ExprAssignXNext(pRet); if( eType==FTS5_STRING ){ int iPhrase; for(iPhrase=0; iPhrasenPhrase; iPhrase++){ pNear->apPhrase[iPhrase]->pNode = pRet; + if( pNear->apPhrase[iPhrase]->nTerm==0 ){ + pRet->xNext = 0; + pRet->eType = FTS5_EOF; + } } - if( pNear->nPhrase==1 - && pNear->apPhrase[0]->nTerm==1 - && pNear->apPhrase[0]->aTerm[0].pSynonym==0 + + if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL + && (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm>1) ){ - pRet->eType = FTS5_TERM; + 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; } + }else{ fts5ExprAddChildren(pRet, pLeft); fts5ExprAddChildren(pRet, pRight); @@ -171592,6 +182690,70 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( return pRet; } +static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( + Fts5Parse *pParse, /* Parse context */ + Fts5ExprNode *pLeft, /* Left hand child expression */ + Fts5ExprNode *pRight /* Right hand child expression */ +){ + Fts5ExprNode *pRet = 0; + Fts5ExprNode *pPrev; + + if( pParse->rc ){ + sqlite3Fts5ParseNodeFree(pLeft); + sqlite3Fts5ParseNodeFree(pRight); + }else{ + + assert( pLeft->eType==FTS5_STRING + || pLeft->eType==FTS5_TERM + || pLeft->eType==FTS5_EOF + || pLeft->eType==FTS5_AND + ); + assert( pRight->eType==FTS5_STRING + || pRight->eType==FTS5_TERM + || pRight->eType==FTS5_EOF + ); + + if( pLeft->eType==FTS5_AND ){ + pPrev = pLeft->apChild[pLeft->nChild-1]; + }else{ + pPrev = pLeft; + } + assert( pPrev->eType==FTS5_STRING + || pPrev->eType==FTS5_TERM + || pPrev->eType==FTS5_EOF + ); + + if( pRight->eType==FTS5_EOF ){ + assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] ); + sqlite3Fts5ParseNodeFree(pRight); + pRet = pLeft; + pParse->nPhrase--; + } + else if( pPrev->eType==FTS5_EOF ){ + Fts5ExprPhrase **ap; + + if( pPrev==pLeft ){ + pRet = pRight; + }else{ + pLeft->apChild[pLeft->nChild-1] = pRight; + pRet = pLeft; + } + + ap = &pParse->apPhrase[pParse->nPhrase-1-pRight->pNear->nPhrase]; + assert( ap[0]==pPrev->pNear->apPhrase[0] ); + memmove(ap, &ap[1], sizeof(Fts5ExprPhrase*)*pRight->pNear->nPhrase); + pParse->nPhrase--; + + sqlite3Fts5ParseNodeFree(pPrev); + } + else{ + pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0); + } + } + + return pRet; +} + static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ int nByte = 0; Fts5ExprTerm *p; @@ -171599,7 +182761,7 @@ static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ /* Determine the maximum amount of space required. */ for(p=pTerm; p; p=p->pSynonym){ - nByte += strlen(pTerm->zTerm) * 2 + 3 + 2; + nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2; } zQuoted = sqlite3_malloc(nByte); @@ -171688,6 +182850,9 @@ static char *fts5ExprPrintTcl( for(iTerm=0; zRet && iTermnTerm; iTerm++){ char *zTerm = pPhrase->aTerm[iTerm].zTerm; zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm); + if( pPhrase->aTerm[iTerm].bPrefix ){ + zRet = fts5PrintfAppend(zRet, "*"); + } } if( zRet ) zRet = fts5PrintfAppend(zRet, "}"); @@ -171723,6 +182888,9 @@ static char *fts5ExprPrintTcl( static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ char *zRet = 0; + if( pExpr->eType==0 ){ + return sqlite3_mprintf("\"\""); + }else if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){ Fts5ExprNearset *pNear = pExpr->pNear; int i; @@ -171783,7 +182951,7 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ zRet = 0; }else{ int e = pExpr->apChild[i]->eType; - int b = (e!=FTS5_STRING && e!=FTS5_TERM); + int b = (e!=FTS5_STRING && e!=FTS5_TERM && e!=FTS5_EOF); zRet = fts5PrintfAppend(zRet, "%s%s%z%s", (i==0 ? "" : zOp), (b?"(":""), z, (b?")":"") @@ -171855,7 +183023,7 @@ static void fts5ExprFunction( } if( rc==SQLITE_OK ){ char *zText; - if( pExpr->pRoot==0 ){ + if( pExpr->pRoot->xNext==0 ){ zText = sqlite3_mprintf(""); }else if( bTcl ){ zText = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRoot); @@ -171955,7 +183123,7 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ int rc = SQLITE_OK; void *pCtx = (void*)pGlobal; - for(i=0; rc==SQLITE_OK && i<(sizeof(aFunc) / sizeof(aFunc[0])); i++){ + for(i=0; rc==SQLITE_OK && iz, -1, SQLITE_UTF8, pCtx, p->x, 0, 0); } @@ -172001,6 +183169,226 @@ static int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){ return nRet; } +struct Fts5PoslistPopulator { + Fts5PoslistWriter writer; + int bOk; /* True if ok to populate */ + int bMiss; +}; + +static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ + Fts5PoslistPopulator *pRet; + pRet = sqlite3_malloc(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); + if( pRet ){ + int i; + memset(pRet, 0, sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); + for(i=0; inPhrase; i++){ + Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist; + Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; + assert( pExpr->apExprPhrase[i]->nTerm==1 ); + if( bLive && + (pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof) + ){ + pRet[i].bMiss = 1; + }else{ + pBuf->n = 0; + } + } + } + return pRet; +} + +struct Fts5ExprCtx { + Fts5Expr *pExpr; + Fts5PoslistPopulator *aPopulator; + i64 iOff; +}; +typedef struct Fts5ExprCtx Fts5ExprCtx; + +/* +** TODO: Make this more efficient! +*/ +static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){ + int i; + for(i=0; inCol; i++){ + if( pColset->aiCol[i]==iCol ) return 1; + } + return 0; +} + +static int fts5ExprPopulatePoslistsCb( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iUnused1, /* Byte offset of token within input text */ + int iUnused2 /* Byte offset of end of token within input text */ +){ + Fts5ExprCtx *p = (Fts5ExprCtx*)pCtx; + Fts5Expr *pExpr = p->pExpr; + int i; + + UNUSED_PARAM2(iUnused1, iUnused2); + + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++; + for(i=0; inPhrase; i++){ + Fts5ExprTerm *pTerm; + if( p->aPopulator[i].bOk==0 ) continue; + for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ + int nTerm = (int)strlen(pTerm->zTerm); + if( (nTerm==nToken || (nTermbPrefix)) + && memcmp(pTerm->zTerm, pToken, nTerm)==0 + ){ + int rc = sqlite3Fts5PoslistWriterAppend( + &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff + ); + if( rc ) return rc; + break; + } + } + } + return SQLITE_OK; +} + +static int sqlite3Fts5ExprPopulatePoslists( + Fts5Config *pConfig, + Fts5Expr *pExpr, + Fts5PoslistPopulator *aPopulator, + int iCol, + const char *z, int n +){ + int i; + Fts5ExprCtx sCtx; + sCtx.pExpr = pExpr; + sCtx.aPopulator = aPopulator; + sCtx.iOff = (((i64)iCol) << 32) - 1; + + for(i=0; inPhrase; i++){ + Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; + Fts5Colset *pColset = pNode->pNear->pColset; + if( (pColset && 0==fts5ExprColsetTest(pColset, iCol)) + || aPopulator[i].bMiss + ){ + aPopulator[i].bOk = 0; + }else{ + aPopulator[i].bOk = 1; + } + } + + return sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, z, n, (void*)&sCtx, fts5ExprPopulatePoslistsCb + ); +} + +static void fts5ExprClearPoslists(Fts5ExprNode *pNode){ + if( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING ){ + pNode->pNear->apPhrase[0]->poslist.n = 0; + }else{ + int i; + for(i=0; inChild; i++){ + fts5ExprClearPoslists(pNode->apChild[i]); + } + } +} + +static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){ + pNode->iRowid = iRowid; + pNode->bEof = 0; + switch( pNode->eType ){ + case FTS5_TERM: + case FTS5_STRING: + return (pNode->pNear->apPhrase[0]->poslist.n>0); + + case FTS5_AND: { + int i; + for(i=0; inChild; i++){ + if( fts5ExprCheckPoslists(pNode->apChild[i], iRowid)==0 ){ + fts5ExprClearPoslists(pNode); + return 0; + } + } + break; + } + + case FTS5_OR: { + int i; + int bRet = 0; + for(i=0; inChild; i++){ + if( fts5ExprCheckPoslists(pNode->apChild[i], iRowid) ){ + bRet = 1; + } + } + return bRet; + } + + default: { + assert( pNode->eType==FTS5_NOT ); + if( 0==fts5ExprCheckPoslists(pNode->apChild[0], iRowid) + || 0!=fts5ExprCheckPoslists(pNode->apChild[1], iRowid) + ){ + fts5ExprClearPoslists(pNode); + return 0; + } + break; + } + } + return 1; +} + +static void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){ + fts5ExprCheckPoslists(pExpr->pRoot, iRowid); +} + +static void fts5ExprClearEof(Fts5ExprNode *pNode){ + int i; + for(i=0; inChild; i++){ + fts5ExprClearEof(pNode->apChild[i]); + } + pNode->bEof = 0; +} +static void sqlite3Fts5ExprClearEof(Fts5Expr *pExpr){ + fts5ExprClearEof(pExpr->pRoot); +} + +/* +** This function is only called for detail=columns tables. +*/ +static int sqlite3Fts5ExprPhraseCollist( + Fts5Expr *pExpr, + int iPhrase, + const u8 **ppCollist, + int *pnCollist +){ + Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase]; + Fts5ExprNode *pNode = pPhrase->pNode; + int rc = SQLITE_OK; + + assert( iPhrase>=0 && iPhrasenPhrase ); + assert( pExpr->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + + if( pNode->bEof==0 + && pNode->iRowid==pExpr->pRoot->iRowid + && pPhrase->poslist.n>0 + ){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[0]; + if( pTerm->pSynonym ){ + Fts5Buffer *pBuf = (Fts5Buffer*)&pTerm->pSynonym[1]; + rc = fts5ExprSynonymList( + pTerm, pNode->iRowid, pBuf, (u8**)ppCollist, pnCollist + ); + }else{ + *ppCollist = pPhrase->aTerm[0].pIter->pData; + *pnCollist = pPhrase->aTerm[0].pIter->nData; + } + }else{ + *ppCollist = 0; + *pnCollist = 0; + } + + return rc; +} + + /* ** 2014 August 11 ** @@ -172017,6 +183405,7 @@ static int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){ +/* #include "fts5Int.h" */ typedef struct Fts5HashEntry Fts5HashEntry; @@ -172028,6 +183417,7 @@ typedef struct Fts5HashEntry Fts5HashEntry; struct Fts5Hash { + int eDetail; /* Copy of Fts5Config.eDetail */ int *pnByte; /* Pointer to bytes counter */ int nEntry; /* Number of entries currently in hash */ int nSlot; /* Size of aSlot[] array */ @@ -172063,9 +183453,10 @@ 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 */ u8 bDel; /* Set delete-flag @ iSzPoslist */ - - int iCol; /* Column of last value written */ + 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 */ @@ -172081,7 +183472,7 @@ struct Fts5HashEntry { /* ** Allocate a new hash table. */ -static int sqlite3Fts5HashNew(Fts5Hash **ppNew, int *pnByte){ +static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){ int rc = SQLITE_OK; Fts5Hash *pNew; @@ -172092,6 +183483,7 @@ static int sqlite3Fts5HashNew(Fts5Hash **ppNew, int *pnByte){ int nByte; memset(pNew, 0, sizeof(Fts5Hash)); pNew->pnByte = pnByte; + pNew->eDetail = pConfig->eDetail; pNew->nSlot = 1024; nByte = sizeof(Fts5HashEntry*) * pNew->nSlot; @@ -172172,7 +183564,7 @@ static int fts5HashResize(Fts5Hash *pHash){ int iHash; Fts5HashEntry *p = apOld[i]; apOld[i] = p->pHashNext; - iHash = fts5HashKey(nNew, (u8*)p->zKey, strlen(p->zKey)); + iHash = fts5HashKey(nNew, (u8*)p->zKey, (int)strlen(p->zKey)); p->pHashNext = apNew[iHash]; apNew[iHash] = p; } @@ -172184,26 +183576,46 @@ static int fts5HashResize(Fts5Hash *pHash){ return SQLITE_OK; } -static void fts5HashAddPoslistSize(Fts5HashEntry *p){ +static void fts5HashAddPoslistSize(Fts5Hash *pHash, Fts5HashEntry *p){ if( p->iSzPoslist ){ u8 *pPtr = (u8*)p; - int nSz = (p->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] = nPos; + if( pHash->eDetail==FTS5_DETAIL_NONE ){ + assert( p->nData==p->iSzPoslist ); + if( p->bDel ){ + pPtr[p->nData++] = 0x00; + if( p->bContent ){ + pPtr[p->nData++] = 0x00; + } + } }else{ - int nByte = sqlite3Fts5GetVarintLen((u32)nPos); - memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1], nSz); - sqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos); - p->nData += (nByte-1); + int nSz = (p->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); + memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1], nSz); + sqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos); + p->nData += (nByte-1); + } } - p->bDel = 0; + p->iSzPoslist = 0; + p->bDel = 0; + p->bContent = 0; } } +/* +** Add an entry to the in-memory hash table. The key is the concatenation +** of bByte and (pToken/nToken). The value is (iRowid/iCol/iPos). +** +** (bByte || pToken) -> (iRowid,iCol,iPos) +** +** Or, if iCol is negative, then the value is a delete marker. +*/ static int sqlite3Fts5HashWrite( Fts5Hash *pHash, i64 iRowid, /* Rowid for this entry */ @@ -172216,13 +183628,16 @@ static int sqlite3Fts5HashWrite( Fts5HashEntry *p; u8 *pPtr; int nIncr = 0; /* Amount to increment (*pHash->pnByte) by */ + int bNew; /* If non-delete entry should be written */ + + bNew = (pHash->eDetail==FTS5_DETAIL_FULL); /* 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 + && p->nKey==nToken && memcmp(&p->zKey[1], pToken, nToken)==0 - && p->zKey[nToken+1]==0 ){ break; } @@ -172230,15 +183645,18 @@ 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; if( nByte<128 ) nByte = 128; + /* Grow the Fts5Hash.aSlot[] array if necessary. */ if( (pHash->nEntry*2)>=pHash->nSlot ){ int rc = fts5HashResize(pHash); if( rc!=SQLITE_OK ) return rc; iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); } + /* Allocate new Fts5HashEntry and add it to the hash table. */ p = (Fts5HashEntry*)sqlite3_malloc(nByte); if( !p ) return SQLITE_NOMEM; memset(p, 0, FTS5_HASHENTRYSIZE); @@ -172246,72 +183664,98 @@ static int sqlite3Fts5HashWrite( p->zKey[0] = bByte; memcpy(&p->zKey[1], pToken, nToken); assert( iHash==fts5HashKey(pHash->nSlot, (u8*)p->zKey, nToken+1) ); + p->nKey = nToken; p->zKey[nToken+1] = '\0'; p->nData = nToken+1 + 1 + FTS5_HASHENTRYSIZE; - p->nData += sqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid); - p->iSzPoslist = p->nData; - p->nData += 1; - p->iRowid = iRowid; p->pHashNext = pHash->aSlot[iHash]; pHash->aSlot[iHash] = p; pHash->nEntry++; - nIncr += p->nData; - } - /* Check there is enough space to append a new entry. Worst case scenario - ** is: - ** - ** + 9 bytes for a new rowid, - ** + 4 byte reserved for the "poslist size" varint. - ** + 1 byte for a "new column" byte, - ** + 3 bytes for a new column number (16-bit max) as a varint, - ** + 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; - Fts5HashEntry *pNew; - Fts5HashEntry **pp; - pNew = (Fts5HashEntry*)sqlite3_realloc(p, nNew); - if( pNew==0 ) return SQLITE_NOMEM; - pNew->nAlloc = nNew; - for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext); - *pp = pNew; - p = pNew; + /* Add the first rowid field to the hash-entry */ + p->nData += sqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid); + p->iRowid = iRowid; + + p->iSzPoslist = p->nData; + if( pHash->eDetail!=FTS5_DETAIL_NONE ){ + p->nData += 1; + p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1); + } + + nIncr += p->nData; + }else{ + + /* Appending to an existing hash-entry. Check that there is enough + ** space to append the largest possible new entry. Worst case scenario + ** is: + ** + ** + 9 bytes for a new rowid, + ** + 4 byte reserved for the "poslist size" varint. + ** + 1 byte for a "new column" byte, + ** + 3 bytes for a new column number (16-bit max) as a varint, + ** + 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; + Fts5HashEntry *pNew; + Fts5HashEntry **pp; + pNew = (Fts5HashEntry*)sqlite3_realloc(p, nNew); + if( pNew==0 ) return SQLITE_NOMEM; + pNew->nAlloc = nNew; + for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext); + *pp = pNew; + p = pNew; + } + nIncr -= p->nData; } + assert( (p->nAlloc - p->nData) >= (9 + 4 + 1 + 3 + 5) ); + pPtr = (u8*)p; - nIncr -= p->nData; /* 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(p); + fts5HashAddPoslistSize(pHash, p); p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iRowid - p->iRowid); - p->iSzPoslist = p->nData; - p->nData += 1; - p->iCol = 0; - p->iPos = 0; p->iRowid = iRowid; + bNew = 1; + p->iSzPoslist = p->nData; + if( pHash->eDetail!=FTS5_DETAIL_NONE ){ + p->nData += 1; + p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1); + p->iPos = 0; + } } if( iCol>=0 ){ - /* Append a new column value, if necessary */ - assert( iCol>=p->iCol ); - if( iCol!=p->iCol ){ - pPtr[p->nData++] = 0x01; - p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol); - p->iCol = iCol; - p->iPos = 0; - } + if( pHash->eDetail==FTS5_DETAIL_NONE ){ + p->bContent = 1; + }else{ + /* Append a new column value, if necessary */ + assert( iCol>=p->iCol ); + if( iCol!=p->iCol ){ + if( pHash->eDetail==FTS5_DETAIL_FULL ){ + pPtr[p->nData++] = 0x01; + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol); + p->iCol = (i16)iCol; + p->iPos = 0; + }else{ + bNew = 1; + p->iCol = (i16)(iPos = iCol); + } + } - /* Append the new position offset */ - p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2); - p->iPos = iPos; + /* Append the new position offset, if necessary */ + if( bNew ){ + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2); + p->iPos = iPos; + } + } }else{ /* This is a delete. Set the delete flag. */ p->bDel = 1; } - nIncr += p->nData; + nIncr += p->nData; *pHash->pnByte += nIncr; return SQLITE_OK; } @@ -172425,7 +183869,7 @@ static int sqlite3Fts5HashQuery( } if( p ){ - fts5HashAddPoslistSize(p); + fts5HashAddPoslistSize(pHash, p); *ppDoclist = (const u8*)&p->zKey[nTerm+1]; *pnDoclist = p->nData - (FTS5_HASHENTRYSIZE + nTerm + 1); }else{ @@ -172460,8 +183904,8 @@ static void sqlite3Fts5HashScanEntry( ){ Fts5HashEntry *p; if( (p = pHash->pScan) ){ - int nTerm = strlen(p->zKey); - fts5HashAddPoslistSize(p); + 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); @@ -172492,6 +183936,7 @@ static void sqlite3Fts5HashScanEntry( */ +/* #include "fts5Int.h" */ /* ** Overview: @@ -172735,6 +184180,7 @@ typedef struct Fts5Data Fts5Data; typedef struct Fts5DlidxIter Fts5DlidxIter; typedef struct Fts5DlidxLvl Fts5DlidxLvl; typedef struct Fts5DlidxWriter Fts5DlidxWriter; +typedef struct Fts5Iter Fts5Iter; typedef struct Fts5PageWriter Fts5PageWriter; typedef struct Fts5SegIter Fts5SegIter; typedef struct Fts5DoclistIter Fts5DoclistIter; @@ -172762,7 +184208,6 @@ struct Fts5Index { ** in-memory hash tables before they are flushed to disk. */ Fts5Hash *pHash; /* Hash table for in-memory data */ - int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ @@ -172778,6 +184223,10 @@ struct Fts5Index { sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */ sqlite3_stmt *pIdxSelect; int nRead; /* Total number of blocks read */ + + sqlite3_stmt *pDataVersion; + i64 iStructVersion; /* data_version when pStruct read */ + Fts5Structure *pStruct; /* Current db structure (or NULL) */ }; struct Fts5DoclistIter { @@ -172848,26 +184297,6 @@ struct Fts5SegWriter { int iBtPage; /* Page number corresponding to btterm */ }; -/* -** Object for iterating through the merged results of one or more segments, -** visiting each term/rowid pair in the merged data. -** -** nSeg is always a power of two greater than or equal to the number of -** segments that this object is merging data from. Both the aSeg[] and -** aFirst[] arrays are sized at nSeg entries. The aSeg[] array is padded -** with zeroed objects - these are handled as if they were iterators opened -** on empty segments. -** -** The results of comparing segments aSeg[N] and aSeg[N+1], where N is an -** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the -** comparison in this context is the index of the iterator that currently -** points to the smaller term/rowid combination. Iterators at EOF are -** considered to be greater than all other iterators. -** -** aFirst[1] contains the index in aSeg[] of the iterator that points to -** the smallest key overall. aFirst[0] is unused. -*/ - typedef struct Fts5CResult Fts5CResult; struct Fts5CResult { u16 iFirst; /* aSeg[] index of firstest iterator */ @@ -172928,6 +184357,9 @@ struct Fts5SegIter { Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ int iLeafOffset; /* Byte offset within current leaf */ + /* Next method */ + void (*xNext)(Fts5Index*, Fts5SegIter*, int*); + /* The page and offset from which the current term was read. The offset ** is the offset of the first rowid in the current doclist. */ int iTermLeafPgno; @@ -172947,7 +184379,7 @@ struct Fts5SegIter { Fts5Buffer term; /* Current term */ i64 iRowid; /* Current rowid */ int nPos; /* Number of bytes in current position list */ - int bDel; /* True if the delete flag is set */ + u8 bDel; /* True if the delete flag is set */ }; /* @@ -172961,7 +184393,6 @@ struct Fts5SegIter { #define FTS5_SEGITER_ONETERM 0x01 #define FTS5_SEGITER_REVERSE 0x02 - /* ** Argument is a pointer to an Fts5Data structure that contains a leaf ** page. This macro evaluates to true if the leaf contains no terms, or @@ -172974,20 +184405,42 @@ struct Fts5SegIter { #define fts5LeafFirstRowidOff(x) (fts5GetU16((x)->p)) /* +** Object for iterating through the merged results of one or more segments, +** visiting each term/rowid pair in the merged data. +** +** nSeg is always a power of two greater than or equal to the number of +** segments that this object is merging data from. Both the aSeg[] and +** aFirst[] arrays are sized at nSeg entries. The aSeg[] array is padded +** with zeroed objects - these are handled as if they were iterators opened +** on empty segments. +** +** The results of comparing segments aSeg[N] and aSeg[N+1], where N is an +** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the +** comparison in this context is the index of the iterator that currently +** points to the smaller term/rowid combination. Iterators at EOF are +** considered to be greater than all other iterators. +** +** aFirst[1] contains the index in aSeg[] of the iterator that points to +** the smallest key overall. aFirst[0] is unused. +** ** poslist: ** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. ** There is no way to tell if this is populated or not. */ -struct Fts5IndexIter { +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 */ + + /* Invoked to set output variables. */ + void (*xSetOutputs)(Fts5Iter*, Fts5SegIter*); int nSeg; /* Size of aSeg[] array */ int bRev; /* True to iterate in reverse order */ u8 bSkipEmpty; /* True to skip deleted entries */ - u8 bEof; /* True at EOF */ - u8 bFiltered; /* True if column-filter already applied */ i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */ Fts5CResult *aFirst; /* Current merge state (see above) */ @@ -173077,17 +184530,6 @@ static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ return (res==0 ? (pLeft->n - pRight->n) : res); } -#ifdef SQLITE_DEBUG -static int fts5BlobCompare( - const u8 *pLeft, int nLeft, - const u8 *pRight, int nRight -){ - int nCmp = MIN(nLeft, nRight); - int res = memcmp(pLeft, pRight, nCmp); - return (res==0 ? (nLeft - nRight) : res); -} -#endif - static int fts5LeafFirstTermOff(Fts5Data *pLeaf){ int ret; fts5GetVarint32(&pLeaf->p[pLeaf->szLeaf], ret); @@ -173179,6 +184621,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ return pRet; } + /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -173346,28 +184789,37 @@ static int fts5StructureDecode( for(iLvl=0; rc==SQLITE_OK && iLvlaLevel[iLvl]; - int nTotal; + int nTotal = 0; int iSeg; - i += fts5GetVarint32(&pData[i], pLvl->nMerge); - i += fts5GetVarint32(&pData[i], nTotal); - assert( nTotal>=pLvl->nMerge ); - pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, - nTotal * sizeof(Fts5StructureSegment) - ); + if( i>=nData ){ + rc = FTS5_CORRUPT; + }else{ + i += fts5GetVarint32(&pData[i], pLvl->nMerge); + i += fts5GetVarint32(&pData[i], nTotal); + assert( nTotal>=pLvl->nMerge ); + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, + nTotal * sizeof(Fts5StructureSegment) + ); + } if( rc==SQLITE_OK ){ pLvl->nSeg = nTotal; for(iSeg=0; iSeg=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); } - }else{ - fts5StructureRelease(pRet); - pRet = 0; } } + if( rc!=SQLITE_OK ){ + fts5StructureRelease(pRet); + pRet = 0; + } } *ppOut = pRet; @@ -173430,6 +184882,50 @@ static void fts5StructureExtendLevel( } } +static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){ + Fts5Structure *pRet = 0; + Fts5Config *pConfig = p->pConfig; + int iCookie; /* Configuration cookie */ + Fts5Data *pData; + + pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID); + if( p->rc==SQLITE_OK ){ + /* 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); + } + fts5DataRelease(pData); + if( p->rc!=SQLITE_OK ){ + fts5StructureRelease(pRet); + pRet = 0; + } + } + + return pRet; +} + +static i64 fts5IndexDataVersion(Fts5Index *p){ + i64 iVersion = 0; + + if( p->rc==SQLITE_OK ){ + if( p->pDataVersion==0 ){ + p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion, + sqlite3_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); + } + p->rc = sqlite3_reset(p->pDataVersion); + } + + return iVersion; +} + /* ** Read, deserialize and return the structure record. ** @@ -173442,26 +184938,49 @@ static void fts5StructureExtendLevel( ** is called, it is a no-op. */ static Fts5Structure *fts5StructureRead(Fts5Index *p){ - Fts5Config *pConfig = p->pConfig; - Fts5Structure *pRet = 0; /* Object to return */ - int iCookie; /* Configuration cookie */ - Fts5Data *pData; - pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID); - if( p->rc ) return 0; - /* 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->pStruct==0 ){ + p->iStructVersion = fts5IndexDataVersion(p); + if( p->rc==SQLITE_OK ){ + p->pStruct = fts5StructureReadUncached(p); + } } - fts5DataRelease(pData); - if( p->rc!=SQLITE_OK ){ - fts5StructureRelease(pRet); - pRet = 0; +#if 0 + else{ + Fts5Structure *pTest = fts5StructureReadUncached(p); + if( pTest ){ + int i, j; + assert_nc( p->pStruct->nSegment==pTest->nSegment ); + assert_nc( p->pStruct->nLevel==pTest->nLevel ); + for(i=0; inLevel; i++){ + assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge ); + assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg ); + for(j=0; jaLevel[i].nSeg; j++){ + Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j]; + Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j]; + assert_nc( p1->iSegid==p2->iSegid ); + assert_nc( p1->pgnoFirst==p2->pgnoFirst ); + assert_nc( p1->pgnoLast==p2->pgnoLast ); + } + } + fts5StructureRelease(pTest); + } + } +#endif + + if( p->rc!=SQLITE_OK ) return 0; + assert( p->iStructVersion!=0 ); + assert( p->pStruct!=0 ); + fts5StructureRef(p->pStruct); + return p->pStruct; +} + +static void fts5StructureInvalidate(Fts5Index *p){ + if( p->pStruct ){ + fts5StructureRelease(p->pStruct); + p->pStruct = 0; } - return pRet; } /* @@ -173482,6 +185001,18 @@ static int fts5StructureCountSegments(Fts5Structure *pStruct){ } #endif +#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \ + assert( (pBuf)->nSpace>=((pBuf)->n+nBlob) ); \ + memcpy(&(pBuf)->p[(pBuf)->n], pBlob, nBlob); \ + (pBuf)->n += nBlob; \ +} + +#define fts5BufferSafeAppendVarint(pBuf, iVal) { \ + (pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \ + assert( (pBuf)->nSpace>=(pBuf)->n ); \ +} + + /* ** Serialize and store the "structure" record. ** @@ -173500,11 +185031,14 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ /* Append the current configuration cookie */ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; - fts5BufferAppend32(&p->rc, &buf, iCookie); - fts5BufferAppendVarint(&p->rc, &buf, pStruct->nLevel); - fts5BufferAppendVarint(&p->rc, &buf, pStruct->nSegment); - fts5BufferAppendVarint(&p->rc, &buf, (i64)pStruct->nWriteCounter); + if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){ + sqlite3Fts5Put32(buf.p, iCookie); + buf.n = 4; + fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); + fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); + fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); + } for(iLvl=0; iLvlnLevel; iLvl++){ int iSeg; /* Used to iterate through segments */ @@ -173954,11 +185488,28 @@ static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ if( p->rc==SQLITE_OK ){ int iOff = pIter->iLeafOffset; /* Offset to read at */ - int nSz; ASSERT_SZLEAF_OK(pIter->pLeaf); - fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz); - pIter->bDel = (nSz & 0x0001); - pIter->nPos = nSz>>1; + if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + int iEod = MIN(pIter->iEndofDoclist, pIter->pLeaf->szLeaf); + pIter->bDel = 0; + pIter->nPos = 1; + if( iOffpLeaf->p[iOff]==0 ){ + pIter->bDel = 1; + iOff++; + if( iOffpLeaf->p[iOff]==0 ){ + pIter->nPos = 1; + iOff++; + }else{ + pIter->nPos = 0; + } + } + }else{ + int nSz; + fts5FastGetVarint32(pIter->pLeaf->p, iOff, nSz); + pIter->bDel = (nSz & 0x0001); + pIter->nPos = nSz>>1; + assert_nc( pIter->nPos>=0 ); + } pIter->iLeafOffset = iOff; } } @@ -174002,6 +185553,10 @@ 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 ){ + p->rc = FTS5_CORRUPT; + return; + } pIter->term.n = nKeep; fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]); iOff += nNew; @@ -174020,6 +185575,20 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ fts5SegIterLoadRowid(p, pIter); } +static void fts5SegIterNext(Fts5Index*, Fts5SegIter*, int*); +static void fts5SegIterNext_Reverse(Fts5Index*, Fts5SegIter*, int*); +static void fts5SegIterNext_None(Fts5Index*, Fts5SegIter*, int*); + +static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ + if( pIter->flags & FTS5_SEGITER_REVERSE ){ + pIter->xNext = fts5SegIterNext_Reverse; + }else if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + pIter->xNext = fts5SegIterNext_None; + }else{ + pIter->xNext = fts5SegIterNext; + } +} + /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when @@ -174045,6 +185614,7 @@ static void fts5SegIterInit( if( p->rc==SQLITE_OK ){ memset(pIter, 0, sizeof(*pIter)); + fts5SegIterSetNext(p, pIter); pIter->pSeg = pSeg; pIter->iLeafPgno = pSeg->pgnoFirst-1; fts5SegIterNextPage(p, pIter); @@ -174076,6 +185646,7 @@ static void fts5SegIterInit( ** byte of the position list content associated with said rowid. */ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ + int eDetail = p->pConfig->eDetail; int n = pIter->pLeaf->szLeaf; int i = pIter->iLeafOffset; u8 *a = pIter->pLeaf->p; @@ -174088,15 +185659,24 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ ASSERT_SZLEAF_OK(pIter->pLeaf); while( 1 ){ i64 iDelta = 0; - int nPos; - int bDummy; - i += fts5GetPoslistSize(&a[i], &nPos, &bDummy); - i += nPos; + if( eDetail==FTS5_DETAIL_NONE ){ + /* todo */ + if( i=n ) break; i += fts5GetVarint(&a[i], (u64*)&iDelta); pIter->iRowid += iDelta; + /* 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)); @@ -174133,12 +185713,13 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ if( pNew ){ /* iTermLeafOffset may be equal to szLeaf if the term is the last ** thing on the page - i.e. the first rowid is on the following page. - ** In this case leaf pIter->pLeaf==0, this iterator is at EOF. */ - if( pIter->iLeafPgno==pIter->iTermLeafPgno - && pIter->iTermLeafOffsetszLeaf - ){ - pIter->pLeaf = pNew; - pIter->iLeafOffset = pIter->iTermLeafOffset; + ** In this case leave pIter->pLeaf==0, this iterator is at EOF. */ + if( pIter->iLeafPgno==pIter->iTermLeafPgno ){ + assert( pIter->pLeaf==0 ); + if( pIter->iTermLeafOffsetszLeaf ){ + pIter->pLeaf = pNew; + pIter->iLeafOffset = pIter->iTermLeafOffset; + } }else{ int iRowidOff; iRowidOff = fts5LeafFirstRowidOff(pNew); @@ -174169,11 +185750,115 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ ** points to a delete marker. A delete marker is an entry with a 0 byte ** position-list. */ -static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5IndexIter *pIter){ +static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5Iter *pIter){ Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0); } +/* +** Advance iterator pIter to the next entry. +** +** This version of fts5SegIterNext() is only used by reverse iterators. +*/ +static void fts5SegIterNext_Reverse( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int *pbUnused /* Unused */ +){ + assert( pIter->flags & FTS5_SEGITER_REVERSE ); + assert( pIter->pNextLeaf==0 ); + UNUSED_PARAM(pbUnused); + + if( pIter->iRowidOffset>0 ){ + u8 *a = pIter->pLeaf->p; + int iOff; + i64 iDelta; + + pIter->iRowidOffset--; + pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset]; + fts5SegIterLoadNPos(p, pIter); + iOff = pIter->iLeafOffset; + if( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){ + iOff += pIter->nPos; + } + fts5GetVarint(&a[iOff], (u64*)&iDelta); + pIter->iRowid -= iDelta; + }else{ + fts5SegIterReverseNewPage(p, pIter); + } +} + +/* +** Advance iterator pIter to the next entry. +** +** This version of fts5SegIterNext() is only used if detail=none and the +** iterator is not a reverse direction iterator. +*/ +static void fts5SegIterNext_None( + Fts5Index *p, /* FTS5 backend object */ + Fts5SegIter *pIter, /* Iterator to advance */ + int *pbNewTerm /* OUT: Set for new term */ +){ + int iOff; + + assert( p->rc==SQLITE_OK ); + assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 ); + assert( p->pConfig->eDetail==FTS5_DETAIL_NONE ); + + ASSERT_SZLEAF_OK(pIter->pLeaf); + iOff = pIter->iLeafOffset; + + /* Next entry is on the next page */ + if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ + fts5SegIterNextPage(p, pIter); + if( p->rc || pIter->pLeaf==0 ) return; + pIter->iRowid = 0; + iOff = 4; + } + + if( iOffiEndofDoclist ){ + /* Next entry is on the current page */ + i64 iDelta; + iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta); + pIter->iLeafOffset = iOff; + pIter->iRowid += iDelta; + }else if( (pIter->flags & FTS5_SEGITER_ONETERM)==0 ){ + if( pIter->pSeg ){ + int nKeep = 0; + if( iOff!=fts5LeafFirstTermOff(pIter->pLeaf) ){ + iOff += fts5GetVarint32(&pIter->pLeaf->p[iOff], nKeep); + } + pIter->iLeafOffset = iOff; + fts5SegIterLoadTerm(p, pIter, nKeep); + }else{ + const u8 *pList = 0; + const char *zTerm = 0; + int nList; + sqlite3Fts5HashScanNext(p->pHash); + sqlite3Fts5HashScanEntry(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); + pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + } + + if( pbNewTerm ) *pbNewTerm = 1; + }else{ + goto next_none_eof; + } + + fts5SegIterLoadNPos(p, pIter); + + return; + next_none_eof: + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; +} + + /* ** Advance iterator pIter to the next entry. ** @@ -174186,131 +185871,132 @@ static void fts5SegIterNext( Fts5SegIter *pIter, /* Iterator to advance */ int *pbNewTerm /* OUT: Set for new term */ ){ - assert( pbNewTerm==0 || *pbNewTerm==0 ); - if( p->rc==SQLITE_OK ){ - if( pIter->flags & FTS5_SEGITER_REVERSE ){ - assert( pIter->pNextLeaf==0 ); - if( pIter->iRowidOffset>0 ){ - u8 *a = pIter->pLeaf->p; - int iOff; - int nPos; - int bDummy; - i64 iDelta; + Fts5Data *pLeaf = pIter->pLeaf; + int iOff; + int bNewTerm = 0; + int nKeep = 0; + u8 *a; + int n; - pIter->iRowidOffset--; - pIter->iLeafOffset = iOff = pIter->aRowidOffset[pIter->iRowidOffset]; - iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDummy); - iOff += nPos; - fts5GetVarint(&a[iOff], (u64*)&iDelta); - pIter->iRowid -= iDelta; - fts5SegIterLoadNPos(p, pIter); - }else{ - fts5SegIterReverseNewPage(p, pIter); + assert( pbNewTerm==0 || *pbNewTerm==0 ); + assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); + + /* Search for the end of the position list within the current page. */ + a = pLeaf->p; + n = pLeaf->szLeaf; + + ASSERT_SZLEAF_OK(pLeaf); + iOff = pIter->iLeafOffset + pIter->nPos; + + if( iOffiEndofDoclist ); + if( iOff>=pIter->iEndofDoclist ){ + bNewTerm = 1; + if( iOff!=fts5LeafFirstTermOff(pLeaf) ){ + iOff += fts5GetVarint32(&a[iOff], nKeep); } }else{ - Fts5Data *pLeaf = pIter->pLeaf; - int iOff; - int bNewTerm = 0; - int nKeep = 0; - - /* Search for the end of the position list within the current page. */ - u8 *a = pLeaf->p; - int n = pLeaf->szLeaf; + u64 iDelta; + iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta); + pIter->iRowid += iDelta; + assert_nc( iDelta>0 ); + } + pIter->iLeafOffset = iOff; + }else if( pIter->pSeg==0 ){ + const u8 *pList = 0; + const char *zTerm = 0; + 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); + } + if( pList==0 ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + }else{ + pIter->pLeaf->p = (u8*)pList; + pIter->pLeaf->nn = nList; + pIter->pLeaf->szLeaf = nList; + pIter->iEndofDoclist = nList+1; + sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), + (u8*)zTerm); + pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + *pbNewTerm = 1; + } + }else{ + iOff = 0; + /* Next entry is not on the current page */ + while( iOff==0 ){ + fts5SegIterNextPage(p, pIter); + pLeaf = pIter->pLeaf; + if( pLeaf==0 ) break; ASSERT_SZLEAF_OK(pLeaf); - iOff = pIter->iLeafOffset + pIter->nPos; - - if( iOffiEndofDoclist ); - if( iOff>=pIter->iEndofDoclist ){ - bNewTerm = 1; - if( iOff!=fts5LeafFirstTermOff(pLeaf) ){ - iOff += fts5GetVarint32(&a[iOff], nKeep); - } - }else{ - u64 iDelta; - iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta); - pIter->iRowid += iDelta; - assert_nc( iDelta>0 ); - } + if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOffszLeaf ){ + iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid); pIter->iLeafOffset = iOff; - }else if( pIter->pSeg==0 ){ - const u8 *pList = 0; - const char *zTerm = 0; - int nList = 0; - if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ - sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); - } - if( pList==0 ){ - fts5DataRelease(pIter->pLeaf); - pIter->pLeaf = 0; - }else{ - pIter->pLeaf->p = (u8*)pList; - pIter->pLeaf->nn = nList; - pIter->pLeaf->szLeaf = nList; - pIter->iEndofDoclist = nList+1; - sqlite3Fts5BufferSet(&p->rc, &pIter->term, strlen(zTerm), (u8*)zTerm); - pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); - if( pbNewTerm ) *pbNewTerm = 1; - } - }else{ - iOff = 0; - /* Next entry is not on the current page */ - while( iOff==0 ){ - fts5SegIterNextPage(p, pIter); - pLeaf = pIter->pLeaf; - if( pLeaf==0 ) break; - ASSERT_SZLEAF_OK(pLeaf); - if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOffszLeaf ){ - iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid); - pIter->iLeafOffset = iOff; - - if( pLeaf->nn>pLeaf->szLeaf ){ - pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( - &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist + if( pLeaf->nn>pLeaf->szLeaf ){ + pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( + &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist ); - } + } - } - else if( pLeaf->nn>pLeaf->szLeaf ){ - pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( - &pLeaf->p[pLeaf->szLeaf], iOff + } + else if( pLeaf->nn>pLeaf->szLeaf ){ + pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( + &pLeaf->p[pLeaf->szLeaf], iOff ); - pIter->iLeafOffset = iOff; - pIter->iEndofDoclist = iOff; - bNewTerm = 1; - } - if( iOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; - return; - } - } + pIter->iLeafOffset = iOff; + pIter->iEndofDoclist = iOff; + bNewTerm = 1; } + assert_nc( iOffszLeaf ); + if( iOff>pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } + } + } - /* Check if the iterator is now at EOF. If so, return early. */ - if( pIter->pLeaf ){ - if( bNewTerm ){ - if( pIter->flags & FTS5_SEGITER_ONETERM ){ - fts5DataRelease(pIter->pLeaf); - pIter->pLeaf = 0; - }else{ - fts5SegIterLoadTerm(p, pIter, nKeep); - fts5SegIterLoadNPos(p, pIter); - if( pbNewTerm ) *pbNewTerm = 1; - } - }else{ - fts5SegIterLoadNPos(p, pIter); - } + /* Check if the iterator is now at EOF. If so, return early. */ + if( pIter->pLeaf ){ + if( bNewTerm ){ + if( pIter->flags & FTS5_SEGITER_ONETERM ){ + fts5DataRelease(pIter->pLeaf); + pIter->pLeaf = 0; + }else{ + fts5SegIterLoadTerm(p, pIter, nKeep); + fts5SegIterLoadNPos(p, pIter); + if( pbNewTerm ) *pbNewTerm = 1; } + }else{ + /* The following could be done by calling fts5SegIterLoadNPos(). But + ** this block is particularly performance critical, so equivalent + ** code is inlined. + ** + ** Later: Switched back to fts5SegIterLoadNPos() because it supports + ** detail=none mode. Not ideal. + */ + int nSz; + assert( p->rc==SQLITE_OK ); + fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); + pIter->bDel = (nSz & 0x0001); + pIter->nPos = nSz>>1; + assert_nc( pIter->nPos>=0 ); } } } #define SWAPVAL(T, a, b) { T tmp; tmp=a; a=b; b=tmp; } +#define fts5IndexSkipVarint(a, iOff) { \ + int iEnd = iOff+9; \ + while( (a[iOff++] & 0x80) && iOffiLeafOffset -= sqlite3Fts5GetVarintLen(pIter->nPos*2+pIter->bDel); + int iPoslist; + if( pIter->iTermLeafPgno==pIter->iLeafPgno ){ + iPoslist = pIter->iTermLeafOffset; + }else{ + iPoslist = 4; + } + fts5IndexSkipVarint(pLeaf->p, iPoslist); + pIter->iLeafOffset = iPoslist; /* If this condition is true then the largest rowid for the current ** term may not be stored on the current page. So search forward to @@ -174415,11 +186108,6 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ pIter->pDlidx = fts5DlidxIterInit(p, bRev, iSeg, pIter->iTermLeafPgno); } -#define fts5IndexSkipVarint(a, iOff) { \ - int iEnd = iOff+9; \ - while( (a[iOff++] & 0x80) && iOffn ){ + p->rc = FTS5_CORRUPT; + return; + } while( 1 ){ @@ -174511,11 +186203,14 @@ static void fts5LeafSeek( if( pIter->pLeaf==0 ) return; a = pIter->pLeaf->p; if( fts5LeafIsTermless(pIter->pLeaf)==0 ){ - fts5GetVarint32(&pIter->pLeaf->p[pIter->pLeaf->szLeaf], iOff); + iPgidx = pIter->pLeaf->szLeaf; + iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); if( iOff<4 || iOff>=pIter->pLeaf->szLeaf ){ p->rc = FTS5_CORRUPT; }else{ nKeep = 0; + iTermOff = iOff; + n = pIter->pLeaf->nn; iOff += fts5GetVarint32(&a[iOff], nNew); break; } @@ -174545,6 +186240,18 @@ static void fts5LeafSeek( fts5SegIterLoadNPos(p, pIter); } +static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){ + if( p->pIdxSelect==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf( + "SELECT pgno FROM '%q'.'%q_idx' WHERE " + "segid=? AND term<=? ORDER BY term DESC LIMIT 1", + pConfig->zDb, pConfig->zName + )); + } + return p->pIdxSelect; +} + /* ** Initialize the object pIter to point to term pTerm/nTerm within segment ** pSeg. If there is no such term in the index, the iterator is set to EOF. @@ -174554,7 +186261,6 @@ static void fts5LeafSeek( */ static void fts5SegIterSeekInit( Fts5Index *p, /* FTS5 backend */ - Fts5Buffer *pBuf, /* Buffer to use for loading pages */ const u8 *pTerm, int nTerm, /* Term to seek to */ int flags, /* Mask of FTS5INDEX_XXX flags */ Fts5StructureSegment *pSeg, /* Description of segment */ @@ -174563,9 +186269,7 @@ static void fts5SegIterSeekInit( int iPg = 1; int bGe = (flags & FTS5INDEX_QUERY_SCAN); int bDlidx = 0; /* True if there is a doclist-index */ - - static int nCall = 0; - nCall++; + sqlite3_stmt *pIdxSelect = 0; assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 ); assert( pTerm && nTerm ); @@ -174574,23 +186278,16 @@ static void fts5SegIterSeekInit( /* This block sets stack variable iPg to the leaf page number that may ** contain term (pTerm/nTerm), if it is present in the segment. */ - if( p->pIdxSelect==0 ){ - Fts5Config *pConfig = p->pConfig; - fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf( - "SELECT pgno FROM '%q'.'%q_idx' WHERE " - "segid=? AND term<=? ORDER BY term DESC LIMIT 1", - pConfig->zDb, pConfig->zName - )); - } + pIdxSelect = fts5IdxSelectStmt(p); if( p->rc ) return; - sqlite3_bind_int(p->pIdxSelect, 1, pSeg->iSegid); - sqlite3_bind_blob(p->pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC); - if( SQLITE_ROW==sqlite3_step(p->pIdxSelect) ){ - i64 val = sqlite3_column_int(p->pIdxSelect, 0); + 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); iPg = (int)(val>>1); bDlidx = (val & 0x0001); } - p->rc = sqlite3_reset(p->pIdxSelect); + p->rc = sqlite3_reset(pIdxSelect); if( iPgpgnoFirst ){ iPg = pSeg->pgnoFirst; @@ -174619,6 +186316,8 @@ static void fts5SegIterSeekInit( } } + fts5SegIterSetNext(p, pIter); + /* Either: ** ** 1) an error has occurred, or @@ -174659,7 +186358,7 @@ static void fts5SegIterHashInit( if( pTerm==0 || (flags & FTS5INDEX_QUERY_SCAN) ){ p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm); sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList); - n = (z ? strlen((const char*)z) : 0); + n = (z ? (int)strlen((const char*)z) : 0); }else{ pIter->flags |= FTS5_SEGITER_ONETERM; sqlite3Fts5HashQuery(p->pHash, (const char*)pTerm, nTerm, &pList, &nList); @@ -174676,7 +186375,7 @@ static void fts5SegIterHashInit( pLeaf->nn = pLeaf->szLeaf = nList; pIter->pLeaf = pLeaf; pIter->iLeafOffset = fts5GetVarint(pLeaf->p, (u64*)&pIter->iRowid); - pIter->iEndofDoclist = pLeaf->nn+1; + pIter->iEndofDoclist = pLeaf->nn; if( flags & FTS5INDEX_QUERY_DESC ){ pIter->flags |= FTS5_SEGITER_REVERSE; @@ -174685,6 +186384,8 @@ static void fts5SegIterHashInit( fts5SegIterLoadNPos(p, pIter); } } + + fts5SegIterSetNext(p, pIter); } /* @@ -174708,7 +186409,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){ ** two iterators. */ static void fts5AssertComparisonResult( - Fts5IndexIter *pIter, + Fts5Iter *pIter, Fts5SegIter *p1, Fts5SegIter *p2, Fts5CResult *pRes @@ -174749,12 +186450,12 @@ static void fts5AssertComparisonResult( ** statement used to verify that the contents of the pIter->aFirst[] array ** are correct. */ -static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5IndexIter *pIter){ +static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){ if( p->rc==SQLITE_OK ){ Fts5SegIter *pFirst = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; int i; - assert( (pFirst->pLeaf==0)==pIter->bEof ); + assert( (pFirst->pLeaf==0)==pIter->base.bEof ); /* Check that pIter->iSwitchRowid is set correctly. */ for(i=0; inSeg; i++){ @@ -174794,7 +186495,7 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5IndexIter *pIter){ ** to a key that is a duplicate of another, higher priority, ** segment-iterator in the pSeg->aSeg[] array. */ -static int fts5MultiIterDoCompare(Fts5IndexIter *pIter, int iOut){ +static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ int i1; /* Index of left-hand Fts5SegIter */ int i2; /* Index of right-hand Fts5SegIter */ int iRes; @@ -174840,7 +186541,7 @@ static int fts5MultiIterDoCompare(Fts5IndexIter *pIter, int iOut){ } } - pRes->iFirst = iRes; + pRes->iFirst = (u16)iRes; return 0; } @@ -174928,7 +186629,7 @@ static void fts5SegIterNextFrom( } do{ - if( bMove ) fts5SegIterNext(p, pIter, 0); + if( bMove && p->rc==SQLITE_OK ) pIter->xNext(p, pIter, 0); if( pIter->pLeaf==0 ) break; if( bRev==0 && pIter->iRowid>=iMatch ) break; if( bRev!=0 && pIter->iRowid<=iMatch ) break; @@ -174940,7 +186641,7 @@ static void fts5SegIterNextFrom( /* ** Free the iterator object passed as the second argument. */ -static void fts5MultiIterFree(Fts5Index *p, Fts5IndexIter *pIter){ +static void fts5MultiIterFree(Fts5Iter *pIter){ if( pIter ){ int i; for(i=0; inSeg; i++){ @@ -174954,7 +186655,7 @@ static void fts5MultiIterFree(Fts5Index *p, Fts5IndexIter *pIter){ static void fts5MultiIterAdvanced( Fts5Index *p, /* FTS5 backend to iterate within */ - Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */ + Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ int iChanged, /* Index of sub-iterator just advanced */ int iMinset /* Minimum entry in aFirst[] to set */ ){ @@ -174962,7 +186663,9 @@ static void fts5MultiIterAdvanced( for(i=(pIter->nSeg+iChanged)/2; i>=iMinset && p->rc==SQLITE_OK; i=i/2){ int iEq; if( (iEq = fts5MultiIterDoCompare(pIter, i)) ){ - fts5SegIterNext(p, &pIter->aSeg[iEq], 0); + Fts5SegIter *pSeg = &pIter->aSeg[iEq]; + assert( p->rc==SQLITE_OK ); + pSeg->xNext(p, pSeg, 0); i = pIter->nSeg + iEq; } } @@ -174979,9 +186682,9 @@ static void fts5MultiIterAdvanced( ** that it deals with more complicated cases as well. */ static int fts5MultiIterAdvanceRowid( - Fts5Index *p, /* FTS5 backend to iterate within */ - Fts5IndexIter *pIter, /* Iterator to update aFirst[] array for */ - int iChanged /* Index of sub-iterator just advanced */ + Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ + int iChanged, /* Index of sub-iterator just advanced */ + Fts5SegIter **ppFirst ){ Fts5SegIter *pNew = &pIter->aSeg[iChanged]; @@ -175007,22 +186710,23 @@ static int fts5MultiIterAdvanceRowid( pIter->iSwitchRowid = pOther->iRowid; } } - pRes->iFirst = (pNew - pIter->aSeg); + pRes->iFirst = (u16)(pNew - pIter->aSeg); if( i==1 ) break; pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0x0001].iFirst ]; } } + *ppFirst = pNew; return 0; } /* ** Set the pIter->bEof variable based on the state of the sub-iterators. */ -static void fts5MultiIterSetEof(Fts5IndexIter *pIter){ +static void fts5MultiIterSetEof(Fts5Iter *pIter){ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; - pIter->bEof = pSeg->pLeaf==0; + pIter->base.bEof = pSeg->pLeaf==0; pIter->iSwitchRowid = pSeg->iRowid; } @@ -175035,46 +186739,84 @@ static void fts5MultiIterSetEof(Fts5IndexIter *pIter){ */ static void fts5MultiIterNext( Fts5Index *p, - Fts5IndexIter *pIter, + Fts5Iter *pIter, int bFrom, /* True if argument iFrom is valid */ i64 iFrom /* Advance at least as far as this */ ){ - if( p->rc==SQLITE_OK ){ - int bUseFrom = bFrom; - do { - int iFirst = pIter->aFirst[1].iFirst; - int bNewTerm = 0; - Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; - assert( p->rc==SQLITE_OK ); - if( bUseFrom && pSeg->pDlidx ){ - fts5SegIterNextFrom(p, pSeg, iFrom); - }else{ - fts5SegIterNext(p, pSeg, &bNewTerm); - } + int bUseFrom = bFrom; + while( p->rc==SQLITE_OK ){ + int iFirst = pIter->aFirst[1].iFirst; + int bNewTerm = 0; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + assert( p->rc==SQLITE_OK ); + if( bUseFrom && pSeg->pDlidx ){ + fts5SegIterNextFrom(p, pSeg, iFrom); + }else{ + pSeg->xNext(p, pSeg, &bNewTerm); + } - if( pSeg->pLeaf==0 || bNewTerm - || fts5MultiIterAdvanceRowid(p, pIter, iFirst) - ){ - fts5MultiIterAdvanced(p, pIter, iFirst, 1); - fts5MultiIterSetEof(pIter); - } - fts5AssertMultiIterSetup(p, pIter); + if( pSeg->pLeaf==0 || bNewTerm + || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) + ){ + fts5MultiIterAdvanced(p, pIter, iFirst, 1); + fts5MultiIterSetEof(pIter); + pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + if( pSeg->pLeaf==0 ) return; + } - bUseFrom = 0; - }while( pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter) ); + fts5AssertMultiIterSetup(p, pIter); + assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); + if( pIter->bSkipEmpty==0 || pSeg->nPos ){ + pIter->xSetOutputs(pIter, pSeg); + return; + } + bUseFrom = 0; } } -static Fts5IndexIter *fts5MultiIterAlloc( +static void fts5MultiIterNext2( + Fts5Index *p, + Fts5Iter *pIter, + int *pbNewTerm /* OUT: True if *might* be new term */ +){ + assert( pIter->bSkipEmpty ); + if( p->rc==SQLITE_OK ){ + do { + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + int bNewTerm = 0; + + assert( p->rc==SQLITE_OK ); + pSeg->xNext(p, pSeg, &bNewTerm); + if( pSeg->pLeaf==0 || bNewTerm + || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) + ){ + fts5MultiIterAdvanced(p, pIter, iFirst, 1); + fts5MultiIterSetEof(pIter); + *pbNewTerm = 1; + }else{ + *pbNewTerm = 0; + } + fts5AssertMultiIterSetup(p, pIter); + + }while( fts5MultiIterIsEmpty(p, pIter) ); + } +} + +static void fts5IterSetOutputs_Noop(Fts5Iter *pUnused1, Fts5SegIter *pUnused2){ + UNUSED_PARAM2(pUnused1, pUnused2); +} + +static Fts5Iter *fts5MultiIterAlloc( Fts5Index *p, /* FTS5 backend to iterate within */ int nSeg ){ - Fts5IndexIter *pNew; + Fts5Iter *pNew; int nSlot; /* Power of two >= nSeg */ for(nSlot=2; nSlotaSeg[] */ sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */ ); @@ -175082,196 +186824,122 @@ static Fts5IndexIter *fts5MultiIterAlloc( pNew->nSeg = nSlot; pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot]; pNew->pIndex = p; + pNew->xSetOutputs = fts5IterSetOutputs_Noop; } return pNew; } -/* -** Allocate a new Fts5IndexIter object. -** -** The new object will be used to iterate through data in structure pStruct. -** If iLevel is -ve, then all data in all segments is merged. Or, if iLevel -** is zero or greater, data from the first nSegment segments on level iLevel -** is merged. -** -** The iterator initially points to the first term/rowid entry in the -** iterated data. -*/ -static void fts5MultiIterNew( - Fts5Index *p, /* FTS5 backend to iterate within */ - Fts5Structure *pStruct, /* Structure of specific index */ - int bSkipEmpty, /* True to ignore delete-keys */ - int flags, /* FTS5INDEX_QUERY_XXX flags */ - const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */ - int iLevel, /* Level to iterate (-1 for all) */ - int nSegment, /* Number of segments to merge (iLevel>=0) */ - Fts5IndexIter **ppOut /* New object */ +static void fts5PoslistCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk ){ - int nSeg = 0; /* Number of segment-iters in use */ - int iIter = 0; /* */ - int iSeg; /* Used to iterate through segments */ - Fts5Buffer buf = {0,0,0}; /* Buffer used by fts5SegIterSeekInit() */ - Fts5StructureLevel *pLvl; - Fts5IndexIter *pNew; + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk); + } +} - assert( (pTerm==0 && nTerm==0) || iLevel<0 ); +typedef struct PoslistCallbackCtx PoslistCallbackCtx; +struct PoslistCallbackCtx { + Fts5Buffer *pBuf; /* Append to this buffer */ + Fts5Colset *pColset; /* Restrict matches to this column */ + int eState; /* See above */ +}; - /* Allocate space for the new multi-seg-iterator. */ - if( p->rc==SQLITE_OK ){ - if( iLevel<0 ){ - assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); - nSeg = pStruct->nSegment; - nSeg += (p->pHash ? 1 : 0); - }else{ - nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment); +typedef struct PoslistOffsetsCtx PoslistOffsetsCtx; +struct PoslistOffsetsCtx { + Fts5Buffer *pBuf; /* Append to this buffer */ + Fts5Colset *pColset; /* Restrict matches to this column */ + int iRead; + int iWrite; +}; + +/* +** TODO: Make this more efficient! +*/ +static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ + int i; + for(i=0; inCol; i++){ + if( pColset->aiCol[i]==iCol ) return 1; + } + return 0; +} + +static void fts5PoslistOffsetsCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk +){ + PoslistOffsetsCtx *pCtx = (PoslistOffsetsCtx*)pContext; + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + int i = 0; + while( iiRead - 2; + pCtx->iRead = iVal; + if( fts5IndexColsetTest(pCtx->pColset, iVal) ){ + fts5BufferSafeAppendVarint(pCtx->pBuf, iVal + 2 - pCtx->iWrite); + pCtx->iWrite = iVal; + } } } - *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); - if( pNew==0 ) return; - pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); - pNew->bSkipEmpty = bSkipEmpty; - pNew->pStruct = pStruct; - fts5StructureRef(pStruct); +} - /* Initialize each of the component segment iterators. */ - if( iLevel<0 ){ - Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; - if( p->pHash ){ - /* Add a segment iterator for the current contents of the hash table. */ - Fts5SegIter *pIter = &pNew->aSeg[iIter++]; - fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter); +static void fts5PoslistFilterCallback( + Fts5Index *pUnused, + void *pContext, + const u8 *pChunk, int nChunk +){ + PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; + UNUSED_PARAM(pUnused); + assert_nc( nChunk>=0 ); + if( nChunk>0 ){ + /* Search through to find the first varint with value 1. This is the + ** start of the next columns hits. */ + int i = 0; + int iStart = 0; + + if( pCtx->eState==2 ){ + int iCol; + fts5FastGetVarint32(pChunk, i, iCol); + if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ + pCtx->eState = 1; + fts5BufferSafeAppendVarint(pCtx->pBuf, 1); + }else{ + pCtx->eState = 0; + } } - for(pLvl=&pStruct->aLevel[0]; pLvlnSeg-1; iSeg>=0; iSeg--){ - Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; - Fts5SegIter *pIter = &pNew->aSeg[iIter++]; - if( pTerm==0 ){ - fts5SegIterInit(p, pSeg, pIter); + + do { + while( ieState ){ + fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); + } + if( i=nChunk ){ + pCtx->eState = 2; }else{ - fts5SegIterSeekInit(p, &buf, pTerm, nTerm, flags, pSeg, pIter); + fts5FastGetVarint32(pChunk, i, iCol); + pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); + if( pCtx->eState ){ + fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); + iStart = i; + } } } - } - }else{ - pLvl = &pStruct->aLevel[iLevel]; - for(iSeg=nSeg-1; iSeg>=0; iSeg--){ - fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); - } + }while( irc==SQLITE_OK ){ - for(iIter=pNew->nSeg-1; iIter>0; iIter--){ - int iEq; - if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ - fts5SegIterNext(p, &pNew->aSeg[iEq], 0); - fts5MultiIterAdvanced(p, pNew, iEq, iIter); - } - } - fts5MultiIterSetEof(pNew); - fts5AssertMultiIterSetup(p, pNew); - - if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ - fts5MultiIterNext(p, pNew, 0, 0); - } - }else{ - fts5MultiIterFree(p, pNew); - *ppOut = 0; - } - fts5BufferFree(&buf); -} - -/* -** Create an Fts5IndexIter that iterates through the doclist provided -** as the second argument. -*/ -static void fts5MultiIterNew2( - Fts5Index *p, /* FTS5 backend to iterate within */ - Fts5Data *pData, /* Doclist to iterate through */ - int bDesc, /* True for descending rowid order */ - Fts5IndexIter **ppOut /* New object */ -){ - Fts5IndexIter *pNew; - pNew = fts5MultiIterAlloc(p, 2); - if( pNew ){ - Fts5SegIter *pIter = &pNew->aSeg[1]; - - pNew->bFiltered = 1; - pIter->flags = FTS5_SEGITER_ONETERM; - if( pData->szLeaf>0 ){ - pIter->pLeaf = pData; - pIter->iLeafOffset = fts5GetVarint(pData->p, (u64*)&pIter->iRowid); - pIter->iEndofDoclist = pData->nn; - pNew->aFirst[1].iFirst = 1; - if( bDesc ){ - pNew->bRev = 1; - pIter->flags |= FTS5_SEGITER_REVERSE; - fts5SegIterReverseInitPage(p, pIter); - }else{ - fts5SegIterLoadNPos(p, pIter); - } - pData = 0; - }else{ - pNew->bEof = 1; - } - - *ppOut = pNew; - } - - fts5DataRelease(pData); -} - -/* -** Return true if the iterator is at EOF or if an error has occurred. -** False otherwise. -*/ -static int fts5MultiIterEof(Fts5Index *p, Fts5IndexIter *pIter){ - assert( p->rc - || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->bEof - ); - return (p->rc || pIter->bEof); -} - -/* -** Return the rowid of the entry that the iterator currently points -** to. If the iterator points to EOF when this function is called the -** results are undefined. -*/ -static i64 fts5MultiIterRowid(Fts5IndexIter *pIter){ - assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf ); - return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid; -} - -/* -** Move the iterator to the next entry at or following iMatch. -*/ -static void fts5MultiIterNextFrom( - Fts5Index *p, - Fts5IndexIter *pIter, - i64 iMatch -){ - while( 1 ){ - i64 iRowid; - fts5MultiIterNext(p, pIter, 1, iMatch); - if( fts5MultiIterEof(p, pIter) ) break; - iRowid = fts5MultiIterRowid(pIter); - if( pIter->bRev==0 && iRowid>=iMatch ) break; - if( pIter->bRev!=0 && iRowid<=iMatch ) break; - } -} - -/* -** Return a pointer to a buffer containing the term associated with the -** entry that the iterator currently points to. -*/ -static const u8 *fts5MultiIterTerm(Fts5IndexIter *pIter, int *pn){ - Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; - *pn = p->term.n; - return p->term.p; } static void fts5ChunkIterate( @@ -175287,6 +186955,9 @@ static void fts5ChunkIterate( int pgno = pSeg->iLeafPgno; int pgnoSave = 0; + /* This function does notmwork with detail=none databases. */ + assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); + if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ pgnoSave = pgno+1; } @@ -175312,7 +186983,455 @@ static void fts5ChunkIterate( } } +/* +** Iterator pIter currently points to a valid entry (not EOF). This +** function appends the position list data for the current entry to +** buffer pBuf. It does not make a copy of the position-list size +** field. +*/ +static void fts5SegiterPoslist( + Fts5Index *p, + Fts5SegIter *pSeg, + Fts5Colset *pColset, + Fts5Buffer *pBuf +){ + if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos) ){ + if( pColset==0 ){ + fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); + }else{ + if( p->pConfig->eDetail==FTS5_DETAIL_FULL ){ + PoslistCallbackCtx sCtx; + sCtx.pBuf = pBuf; + sCtx.pColset = pColset; + sCtx.eState = fts5IndexColsetTest(pColset, 0); + assert( sCtx.eState==0 || sCtx.eState==1 ); + fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); + }else{ + PoslistOffsetsCtx sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pBuf = pBuf; + sCtx.pColset = pColset; + fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistOffsetsCallback); + } + } + } +} +/* +** IN/OUT parameter (*pa) points to a position list n bytes in size. If +** the position list contains entries for column iCol, then (*pa) is set +** to point to the sub-position-list for that column and the number of +** bytes in it returned. Or, if the argument position list does not +** contain any entries for column iCol, return 0. +*/ +static int fts5IndexExtractCol( + const u8 **pa, /* IN/OUT: Pointer to poslist */ + int n, /* IN: Size of poslist in bytes */ + int iCol /* Column to extract from poslist */ +){ + int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ + const u8 *p = *pa; + const u8 *pEnd = &p[n]; /* One byte past end of position list */ + + while( iCol>iCurrent ){ + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint. Note that it is not possible for a negative + ** or extremely large varint to occur within an uncorrupted position + ** list. So the last byte of each varint may be assumed to have a clear + ** 0x80 bit. */ + while( *p!=0x01 ){ + while( *p++ & 0x80 ); + if( p>=pEnd ) return 0; + } + *pa = p++; + iCurrent = *p++; + if( iCurrent & 0x80 ){ + p--; + p += fts5GetVarint32(p, iCurrent); + } + } + if( iCol!=iCurrent ) return 0; + + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint */ + while( pnCol; i++){ + const u8 *pSub = pPos; + int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); + if( nSub ){ + fts5BufferAppendBlob(&rc, pBuf, nSub, pSub); + } + } + return rc; +} + +/* +** xSetOutputs callback used by detail=none tables. +*/ +static void fts5IterSetOutputs_None(Fts5Iter *pIter, Fts5SegIter *pSeg){ + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_NONE ); + pIter->base.iRowid = pSeg->iRowid; + pIter->base.nData = pSeg->nPos; +} + +/* +** xSetOutputs callback used by detail=full and detail=col tables when no +** column filters are specified. +*/ +static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){ + pIter->base.iRowid = pSeg->iRowid; + pIter->base.nData = pSeg->nPos; + + assert( pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_NONE ); + assert( pIter->pColset==0 ); + + if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ + /* All data is stored on the current page. Populate the output + ** variables to point into the body of the page object. */ + pIter->base.pData = &pSeg->pLeaf->p[pSeg->iLeafOffset]; + }else{ + /* The data is distributed over two or more pages. Copy it into the + ** Fts5Iter.poslist buffer and then set the output pointer to point + ** to this buffer. */ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, 0, &pIter->poslist); + pIter->base.pData = pIter->poslist.p; + } +} + +/* +** xSetOutputs callback used by detail=col when there is a column filter +** and there are 100 or more columns. Also called as a fallback from +** fts5IterSetOutputs_Col100 if the column-list spans more than one page. +*/ +static void fts5IterSetOutputs_Col(Fts5Iter *pIter, Fts5SegIter *pSeg){ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, pIter->pColset, &pIter->poslist); + pIter->base.iRowid = pSeg->iRowid; + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; +} + +/* +** xSetOutputs callback used when: +** +** * detail=col, +** * there is a column filter, and +** * the table contains 100 or fewer columns. +** +** The last point is to ensure all column numbers are stored as +** single-byte varints. +*/ +static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){ + + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + assert( pIter->pColset ); + + if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){ + fts5IterSetOutputs_Col(pIter, pSeg); + }else{ + u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset]; + u8 *pEnd = (u8*)&a[pSeg->nPos]; + int iPrev = 0; + int *aiCol = pIter->pColset->aiCol; + int *aiColEnd = &aiCol[pIter->pColset->nCol]; + + u8 *aOut = pIter->poslist.p; + int iPrevOut = 0; + + pIter->base.iRowid = pSeg->iRowid; + + while( abase.pData = pIter->poslist.p; + pIter->base.nData = aOut - pIter->poslist.p; + } +} + +/* +** xSetOutputs callback used by detail=full when there is a column filter. +*/ +static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ + Fts5Colset *pColset = pIter->pColset; + pIter->base.iRowid = pSeg->iRowid; + + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_FULL ); + assert( pColset ); + + if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ + /* All data is stored on the current page. Populate the output + ** variables to point into the body of the page object. */ + const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset]; + if( pColset->nCol==1 ){ + pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]); + pIter->base.pData = a; + }else{ + fts5BufferZero(&pIter->poslist); + fts5IndexExtractColset(pColset, a, pSeg->nPos, &pIter->poslist); + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + } + }else{ + /* The data is distributed over two or more pages. Copy it into the + ** Fts5Iter.poslist buffer and then set the output pointer to point + ** to this buffer. */ + fts5BufferZero(&pIter->poslist); + fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist); + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + } +} + +static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ + if( *pRc==SQLITE_OK ){ + Fts5Config *pConfig = pIter->pIndex->pConfig; + if( pConfig->eDetail==FTS5_DETAIL_NONE ){ + pIter->xSetOutputs = fts5IterSetOutputs_None; + } + + else if( pIter->pColset==0 ){ + pIter->xSetOutputs = fts5IterSetOutputs_Nocolset; + } + + else if( pConfig->eDetail==FTS5_DETAIL_FULL ){ + pIter->xSetOutputs = fts5IterSetOutputs_Full; + } + + else{ + assert( pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + if( pConfig->nCol<=100 ){ + pIter->xSetOutputs = fts5IterSetOutputs_Col100; + sqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol); + }else{ + pIter->xSetOutputs = fts5IterSetOutputs_Col; + } + } + } +} + + +/* +** Allocate a new Fts5Iter object. +** +** The new object will be used to iterate through data in structure pStruct. +** If iLevel is -ve, then all data in all segments is merged. Or, if iLevel +** is zero or greater, data from the first nSegment segments on level iLevel +** is merged. +** +** The iterator initially points to the first term/rowid entry in the +** iterated data. +*/ +static void fts5MultiIterNew( + Fts5Index *p, /* FTS5 backend to iterate within */ + Fts5Structure *pStruct, /* Structure of specific index */ + int flags, /* FTS5INDEX_QUERY_XXX flags */ + Fts5Colset *pColset, /* Colset to filter on (or NULL) */ + const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */ + int iLevel, /* Level to iterate (-1 for all) */ + int nSegment, /* Number of segments to merge (iLevel>=0) */ + Fts5Iter **ppOut /* New object */ +){ + int nSeg = 0; /* Number of segment-iters in use */ + int iIter = 0; /* */ + int iSeg; /* Used to iterate through segments */ + Fts5StructureLevel *pLvl; + Fts5Iter *pNew; + + assert( (pTerm==0 && nTerm==0) || iLevel<0 ); + + /* Allocate space for the new multi-seg-iterator. */ + if( p->rc==SQLITE_OK ){ + if( iLevel<0 ){ + assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); + nSeg = pStruct->nSegment; + nSeg += (p->pHash ? 1 : 0); + }else{ + nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment); + } + } + *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); + 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); + } + + /* Initialize each of the component segment iterators. */ + if( p->rc==SQLITE_OK ){ + if( iLevel<0 ){ + Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; + if( p->pHash ){ + /* Add a segment iterator for the current contents of the hash table. */ + Fts5SegIter *pIter = &pNew->aSeg[iIter++]; + fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter); + } + for(pLvl=&pStruct->aLevel[0]; pLvlnSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + Fts5SegIter *pIter = &pNew->aSeg[iIter++]; + if( pTerm==0 ){ + fts5SegIterInit(p, pSeg, pIter); + }else{ + fts5SegIterSeekInit(p, pTerm, nTerm, flags, pSeg, pIter); + } + } + } + }else{ + pLvl = &pStruct->aLevel[iLevel]; + for(iSeg=nSeg-1; iSeg>=0; iSeg--){ + fts5SegIterInit(p, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]); + } + } + assert( iIter==nSeg ); + } + + /* If the above was successful, each component iterators now points + ** to the first entry in its segment. In this case initialize the + ** aFirst[] array. Or, if an error has occurred, free the iterator + ** object and set the output variable to NULL. */ + if( p->rc==SQLITE_OK ){ + for(iIter=pNew->nSeg-1; iIter>0; iIter--){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ + Fts5SegIter *pSeg = &pNew->aSeg[iEq]; + if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); + fts5MultiIterAdvanced(p, pNew, iEq, iIter); + } + } + fts5MultiIterSetEof(pNew); + fts5AssertMultiIterSetup(p, pNew); + + if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ + fts5MultiIterNext(p, pNew, 0, 0); + }else if( pNew->base.bEof==0 ){ + Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; + pNew->xSetOutputs(pNew, pSeg); + } + + }else{ + fts5MultiIterFree(pNew); + *ppOut = 0; + } +} + +/* +** Create an Fts5Iter that iterates through the doclist provided +** as the second argument. +*/ +static void fts5MultiIterNew2( + Fts5Index *p, /* FTS5 backend to iterate within */ + Fts5Data *pData, /* Doclist to iterate through */ + int bDesc, /* True for descending rowid order */ + Fts5Iter **ppOut /* New object */ +){ + Fts5Iter *pNew; + pNew = fts5MultiIterAlloc(p, 2); + if( pNew ){ + Fts5SegIter *pIter = &pNew->aSeg[1]; + + pIter->flags = FTS5_SEGITER_ONETERM; + if( pData->szLeaf>0 ){ + pIter->pLeaf = pData; + pIter->iLeafOffset = fts5GetVarint(pData->p, (u64*)&pIter->iRowid); + pIter->iEndofDoclist = pData->nn; + pNew->aFirst[1].iFirst = 1; + if( bDesc ){ + pNew->bRev = 1; + pIter->flags |= FTS5_SEGITER_REVERSE; + fts5SegIterReverseInitPage(p, pIter); + }else{ + fts5SegIterLoadNPos(p, pIter); + } + pData = 0; + }else{ + pNew->base.bEof = 1; + } + fts5SegIterSetNext(p, pIter); + + *ppOut = pNew; + } + + fts5DataRelease(pData); +} + +/* +** Return true if the iterator is at EOF or if an error has occurred. +** False otherwise. +*/ +static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ + assert( p->rc + || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof + ); + return (p->rc || pIter->base.bEof); +} + +/* +** Return the rowid of the entry that the iterator currently points +** to. If the iterator points to EOF when this function is called the +** results are undefined. +*/ +static i64 fts5MultiIterRowid(Fts5Iter *pIter){ + assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf ); + return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid; +} + +/* +** Move the iterator to the next entry at or following iMatch. +*/ +static void fts5MultiIterNextFrom( + Fts5Index *p, + Fts5Iter *pIter, + i64 iMatch +){ + while( 1 ){ + i64 iRowid; + fts5MultiIterNext(p, pIter, 1, iMatch); + if( fts5MultiIterEof(p, pIter) ) break; + iRowid = fts5MultiIterRowid(pIter); + if( pIter->bRev==0 && iRowid>=iMatch ) break; + if( pIter->bRev!=0 && iRowid<=iMatch ) break; + } +} + +/* +** Return a pointer to a buffer containing the term associated with the +** entry that the iterator currently points to. +*/ +static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){ + Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; + *pn = p->term.n; + return p->term.p; +} /* ** Allocate a new segment-id for the structure pStruct. The new segment @@ -175330,18 +187449,46 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){ if( pStruct->nSegment>=FTS5_MAX_SEGMENT ){ p->rc = SQLITE_FULL; }else{ - while( iSegid==0 ){ - int iLvl, iSeg; - sqlite3_randomness(sizeof(u32), (void*)&iSegid); - iSegid = iSegid & ((1 << FTS5_DATA_ID_B)-1); - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ - if( iSegid==pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ){ - iSegid = 0; - } + /* FTS5_MAX_SEGMENT is currently defined as 2000. So the following + ** array is 63 elements, or 252 bytes, in size. */ + u32 aUsed[(FTS5_MAX_SEGMENT+31) / 32]; + int iLvl, iSeg; + int i; + u32 mask; + memset(aUsed, 0, sizeof(aUsed)); + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid; + if( iId<=FTS5_MAX_SEGMENT ){ + aUsed[(iId-1) / 32] |= 1 << ((iId-1) % 32); } } } + + for(i=0; aUsed[i]==0xFFFFFFFF; i++); + mask = aUsed[i]; + for(iSegid=0; mask & (1 << iSegid); iSegid++); + iSegid += 1 + i*32; + +#ifdef SQLITE_DEBUG + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + assert( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ); + } + } + assert( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT ); + + { + sqlite3_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); + } + } +#endif } } @@ -175360,15 +187507,14 @@ static void fts5IndexDiscardData(Fts5Index *p){ } /* -** Return the size of the prefix, in bytes, that buffer (nNew/pNew) shares -** with buffer (nOld/pOld). +** Return the size of the prefix, in bytes, that buffer +** (pNew/) shares with buffer (pOld/nOld). +** +** Buffer (pNew/) is guaranteed to be greater +** than buffer (pOld/nOld). */ -static int fts5PrefixCompress( - int nOld, const u8 *pOld, - int nNew, const u8 *pNew -){ +static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){ int i; - assert( fts5BlobCompare(pOld, nOld, pNew, nNew)<0 ); for(i=0; iwriter; i64 iRowid; +static int nCall = 0; +nCall++; + assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) ); /* Set the szLeaf header field. */ assert( 0==fts5GetU16(&pPage->buf.p[2]) ); - fts5PutU16(&pPage->buf.p[2], pPage->buf.n); + fts5PutU16(&pPage->buf.p[2], (u16)pPage->buf.n); if( pWriter->bFirstTermInPage ){ /* No term was written to this page. */ @@ -175678,13 +187827,13 @@ static void fts5WriteAppendTerm( ** inefficient, but still correct. */ int n = nTerm; if( pPage->term.n ){ - n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm); + n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm); } fts5WriteBtreeTerm(p, pWriter, n, pTerm); pPage = &pWriter->writer; } }else{ - nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm); + nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm); fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix); } @@ -175710,8 +187859,7 @@ static void fts5WriteAppendTerm( static void fts5WriteAppendRowid( Fts5Index *p, Fts5SegWriter *pWriter, - i64 iRowid, - int nPos + i64 iRowid ){ if( p->rc==SQLITE_OK ){ Fts5PageWriter *pPage = &pWriter->writer; @@ -175724,7 +187872,7 @@ static void fts5WriteAppendRowid( ** rowid-pointer in the page-header. Also append a value to the dlidx ** buffer, in case a doclist-index is required. */ if( pWriter->bFirstRowidInPage ){ - fts5PutU16(pPage->buf.p, pPage->buf.n); + fts5PutU16(pPage->buf.p, (u16)pPage->buf.n); fts5WriteDlidxAppend(p, pWriter, iRowid); } @@ -175738,8 +187886,6 @@ static void fts5WriteAppendRowid( pWriter->iPrevRowid = iRowid; pWriter->bFirstRowidInDoclist = 0; pWriter->bFirstRowidInPage = 0; - - fts5BufferAppendVarint(&p->rc, &pPage->buf, nPos); } } @@ -175790,7 +187936,9 @@ static void fts5WriteFinish( fts5WriteFlushLeaf(p, pWriter); } *pnLeaf = pLeaf->pgno-1; - fts5WriteFlushBtree(p, pWriter); + if( pLeaf->pgno>1 ){ + fts5WriteFlushBtree(p, pWriter); + } } fts5BufferFree(&pLeaf->term); fts5BufferFree(&pLeaf->buf); @@ -175818,9 +187966,12 @@ static void fts5WriteInit( pWriter->bFirstTermInPage = 1; pWriter->iBtPage = 1; + assert( pWriter->writer.buf.n==0 ); + assert( pWriter->writer.pgidx.n==0 ); + /* Grow the two buffers to pgsz + padding bytes in size. */ - fts5BufferGrow(&p->rc, &pWriter->writer.pgidx, nBuffer); - fts5BufferGrow(&p->rc, &pWriter->writer.buf, nBuffer); + sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.pgidx, nBuffer); + sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.buf, nBuffer); if( p->pIdxWriter==0 ){ Fts5Config *pConfig = p->pConfig; @@ -175847,7 +187998,7 @@ static void fts5WriteInit( ** incremental merge operation. This function is called if the incremental ** merge step has finished but the input has not been completely exhausted. */ -static void fts5TrimSegments(Fts5Index *p, Fts5IndexIter *pIter){ +static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ int i; Fts5Buffer buf; memset(&buf, 0, sizeof(Fts5Buffer)); @@ -175879,7 +188030,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5IndexIter *pIter){ fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff, &pData->p[iOff]); if( p->rc==SQLITE_OK ){ /* Set the szLeaf field */ - fts5PutU16(&buf.p[2], buf.n); + fts5PutU16(&buf.p[2], (u16)buf.n); } /* Set up the new page-index array */ @@ -175925,13 +188076,15 @@ static void fts5IndexMergeLevel( Fts5Structure *pStruct = *ppStruct; Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; Fts5StructureLevel *pLvlOut; - Fts5IndexIter *pIter = 0; /* Iterator to read input data */ + Fts5Iter *pIter = 0; /* Iterator to read input data */ int nRem = pnRem ? *pnRem : 0; /* Output leaf pages left to write */ int nInput; /* Number of input segments */ Fts5SegWriter writer; /* Writer object */ Fts5StructureSegment *pSeg; /* Output segment */ Fts5Buffer term; int bOldest; /* True if the output segment is the oldest */ + int eDetail = p->pConfig->eDetail; + const int flags = FTS5INDEX_QUERY_NOOUTPUT; assert( iLvlnLevel ); assert( pLvl->nMerge<=pLvl->nSeg ); @@ -175976,7 +188129,7 @@ static void fts5IndexMergeLevel( bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); assert( iLvl>=0 ); - for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, iLvl, nInput, &pIter); + for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, iLvl, nInput, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter, 0, 0) ){ @@ -176001,11 +188154,21 @@ static void fts5IndexMergeLevel( /* Append the rowid to the output */ /* WRITEPOSLISTSIZE */ - nPos = pSegIter->nPos*2 + pSegIter->bDel; - fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter), nPos); + fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter)); - /* Append the position-list data to the output */ - fts5ChunkIterate(p, pSegIter, (void*)&writer, fts5MergeChunkCallback); + if( eDetail==FTS5_DETAIL_NONE ){ + if( pSegIter->bDel ){ + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); + if( pSegIter->nPos>0 ){ + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, 0); + } + } + }else{ + /* Append the position-list data to the output */ + nPos = pSegIter->nPos*2 + pSegIter->bDel; + fts5BufferAppendVarint(&p->rc, &writer.writer.buf, nPos); + fts5ChunkIterate(p, pSegIter, (void*)&writer, fts5MergeChunkCallback); + } } /* Flush the last leaf page to disk. Set the output segment b-tree height @@ -176038,20 +188201,24 @@ static void fts5IndexMergeLevel( pLvl->nMerge = nInput; } - fts5MultiIterFree(p, pIter); + fts5MultiIterFree(pIter); fts5BufferFree(&term); if( pnRem ) *pnRem -= writer.nLeafWritten; } /* ** Do up to nPg pages of automerge work on the index. +** +** Return true if any changes were actually made, or false otherwise. */ -static void fts5IndexMerge( +static int fts5IndexMerge( Fts5Index *p, /* FTS5 backend object */ Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ - int nPg /* Pages of work to do */ + int nPg, /* Pages of work to do */ + int nMin /* Minimum number of segments to merge */ ){ int nRem = nPg; + int bRet = 0; Fts5Structure *pStruct = *ppStruct; while( nRem>0 && p->rc==SQLITE_OK ){ int iLvl; /* To iterate through levels */ @@ -176082,17 +188249,17 @@ static void fts5IndexMerge( } #endif - if( nBestpConfig->nAutomerge - && pStruct->aLevel[iBestLvl].nMerge==0 - ){ + if( nBestaLevel[iBestLvl].nMerge==0 ){ break; } + bRet = 1; fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ fts5StructurePromote(p, iBestLvl+1, pStruct); } } *ppStruct = pStruct; + return bRet; } /* @@ -176120,7 +188287,7 @@ static void fts5IndexAutomerge( pStruct->nWriteCounter += nLeaf; nRem = (int)(p->nWorkUnit * nWork * pStruct->nLevel); - fts5IndexMerge(p, ppStruct, nRem); + fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge); } } @@ -176173,17 +188340,6 @@ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ return ret; } -#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \ - assert( (pBuf)->nSpace>=((pBuf)->n+nBlob) ); \ - memcpy(&(pBuf)->p[(pBuf)->n], pBlob, nBlob); \ - (pBuf)->n += nBlob; \ -} - -#define fts5BufferSafeAppendVarint(pBuf, iVal) { \ - (pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \ - assert( (pBuf)->nSpace>=(pBuf)->n ); \ -} - /* ** Flush the contents of in-memory hash table iHash to a new level-0 ** segment on disk. Also update the corresponding structure record. @@ -176201,10 +188357,11 @@ static void fts5FlushOneHash(Fts5Index *p){ ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); iSegid = fts5AllocateSegid(p, pStruct); + fts5StructureInvalidate(p); if( iSegid ){ const int pgsz = p->pConfig->pgsz; - + int eDetail = p->pConfig->eDetail; Fts5StructureSegment *pSeg; /* New segment within pStruct */ Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ @@ -176232,7 +188389,7 @@ static void fts5FlushOneHash(Fts5Index *p){ /* Write the term for this entry to disk. */ sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - fts5WriteAppendTerm(p, &writer, strlen(zTerm), (const u8*)zTerm); + fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm); assert( writer.bFirstRowidInPage==0 ); if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ @@ -176247,16 +188404,11 @@ static void fts5FlushOneHash(Fts5Index *p){ ** loop iterates through the poslists that make up the current ** doclist. */ while( p->rc==SQLITE_OK && iOffp[0], pBuf->n); /* first rowid on page */ + fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); writer.bFirstRowidInPage = 0; fts5WriteDlidxAppend(p, &writer, iRowid); @@ -176265,34 +188417,52 @@ static void fts5FlushOneHash(Fts5Index *p){ } assert( pBuf->n<=pBuf->nSpace ); - if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ - /* The entire poslist will fit on the current leaf. So copy - ** it in one go. */ - fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); - }else{ - /* The entire poslist will not fit on this leaf. So it needs - ** to be broken into sections. The only qualification being - ** that each varint must be stored contiguously. */ - const u8 *pPoslist = &pDoclist[iOff]; - int iPos = 0; - while( p->rc==SQLITE_OK ){ - int nSpace = pgsz - pBuf->n - pPgidx->n; - int n = 0; - if( (nCopy - iPos)<=nSpace ){ - n = nCopy - iPos; - }else{ - n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + if( eDetail==FTS5_DETAIL_NONE ){ + if( iOffp[pBuf->n++] = 0; + iOff++; + if( iOffp[pBuf->n++] = 0; + iOff++; } - assert( n>0 ); - fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); - iPos += n; - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); - } - if( iPos>=nCopy ) break; } + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + }else{ + int bDummy; + int nPos; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); + nCopy += nPos; + if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ + /* The entire poslist will fit on the current leaf. So copy + ** it in one go. */ + fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); + }else{ + /* The entire poslist will not fit on this leaf. So it needs + ** to be broken into sections. The only qualification being + ** that each varint must be stored contiguously. */ + const u8 *pPoslist = &pDoclist[iOff]; + int iPos = 0; + while( p->rc==SQLITE_OK ){ + int nSpace = pgsz - pBuf->n - pPgidx->n; + int n = 0; + if( (nCopy - iPos)<=nSpace ){ + n = nCopy - iPos; + }else{ + n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + } + assert( n>0 ); + fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); + iPos += n; + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + if( iPos>=nCopy ) break; + } + } + iOff += nCopy; } - iOff += nCopy; } } @@ -176338,28 +188508,41 @@ static void fts5IndexFlush(Fts5Index *p){ } } - -static int sqlite3Fts5IndexOptimize(Fts5Index *p){ - Fts5Structure *pStruct; +static Fts5Structure *fts5IndexOptimizeStruct( + Fts5Index *p, + Fts5Structure *pStruct +){ Fts5Structure *pNew = 0; - int nSeg = 0; + int nByte = sizeof(Fts5Structure); + int nSeg = pStruct->nSegment; + int i; - assert( p->rc==SQLITE_OK ); - fts5IndexFlush(p); - pStruct = fts5StructureRead(p); - - if( pStruct ){ - assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); - nSeg = pStruct->nSegment; - if( nSeg>1 ){ - int nByte = sizeof(Fts5Structure); - nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); - pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); + /* Figure out if this structure requires optimization. A structure does + ** not require optimization if either: + ** + ** + it consists of fewer than two segments, or + ** + all segments are on the same level, or + ** + all segments except one are currently inputs to a merge operation. + ** + ** In the first case, return NULL. In the second, increment the ref-count + ** on *pStruct and return a copy of the pointer to it. + */ + if( nSeg<2 ) return 0; + for(i=0; inLevel; i++){ + int nThis = pStruct->aLevel[i].nSeg; + if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){ + fts5StructureRef(pStruct); + return pStruct; } + assert( pStruct->aLevel[i].nMerge<=nThis ); } + + nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); + if( pNew ){ Fts5StructureLevel *pLvl; - int nByte = nSeg * sizeof(Fts5StructureSegment); + nByte = nSeg * sizeof(Fts5StructureSegment); pNew->nLevel = pStruct->nLevel+1; pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; @@ -176368,7 +188551,10 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ if( pLvl->aSeg ){ int iLvl, iSeg; int iSegOut = 0; - for(iLvl=0; iLvlnLevel; iLvl++){ + /* Iterate through all segments, from oldest to newest. Add them to + ** the new Fts5Level object so that pLvl->aSeg[0] is the oldest + ** segment in the data structure. */ + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg]; iSegOut++; @@ -176381,8 +188567,27 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ } } + return pNew; +} + +static int sqlite3Fts5IndexOptimize(Fts5Index *p){ + Fts5Structure *pStruct; + Fts5Structure *pNew = 0; + + assert( p->rc==SQLITE_OK ); + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); + fts5StructureInvalidate(p); + + if( pStruct ){ + pNew = fts5IndexOptimizeStruct(p, pStruct); + } + fts5StructureRelease(pStruct); + + assert( pNew==0 || pNew->nSegment>0 ); if( pNew ){ - int iLvl = pNew->nLevel-1; + int iLvl; + for(iLvl=0; pNew->aLevel[iLvl].nSeg==0; iLvl++){} while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){ int nRem = FTS5_OPT_WORK_UNIT; fts5IndexMergeLevel(p, &pNew, iLvl, &nRem); @@ -176392,240 +188597,61 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ fts5StructureRelease(pNew); } - fts5StructureRelease(pStruct); return fts5IndexReturn(p); } +/* +** This is called to implement the special "VALUES('merge', $nMerge)" +** INSERT command. +*/ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ - Fts5Structure *pStruct; - - pStruct = fts5StructureRead(p); - if( pStruct && pStruct->nLevel ){ - fts5IndexMerge(p, &pStruct, nMerge); - fts5StructureWrite(p, pStruct); + Fts5Structure *pStruct = fts5StructureRead(p); + if( pStruct ){ + int nMin = p->pConfig->nUsermerge; + fts5StructureInvalidate(p); + if( nMerge<0 ){ + Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); + fts5StructureRelease(pStruct); + pStruct = pNew; + nMin = 2; + nMerge = nMerge*-1; + } + if( pStruct && pStruct->nLevel ){ + if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){ + fts5StructureWrite(p, pStruct); + } + } + fts5StructureRelease(pStruct); } - fts5StructureRelease(pStruct); - return fts5IndexReturn(p); } -static void fts5PoslistCallback( - Fts5Index *p, - void *pContext, - const u8 *pChunk, int nChunk -){ - assert_nc( nChunk>=0 ); - if( nChunk>0 ){ - fts5BufferSafeAppendBlob((Fts5Buffer*)pContext, pChunk, nChunk); - } -} - -typedef struct PoslistCallbackCtx PoslistCallbackCtx; -struct PoslistCallbackCtx { - Fts5Buffer *pBuf; /* Append to this buffer */ - Fts5Colset *pColset; /* Restrict matches to this column */ - int eState; /* See above */ -}; - -/* -** TODO: Make this more efficient! -*/ -static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ - int i; - for(i=0; inCol; i++){ - if( pColset->aiCol[i]==iCol ) return 1; - } - return 0; -} - -static void fts5PoslistFilterCallback( - Fts5Index *p, - void *pContext, - const u8 *pChunk, int nChunk -){ - PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; - assert_nc( nChunk>=0 ); - if( nChunk>0 ){ - /* Search through to find the first varint with value 1. This is the - ** start of the next columns hits. */ - int i = 0; - int iStart = 0; - - if( pCtx->eState==2 ){ - int iCol; - fts5FastGetVarint32(pChunk, i, iCol); - if( fts5IndexColsetTest(pCtx->pColset, iCol) ){ - pCtx->eState = 1; - fts5BufferSafeAppendVarint(pCtx->pBuf, 1); - }else{ - pCtx->eState = 0; - } - } - - do { - while( ieState ){ - fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); - } - if( i=nChunk ){ - pCtx->eState = 2; - }else{ - fts5FastGetVarint32(pChunk, i, iCol); - pCtx->eState = fts5IndexColsetTest(pCtx->pColset, iCol); - if( pCtx->eState ){ - fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); - iStart = i; - } - } - } - }while( irc, pBuf, pSeg->nPos) ){ - if( pColset==0 ){ - fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); - }else{ - PoslistCallbackCtx sCtx; - sCtx.pBuf = pBuf; - sCtx.pColset = pColset; - sCtx.eState = pColset ? fts5IndexColsetTest(pColset, 0) : 1; - assert( sCtx.eState==0 || sCtx.eState==1 ); - fts5ChunkIterate(p, pSeg, (void*)&sCtx, fts5PoslistFilterCallback); - } - } -} - -/* -** IN/OUT parameter (*pa) points to a position list n bytes in size. If -** the position list contains entries for column iCol, then (*pa) is set -** to point to the sub-position-list for that column and the number of -** bytes in it returned. Or, if the argument position list does not -** contain any entries for column iCol, return 0. -*/ -static int fts5IndexExtractCol( - const u8 **pa, /* IN/OUT: Pointer to poslist */ - int n, /* IN: Size of poslist in bytes */ - int iCol /* Column to extract from poslist */ -){ - int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ - const u8 *p = *pa; - const u8 *pEnd = &p[n]; /* One byte past end of position list */ - u8 prev = 0; - - while( iCol!=iCurrent ){ - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint */ - while( (prev & 0x80) || *p!=0x01 ){ - prev = *p++; - if( p==pEnd ) return 0; - } - *pa = p++; - p += fts5GetVarint32(p, iCurrent); - } - - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint */ - assert( (prev & 0x80)==0 ); - while( prc. -*/ -static int fts5AppendPoslist( +static void fts5AppendRowid( Fts5Index *p, i64 iDelta, - Fts5IndexIter *pMulti, - Fts5Colset *pColset, + Fts5Iter *pUnused, Fts5Buffer *pBuf ){ - if( p->rc==SQLITE_OK ){ - Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ]; - assert( fts5MultiIterEof(p, pMulti)==0 ); - assert( pSeg->nPos>0 ); - if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+9+9) ){ - int iSv1; - int iSv2; - int iData; - - /* Append iDelta */ - iSv1 = pBuf->n; - fts5BufferSafeAppendVarint(pBuf, iDelta); - - /* WRITEPOSLISTSIZE */ - iSv2 = pBuf->n; - fts5BufferSafeAppendVarint(pBuf, pSeg->nPos*2); - iData = pBuf->n; - - if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf - && (pColset==0 || pColset->nCol==1) - ){ - const u8 *pPos = &pSeg->pLeaf->p[pSeg->iLeafOffset]; - int nPos; - if( pColset ){ - nPos = fts5IndexExtractCol(&pPos, pSeg->nPos, pColset->aiCol[0]); - }else{ - nPos = pSeg->nPos; - } - fts5BufferSafeAppendBlob(pBuf, pPos, nPos); - }else{ - fts5SegiterPoslist(p, pSeg, pColset, pBuf); - } - - if( pColset ){ - int nActual = pBuf->n - iData; - if( nActual!=pSeg->nPos ){ - if( nActual==0 ){ - pBuf->n = iSv1; - return 1; - }else{ - int nReq = sqlite3Fts5GetVarintLen((u32)(nActual*2)); - while( iSv2<(iData-nReq) ){ pBuf->p[iSv2++] = 0x80; } - sqlite3Fts5PutVarint(&pBuf->p[iSv2], nActual*2); - } - } - } - } - } - - return 0; + UNUSED_PARAM(pUnused); + fts5BufferAppendVarint(&p->rc, pBuf, iDelta); } +static void fts5AppendPoslist( + Fts5Index *p, + i64 iDelta, + Fts5Iter *pMulti, + Fts5Buffer *pBuf +){ + int nData = pMulti->base.nData; + assert( nData>0 ); + if( p->rc==SQLITE_OK && 0==fts5BufferGrow(&p->rc, pBuf, nData+9+9) ){ + fts5BufferSafeAppendVarint(pBuf, iDelta); + fts5BufferSafeAppendVarint(pBuf, nData*2); + fts5BufferSafeAppendBlob(pBuf, pMulti->base.pData, nData); + } +} + + static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist; @@ -176686,6 +188712,69 @@ static void fts5MergeAppendDocid( (iLastRowid) = (iRowid); \ } +/* +** Swap the contents of buffer *p1 with that of *p2. +*/ +static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){ + Fts5Buffer tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} + +static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){ + int i = *piOff; + if( i>=pBuf->n ){ + *piOff = -1; + }else{ + u64 iVal; + *piOff = i + sqlite3Fts5GetVarint(&pBuf->p[i], &iVal); + *piRowid += iVal; + } +} + +/* +** This is the equivalent of fts5MergePrefixLists() for detail=none mode. +** In this case the buffers consist of a delta-encoded list of rowids only. +*/ +static void fts5MergeRowidLists( + Fts5Index *p, /* FTS5 backend object */ + Fts5Buffer *p1, /* First list to merge */ + Fts5Buffer *p2 /* Second list to merge */ +){ + int i1 = 0; + int i2 = 0; + i64 iRowid1 = 0; + i64 iRowid2 = 0; + i64 iOut = 0; + + Fts5Buffer out; + memset(&out, 0, sizeof(out)); + sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); + if( p->rc ) return; + + fts5NextRowid(p1, &i1, &iRowid1); + fts5NextRowid(p2, &i2, &iRowid2); + while( i1>=0 || i2>=0 ){ + if( i1>=0 && (i2<0 || iRowid1iOut ); + fts5BufferSafeAppendVarint(&out, iRowid1 - iOut); + iOut = iRowid1; + fts5NextRowid(p1, &i1, &iRowid1); + }else{ + assert( iOut==0 || iRowid2>iOut ); + fts5BufferSafeAppendVarint(&out, iRowid2 - iOut); + iOut = iRowid2; + if( i1>=0 && iRowid1==iRowid2 ){ + fts5NextRowid(p1, &i1, &iRowid1); + } + fts5NextRowid(p2, &i2, &iRowid2); + } + } + + fts5BufferSwap(&out, p1); + fts5BufferFree(&out); +} + /* ** Buffers p1 and p2 contain doclists. This function merges the content ** of the two doclists together and sets buffer p1 to the result before @@ -176703,28 +188792,30 @@ static void fts5MergePrefixLists( i64 iLastRowid = 0; Fts5DoclistIter i1; Fts5DoclistIter i2; - Fts5Buffer out; - Fts5Buffer tmp; - memset(&out, 0, sizeof(out)); - memset(&tmp, 0, sizeof(tmp)); + Fts5Buffer out = {0, 0, 0}; + Fts5Buffer tmp = {0, 0, 0}; - sqlite3Fts5BufferGrow(&p->rc, &out, p1->n + p2->n); + if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n) ) return; fts5DoclistIterInit(p1, &i1); fts5DoclistIterInit(p2, &i2); - while( p->rc==SQLITE_OK && (i1.aPoslist!=0 || i2.aPoslist!=0) ){ - if( i2.aPoslist==0 || (i1.aPoslist && i1.iRowidrc, &tmp, i1.nPoslist + i2.nPoslist); + if( p->rc ) break; sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); + assert( iPos1>=0 && iPos2>=0 ); - while( p->rc==SQLITE_OK && (iPos1>=0 || iPos2>=0) ){ - i64 iNew; - if( iPos2<0 || (iPos1>=0 && iPos1=0 && iPos2>=0 ){ + while( 1 ){ + if( iPos1rc = sqlite3Fts5PoslistWriterAppend(&tmp, &writer, iNew); + } + + if( iPos1>=0 ){ + if( iPos1!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); + } + fts5BufferSafeAppendBlob(&tmp, &a1[iOff1], i1.nPoslist-iOff1); + }else{ + assert( iPos2>=0 && iPos2!=iPrev ); + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); + fts5BufferSafeAppendBlob(&tmp, &a2[iOff2], i2.nPoslist-iOff2); } /* WRITEPOSLISTSIZE */ @@ -176762,81 +188877,105 @@ static void fts5MergePrefixLists( fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); fts5DoclistIterNext(&i1); fts5DoclistIterNext(&i2); + if( i1.aPoslist==0 || i2.aPoslist==0 ) break; } } + if( i1.aPoslist ){ + fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); + fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist); + } + else if( i2.aPoslist ){ + fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); + fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist); + } + fts5BufferSet(&p->rc, p1, out.n, out.p); fts5BufferFree(&tmp); fts5BufferFree(&out); } } -static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){ - Fts5Buffer tmp = *p1; - *p1 = *p2; - *p2 = tmp; -} - static void fts5SetupPrefixIter( Fts5Index *p, /* Index to read from */ int bDesc, /* True for "ORDER BY rowid DESC" */ const u8 *pToken, /* Buffer containing prefix to match */ int nToken, /* Size of buffer pToken in bytes */ Fts5Colset *pColset, /* Restrict matches to these columns */ - Fts5IndexIter **ppIter /* OUT: New iterator */ + Fts5Iter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; Fts5Buffer *aBuf; const int nBuf = 32; + void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*); + void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*); + if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ + xMerge = fts5MergeRowidLists; + xAppend = fts5AppendRowid; + }else{ + xMerge = fts5MergePrefixLists; + xAppend = fts5AppendPoslist; + } + aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); pStruct = fts5StructureRead(p); if( aBuf && pStruct ){ - const int flags = FTS5INDEX_QUERY_SCAN; + const int flags = FTS5INDEX_QUERY_SCAN + | FTS5INDEX_QUERY_SKIPEMPTY + | FTS5INDEX_QUERY_NOOUTPUT; int i; i64 iLastRowid = 0; - Fts5IndexIter *p1 = 0; /* Iterator used to gather data from index */ + Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ Fts5Data *pData; Fts5Buffer doclist; + int bNewTerm = 1; memset(&doclist, 0, sizeof(doclist)); - for(fts5MultiIterNew(p, pStruct, 1, flags, pToken, nToken, -1, 0, &p1); + fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); + fts5IterSetOutputCb(&p->rc, p1); + for( /* no-op */ ; fts5MultiIterEof(p, p1)==0; - fts5MultiIterNext(p, p1, 0, 0) + fts5MultiIterNext2(p, p1, &bNewTerm) ){ - i64 iRowid = fts5MultiIterRowid(p1); - int nTerm; - const u8 *pTerm = fts5MultiIterTerm(p1, &nTerm); - assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); - if( nTermaSeg[ p1->aFirst[1].iFirst ]; + int nTerm = pSeg->term.n; + const u8 *pTerm = pSeg->term.p; + p1->xSetOutputs(p1, pSeg); - if( doclist.n>0 && iRowid<=iLastRowid ){ + assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); + if( bNewTerm ){ + if( nTermbase.nData==0 ) continue; + + if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ assert( ibase.iRowid-iLastRowid, p1, &doclist); + iLastRowid = p1->base.iRowid; } for(i=0; irc==SQLITE_OK ){ - fts5MergePrefixLists(p, &doclist, &aBuf[i]); + xMerge(p, &doclist, &aBuf[i]); } fts5BufferFree(&aBuf[i]); } - fts5MultiIterFree(p, p1); + fts5MultiIterFree(p1); pData = fts5IdxMalloc(p, sizeof(Fts5Data) + doclist.n); if( pData ){ @@ -176862,13 +189001,13 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ /* Allocate the hash table if it has not already been allocated */ if( p->pHash==0 ){ - p->rc = sqlite3Fts5HashNew(&p->pHash, &p->nPendingData); + p->rc = sqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData); } /* Flush the hash table to disk if required */ if( iRowidiWriteRowid || (iRowid==p->iWriteRowid && p->bDelete==0) - || (p->nPendingData > p->nMaxPendingData) + || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } @@ -176897,7 +189036,8 @@ static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){ static int sqlite3Fts5IndexRollback(Fts5Index *p){ fts5CloseReader(p); fts5IndexDiscardData(p); - assert( p->rc==SQLITE_OK ); + fts5StructureInvalidate(p); + /* assert( p->rc==SQLITE_OK ); */ return SQLITE_OK; } @@ -176908,6 +189048,7 @@ static int sqlite3Fts5IndexRollback(Fts5Index *p){ */ static int sqlite3Fts5IndexReinit(Fts5Index *p){ Fts5Structure s; + fts5StructureInvalidate(p); memset(&s, 0, sizeof(Fts5Structure)); fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); fts5StructureWrite(p, &s); @@ -176934,7 +189075,6 @@ static int sqlite3Fts5IndexOpen( if( rc==SQLITE_OK ){ p->pConfig = pConfig; p->nWorkUnit = FTS5_WORK_UNIT; - p->nMaxPendingData = 1024*1024; p->zDataTbl = sqlite3Fts5Mprintf(&rc, "%s_data", pConfig->zName); if( p->zDataTbl && bCreate ){ rc = sqlite3Fts5CreateTable( @@ -176967,11 +189107,13 @@ static int sqlite3Fts5IndexClose(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); @@ -176984,7 +189126,11 @@ 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 fts5IndexCharlenToBytelen(const char *p, int nByte, int nChar){ +static int sqlite3Fts5IndexCharlenToBytelen( + const char *p, + int nByte, + int nChar +){ int n = 0; int i; for(i=0; inPrefix && rc==SQLITE_OK; i++){ - int nByte = fts5IndexCharlenToBytelen(pToken, nToken, pConfig->aPrefix[i]); + const int nChar = pConfig->aPrefix[i]; + int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); if( nByte ){ rc = sqlite3Fts5HashWrite(p->pHash, - p->iWriteRowid, iCol, iPos, FTS5_MAIN_PREFIX+i+1, pToken, nByte + p->iWriteRowid, iCol, iPos, (char)(FTS5_MAIN_PREFIX+i+1), pToken, + nByte ); } } @@ -177064,24 +189212,27 @@ static int sqlite3Fts5IndexQuery( Fts5IndexIter **ppIter /* OUT: New iterator object */ ){ Fts5Config *pConfig = p->pConfig; - Fts5IndexIter *pRet = 0; - int iIdx = 0; + Fts5Iter *pRet = 0; Fts5Buffer buf = {0, 0, 0}; /* If the QUERY_SCAN flag is set, all other flags must be clear. */ - assert( (flags & FTS5INDEX_QUERY_SCAN)==0 - || (flags & FTS5INDEX_QUERY_SCAN)==FTS5INDEX_QUERY_SCAN - ); + assert( (flags & FTS5INDEX_QUERY_SCAN)==0 || flags==FTS5INDEX_QUERY_SCAN ); - if( sqlite3Fts5BufferGrow(&p->rc, &buf, nToken+1)==0 ){ + if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ + int iIdx = 0; /* Index to search */ memcpy(&buf.p[1], pToken, nToken); -#ifdef SQLITE_DEBUG - /* If the QUERY_TEST_NOIDX flag was specified, then this must be a + /* 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 + ** greater than pConfig->nPrefix to indicate that the query will be + ** satisfied by scanning multiple terms in the main index. + ** + ** If the QUERY_TEST_NOIDX flag was specified, then this must be a ** prefix-query. Instead of using a prefix-index (if one exists), ** evaluate the prefix query using the main FTS index. This is used ** for internal sanity checking by the integrity-check in debug ** mode only. */ +#ifdef SQLITE_DEBUG if( pConfig->bPrefixIndex==0 || (flags & FTS5INDEX_QUERY_TEST_NOIDX) ){ assert( flags & FTS5INDEX_QUERY_PREFIX ); iIdx = 1+pConfig->nPrefix; @@ -177095,24 +189246,35 @@ static int sqlite3Fts5IndexQuery( } if( iIdx<=pConfig->nPrefix ){ + /* Straight index lookup */ Fts5Structure *pStruct = fts5StructureRead(p); - buf.p[0] = FTS5_MAIN_PREFIX + iIdx; + buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); if( pStruct ){ - fts5MultiIterNew(p, pStruct, 1, flags, buf.p, nToken+1, -1, 0, &pRet); + fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, + pColset, buf.p, nToken+1, -1, 0, &pRet + ); fts5StructureRelease(pStruct); } }else{ + /* Scan multiple terms in the main index */ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; buf.p[0] = FTS5_MAIN_PREFIX; fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet); + assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); + fts5IterSetOutputCb(&p->rc, pRet); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; + if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + } } if( p->rc ){ - sqlite3Fts5IterClose(pRet); + sqlite3Fts5IterClose(&pRet->base); pRet = 0; fts5CloseReader(p); } - *ppIter = pRet; + + *ppIter = &pRet->base; sqlite3Fts5BufferFree(&buf); } return fts5IndexReturn(p); @@ -177121,15 +189283,11 @@ static int sqlite3Fts5IndexQuery( /* ** Return true if the iterator passed as the only argument is at EOF. */ -static int sqlite3Fts5IterEof(Fts5IndexIter *pIter){ - assert( pIter->pIndex->rc==SQLITE_OK ); - return pIter->bEof; -} - /* ** Move to the next matching rowid. */ -static int sqlite3Fts5IterNext(Fts5IndexIter *pIter){ +static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; assert( pIter->pIndex->rc==SQLITE_OK ); fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); return fts5IndexReturn(pIter->pIndex); @@ -177138,7 +189296,8 @@ static int sqlite3Fts5IterNext(Fts5IndexIter *pIter){ /* ** Move to the next matching term/rowid. Used by the fts5vocab module. */ -static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ +static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; Fts5Index *p = pIter->pIndex; assert( pIter->pIndex->rc==SQLITE_OK ); @@ -177149,7 +189308,7 @@ static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ if( pSeg->pLeaf && pSeg->term.p[0]!=FTS5_MAIN_PREFIX ){ fts5DataRelease(pSeg->pLeaf); pSeg->pLeaf = 0; - pIter->bEof = 1; + pIter->base.bEof = 1; } } @@ -177161,111 +189320,30 @@ static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIter){ ** definition of "at or after" depends on whether this iterator iterates ** in ascending or descending rowid order. */ -static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){ +static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); return fts5IndexReturn(pIter->pIndex); } -/* -** Return the current rowid. -*/ -static i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){ - return fts5MultiIterRowid(pIter); -} - /* ** Return the current term. */ -static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIter, int *pn){ +static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ int n; - const char *z = (const char*)fts5MultiIterTerm(pIter, &n); + const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); *pn = n-1; return &z[1]; } - -static int fts5IndexExtractColset ( - 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; inCol; i++){ - const u8 *pSub = pPos; - int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); - if( nSub ){ - fts5BufferAppendBlob(&rc, pBuf, nSub, pSub); - } - } - return rc; -} - - -/* -** Return a pointer to a buffer containing a copy of the position list for -** the current entry. Output variable *pn is set to the size of the buffer -** in bytes before returning. -** -** The returned position list does not include the "number of bytes" varint -** field that starts the position list on disk. -*/ -static int sqlite3Fts5IterPoslist( - Fts5IndexIter *pIter, - Fts5Colset *pColset, /* Column filter (or NULL) */ - const u8 **pp, /* OUT: Pointer to position-list data */ - int *pn, /* OUT: Size of position-list in bytes */ - i64 *piRowid /* OUT: Current rowid */ -){ - Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; - assert( pIter->pIndex->rc==SQLITE_OK ); - *piRowid = pSeg->iRowid; - if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ - u8 *pPos = &pSeg->pLeaf->p[pSeg->iLeafOffset]; - if( pColset==0 || pIter->bFiltered ){ - *pn = pSeg->nPos; - *pp = pPos; - }else if( pColset->nCol==1 ){ - *pp = pPos; - *pn = fts5IndexExtractCol(pp, pSeg->nPos, pColset->aiCol[0]); - }else{ - fts5BufferZero(&pIter->poslist); - fts5IndexExtractColset(pColset, pPos, pSeg->nPos, &pIter->poslist); - *pp = pIter->poslist.p; - *pn = pIter->poslist.n; - } - }else{ - fts5BufferZero(&pIter->poslist); - fts5SegiterPoslist(pIter->pIndex, pSeg, pColset, &pIter->poslist); - *pp = pIter->poslist.p; - *pn = pIter->poslist.n; - } - return fts5IndexReturn(pIter->pIndex); -} - -/* -** This function is similar to sqlite3Fts5IterPoslist(), except that it -** copies the position list into the buffer supplied as the second -** argument. -*/ -static int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf){ - Fts5Index *p = pIter->pIndex; - Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; - assert( p->rc==SQLITE_OK ); - fts5BufferZero(pBuf); - fts5SegiterPoslist(p, pSeg, 0, pBuf); - return fts5IndexReturn(p); -} - /* ** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). */ -static void sqlite3Fts5IterClose(Fts5IndexIter *pIter){ - if( pIter ){ +static void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ + if( pIndexIter ){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; Fts5Index *pIndex = pIter->pIndex; - fts5MultiIterFree(pIter->pIndex, pIter); + fts5MultiIterFree(pIter); fts5CloseReader(pIndex); } } @@ -177358,7 +189436,7 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ /* ** Return a simple checksum value based on the arguments. */ -static u64 fts5IndexEntryCksum( +static u64 sqlite3Fts5IndexEntryCksum( i64 iRowid, int iCol, int iPos, @@ -177428,30 +189506,32 @@ static int fts5QueryCksum( int flags, /* Flags for Fts5IndexQuery */ u64 *pCksum /* IN/OUT: Checksum value */ ){ + int eDetail = p->pConfig->eDetail; u64 cksum = *pCksum; - Fts5IndexIter *pIdxIter = 0; - int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIdxIter); + Fts5IndexIter *pIter = 0; + int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); - while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){ - i64 dummy; - const u8 *pPos; - int nPos; - i64 rowid = sqlite3Fts5IterRowid(pIdxIter); - rc = sqlite3Fts5IterPoslist(pIdxIter, 0, &pPos, &nPos, &dummy); - if( rc==SQLITE_OK ){ + while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){ + i64 rowid = pIter->iRowid; + + if( eDetail==FTS5_DETAIL_NONE ){ + cksum ^= sqlite3Fts5IndexEntryCksum(rowid, 0, 0, iIdx, z, n); + }else{ Fts5PoslistReader sReader; - for(sqlite3Fts5PoslistReaderInit(pPos, nPos, &sReader); + for(sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &sReader); sReader.bEof==0; sqlite3Fts5PoslistReaderNext(&sReader) ){ int iCol = FTS5_POS2COLUMN(sReader.iPos); int iOff = FTS5_POS2OFFSET(sReader.iPos); - cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n); + cksum ^= sqlite3Fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n); } - rc = sqlite3Fts5IterNext(pIdxIter); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IterNext(pIter); } } - sqlite3Fts5IterClose(pIdxIter); + sqlite3Fts5IterClose(pIter); *pCksum = cksum; return rc; @@ -177670,7 +189750,6 @@ static void fts5IndexIntegrityCheckSegment( fts5DataRelease(pLeaf); if( p->rc ) break; - /* Now check that the iter.nEmpty leaves following the current leaf ** (a) exist and (b) contain no terms. */ fts5IndexIntegrityCheckEmpty( @@ -177746,7 +189825,7 @@ 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 fts5IndexEntryCksum() is cksum. +** as calculated by sqlite3Fts5IndexEntryCksum() 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 @@ -177754,14 +189833,18 @@ static void fts5IndexIntegrityCheckSegment( ** occurs. */ static int sqlite3Fts5IndexIntegrityCheck(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 */ - Fts5IndexIter *pIter; /* Used to iterate through entire index */ + Fts5Iter *pIter; /* Used to iterate through entire index */ Fts5Structure *pStruct; /* Index structure */ +#ifdef SQLITE_DEBUG /* Used by extra internal tests only run if NDEBUG is not defined */ u64 cksum3 = 0; /* Checksum based on contents of indexes */ Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ +#endif + const int flags = FTS5INDEX_QUERY_NOOUTPUT; /* Load the FTS index structure */ pStruct = fts5StructureRead(p); @@ -177790,7 +189873,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ ** same term is performed. cksum3 is calculated based on the entries ** extracted by these queries. */ - for(fts5MultiIterNew(p, pStruct, 0, 0, 0, 0, -1, 0, &pIter); + for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, -1, 0, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter, 0, 0) ){ @@ -177803,53 +189886,33 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ /* If this is a new term, query for it. Update cksum3 with the results. */ fts5TestTerm(p, &term, z, n, cksum2, &cksum3); - poslist.n = 0; - fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst] , 0, &poslist); - while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){ - int iCol = FTS5_POS2COLUMN(iPos); - int iTokOff = FTS5_POS2OFFSET(iPos); - cksum2 ^= fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n); + if( eDetail==FTS5_DETAIL_NONE ){ + if( 0==fts5MultiIterIsEmpty(p, pIter) ){ + cksum2 ^= sqlite3Fts5IndexEntryCksum(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) ){ + int iCol = FTS5_POS2COLUMN(iPos); + int iTokOff = FTS5_POS2OFFSET(iPos); + cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n); + } } } fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); - fts5MultiIterFree(p, pIter); + fts5MultiIterFree(pIter); if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT; fts5StructureRelease(pStruct); +#ifdef SQLITE_DEBUG fts5BufferFree(&term); +#endif fts5BufferFree(&poslist); return fts5IndexReturn(p); } - -/* -** Calculate and return a checksum that is the XOR of the index entry -** checksum of all entries that would be generated by the token specified -** by the final 5 arguments. -*/ -static u64 sqlite3Fts5IndexCksum( - Fts5Config *pConfig, /* Configuration object */ - i64 iRowid, /* Document term appears in */ - int iCol, /* Column term appears in */ - int iPos, /* Position term appears in */ - const char *pTerm, int nTerm /* Term at iPos */ -){ - u64 ret = 0; /* Return value */ - int iIdx; /* For iterating through indexes */ - - ret = fts5IndexEntryCksum(iRowid, iCol, iPos, 0, pTerm, nTerm); - - for(iIdx=0; iIdxnPrefix; iIdx++){ - int nByte = fts5IndexCharlenToBytelen(pTerm, nTerm, pConfig->aPrefix[iIdx]); - if( nByte ){ - ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, iIdx+1, pTerm, nByte); - } - } - - return ret; -} - /************************************************************************* ************************************************************************** ** Below this point is the implementation of the fts5_decode() scalar @@ -178002,8 +190065,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } while( iOffpStruct==0 || p->iStructVersion!=0 ); + if( fts5IndexDataVersion(p)!=p->iStructVersion ){ + fts5StructureInvalidate(p); + } + return fts5IndexReturn(p); +} + /* ** 2014 Jun 09 ** @@ -178224,6 +190395,7 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){ */ +/* #include "fts5Int.h" */ /* ** This variable is set to false when running tests for which the on disk @@ -178429,12 +190601,13 @@ struct Fts5Cursor { /* ** Values for Fts5Cursor.csrflags */ -#define FTS5CSR_REQUIRE_CONTENT 0x01 -#define FTS5CSR_REQUIRE_DOCSIZE 0x02 -#define FTS5CSR_REQUIRE_INST 0x04 -#define FTS5CSR_EOF 0x08 +#define FTS5CSR_EOF 0x01 +#define FTS5CSR_REQUIRE_CONTENT 0x02 +#define FTS5CSR_REQUIRE_DOCSIZE 0x04 +#define FTS5CSR_REQUIRE_INST 0x08 #define FTS5CSR_FREE_ZRANK 0x10 #define FTS5CSR_REQUIRE_RESEEK 0x20 +#define FTS5CSR_REQUIRE_POSLIST 0x40 #define BitFlagAllTest(x,y) (((x) & (y))==(y)) #define BitFlagTest(x,y) (((x) & (y))!=0) @@ -178604,6 +190777,15 @@ static int fts5InitVtab( rc = sqlite3Fts5ConfigDeclareVtab(pConfig); } + /* Load the initial configuration */ + if( rc==SQLITE_OK ){ + assert( pConfig->pzErrmsg==0 ); + pConfig->pzErrmsg = pzErr; + rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex); + sqlite3Fts5IndexRollback(pTab->pIndex); + pConfig->pzErrmsg = 0; + } + if( rc!=SQLITE_OK ){ fts5FreeVtab(pTab); pTab = 0; @@ -178656,7 +190838,10 @@ static int fts5CreateMethod( */ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ #if SQLITE_VERSION_NUMBER>=3008012 - if( sqlite3_libversion_number()>=3008012 ){ +#ifndef SQLITE_CORE + if( sqlite3_libversion_number()>=3008012 ) +#endif + { pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; } #endif @@ -178734,7 +190919,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; int j; - for(j=0; jiColumn==aColMap[pC->iCol] && p->op & pC->op ){ if( p->usable ){ @@ -178781,11 +190966,11 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* Assign argvIndex values to each constraint in use. */ iNext = 1; - for(i=0; iiConsIndex>=0 ){ pInfo->aConstraintUsage[pC->iConsIndex].argvIndex = iNext++; - pInfo->aConstraintUsage[pC->iConsIndex].omit = pC->omit; + pInfo->aConstraintUsage[pC->iConsIndex].omit = (unsigned char)pC->omit; } } @@ -178793,27 +190978,38 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ return SQLITE_OK; } +static int fts5NewTransaction(Fts5Table *pTab){ + Fts5Cursor *pCsr; + for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ + if( pCsr->base.pVtab==(sqlite3_vtab*)pTab ) return SQLITE_OK; + } + return sqlite3Fts5StorageReset(pTab->pStorage); +} + /* ** Implementation of xOpen method. */ static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ Fts5Table *pTab = (Fts5Table*)pVTab; Fts5Config *pConfig = pTab->pConfig; - Fts5Cursor *pCsr; /* New cursor object */ + Fts5Cursor *pCsr = 0; /* New cursor object */ int nByte; /* Bytes of space to allocate */ - int rc = SQLITE_OK; /* Return code */ + int rc; /* Return code */ - nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int); - pCsr = (Fts5Cursor*)sqlite3_malloc(nByte); - if( pCsr ){ - Fts5Global *pGlobal = pTab->pGlobal; - memset(pCsr, 0, nByte); - pCsr->aColumnSize = (int*)&pCsr[1]; - pCsr->pNext = pGlobal->pCsr; - pGlobal->pCsr = pCsr; - pCsr->iCsrId = ++pGlobal->iNextId; - }else{ - rc = SQLITE_NOMEM; + rc = fts5NewTransaction(pTab); + if( rc==SQLITE_OK ){ + nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int); + pCsr = (Fts5Cursor*)sqlite3_malloc(nByte); + if( pCsr ){ + Fts5Global *pGlobal = pTab->pGlobal; + memset(pCsr, 0, nByte); + pCsr->aColumnSize = (int*)&pCsr[1]; + pCsr->pNext = pGlobal->pCsr; + pGlobal->pCsr = pCsr; + pCsr->iCsrId = ++pGlobal->iNextId; + }else{ + rc = SQLITE_NOMEM; + } } *ppCsr = (sqlite3_vtab_cursor*)pCsr; return rc; @@ -178836,6 +191032,7 @@ static void fts5CsrNewrow(Fts5Cursor *pCsr){ FTS5CSR_REQUIRE_CONTENT | FTS5CSR_REQUIRE_DOCSIZE | FTS5CSR_REQUIRE_INST + | FTS5CSR_REQUIRE_POSLIST ); } @@ -178918,15 +191115,18 @@ static int fts5SorterNext(Fts5Cursor *pCsr){ nBlob = sqlite3_column_bytes(pSorter->pStmt, 1); aBlob = a = sqlite3_column_blob(pSorter->pStmt, 1); - for(i=0; i<(pSorter->nIdx-1); i++){ - int iVal; - a += fts5GetVarint32(a, iVal); - iOff += iVal; - pSorter->aIdx[i] = iOff; + /* nBlob==0 in detail=none mode. */ + if( nBlob>0 ){ + for(i=0; i<(pSorter->nIdx-1); i++){ + int iVal; + a += fts5GetVarint32(a, iVal); + iOff += iVal; + pSorter->aIdx[i] = iOff; + } + pSorter->aIdx[i] = &aBlob[nBlob] - a; + pSorter->aPoslist = a; } - pSorter->aIdx[i] = &aBlob[nBlob] - a; - pSorter->aPoslist = a; fts5CsrNewrow(pCsr); } @@ -178970,7 +191170,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, iRowid, bDesc); - if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ + if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ *pbSkip = 1; } @@ -178978,6 +191178,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ fts5CsrNewrow(pCsr); if( sqlite3Fts5ExprEof(pCsr->pExpr) ){ CsrFlagSet(pCsr, FTS5CSR_EOF); + *pbSkip = 1; } } return rc; @@ -178994,24 +191195,24 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ */ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; - int rc = SQLITE_OK; + int rc; assert( (pCsr->ePlan<3)== (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) ); + assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); if( pCsr->ePlan<3 ){ int bSkip = 0; if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid); - if( sqlite3Fts5ExprEof(pCsr->pExpr) ){ - CsrFlagSet(pCsr, FTS5CSR_EOF); - } + CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr)); fts5CsrNewrow(pCsr); }else{ switch( pCsr->ePlan ){ case FTS5_PLAN_SPECIAL: { CsrFlagSet(pCsr, FTS5CSR_EOF); + rc = SQLITE_OK; break; } @@ -179035,13 +191236,41 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ return rc; } + +static int fts5PrepareStatement( + sqlite3_stmt **ppStmt, + Fts5Config *pConfig, + const char *zFmt, + ... +){ + sqlite3_stmt *pRet = 0; + int rc; + char *zSql; + va_list ap; + + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pRet, 0); + if( rc!=SQLITE_OK ){ + *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); + } + sqlite3_free(zSql); + } + + va_end(ap); + *ppStmt = pRet; + return rc; +} + static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){ Fts5Config *pConfig = pTab->pConfig; Fts5Sorter *pSorter; int nPhrase; int nByte; - int rc = SQLITE_OK; - char *zSql; + int rc; const char *zRank = pCsr->zRank; const char *zRankArgs = pCsr->zRankArgs; @@ -179059,17 +191288,13 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){ ** table, saving it creates a circular reference. ** ** If SQLite a built-in statement cache, this wouldn't be a problem. */ - zSql = sqlite3Fts5Mprintf(&rc, + rc = fts5PrepareStatement(&pSorter->pStmt, pConfig, "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s", pConfig->zDb, pConfig->zName, zRank, pConfig->zName, (zRankArgs ? ", " : ""), (zRankArgs ? zRankArgs : ""), bDesc ? "DESC" : "ASC" ); - if( zSql ){ - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pSorter->pStmt, 0); - sqlite3_free(zSql); - } pCsr->pSorter = pSorter; if( rc==SQLITE_OK ){ @@ -179263,7 +191488,7 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){ static int fts5FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ - const char *idxStr, /* Unused */ + const char *zUnused, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ @@ -179281,6 +191506,9 @@ static int fts5FilterMethod( sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ char **pzErrmsg = pConfig->pzErrmsg; + UNUSED_PARAM(zUnused); + UNUSED_PARAM(nVal); + if( pCsr->ePlan ){ fts5FreeCursorComponents(pCsr); memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr)); @@ -179339,6 +191567,7 @@ static int fts5FilterMethod( pCsr->ePlan = FTS5_PLAN_SOURCE; pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); + sqlite3Fts5ExprClearEof(pCsr->pExpr); }else if( pMatch ){ const char *zExpr = (const char*)sqlite3_value_text(apVal[0]); if( zExpr==0 ) zExpr = ""; @@ -179564,14 +191793,13 @@ static int fts5SpecialInsert( static int fts5SpecialDelete( Fts5Table *pTab, - sqlite3_value **apVal, - sqlite3_int64 *piRowid + sqlite3_value **apVal ){ int rc = SQLITE_OK; int eType1 = sqlite3_value_type(apVal[1]); if( eType1==SQLITE_INTEGER ){ sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]); - rc = sqlite3Fts5StorageSpecialDelete(pTab->pStorage, iDel, &apVal[2]); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]); } return rc; } @@ -179641,7 +191869,7 @@ static int fts5UpdateMethod( if( pConfig->eContent!=FTS5_CONTENT_NORMAL && 0==sqlite3_stricmp("delete", z) ){ - rc = fts5SpecialDelete(pTab, apVal, pRowid); + rc = fts5SpecialDelete(pTab, apVal); }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } @@ -179657,7 +191885,10 @@ static int fts5UpdateMethod( ** ** Cases 3 and 4 may violate the rowid constraint. */ - int eConflict = sqlite3_vtab_on_conflict(pConfig->db); + int eConflict = SQLITE_ABORT; + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + eConflict = sqlite3_vtab_on_conflict(pConfig->db); + } assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); assert( nArg!=1 || eType0==SQLITE_INTEGER ); @@ -179672,46 +191903,46 @@ static int fts5UpdateMethod( rc = SQLITE_ERROR; } - /* Case 1: DELETE */ + /* DELETE */ else if( nArg==1 ){ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); } - /* Case 2: INSERT */ + /* 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); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); } fts5StorageInsert(&rc, pTab, apVal, pRowid); } - /* Case 2: UPDATE */ + /* 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); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); } fts5StorageInsert(&rc, pTab, apVal, pRowid); }else{ rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid); } } }else{ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); fts5StorageInsert(&rc, pTab, apVal, pRowid); } } @@ -179740,6 +191971,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){ */ static int fts5BeginMethod(sqlite3_vtab *pVtab){ fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0); + fts5NewTransaction((Fts5Table*)pVtab); return SQLITE_OK; } @@ -179749,6 +191981,7 @@ static int fts5BeginMethod(sqlite3_vtab *pVtab){ ** by fts5SyncMethod(). */ static int fts5CommitMethod(sqlite3_vtab *pVtab){ + UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_COMMIT, 0); return SQLITE_OK; } @@ -179765,6 +191998,8 @@ static int fts5RollbackMethod(sqlite3_vtab *pVtab){ return rc; } +static int fts5CsrPoslist(Fts5Cursor*, int, const u8**, int*); + static void *fts5ApiUserData(Fts5Context *pCtx){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; return pCsr->pAux->pUserData; @@ -179814,17 +192049,72 @@ static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){ return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase); } -static int fts5CsrPoslist(Fts5Cursor *pCsr, int iPhrase, const u8 **pa){ - int n; - if( pCsr->pSorter ){ +static int fts5ApiColumnText( + Fts5Context *pCtx, + int iCol, + const char **pz, + int *pn +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + if( fts5IsContentless((Fts5Table*)(pCsr->base.pVtab)) ){ + *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); + } + } + return rc; +} + +static int fts5CsrPoslist( + Fts5Cursor *pCsr, + int iPhrase, + const u8 **pa, + int *pn +){ + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + int rc = SQLITE_OK; + int bLive = (pCsr->pSorter==0); + + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ + + if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5PoslistPopulator *aPopulator; + int i; + aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); + if( aPopulator==0 ) rc = SQLITE_NOMEM; + for(i=0; inCol && rc==SQLITE_OK; i++){ + int n; const char *z; + rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprPopulatePoslists( + pConfig, pCsr->pExpr, aPopulator, i, z, n + ); + } + } + sqlite3_free(aPopulator); + + if( pCsr->pSorter ){ + sqlite3Fts5ExprCheckPoslists(pCsr->pExpr, pCsr->pSorter->iRowid); + } + } + CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); + } + + if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ Fts5Sorter *pSorter = pCsr->pSorter; int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - n = pSorter->aIdx[iPhrase] - i1; + *pn = pSorter->aIdx[iPhrase] - i1; *pa = &pSorter->aPoslist[i1]; }else{ - n = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); } - return n; + + return rc; } /* @@ -179849,43 +192139,48 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ int i; /* Initialize all iterators */ - for(i=0; i=pCsr->nInstAlloc ){ - pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; - aInst = (int*)sqlite3_realloc( - pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3 - ); - if( aInst ){ - pCsr->aInst = aInst; - }else{ - rc = SQLITE_NOMEM; - break; + nInst++; + if( nInst>=pCsr->nInstAlloc ){ + pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; + aInst = (int*)sqlite3_realloc( + pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3 + ); + if( aInst ){ + pCsr->aInst = aInst; + }else{ + rc = SQLITE_NOMEM; + break; + } } - } - aInst = &pCsr->aInst[3 * (nInst-1)]; - aInst[0] = iBest; - aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos); - aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos); - sqlite3Fts5PoslistReaderNext(&aIter[iBest]); + aInst = &pCsr->aInst[3 * (nInst-1)]; + aInst[0] = iBest; + aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos); + aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos); + sqlite3Fts5PoslistReaderNext(&aIter[iBest]); + } } pCsr->nInstCount = nInst; @@ -179918,6 +192213,12 @@ static int fts5ApiInst( ){ if( iIdx<0 || iIdx>=pCsr->nInstCount ){ rc = SQLITE_RANGE; +#if 0 + }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ + *piPhrase = pCsr->aInst[iIdx*3]; + *piCol = pCsr->aInst[iIdx*3 + 2]; + *piOff = -1; +#endif }else{ *piPhrase = pCsr->aInst[iIdx*3]; *piCol = pCsr->aInst[iIdx*3 + 1]; @@ -179931,36 +192232,17 @@ static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){ return fts5CursorRowid((Fts5Cursor*)pCtx); } -static int fts5ApiColumnText( - Fts5Context *pCtx, - int iCol, - const char **pz, - int *pn -){ - int rc = SQLITE_OK; - Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - if( fts5IsContentless((Fts5Table*)(pCsr->base.pVtab)) ){ - *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); - } - } - return rc; -} - static int fts5ColumnSizeCb( void *pContext, /* Pointer to int */ int tflags, - const char *pToken, /* Buffer containing token */ - int nToken, /* Size of token in bytes */ - int iStart, /* Start offset of token */ - int iEnd /* End offset of token */ + const char *pUnused, /* Buffer containing token */ + int nUnused, /* Size of token in bytes */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ ){ int *pCnt = (int*)pContext; + UNUSED_PARAM2(pUnused, nUnused); + UNUSED_PARAM2(iUnused1, iUnused2); if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){ (*pCnt)++; } @@ -180076,10 +192358,11 @@ static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){ } static void fts5ApiPhraseNext( - Fts5Context *pCtx, + Fts5Context *pUnused, Fts5PhraseIter *pIter, int *piCol, int *piOff ){ + UNUSED_PARAM(pUnused); if( pIter->a>=pIter->b ){ *piCol = -1; *piOff = -1; @@ -180096,20 +192379,98 @@ static void fts5ApiPhraseNext( } } -static void fts5ApiPhraseFirst( +static int fts5ApiPhraseFirst( Fts5Context *pCtx, int iPhrase, Fts5PhraseIter *pIter, int *piCol, int *piOff ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - int n = fts5CsrPoslist(pCsr, iPhrase, &pIter->a); - pIter->b = &pIter->a[n]; - *piCol = 0; - *piOff = 0; - fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); + int n; + int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); + if( rc==SQLITE_OK ){ + pIter->b = &pIter->a[n]; + *piCol = 0; + *piOff = 0; + fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); + } + return rc; } +static void fts5ApiPhraseNextColumn( + Fts5Context *pCtx, + Fts5PhraseIter *pIter, + int *piCol +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + if( pIter->a>=pIter->b ){ + *piCol = -1; + }else{ + int iIncr; + pIter->a += fts5GetVarint32(&pIter->a[0], iIncr); + *piCol += (iIncr-2); + } + }else{ + while( 1 ){ + int dummy; + if( pIter->a>=pIter->b ){ + *piCol = -1; + return; + } + if( pIter->a[0]==0x01 ) break; + pIter->a += fts5GetVarint32(pIter->a, dummy); + } + pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); + } +} + +static int fts5ApiPhraseFirstColumn( + Fts5Context *pCtx, + int iPhrase, + Fts5PhraseIter *pIter, + int *piCol +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int n; + if( pSorter ){ + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + n = pSorter->aIdx[iPhrase] - i1; + pIter->a = &pSorter->aPoslist[i1]; + }else{ + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); + } + if( rc==SQLITE_OK ){ + pIter->b = &pIter->a[n]; + *piCol = 0; + fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + } + }else{ + int n; + rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); + if( rc==SQLITE_OK ){ + pIter->b = &pIter->a[n]; + if( n<=0 ){ + *piCol = -1; + }else if( pIter->a[0]==0x01 ){ + pIter->a += 1 + fts5GetVarint32(&pIter->a[1], *piCol); + }else{ + *piCol = 0; + } + } + } + + return rc; +} + + static int fts5ApiQueryPhrase(Fts5Context*, int, void*, int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) ); @@ -180133,9 +192494,10 @@ static const Fts5ExtensionApi sFts5Api = { fts5ApiGetAuxdata, fts5ApiPhraseFirst, fts5ApiPhraseNext, + fts5ApiPhraseFirstColumn, + fts5ApiPhraseNextColumn, }; - /* ** Implementation of API function xQueryPhrase(). */ @@ -180152,12 +192514,11 @@ static int fts5ApiQueryPhrase( rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew); if( rc==SQLITE_OK ){ - Fts5Config *pConf = pTab->pConfig; pNew->ePlan = FTS5_PLAN_MATCH; pNew->iFirstRowid = SMALLEST_INT64; pNew->iLastRowid = LARGEST_INT64; pNew->base.pVtab = (sqlite3_vtab*)pTab; - rc = sqlite3Fts5ExprClonePhrase(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr); + rc = sqlite3Fts5ExprClonePhrase(pCsr->pExpr, iPhrase, &pNew->pExpr); } if( rc==SQLITE_OK ){ @@ -180227,20 +192588,20 @@ static void fts5ApiCallback( ** Given cursor id iId, return a pointer to the corresponding Fts5Index ** object. Or NULL If the cursor id does not exist. ** -** If successful, set *pnCol to the number of indexed columns in the -** table before returning. +** If successful, set *ppConfig to point to the associated config object +** before returning. */ static Fts5Index *sqlite3Fts5IndexFromCsrid( - Fts5Global *pGlobal, - i64 iCsrId, - int *pnCol + Fts5Global *pGlobal, /* FTS5 global context for db handle */ + i64 iCsrId, /* Id of cursor to find */ + Fts5Config **ppConfig /* OUT: Configuration object */ ){ Fts5Cursor *pCsr; Fts5Table *pTab; pCsr = fts5CursorFromCsrid(pGlobal, iCsrId); pTab = (Fts5Table*)pCsr->base.pVtab; - *pnCol = pTab->pConfig->nCol; + *ppConfig = pTab->pConfig; return pTab->pIndex; } @@ -180267,20 +192628,46 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){ Fts5Buffer val; memset(&val, 0, sizeof(Fts5Buffer)); + switch( ((Fts5Table*)(pCsr->base.pVtab))->pConfig->eDetail ){ + case FTS5_DETAIL_FULL: - /* Append the varints */ - for(i=0; i<(nPhrase-1); i++){ - const u8 *dummy; - int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy); - sqlite3Fts5BufferAppendVarint(&rc, &val, nByte); - } + /* Append the varints */ + for(i=0; i<(nPhrase-1); i++){ + const u8 *dummy; + int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy); + sqlite3Fts5BufferAppendVarint(&rc, &val, nByte); + } - /* Append the position lists */ - for(i=0; ipExpr, i, &pPoslist); - sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); + /* Append the position lists */ + for(i=0; ipExpr, i, &pPoslist); + sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); + } + break; + + case FTS5_DETAIL_COLUMNS: + + /* Append the varints */ + 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); + } + + /* Append the position lists */ + for(i=0; rc==SQLITE_OK && ipExpr, i, &pPoslist, &nPoslist); + sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist); + } + break; + + default: + break; } sqlite3_result_blob(pCtx, val.p, val.n, sqlite3_free); @@ -180344,7 +192731,7 @@ static int fts5ColumnMethod( */ static int fts5FindFunctionMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ - int nArg, /* Number of SQL function arguments */ + int nUnused, /* Number of SQL function arguments */ const char *zName, /* Name of SQL function */ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ void **ppArg /* OUT: User data for *pxFunc */ @@ -180352,6 +192739,7 @@ static int fts5FindFunctionMethod( Fts5Table *pTab = (Fts5Table*)pVtab; Fts5Auxiliary *pAux; + UNUSED_PARAM(nUnused); pAux = fts5FindAuxiliary(pTab, zName); if( pAux ){ *pxFunc = fts5ApiCallback; @@ -180381,6 +192769,7 @@ static int fts5RenameMethod( */ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5Table *pTab = (Fts5Table*)pVtab; + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); fts5TripCursors(pTab); return sqlite3Fts5StorageSync(pTab->pStorage, 0); @@ -180393,6 +192782,7 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ */ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5Table *pTab = (Fts5Table*)pVtab; + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint); fts5TripCursors(pTab); return sqlite3Fts5StorageSync(pTab->pStorage, 0); @@ -180405,6 +192795,7 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ */ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5Table *pTab = (Fts5Table*)pVtab; + UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); return sqlite3Fts5StorageRollback(pTab->pStorage); @@ -180584,10 +192975,11 @@ static void fts5ModuleDestroy(void *pCtx){ static void fts5Fts5Func( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args */ - sqlite3_value **apVal /* Function arguments */ + sqlite3_value **apUnused /* 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)); @@ -180600,10 +192992,11 @@ static void fts5Fts5Func( static void fts5SourceIdFunc( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args */ - sqlite3_value **apVal /* Function arguments */ + sqlite3_value **apUnused /* Function arguments */ ){ assert( nArg==0 ); - sqlite3_result_text(pCtx, "fts5: 2015-11-02 18:31:45 bda77dda9697c463c3d0704014d51627fceee328", -1, SQLITE_TRANSIENT); + UNUSED_PARAM2(nArg, apUnused); + sqlite3_result_text(pCtx, "fts5: 2016-05-18 10:57:30 fc49f556e48970561d7ab6a2f24fdd7d9eb81ff2", -1, SQLITE_TRANSIENT); } static int fts5Init(sqlite3 *db){ @@ -180664,6 +193057,17 @@ static int fts5Init(sqlite3 *db){ ); } } + + /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file + ** fts5_test_mi.c is compiled and linked into the executable. And call + ** 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); + } +#endif + return rc; } @@ -180724,6 +193128,7 @@ SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3 *db){ +/* #include "fts5Int.h" */ struct Fts5Storage { Fts5Config *pConfig; @@ -180854,6 +193259,7 @@ static int fts5StorageGetStmt( } *ppStmt = p->aStmt[eStmt]; + sqlite3_reset(*ppStmt); return rc; } @@ -181006,10 +193412,10 @@ static int sqlite3Fts5StorageOpen( int i; int iOff; sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); - iOff = strlen(zDefn); + iOff = (int)strlen(zDefn); for(i=0; inCol; i++){ sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); - iOff += strlen(&zDefn[iOff]); + iOff += (int)strlen(&zDefn[iOff]); } rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); } @@ -181071,11 +193477,13 @@ static int fts5StorageInsertCallback( int tflags, const char *pToken, /* Buffer containing token */ int nToken, /* Size of token in bytes */ - int iStart, /* Start offset of token */ - int iEnd /* End offset of token */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ ){ Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext; Fts5Index *pIdx = pCtx->pStorage->pIndex; + UNUSED_PARAM2(iUnused1, iUnused2); + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ pCtx->szCol++; } @@ -181087,39 +193495,52 @@ static int fts5StorageInsertCallback( ** delete-markers to the FTS index necessary to delete it. Do not actually ** remove the %_content row at this time though. */ -static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){ +static int fts5StorageDeleteFromIndex( + Fts5Storage *p, + i64 iDel, + sqlite3_value **apVal +){ Fts5Config *pConfig = p->pConfig; - sqlite3_stmt *pSeek; /* SELECT to read row iDel from %_data */ + sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ int rc; /* Return code */ + int rc2; /* sqlite3_reset() return code */ + int iCol; + Fts5InsertCtx ctx; - rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); - if( rc==SQLITE_OK ){ - int rc2; + 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 ){ - int iCol; - Fts5InsertCtx ctx; - ctx.pStorage = p; - ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); - for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ - if( pConfig->abUnindexed[iCol-1] ) continue; - ctx.szCol = 0; - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_column_text(pSeek, iCol), - sqlite3_column_bytes(pSeek, iCol), - (void*)&ctx, - fts5StorageInsertCallback - ); - p->aTotalSize[iCol-1] -= (i64)ctx.szCol; - } - p->nTotalRow--; + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + return sqlite3_reset(pSeek); } - rc2 = sqlite3_reset(pSeek); - if( rc==SQLITE_OK ) rc = rc2; } + ctx.pStorage = p; + ctx.iCol = -1; + rc = sqlite3Fts5IndexBeginWrite(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); + }else{ + zText = (const char*)sqlite3_value_text(apVal[iCol-1]); + nText = sqlite3_value_bytes(apVal[iCol-1]); + } + ctx.szCol = 0; + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, + zText, nText, (void*)&ctx, fts5StorageInsertCallback + ); + p->aTotalSize[iCol-1] -= (i64)ctx.szCol; + } + } + p->nTotalRow--; + + rc2 = sqlite3_reset(pSeek); + if( rc==SQLITE_OK ) rc = rc2; return rc; } @@ -181199,16 +193620,17 @@ static int fts5StorageSaveTotals(Fts5Storage *p){ /* ** Remove a row from the FTS table. */ -static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel){ +static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ Fts5Config *pConfig = p->pConfig; int rc; sqlite3_stmt *pDel = 0; + assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 ); rc = fts5StorageLoadTotals(p, 1); /* Delete the index records */ if( rc==SQLITE_OK ){ - rc = fts5StorageDeleteFromIndex(p, iDel); + rc = fts5StorageDeleteFromIndex(p, iDel, apVal); } /* Delete the %_docsize record */ @@ -181222,62 +193644,9 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel){ } /* Delete the %_content record */ - if( rc==SQLITE_OK ){ - 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); - } - - /* Write the averages record */ - if( rc==SQLITE_OK ){ - rc = fts5StorageSaveTotals(p); - } - - return rc; -} - -static int sqlite3Fts5StorageSpecialDelete( - Fts5Storage *p, - i64 iDel, - sqlite3_value **apVal -){ - Fts5Config *pConfig = p->pConfig; - int rc; - sqlite3_stmt *pDel = 0; - - assert( pConfig->eContent!=FTS5_CONTENT_NORMAL ); - rc = fts5StorageLoadTotals(p, 1); - - /* Delete the index records */ - if( rc==SQLITE_OK ){ - int iCol; - Fts5InsertCtx ctx; - ctx.pStorage = p; - ctx.iCol = -1; - - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); - for(iCol=0; rc==SQLITE_OK && iColnCol; iCol++){ - if( pConfig->abUnindexed[iCol] ) continue; - ctx.szCol = 0; - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_value_text(apVal[iCol]), - sqlite3_value_bytes(apVal[iCol]), - (void*)&ctx, - fts5StorageInsertCallback - ); - p->aTotalSize[iCol] -= (i64)ctx.szCol; - } - p->nTotalRow--; - } - - /* Delete the %_docsize record */ - if( pConfig->bColumnsize ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); + rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); } if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDel, 1, iDel); @@ -181386,6 +193755,10 @@ static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){ return sqlite3Fts5IndexMerge(p->pIndex, nMerge); } +static int sqlite3Fts5StorageReset(Fts5Storage *p){ + return sqlite3Fts5IndexReset(p->pIndex); +} + /* ** Allocate a new rowid. This is used for "external content" tables when ** a NULL value is inserted into the rowid column. The new rowid is allocated @@ -181434,17 +193807,7 @@ static int sqlite3Fts5StorageContentInsert( }else{ sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ int i; /* Counter variable */ -#if 0 - if( eConflict==SQLITE_REPLACE ){ - eStmt = FTS5_STMT_REPLACE_CONTENT; - rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1])); - }else{ - eStmt = FTS5_STMT_INSERT_CONTENT; - } -#endif - if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); - } + 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]); } @@ -181542,28 +193905,76 @@ struct Fts5IntegrityCtx { int iCol; int szCol; u64 cksum; + Fts5Termset *pTermset; Fts5Config *pConfig; }; + /* ** Tokenization callback used by integrity check. */ static int fts5StorageIntegrityCallback( - void *pContext, /* Pointer to Fts5InsertCtx object */ + void *pContext, /* Pointer to Fts5IntegrityCtx object */ int tflags, const char *pToken, /* Buffer containing token */ int nToken, /* Size of token in bytes */ - int iStart, /* Start offset of token */ - int iEnd /* End offset of token */ + int iUnused1, /* Start offset of token */ + int iUnused2 /* End offset of token */ ){ Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext; + Fts5Termset *pTermset = pCtx->pTermset; + int bPresent; + int ii; + int rc = SQLITE_OK; + int iPos; + int iCol; + + UNUSED_PARAM2(iUnused1, iUnused2); + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){ pCtx->szCol++; } - pCtx->cksum ^= sqlite3Fts5IndexCksum( - pCtx->pConfig, pCtx->iRowid, pCtx->iCol, pCtx->szCol-1, pToken, nToken - ); - return SQLITE_OK; + + switch( pCtx->pConfig->eDetail ){ + case FTS5_DETAIL_FULL: + iPos = pCtx->szCol-1; + iCol = pCtx->iCol; + break; + + case FTS5_DETAIL_COLUMNS: + iPos = pCtx->iCol; + iCol = 0; + break; + + default: + assert( pCtx->pConfig->eDetail==FTS5_DETAIL_NONE ); + iPos = 0; + iCol = 0; + break; + } + + rc = sqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent); + if( rc==SQLITE_OK && bPresent==0 ){ + pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( + pCtx->iRowid, iCol, iPos, 0, pToken, nToken + ); + } + + for(ii=0; rc==SQLITE_OK && iipConfig->nPrefix; ii++){ + const int nChar = pCtx->pConfig->aPrefix[ii]; + int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); + if( nByte ){ + rc = sqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent); + if( bPresent==0 ){ + pCtx->cksum ^= sqlite3Fts5IndexEntryCksum( + pCtx->iRowid, iCol, iPos, ii+1, pToken, nByte + ); + } + } + } + + return rc; } /* @@ -181599,22 +194010,37 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ if( pConfig->bColumnsize ){ rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); } + if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } for(i=0; rc==SQLITE_OK && inCol; i++){ if( pConfig->abUnindexed[i] ) continue; ctx.iCol = i; ctx.szCol = 0; - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_column_text(pScan, i+1), - sqlite3_column_bytes(pScan, i+1), - (void*)&ctx, - fts5StorageIntegrityCallback - ); - if( pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + (const char*)sqlite3_column_text(pScan, i+1), + sqlite3_column_bytes(pScan, i+1), + (void*)&ctx, + fts5StorageIntegrityCallback + ); + } + if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ rc = FTS5_CORRUPT; } aTotalSize[i] += ctx.szCol; + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + } } + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + if( rc!=SQLITE_OK ) break; } rc2 = sqlite3_reset(pScan); @@ -181633,12 +194059,12 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ /* Check that the %_docsize and %_content tables contain the expected ** number of rows. */ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ - i64 nRow; + i64 nRow = 0; rc = fts5StorageCount(p, "content", &nRow); if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; } if( rc==SQLITE_OK && pConfig->bColumnsize ){ - i64 nRow; + i64 nRow = 0; rc = fts5StorageCount(p, "docsize", &nRow); if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; } @@ -181817,8 +194243,6 @@ static int sqlite3Fts5StorageConfigValue( return rc; } - - /* ** 2014 May 31 ** @@ -181833,6 +194257,7 @@ static int sqlite3Fts5StorageConfigValue( */ +/* #include "fts5Int.h" */ /************************************************************************** ** Start of ascii tokenizer implementation. @@ -181882,12 +194307,13 @@ static void fts5AsciiDelete(Fts5Tokenizer *p){ ** Create an "ascii" tokenizer. */ static int fts5AsciiCreate( - void *pCtx, + void *pUnused, const char **azArg, int nArg, Fts5Tokenizer **ppOut ){ int rc = SQLITE_OK; AsciiTokenizer *p = 0; + UNUSED_PARAM(pUnused); if( nArg%2 ){ rc = SQLITE_ERROR; }else{ @@ -181936,7 +194362,7 @@ static void asciiFold(char *aOut, const char *aIn, int nByte){ static int fts5AsciiTokenize( Fts5Tokenizer *pTokenizer, void *pCtx, - int flags, + int iUnused, const char *pText, int nText, int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) ){ @@ -181950,6 +194376,8 @@ static int fts5AsciiTokenize( char *pFold = aFold; unsigned char *a = p->aTokenChar; + UNUSED_PARAM(iUnused); + while( is0 ){ @@ -182076,7 +194504,7 @@ static int fts5UnicodeAddExceptions( int bToken; READ_UTF8(zCsr, zTerm, iCode); if( iCode<128 ){ - p->aTokenChar[iCode] = bTokenChars; + p->aTokenChar[iCode] = (unsigned char)bTokenChars; }else{ bToken = sqlite3Fts5UnicodeIsalnum(iCode); assert( (bToken==0 || bToken==1) ); @@ -182143,13 +194571,15 @@ static void fts5UnicodeDelete(Fts5Tokenizer *pTok){ ** Create a "unicode61" tokenizer. */ static int fts5UnicodeCreate( - void *pCtx, + void *pUnused, const char **azArg, int nArg, Fts5Tokenizer **ppOut ){ int rc = SQLITE_OK; /* Return code */ Unicode61Tokenizer *p = 0; /* New tokenizer object */ + UNUSED_PARAM(pUnused); + if( nArg%2 ){ rc = SQLITE_ERROR; }else{ @@ -182206,7 +194636,7 @@ static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){ static int fts5UnicodeTokenize( Fts5Tokenizer *pTokenizer, void *pCtx, - int flags, + int iUnused, const char *pText, int nText, int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) ){ @@ -182222,6 +194652,8 @@ static int fts5UnicodeTokenize( int nFold = p->nFold; const char *pEnd = &aFold[nFold-6]; + UNUSED_PARAM(iUnused); + /* Each iteration of this loop gobbles up a contiguous run of separators, ** then the next token. */ while( rc==SQLITE_OK ){ @@ -183040,7 +195472,7 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){ int rc = SQLITE_OK; /* Return code */ int i; /* To iterate through builtin functions */ - for(i=0; rc==SQLITE_OK && ixCreateTokenizer(pApi, aBuiltin[i].zName, (void*)pApi, @@ -183181,9 +195613,9 @@ static int sqlite3Fts5UnicodeIsalnum(int c){ 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001, }; - if( c<128 ){ + if( (unsigned int)c<128 ){ return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 ); - }else if( c<(1<<22) ){ + }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; @@ -183431,6 +195863,7 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){ */ +/* #include "fts5Int.h" */ /* ** This is a copy of the sqlite3GetVarint32() routine from the SQLite core. @@ -183749,7 +196182,10 @@ static int sqlite3Fts5PutVarint(unsigned char *p, u64 v){ static int sqlite3Fts5GetVarintLen(u32 iVal){ +#if 0 if( iVal<(1 << 7 ) ) return 1; +#endif + assert( iVal>=(1 << 7) ); if( iVal<(1 << 14) ) return 2; if( iVal<(1 << 21) ) return 3; if( iVal<(1 << 28) ) return 4; @@ -183791,6 +196227,7 @@ static int sqlite3Fts5GetVarintLen(u32 iVal){ */ +/* #include "fts5Int.h" */ typedef struct Fts5VocabTable Fts5VocabTable; @@ -183813,16 +196250,18 @@ struct Fts5VocabCursor { int bEof; /* True if this cursor is at EOF */ Fts5IndexIter *pIter; /* Term/rowid iterator object */ + int nLeTerm; /* Size of zLeTerm in bytes */ + char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ + /* These are used by 'col' tables only */ - int nCol; + Fts5Config *pConfig; /* Fts5 table configuration */ int iCol; i64 *aCnt; i64 *aDoc; - /* Output values */ + /* Output values used by 'row' and 'col' tables */ i64 rowid; /* This table's current rowid value */ Fts5Buffer term; /* Current value of 'term' column */ - i64 aVal[3]; /* Up to three columns left of 'term' */ }; #define FTS5_VOCAB_COL 0 @@ -183831,6 +196270,14 @@ struct Fts5VocabCursor { #define FTS5_VOCAB_COL_SCHEMA "term, col, doc, cnt" #define FTS5_VOCAB_ROW_SCHEMA "term, doc, cnt" +/* +** Bits for the mask used as the idxNum value by xBestIndex/xFilter. +*/ +#define FTS5_VOCAB_TERM_EQ 0x01 +#define FTS5_VOCAB_TERM_GE 0x02 +#define FTS5_VOCAB_TERM_LE 0x04 + + /* ** Translate a string containing an fts5vocab table type to an ** FTS5_VOCAB_XXX constant. If successful, set *peType to the output @@ -183926,13 +196373,13 @@ static int fts5VocabInitVtab( const char *zDb = bDb ? argv[3] : argv[1]; const char *zTab = bDb ? argv[4] : argv[3]; const char *zType = bDb ? argv[5] : argv[4]; - int nDb = strlen(zDb)+1; - int nTab = strlen(zTab)+1; - int eType; + int nDb = (int)strlen(zDb)+1; + int nTab = (int)strlen(zTab)+1; + int eType = 0; rc = fts5VocabTableType(zType, pzErr, &eType); if( rc==SQLITE_OK ){ - assert( eType>=0 && eType=0 && eTypenConstraint; i++){ + struct sqlite3_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; + if( p->op==SQLITE_INDEX_CONSTRAINT_LE ) iTermLe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_LT ) iTermLe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_GE ) iTermGe = i; + if( p->op==SQLITE_INDEX_CONSTRAINT_GT ) iTermGe = i; + } + } + + if( iTermEq>=0 ){ + idxNum |= FTS5_VOCAB_TERM_EQ; + pInfo->aConstraintUsage[iTermEq].argvIndex = ++nArg; + pInfo->estimatedCost = 100; + }else{ + pInfo->estimatedCost = 1000000; + if( iTermGe>=0 ){ + idxNum |= FTS5_VOCAB_TERM_GE; + pInfo->aConstraintUsage[iTermGe].argvIndex = ++nArg; + pInfo->estimatedCost = pInfo->estimatedCost / 2; + } + if( iTermLe>=0 ){ + idxNum |= FTS5_VOCAB_TERM_LE; + pInfo->aConstraintUsage[iTermLe].argvIndex = ++nArg; + pInfo->estimatedCost = pInfo->estimatedCost / 2; + } + } + + pInfo->idxNum = idxNum; + return SQLITE_OK; } @@ -184000,12 +196488,11 @@ static int fts5VocabOpenMethod( ){ Fts5VocabTable *pTab = (Fts5VocabTable*)pVTab; Fts5Index *pIndex = 0; - int nCol = 0; + Fts5Config *pConfig = 0; Fts5VocabCursor *pCsr = 0; int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; char *zSql = 0; - int nByte; zSql = sqlite3Fts5Mprintf(&rc, "SELECT t.%Q FROM %Q.%Q AS t WHERE t.%Q MATCH '*id'", @@ -184020,7 +196507,7 @@ static int fts5VocabOpenMethod( if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ i64 iId = sqlite3_column_int64(pStmt, 0); - pIndex = sqlite3Fts5IndexFromCsrid(pTab->pGlobal, iId, &nCol); + pIndex = sqlite3Fts5IndexFromCsrid(pTab->pGlobal, iId, &pConfig); } if( rc==SQLITE_OK && pIndex==0 ){ @@ -184034,14 +196521,17 @@ static int fts5VocabOpenMethod( } } - nByte = nCol * sizeof(i64) * 2 + sizeof(Fts5VocabCursor); - pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); + if( rc==SQLITE_OK ){ + int nByte = pConfig->nCol * sizeof(i64) * 2 + sizeof(Fts5VocabCursor); + pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); + } + if( pCsr ){ pCsr->pIndex = pIndex; pCsr->pStmt = pStmt; - pCsr->nCol = nCol; + pCsr->pConfig = pConfig; pCsr->aCnt = (i64*)&pCsr[1]; - pCsr->aDoc = &pCsr->aCnt[nCol]; + pCsr->aDoc = &pCsr->aCnt[pConfig->nCol]; }else{ sqlite3_finalize(pStmt); } @@ -184054,6 +196544,9 @@ static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){ pCsr->rowid = 0; sqlite3Fts5IterClose(pCsr->pIter); pCsr->pIter = 0; + sqlite3_free(pCsr->zLeTerm); + pCsr->nLeTerm = -1; + pCsr->zLeTerm = 0; } /* @@ -184077,16 +196570,17 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; int rc = SQLITE_OK; + int nCol = pCsr->pConfig->nCol; pCsr->rowid++; if( pTab->eType==FTS5_VOCAB_COL ){ - for(pCsr->iCol++; pCsr->iColnCol; pCsr->iCol++){ - if( pCsr->aCnt[pCsr->iCol] ) break; + for(pCsr->iCol++; pCsr->iColiCol++){ + if( pCsr->aDoc[pCsr->iCol] ) break; } } - if( pTab->eType==FTS5_VOCAB_ROW || pCsr->iCol>=pCsr->nCol ){ + if( pTab->eType==FTS5_VOCAB_ROW || pCsr->iCol>=nCol ){ if( sqlite3Fts5IterEof(pCsr->pIter) ){ pCsr->bEof = 1; }else{ @@ -184094,53 +196588,93 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ int nTerm; zTerm = sqlite3Fts5IterTerm(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->nLeTermbEof = 1; + return SQLITE_OK; + } + } + sqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm); - memset(pCsr->aVal, 0, sizeof(pCsr->aVal)); - memset(pCsr->aCnt, 0, pCsr->nCol * sizeof(i64)); - memset(pCsr->aDoc, 0, pCsr->nCol * sizeof(i64)); + 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 ){ - i64 dummy; const u8 *pPos; int nPos; /* Position list */ i64 iPos = 0; /* 64-bit position read from poslist */ int iOff = 0; /* Current offset within position list */ - rc = sqlite3Fts5IterPoslist(pCsr->pIter, 0, &pPos, &nPos, &dummy); - if( rc==SQLITE_OK ){ - if( pTab->eType==FTS5_VOCAB_ROW ){ - while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ - pCsr->aVal[1]++; - } - pCsr->aVal[0]++; - }else{ - int iCol = -1; - while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ - int ii = FTS5_POS2COLUMN(iPos); - pCsr->aCnt[ii]++; - if( iCol!=ii ){ - pCsr->aDoc[ii]++; - iCol = ii; + 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) ){ + pCsr->aCnt[0]++; + } + pCsr->aDoc[0]++; + }else{ + int iCol = -1; + while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ + int ii = FTS5_POS2COLUMN(iPos); + pCsr->aCnt[ii]++; + if( iCol!=ii ){ + if( ii>=nCol ){ + rc = FTS5_CORRUPT; + break; + } + pCsr->aDoc[ii]++; + iCol = ii; + } } } - } + break; + + case FTS5_DETAIL_COLUMNS: + if( pTab->eType==FTS5_VOCAB_ROW ){ + pCsr->aDoc[0]++; + }else{ + while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){ + assert_nc( iPos>=0 && iPos=nCol ){ + rc = FTS5_CORRUPT; + break; + } + pCsr->aDoc[iPos]++; + } + } + break; + + default: + assert( pCsr->pConfig->eDetail==FTS5_DETAIL_NONE ); + pCsr->aDoc[0]++; + break; + } + + if( rc==SQLITE_OK ){ rc = sqlite3Fts5IterNextScan(pCsr->pIter); } + if( rc==SQLITE_OK ){ zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm); - if( nTerm!=pCsr->term.n || memcmp(zTerm, pCsr->term.p, nTerm) ) break; + if( nTerm!=pCsr->term.n || memcmp(zTerm, pCsr->term.p, nTerm) ){ + break; + } if( sqlite3Fts5IterEof(pCsr->pIter) ) break; } } } } - if( pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){ - while( pCsr->aCnt[pCsr->iCol]==0 ) pCsr->iCol++; - pCsr->aVal[0] = pCsr->iCol; - pCsr->aVal[1] = pCsr->aDoc[pCsr->iCol]; - pCsr->aVal[2] = pCsr->aCnt[pCsr->iCol]; + if( rc==SQLITE_OK && pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){ + while( pCsr->aDoc[pCsr->iCol]==0 ) pCsr->iCol++; + assert( pCsr->iColpConfig->nCol ); } return rc; } @@ -184151,16 +196685,54 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ static int fts5VocabFilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ - const char *idxStr, /* Unused */ - int nVal, /* Number of elements in apVal */ + const char *zUnused, /* Unused */ + int nUnused, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; - int rc; - const int flags = FTS5INDEX_QUERY_SCAN; + int rc = SQLITE_OK; + + int iVal = 0; + int f = FTS5INDEX_QUERY_SCAN; + const char *zTerm = 0; + int nTerm = 0; + + sqlite3_value *pEq = 0; + sqlite3_value *pGe = 0; + sqlite3_value *pLe = 0; + + UNUSED_PARAM2(zUnused, nUnused); fts5VocabResetCursor(pCsr); - rc = sqlite3Fts5IndexQuery(pCsr->pIndex, 0, 0, flags, 0, &pCsr->pIter); + if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++]; + if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++]; + if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++]; + + if( pEq ){ + zTerm = (const char *)sqlite3_value_text(pEq); + nTerm = sqlite3_value_bytes(pEq); + f = 0; + }else{ + if( pGe ){ + zTerm = (const char *)sqlite3_value_text(pGe); + nTerm = sqlite3_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); + if( pCsr->zLeTerm==0 ){ + rc = SQLITE_NOMEM; + }else{ + memcpy(pCsr->zLeTerm, zCopy, pCsr->nLeTerm+1); + } + } + } + + + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexQuery(pCsr->pIndex, zTerm, nTerm, f, 0, &pCsr->pIter); + } if( rc==SQLITE_OK ){ rc = fts5VocabNextMethod(pCursor); } @@ -184183,18 +196755,36 @@ static int fts5VocabColumnMethod( int iCol /* Index of column to read value from */ ){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; - switch( iCol ){ - case 0: /* term */ - sqlite3_result_text( - pCtx, (const char*)pCsr->term.p, pCsr->term.n, SQLITE_TRANSIENT - ); - break; + int eDetail = pCsr->pConfig->eDetail; + int eType = ((Fts5VocabTable*)(pCursor->pVtab))->eType; + i64 iVal = 0; - default: - assert( iCol<4 && iCol>0 ); - sqlite3_result_int64(pCtx, pCsr->aVal[iCol-1]); - break; + if( iCol==0 ){ + sqlite3_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); + } + }else if( iCol==2 ){ + iVal = pCsr->aDoc[pCsr->iCol]; + }else{ + iVal = pCsr->aCnt[pCsr->iCol]; + } + }else{ + assert( iCol==1 || iCol==2 ); + if( iCol==1 ){ + iVal = pCsr->aDoc[0]; + }else{ + iVal = pCsr->aCnt[0]; + } } + + if( iVal>0 ) sqlite3_result_int64(pCtx, iVal); return SQLITE_OK; } diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h index 928b43077..313b5ec3c 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.h +++ b/TMessagesProj/jni/sqlite/sqlite3.h @@ -31,7 +31,7 @@ ** part of the build process. */ #ifndef _SQLITE3_H_ -#define _SQLITE3_H_ +#define _SQLITE3_H_ #include /* Needed for the definition of va_list */ /* @@ -111,9 +111,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.9.2" -#define SQLITE_VERSION_NUMBER 3009002 -#define SQLITE_SOURCE_ID "2015-11-02 18:31:45 bda77dda9697c463c3d0704014d51627fceee328" +#define SQLITE_VERSION "3.13.0" +#define SQLITE_VERSION_NUMBER 3013000 +#define SQLITE_SOURCE_ID "2016-05-18 10:57:30 fc49f556e48970561d7ab6a2f24fdd7d9eb81ff2" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -347,7 +347,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** from [sqlite3_malloc()] and passed back through the 5th parameter. ** To avoid memory leaks, the application should invoke [sqlite3_free()] ** on error message strings returned through the 5th parameter of -** of sqlite3_exec() after the error message string is no longer needed. +** 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 ** NULL before returning. @@ -478,6 +478,7 @@ SQLITE_API int SQLITE_STDCALL sqlite3_exec( #define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) #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_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) @@ -793,8 +794,13 @@ struct sqlite3_io_methods { **
  • [[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 -** connection. See the [sqlite3_file_control()] documentation for -** additional information. +** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER]. +** +**
  • [[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 +** the [rollback journal] or the [write-ahead log]) for a particular database +** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** **
  • [[SQLITE_FCNTL_SYNC_OMITTED]] ** No longer in use. @@ -881,6 +887,15 @@ struct sqlite3_io_methods { ** pointer in case this file-control is not implemented. This file-control ** is intended for diagnostic use only. ** +**
  • [[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 +** 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. +** **
  • [[SQLITE_FCNTL_PRAGMA]] ** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding @@ -999,6 +1014,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_WAL_BLOCK 24 #define SQLITE_FCNTL_ZIPVFS 25 #define SQLITE_FCNTL_RBU 26 +#define SQLITE_FCNTL_VFS_POINTER 27 +#define SQLITE_FCNTL_JOURNAL_POINTER 28 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1211,7 +1228,7 @@ struct sqlite3_vfs { const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. - ** New fields may be appended in figure versions. The iVersion + ** New fields may be appended in future versions. The iVersion ** value will increment whenever this happens. */ }; @@ -1598,29 +1615,34 @@ struct sqlite3_mem_methods { ** ** ** [[SQLITE_CONFIG_PAGECACHE]]
    SQLITE_CONFIG_PAGECACHE
    -**
    ^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +**
    ^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 should not be used if an application-define page -** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] -** configuration option. +** This configuration option is a no-op if an application-define 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, the size of each page buffer (sz), and the number of pages (N). +** 8-byte aligned memory (pMem), the size of each page cache line (sz), +** and the number of cache lines (N). ** The sz argument should be the size of the largest database page ** (a power of two between 512 and 65536) plus some extra bytes for each ** page header. ^The number of extra bytes needed by the page header -** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option -** to [sqlite3_config()]. +** can be determined using [SQLITE_CONFIG_PCACHE_HDRSZ]. ** ^It is harmless, apart from the wasted memory, -** for the sz parameter to be larger than necessary. The first -** argument should pointer to an 8-byte aligned block of memory that -** is at least sz*N bytes of memory, otherwise subsequent behavior is -** undefined. -** ^SQLite will use the memory provided by the first argument to satisfy its -** memory needs for the first N pages that it adds to cache. ^If additional -** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space.
    +** for the sz parameter to be larger than necessary. The pMem +** argument must be either a NULL pointer or a pointer to an 8-byte +** 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 +** 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 +** 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 +** additional cache line. ** ** [[SQLITE_CONFIG_HEAP]]
    SQLITE_CONFIG_HEAP
    **
    ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer @@ -1798,6 +1820,20 @@ struct sqlite3_mem_methods { ** is enabled (using the [PRAGMA threads] command) and the amount of content ** to be sorted exceeds the page size times the minimum of the ** [PRAGMA cache_size] setting and this value. +** +** [[SQLITE_CONFIG_STMTJRNL_SPILL]] +**
    SQLITE_CONFIG_STMTJRNL_SPILL +**
    ^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which +** becomes the [statement journal] spill-to-disk threshold. +** [Statement journals] are held in memory until their size (in bytes) +** exceeds this threshold, at which point they are written to disk. +** Or if the threshold is -1, statement journals are always held +** exclusively in memory. +** Since many statement journals never become large, setting the spill +** threshold to a value such as 64KiB can greatly reduce the amount of +** I/O required to support statement rollback. +** The default value for this setting is controlled by the +** [SQLITE_STMTJRNL_SPILL] compile-time option. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -1825,6 +1861,7 @@ struct sqlite3_mem_methods { #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 */ /* ** CAPI3REF: Database Connection Configuration Options @@ -1882,11 +1919,43 @@ 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.
    ** +**
    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
    +**
    ^This option is used to enable or disable the two-argument +** version of 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 +** positive to enable fts3_tokenizer() 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 fts3_tokenizer 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.
    +** +**
    SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
    +**
    ^This option is used to enable or disable the [sqlite3_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()]. +** 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 argment to +** this interface is 0, then both the C-API and the SQL function are disabled. +** 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 disabled or enabled following this call. The second parameter may +** be a NULL pointer, in which case the new setting is not reported back. +**
    +** ** */ -#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ -#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ -#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ +#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ +#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* */ /* @@ -4389,8 +4458,8 @@ SQLITE_API unsigned int SQLITE_STDCALL sqlite3_value_subtype(sqlite3_value*); ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer ** then sqlite3_value_free(V) is a harmless no-op. */ -SQLITE_API SQLITE_EXPERIMENTAL sqlite3_value *SQLITE_STDCALL sqlite3_value_dup(const sqlite3_value*); -SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_value_free(sqlite3_value*); +SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_value_dup(const sqlite3_value*); +SQLITE_API void SQLITE_STDCALL sqlite3_value_free(sqlite3_value*); /* ** CAPI3REF: Obtain Aggregate Function Context @@ -5136,7 +5205,7 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), ** ^The sqlite3_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. +** a [rowid table]. ** ^Any callback set by a previous call to this function ** for the same database connection is overridden. ** @@ -5175,8 +5244,8 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), ** on the same [database connection] D, or NULL for ** the first call on D. ** -** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] -** interfaces. +** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()], +** and [sqlite3_preupdate_hook()] interfaces. */ SQLITE_API void *SQLITE_STDCALL sqlite3_update_hook( sqlite3*, @@ -5423,9 +5492,18 @@ SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( ** should free this memory by calling [sqlite3_free()]. ** ** ^Extension loading must be enabled using -** [sqlite3_enable_load_extension()] prior to calling this API, +** [sqlite3_enable_load_extension()] or +** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL) +** prior to calling this API, ** otherwise an error will be returned. ** +** Security warning: 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 +** 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 SQLITE_STDCALL sqlite3_load_extension( @@ -5448,6 +5526,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_load_extension( ** ^Call the sqlite3_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],..) +** to enable or disable only the C-API. +** +** Security warning: It is recommended that extension loading +** be disabled 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 SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff); @@ -5609,6 +5698,17 @@ struct sqlite3_module { ** ^Information about the ORDER BY clause is stored in aOrderBy[]. ** ^Each term of aOrderBy records a column of the ORDER BY clause. ** +** 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), +** 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 +** non-zero. +** ** The [xBestIndex] method must fill aConstraintUsage[] with information ** about what parameters to pass to xFilter. ^If argvIndex>0 then ** the right-hand side of the corresponding aConstraint[] is evaluated @@ -5664,7 +5764,7 @@ struct sqlite3_index_info { /* Inputs */ int nConstraint; /* Number of entries in aConstraint */ struct sqlite3_index_constraint { - int iColumn; /* Column on left-hand side of 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 */ @@ -5688,6 +5788,8 @@ struct sqlite3_index_info { sqlite3_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 */ }; /* @@ -5703,12 +5805,15 @@ struct sqlite3_index_info { ** 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_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 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -6572,7 +6677,8 @@ SQLITE_API int SQLITE_STDCALL sqlite3_status64( ** The value written into the *pCurrent parameter is undefined.)^ ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
    SQLITE_STATUS_PARSER_STACK
    -**
    This parameter records the deepest parser stack. It is only +**
    The *pHighwater parameter records the deepest parser stack. +** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
    )^ ** ** @@ -7069,7 +7175,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** ^A call to sqlite3_backup_init() will fail, returning NULL, if ** there is already a read or read-write transaction open on the ** destination database. ** @@ -7358,18 +7464,43 @@ SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *, const char *, int); /* ** CAPI3REF: String Globbing * -** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches -** the glob pattern P, and it returns non-zero if string X does not match -** the glob pattern P. ^The definition of glob pattern matching used in +** ^The [sqlite3_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 used by SQLite. ^The sqlite3_strglob(P,X) function is case -** sensitive. +** SQL dialect understood by SQLite. ^The [sqlite3_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()]. +** +** See also: [sqlite3_strlike()]. */ SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlob, const char *zStr); +/* +** CAPI3REF: String LIKE Matching +* +** ^The [sqlite3_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" +** 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 +** insensitive - equivalent upper and lower case ASCII characters match +** one another. +** +** ^The [sqlite3_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()]. +** +** See also: [sqlite3_strglob()]. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc); + /* ** CAPI3REF: Error Logging Interface ** @@ -7425,7 +7556,7 @@ SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...) ** 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 -** those overwrite any prior [sqlite3_wal_hook()] settings. +** overwrite any prior [sqlite3_wal_hook()] settings. */ SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook( sqlite3*, @@ -7790,6 +7921,277 @@ SQLITE_API int SQLITE_STDCALL sqlite3_stmt_scanstatus( */ SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_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 +** 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)] +** interface flushes caches for all schemas - "main", "temp", and +** any [attached] databases. +** +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained +** immediately and there is a busy-handler callback configured, it is invoked +** in the usual manner. ^If the required lock still cannot be obtained, then +** the database is skipped and an attempt made to flush any dirty pages +** belonging to the next (if any) database. ^If any databases are skipped +** because locks cannot be obtained, but no other error occurs, this +** function returns SQLITE_BUSY. +** +** ^If any other error occurs while flushing dirty pages to disk (for +** 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. +** +** ^This function does not set the database handle error code or message +** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_db_cacheflush(sqlite3*); + +/* +** CAPI3REF: The pre-update hook. +** +** ^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 +** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation +** on a [rowid table]. +** ^At most one preupdate hook may be registered at a time on a single +** [database connection]; each call to [sqlite3_preupdate_hook()] overrides +** the previous setting. +** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] +** with a NULL pointer as the second parameter. +** ^The third parameter to [sqlite3_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 second parameter to the preupdate callback is a pointer to +** the [database connection] that registered the preupdate hook. +** ^The third parameter to the preupdate callback is one of the constants +** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to indentify the +** kind of update operation that is about to occur. +** ^(The fourth parameter to the preupdate callback is the name of the +** database within the database connection that is being modified. This +** will be "main" for the main database or "temp" for TEMP tables or +** the name given after the AS keyword in the [ATTACH] statement for attached +** 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 +** 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 +** [database connection] pointer that is different from the one supplied +** to the preupdate callback results in undefined and probably undesirable +** behavior. +** +** ^The [sqlite3_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 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 +** 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 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 +** will be destroyed when the preupdate callback returns. +** +** ^The [sqlite3_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()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL void *SQLITE_STDCALL sqlite3_preupdate_hook( + sqlite3 *db, + void(*xPreUpdate)( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *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) */ + ), + void* +); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_count(sqlite3 *); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); + +/* +** CAPI3REF: Low-level system error code +** +** ^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 +** called to get back the underlying "errno" that caused the problem, such +** as ENOSPC, EAUTH, EISDIR, and so forth. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_system_errno(sqlite3*); + +/* +** CAPI3REF: Database Snapshot +** KEYWORDS: {snapshot} +** EXPERIMENTAL +** +** An instance of the snapshot object records the state of a [WAL mode] +** database for some specific point in history. +** +** In [WAL mode], multiple [database connections] that are open on the +** same database file can each be reading a different historical version +** of the database file. When a [database connection] begins a read +** transaction, that connection sees an unchanging copy of the database +** as it existed for the point in time when the transaction first started. +** 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 +** 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; + +/* +** CAPI3REF: Record A Database Snapshot +** EXPERIMENTAL +** +** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a +** new [sqlite3_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()] +** to avoid a memory leak. +** +** The [sqlite3_snapshot_get()] interface is only available when the +** SQLITE_ENABLE_SNAPSHOT compile-time option is used. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_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 +** 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 +** I/O on that database connection, or if the database entered [WAL mode] +** after the most recent I/O on the database connection.)^ +** (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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); + +/* +** CAPI3REF: Destroy a snapshot +** EXPERIMENTAL +** +** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P. +** The application must eventually free every [sqlite3_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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_snapshot_free(sqlite3_snapshot*); + +/* +** CAPI3REF: Compare the ages of two snapshot handles. +** EXPERIMENTAL +** +** The sqlite3_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 +** 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 +** wal file was last deleted, the value returned by this function +** is undefined. +** +** 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. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_snapshot_cmp( + sqlite3_snapshot *p1, + sqlite3_snapshot *p2 +); /* ** Undo the hack that converts floating point types to integer for @@ -7804,6 +8206,7 @@ SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); #endif #endif /* _SQLITE3_H_ */ +/******** Begin file sqlite3rtree.h *********/ /* ** 2010 August 30 ** @@ -7921,6 +8324,1287 @@ struct sqlite3_rtree_query_info { #endif /* ifndef _SQLITE3RTREE_H_ */ +/******** End of sqlite3rtree.h *********/ +/******** Begin file sqlite3session.h *********/ + +#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) +#define __SQLITESESSION_H_ 1 + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + + +/* +** CAPI3REF: Session Object Handle +*/ +typedef struct sqlite3_session sqlite3_session; + +/* +** CAPI3REF: Changeset Iterator Handle +*/ +typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; + +/* +** CAPI3REF: Create A New Session Object +** +** 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 +** returned. If an error occurs, *ppSession is set to NULL and an SQLite +** error code (e.g. SQLITE_NOMEM) is returned. +** +** It is possible to create multiple session objects attached to a single +** database handle. +** +** Session objects created using this function should be deleted using the +** [sqlite3session_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 +** are undefined. +** +** Because the session module uses the [sqlite3_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 +** which a pre-update hook is already defined. The results of attempting +** either of these things are undefined. +** +** The session object will be used to create changesets for tables in +** database zDb, where zDb is either "main", or "temp", or the name of an +** 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 */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +); + +/* +** CAPI3REF: Delete A Session Object +** +** Delete a session object previously allocated using +** [sqlite3session_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. +*/ +void sqlite3session_delete(sqlite3_session *pSession); + + +/* +** CAPI3REF: Enable Or Disable A Session Object +** +** 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 +** details regarding how enabling and disabling a session object affects +** the eventual changesets. +** +** Passing zero to this function disables the session. Passing a value +** greater than zero enables it. Passing a value less than zero is a +** no-op, and may be used to query the current state of the session. +** +** 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); + +/* +** CAPI3REF: Set Or Clear the Indirect Change Flag +** +** Each change recorded by a session object is marked as either direct or +** indirect. A change is marked as indirect if either: +** +**
      +**
    • The session object "indirect" flag is set when the change is +** made, or +**
    • The change is made by an SQL trigger or foreign key action +** instead of directly as a result of a users SQL statement. +**
    +** +** If a single row is affected by more than one operation within a session, +** then the change is considered indirect if all operations meet the criteria +** for an indirect change above, or direct otherwise. +** +** This function is used to set, clear or query the session object indirect +** flag. If the second argument passed to this function is zero, then the +** indirect flag is cleared. If it is greater than zero, the indirect flag +** is set. Passing a value less than zero does not modify the current value +** of the indirect flag, and may be used to query the current state of the +** indirect flag for the specified session object. +** +** 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); + +/* +** CAPI3REF: Attach A Table To A Session Object +** +** 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. +** +** 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 +** executing "CREATE TABLE" statements) after this call is made, changes for +** the new tables are also recorded. +** +** Changes can only be recorded for tables that have a PRIMARY KEY explicitly +** defined as part of their CREATE TABLE statement. It does not matter if the +** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY +** KEY may consist of a single column, or may be a composite key. +** +** It is not an error if the named table does not exist in the database. Nor +** is it an error if the named table does not have a PRIMARY KEY. However, +** no changes will be recorded in either of these scenarios. +** +** Changes are not recorded for individual rows that have NULL values stored +** in one or more of their PRIMARY KEY columns. +** +** 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 */ + const char *zTab /* Table name */ +); + +/* +** CAPI3REF: Set a table filter on a Session Object. +** +** The second argument (xFilter) is the "filter callback". For changes to rows +** in tables that are not attached to the Session oject, 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 +** attached, xFilter will not be called again. +*/ +void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); + +/* +** CAPI3REF: Generate A Changeset From A Session Object +** +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset +** and *pnChangeset to the size of the changeset in bytes before returning +** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to +** zero and return an SQLite error code. +** +** A changeset consists of zero or more INSERT, UPDATE and/or DELETE changes, +** each representing a change to a single row of an attached table. An INSERT +** change contains the values of each field of a new database row. A DELETE +** contains the original values of each field of a deleted database row. An +** UPDATE change contains the original values of each field of an updated +** database row along with the updated values for each updated non-primary-key +** column. It is not possible for an UPDATE change to represent a change that +** modifies the values of primary key columns. If such a change is made, it +** is represented in a changeset as a DELETE followed by an INSERT. +** +** Changes are not recorded for rows that have NULL values stored in one or +** more of their PRIMARY KEY columns. If such a row is inserted or deleted, +** no corresponding change is present in the changesets returned by this +** function. If an existing row with one or more NULL values stored in +** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL, +** only an INSERT is appears in the changeset. Similarly, if an existing row +** with non-NULL PRIMARY KEY values is updated so that one or more of its +** PRIMARY KEY columns are set to NULL, the resulting changeset contains a +** 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()] +** API. +** +** Within a changeset generated by this function, all changes related to a +** single table are grouped together. In other words, when iterating through +** 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 +** 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()]. +** +**

    Changeset Generation

    +** +** Once a table has been attached to a session object, the session object +** records the primary key values of all new rows inserted into the table. +** It also records the original primary key and other column values of any +** deleted or updated rows. For each unique primary key value, data is only +** recorded once - the first time a row with said primary key is inserted, +** updated or deleted in the lifetime of the session. +** +** There is one exception to the previous paragraph: when a row is inserted, +** updated or deleted, if one or more of its primary key columns contain a +** NULL value, no record of the change is made. +** +** The session object therefore accumulates two types of records - those +** that consist of primary key values only (created when the user inserts +** a new record) and those that consist of the primary key values and the +** original values of other table columns (created when the users deletes +** or updates a record). +** +** When this function is called, the requested changeset is created using +** both the accumulated records and the current contents of the database +** file. Specifically: +** +**
      +**
    • For each record generated by an insert, the database is queried +** for a row with a matching primary key. If one is found, an INSERT +** change is added to the changeset. If no such row is found, no change +** is added to the changeset. +** +**
    • For each record generated by an update or delete, the database is +** queried for a row with a matching primary key. If such a row is +** found and one or more of the non-primary key fields have been +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE +** change is added to the changeset. If there is a row with a matching +** primary key in the database, but all fields contain their original +** values, no change is added to the changeset. +**
    +** +** This means, amongst other things, that if a row is inserted and then later +** deleted while a session object is active, neither the insert nor the delete +** will be present in the changeset. Or if a row is deleted and then later a +** row with the same primary key values inserted while a session object is +** 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), +** 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 +** is inserted while a session object is enabled, then later deleted while +** the same session object is disabled, no INSERT record will appear in the +** changeset, even though the delete took place while the session was disabled. +** Or, if one field of a row is updated while a session is disabled, and +** 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 *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +); + +/* +** CAPI3REF: Load The Difference Between Tables Into A 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 +** does not have a primary key, this function is a no-op (but does not return +** an error). +** +** Argument zFromDb must be the name of a database ("main", "temp" etc.) +** attached to the same database handle as the session object that contains +** a table compatible with the table attached to the session by this function. +** A table is considered compatible if it: +** +**
      +**
    • Has the same name, +**
    • Has the same set of columns declared in the same order, and +**
    • Has the same PRIMARY KEY definition. +**
    +** +** If the tables are not compatible, SQLITE_SCHEMA is returned. If the tables +** are compatible but do not have any PRIMARY KEY columns, it is not an error +** but no changes are added to the session object. As with other session +** APIs, tables without PRIMARY KEYs are simply ignored. +** +** This function adds a set of changes to the session object that could be +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session +** object (call this the "to-table"). Specifically: +** +**
      +**
    • For each row (primary key) that exists in the to-table but not in +** the from-table, an INSERT record is added to the session object. +** +**
    • For each row (primary key) that exists in the to-table but not in +** the from-table, a DELETE record is added to the session object. +** +**
    • For each row (primary key) that exists in both tables, but features +** different in each, an UPDATE record is added to the session. +**
    +** +** To clarify, if this function is called and then a changeset constructed +** using [sqlite3session_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 +** 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(). +*/ +int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFromDb, + const char *zTbl, + char **pzErrMsg +); + + +/* +** CAPI3REF: Generate A Patchset From A Session Object +** +** The differences between a patchset and a changeset are that: +** +**
      +**
    • DELETE records consist of the primary key fields only. The +** original values of other fields are omitted. +**
    • The original values of any modified fields are omitted from +** UPDATE records. +**
    +** +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_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. +** +** 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 +** 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 +** 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 */ +); + +/* +** CAPI3REF: Test if a changeset has recorded any changes. +** +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or +** 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 +** 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 +** changeset containing zero changes. +*/ +int sqlite3session_isempty(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset +** +** 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 +** is returned. Otherwise, if an error occurs, *pp is set to zero and an +** SQLite error code is returned. +** +** The following functions can be used to advance and query a changeset +** iterator created by this function: +** +**
      +**
    • [sqlite3changeset_next()] +**
    • [sqlite3changeset_op()] +**
    • [sqlite3changeset_new()] +**
    • [sqlite3changeset_old()] +**
    +** +** It is the responsibility of the caller to eventually destroy the iterator +** by passing it to [sqlite3changeset_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 +** 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 visted +** 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. +*/ +int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset /* Pointer to blob containing changeset */ +); + + +/* +** CAPI3REF: Advance A Changeset Iterator +** +** 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 +** is returned and the call has no effect. +** +** Immediately after an iterator is created by sqlite3changeset_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. +** Otherwise, if all changes in the changeset have already been visited, +** SQLITE_DONE is returned. +** +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** SQLITE_NOMEM. +*/ +int sqlite3changeset_next(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Obtain The Current Operation From A Changeset Iterator +** +** 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 +** 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 +** 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 +** is an indirect change, or false (0) otherwise. See the documentation for +** [sqlite3session_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. +** +** If no error occurs, SQLITE_OK is returned. If an error does occur, an +** 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 */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ +); + +/* +** CAPI3REF: Obtain The Primary Key Definition Of A Table +** +** For each modified table, a changeset includes the following: +** +**
      +**
    • The number of columns in the table, and +**
    • Which of those columns make up the tables PRIMARY KEY. +**
    +** +** This function is used to find which columns comprise the PRIMARY KEY of +** the table modified by the change that iterator pIter currently points to. +** If successful, *pabPK is set to point to an array of nCol entries, where +** nCol is the number of columns in the table. Elements of *pabPK are set to +** 0x01 if the corresponding column is part of the tables primary key, or +** 0x00 if it is not. +** +** If argumet pnCol is not NULL, then *pnCol is set to the number of columns +** in the table. +** +** If this function is called when the iterator does not point to a valid +** entry, SQLITE_MISUSE is returned and the output variables zeroed. Otherwise, +** SQLITE_OK is returned and the output variables populated as described +** above. +*/ +int sqlite3changeset_pk( + sqlite3_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 +** +** 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. +** 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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** 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. +** +** 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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain new.* Values From A Changeset Iterator +** +** 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. +** 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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** 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 +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete +** triggers. +** +** 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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +); + +/* +** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator +** +** This function should only be used with iterator objects passed to a +** conflict-handler callback by [sqlite3changeset_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. +** +** Argument iVal must be greater than or equal to 0, and less than the number +** of columns in the table affected by the current change. Otherwise, +** [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 +** "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 iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +); + +/* +** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations +** +** This function may only be called with an iterator passed to an +** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case +** it sets the output variable to the total number of known foreign key +** violations in the destination database and returns SQLITE_OK. +** +** In all other cases this function returns SQLITE_MISUSE. +*/ +int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +); + + +/* +** CAPI3REF: Finalize A Changeset Iterator +** +** This function is used to finalize an iterator allocated with +** [sqlite3changeset_start()]. +** +** This function should only be called on iterators created using the +** [sqlite3changeset_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 +** 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 +** 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() ){ +** // Do something with change. +** } +** rc = sqlite3changeset_finalize(); +** if( rc!=SQLITE_OK ){ +** // An error has occurred +** } +*/ +int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); + +/* +** CAPI3REF: Invert A Changeset +** +** This function is used to "invert" a changeset object. Applying an inverted +** changeset to a database reverses the effects of applying the uninverted +** changeset. Specifically: +** +**
      +**
    • Each DELETE change is changed to an INSERT, and +**
    • Each INSERT change is changed to a DELETE, and +**
    • For each UPDATE change, the old.* and new.* values are exchanged. +**
    +** +** This function does not change the order in which changes appear within +** the changeset. It merely reverses the sense of each individual change. +** +** If successful, a pointer to a buffer containing the inverted changeset +** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and +** 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() +** 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 nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); + +/* +** CAPI3REF: Concatenate Two Changeset Objects +** +** This function is used to concatenate two changesets, A and B, into a +** single changeset. The result is a changeset equivalent to applying +** 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 +** 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); +** if( rc==SQLITE_OK ){ +** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut); +** }else{ +** *ppOut = 0; +** *pnOut = 0; +** } +** +** Refer to the sqlite3_changegroup documentation below for details. +*/ +int sqlite3changeset_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 */ + void *pB, /* Pointer to buffer containing changeset B */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Buffer containing output changeset */ +); + + +/* +** Changegroup handle. +*/ +typedef struct sqlite3_changegroup sqlite3_changegroup; + +/* +** CAPI3REF: Combine two or more changesets into a single changeset. +** +** An sqlite3_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 +** should eventually free the returned object using a call to +** sqlite3changegroup_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: +** +**
      +**
    • It is created using a call to sqlite3changegroup_new(). +** +**
    • Zero or more changesets (or patchsets) are added to the object +** by calling sqlite3changegroup_add(). +** +**
    • The result of combining all input changesets together is obtained +** by the application via a call to sqlite3changegroup_output(). +** +**
    • The object is deleted using a call to sqlite3changegroup_delete(). +**
    +** +** 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(). +*/ +int sqlite3changegroup_new(sqlite3_changegroup **pp); + +/* +** Add all changes within the changeset (or patchset) in buffer pData (size +** nData bytes) to the changegroup. +** +** If the buffer contains a patchset, then all prior calls to this function +** on the same changegroup object must also have specified patchsets. Or, if +** the buffer contains a changeset, so must have the earlier calls to this +** function. Otherwise, SQLITE_ERROR is returned and no changes are added +** to the changegroup. +** +** Rows within the changeset and changegroup are identified by the values in +** their PRIMARY KEY columns. A change in the changeset is considered to +** apply to the same row as a change already present in the changegroup if +** the two rows have the same primary key. +** +** Changes to rows that that do not already appear in the changegroup are +** simply copied into it. Or, if both the new changeset and the changegroup +** contain changes that apply to a single row, the final contents of the +** changegroup depends on the type of each change, as follows: +** +**
  • +** +** +**
    Existing Change New Change Output Change +**
    INSERT INSERT +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    INSERT UPDATE +** The INSERT change remains in the changegroup. The values in the +** INSERT change are modified as if the row was inserted by the +** existing change and then updated according to the new change. +**
    INSERT DELETE +** The existing INSERT is removed from the changegroup. The DELETE is +** not added. +**
    UPDATE INSERT +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    UPDATE UPDATE +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once +** by the existing change and then again by the new change. +**
    UPDATE DELETE +** The existing UPDATE is replaced by the new DELETE within the +** changegroup. +**
    DELETE INSERT +** If one or more of the column values in the row inserted by the +** new change differ from those in the row deleted by the existing +** change, the existing DELETE is replaced by an UPDATE within the +** changegroup. Otherwise, if the inserted row is exactly the same +** as the deleted row, the existing DELETE is simply discarded. +**
    DELETE UPDATE +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    DELETE DELETE +** The new change is ignored. This case does not occur if the new +** changeset was recorded immediately after the changesets already +** added to the changegroup. +**
    +** +** If the new changeset contains changes to a table that is already present +** in the changegroup, then the number of columns and the position of the +** primary key columns for the table must be consistent. If this is not the +** 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. +** +** If no error occurs, SQLITE_OK is returned. +*/ +int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** 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 +** 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 +** changes for tables that do not appear in the first changeset, they are +** appended onto the end of the output changeset, again in the order in +** which they are first encountered. +** +** If an error occurs, an SQLite error code is returned and the output +** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK +** 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(). +*/ +int sqlite3changegroup_output( + sqlite3_changegroup*, + int *pnData, /* OUT: Size of output buffer in bytes */ + void **ppData /* OUT: Pointer to output buffer */ +); + +/* +** Delete a changegroup object. +*/ +void sqlite3changegroup_delete(sqlite3_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. +** +** The fourth argument (xFilter) passed to this function 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. +** +** 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 +** considered compatible if all of the following are true: +** +**
      +**
    • The table has the same name as the name recorded in the +** changeset, and +**
    • The table has the same number of columns as recorded in the +** changeset, and +**
    • The table has primary key columns in the same position as +** recorded in the changeset. +**
    +** +** 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 +** 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 +** invoked. A description of exactly when the conflict handler is invoked for +** each type of change is below. +** +** Unlike the xFilter argument, xConflict may not be passed NULL. The results +** of passing anything other than a valid function pointer as the xConflict +** argument are undefined. +** +** Each time the conflict handler function is invoked, it must return one +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned +** 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 +** returned by each invocation of the conflict-handler function. Refer to +** the documentation for the three +** [SQLITE_CHANGESET_OMIT|available return values] for details. +** +**
    +**
    DELETE Changes
    +** For each DELETE change, this 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 deleted from 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 the original +** row value stored in the changeset, the conflict-handler function is +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the DELETE operation is attempted, but SQLite returns SQLITE_CONSTRAINT +** (which can only happen if a foreign key constraint is violated), the +** conflict-handler function is invoked with [SQLITE_CHANGESET_CONSTRAINT] +** passed as the second argument. This includes the case where the DELETE +** operation is attempted because an earlier call to the conflict handler +** function returned [SQLITE_CHANGESET_REPLACE]. +** +**
    INSERT Changes
    +** For each INSERT change, an attempt is made to insert the new row into +** the database. +** +** If the attempt to insert the row fails because the database already +** contains a row with the same primary key values, the conflict handler +** function is invoked with the second argument set to +** [SQLITE_CHANGESET_CONFLICT]. +** +** If the attempt to insert the row fails because of some other constraint +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +** +**
    UPDATE Changes
    +** For each UPDATE change, this 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. +** +** 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 +** 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. +** +** If no row with matching primary key values is found in the database, +** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] +** passed as the second argument. +** +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. +** This includes the case where the UPDATE operation is attempted after +** an earlier call to the conflict handler function returned +** [SQLITE_CHANGESET_REPLACE]. +**
    +** +** 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 +** resolution strategy. +** +** All changes made by this function 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 */ + 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 */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); + +/* +** CAPI3REF: Constants Passed To The Conflict Handler +** +** Values that may be passed as the second argument to a conflict-handler. +** +**
    +**
    SQLITE_CHANGESET_DATA
    +** The conflict handler is invoked with CHANGESET_DATA as the second argument +** when processing a DELETE or UPDATE change if a row with the required +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the +** expected "before" values. +** +** The conflicting row, in this case, is the database row with the matching +** primary key. +** +**
    SQLITE_CHANGESET_NOTFOUND
    +** The conflict handler is invoked with CHANGESET_NOTFOUND as the second +** argument when processing a DELETE or UPDATE change if a row with the +** 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. +** +**
    SQLITE_CHANGESET_CONFLICT
    +** CHANGESET_CONFLICT is passed as the second argument to the conflict +** handler while processing an INSERT change if the operation would result +** in duplicate primary key values. +** +** The conflicting row in this case is the database row with the matching +** primary key. +** +**
    SQLITE_CHANGESET_FOREIGN_KEY
    +** If foreign key handling is enabled, and applying a changeset leaves the +** database in a state containing foreign key violations, the conflict +** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument +** exactly once before the changeset is committed. If the conflict handler +** returns CHANGESET_OMIT, the changes, including those that caused the +** foreign key constraint violation, are committed. Or, if it returns +** 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(). +** +**
    SQLITE_CHANGESET_CONSTRAINT
    +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** 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. +** +**
    +*/ +#define SQLITE_CHANGESET_DATA 1 +#define SQLITE_CHANGESET_NOTFOUND 2 +#define SQLITE_CHANGESET_CONFLICT 3 +#define SQLITE_CHANGESET_CONSTRAINT 4 +#define SQLITE_CHANGESET_FOREIGN_KEY 5 + +/* +** CAPI3REF: Constants Returned By The Conflict Handler +** +** A conflict handler callback must return one of the following three values. +** +**
    +**
    SQLITE_CHANGESET_OMIT
    +** If a conflict handler returns this value no special action is taken. The +** change that caused the conflict is not applied. The session module +** continues to the next change in the changeset. +** +**
    SQLITE_CHANGESET_REPLACE
    +** 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. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict +** handler, then the conflicting row is either updated or deleted, depending +** on the type of change. +** +** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict +** handler, then the conflicting row is removed from the database and a +** second attempt to apply the change is made. If this second attempt fails, +** the original row is restored to the database before continuing. +** +**
    SQLITE_CHANGESET_ABORT
    +** If this value is returned, any changes applied so far are rolled back +** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. +**
    +*/ +#define SQLITE_CHANGESET_OMIT 0 +#define SQLITE_CHANGESET_REPLACE 1 +#define SQLITE_CHANGESET_ABORT 2 + +/* +** CAPI3REF: Streaming Versions of API functions. +** +** The six streaming API xxx_strm() functions serve similar purposes to the +** corresponding non-streaming API functions: +** +** +** +**
    Streaming functionNon-streaming equivalent
    sqlite3changeset_apply_str[sqlite3changeset_apply] +**
    sqlite3changeset_concat_str[sqlite3changeset_concat] +**
    sqlite3changeset_invert_str[sqlite3changeset_invert] +**
    sqlite3changeset_start_str[sqlite3changeset_start] +**
    sqlite3session_changeset_str[sqlite3session_changeset] +**
    sqlite3session_patchset_str[sqlite3session_patchset] +**
    +** +** 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(). +** 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. +** +** In order to avoid this problem, instead of a single large buffer, input +** is passed to a streaming API functions by way of a callback function that +** the sessions module invokes to incrementally request input data as it is +** required. In all cases, a pair of API function parameters such as +** +**
    +**        int nChangeset,
    +**        void *pChangeset,
    +**  
    +** +** Is replaced by: +** +**
    +**        int (*xInput)(void *pIn, void *pData, int *pnData),
    +**        void *pIn,
    +**  
    +** +** Each time the xInput callback is invoked by the sessions module, the first +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** error code should be returned. In all cases, if an xInput callback returns +** 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 +** 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 +** immediately fail with the same error code as returned by xInput. +** +** Similarly, streaming API functions that return changesets (or patchsets) +** return them in chunks by way of a callback function instead of via a +** pointer to a single large buffer. In this case, a pair of parameters such +** as: +** +**
    +**        int *pnChangeset,
    +**        void **ppChangeset,
    +**  
    +** +** Is replaced by: +** +**
    +**        int (*xOutput)(void *pOut, const void *pData, int nData),
    +**        void *pOut
    +**  
    +** +** The xOutput callback is invoked zero or more times to return data to +** the application. The first parameter passed to each call is a copy of the +** pOut pointer supplied by the application. The second parameter, pData, +** points to a buffer nData bytes in size containing the chunk of output +** data being returned. If the xOutput callback successfully processes the +** supplied data, it should return SQLITE_OK to indicate success. Otherwise, +** it should return some other SQLite error code. In this case processing +** is immediately abandoned and the streaming API function returns a copy +** of the xOutput error code to the application. +** +** The sessions module never invokes an xOutput callback with the third +** 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 (*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 */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3changeset_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 (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +int sqlite3changegroup_add_strm(sqlite3_changegroup*, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +int sqlite3changegroup_output_strm(sqlite3_changegroup*, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); + + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +} +#endif + +#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ + +/******** End of sqlite3session.h *********/ +/******** Begin file fts5.h *********/ /* ** 2014 May 31 ** @@ -8006,6 +9690,9 @@ struct Fts5PhraseIter { ** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** +** This function may be quite inefficient if used with an FTS5 table +** created with the "columnsize=0" option. +** ** xColumnText: ** This function attempts to retrieve the text of column iCol of the ** current document. If successful, (*pz) is set to point to a buffer @@ -8026,15 +9713,29 @@ struct Fts5PhraseIter { ** the query within the current row. Return 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. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always returns 0. +** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value ** output by xInstCount(). ** +** 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. ** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** ** xRowid: ** Returns the rowid of the current row. ** @@ -8048,11 +9749,13 @@ struct Fts5PhraseIter { ** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid ** ** with $p set to a phrase equivalent to the phrase iPhrase of the -** current query is executed. For each row visited, the callback function -** passed as the fourth argument is invoked. The context and API objects -** passed to the callback function may be used to access the properties of -** each matched row. Invoking Api.xUserData() returns a copy of the pointer -** passed as the third argument to pUserData. +** current query is executed. Any column filter that applies to +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback +** function may be used to access the properties of each matched row. +** Invoking Api.xUserData() returns a copy of the pointer passed as +** the third argument to pUserData. ** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. @@ -8118,7 +9821,7 @@ struct Fts5PhraseIter { ** Fts5PhraseIter iter; ** int iCol, iOff; ** for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); -** iOff>=0; +** iCol>=0; ** pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) ** ){ ** // An instance of phrase iPhrase at offset iOff of column iCol @@ -8126,13 +9829,51 @@ struct Fts5PhraseIter { ** ** The Fts5PhraseIter structure is defined above. Applications should not ** modify this structure directly - it should only be used as shown above -** with the xPhraseFirst() and xPhraseNext() API methods. +** with the xPhraseFirst() and xPhraseNext() API methods (and by +** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option +** (i.e. if it is a contentless table), then this API always iterates +** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** ** xPhraseNext() ** See xPhraseFirst above. +** +** xPhraseFirstColumn() +** This function and xPhraseNextColumn() are similar to the xPhraseFirst() +** and xPhraseNext() APIs described above. The difference is that instead +** of iterating through all instances of a phrase in the current row, these +** APIs are used to iterate through the set of columns in the current row +** that contain one or more instances of a specified phrase. For example: +** +** Fts5PhraseIter iter; +** int iCol; +** for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); +** iCol>=0; +** pApi->xPhraseNextColumn(pFts, &iter, &iCol) +** ){ +** // Column iCol contains at least one instance of phrase iPhrase +** } +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to +** xPhraseFirstColumn() set iCol to -1). +** +** The information accessed using this API and its companion +** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext +** (or xInst/xInstCount). The chief advantage of this API is that it is +** significantly more efficient than those alternatives when used with +** "detail=column" tables. +** +** xPhraseNextColumn() +** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 1 */ + int iVersion; /* Currently always set to 3 */ void *(*xUserData)(Fts5Context*); @@ -8162,8 +9903,11 @@ struct Fts5ExtensionApi { int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); void *(*xGetAuxdata)(Fts5Context*, int bClear); - void (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); }; /* @@ -8441,3 +10185,4 @@ struct fts5_api { #endif /* _FTS5_H */ +/******** End of fts5.h *********/ diff --git a/TMessagesProj/jni/sqlite_cursor.c b/TMessagesProj/jni/sqlite_cursor.c deleted file mode 100755 index de3f7469d..000000000 --- a/TMessagesProj/jni/sqlite_cursor.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "sqlite.h" - -int Java_org_telegram_SQLite_SQLiteCursor_columnType(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - return sqlite3_column_type(handle, columnIndex); -} - -int Java_org_telegram_SQLite_SQLiteCursor_columnIsNull(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - int valType = sqlite3_column_type(handle, columnIndex); - return SQLITE_NULL == valType; -} - -int Java_org_telegram_SQLite_SQLiteCursor_columnIntValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - int valType = sqlite3_column_type(handle, columnIndex); - if (SQLITE_NULL == valType) { - return 0; - } - return sqlite3_column_int(handle, columnIndex); -} - -long long Java_org_telegram_SQLite_SQLiteCursor_columnLongValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - int valType = sqlite3_column_type(handle, columnIndex); - if (SQLITE_NULL == valType) { - return 0; - } - return sqlite3_column_int64(handle, columnIndex); -} - -double Java_org_telegram_SQLite_SQLiteCursor_columnDoubleValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - int valType = sqlite3_column_type(handle, columnIndex); - if (SQLITE_NULL == valType) { - return 0; - } - return sqlite3_column_double(handle, columnIndex); -} - -jstring Java_org_telegram_SQLite_SQLiteCursor_columnStringValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - const char *str = sqlite3_column_text(handle, columnIndex); - if (str != 0) { - return (*env)->NewStringUTF(env, str); - } - return 0; -} - -jbyteArray Java_org_telegram_SQLite_SQLiteCursor_columnByteArrayValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - void *buf = sqlite3_column_blob(handle, columnIndex); - int length = sqlite3_column_bytes(handle, columnIndex); - if (buf != 0 && length > 0) { - jbyteArray result = (*env)->NewByteArray(env, length); - (*env)->SetByteArrayRegion(env, result, 0, length, buf); - return result; - } - return 0; -} - -int Java_org_telegram_SQLite_SQLiteCursor_columnByteArrayLength(JNIEnv *env, jobject object, int statementHandle, int columnIndex) { - return sqlite3_column_bytes((sqlite3_stmt *)statementHandle, columnIndex); -} - -int Java_org_telegram_SQLite_SQLiteCursor_columnByteBufferValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex, jobject buffer) { - if (!buffer) { - return 0; - } - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - void *buf = sqlite3_column_blob(handle, columnIndex); - int length = sqlite3_column_bytes(handle, columnIndex); - if (buf != 0 && length > 0) { - jbyte *byteBuff = (*env)->GetDirectBufferAddress(env, buffer); - memcpy(byteBuff, buf, length); - return length; - } - return 0; -} diff --git a/TMessagesProj/jni/sqlite_database.c b/TMessagesProj/jni/sqlite_database.c deleted file mode 100755 index a249cff43..000000000 --- a/TMessagesProj/jni/sqlite_database.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "sqlite.h" - -void Java_org_telegram_SQLite_SQLiteDatabase_closedb(JNIEnv *env, jobject object, int sqliteHandle) { - sqlite3 *handle = (sqlite3 *)sqliteHandle; - int err = sqlite3_close(handle); - if (SQLITE_OK != err) { - throw_sqlite3_exception(env, handle, err); - } -} - -void Java_org_telegram_SQLite_SQLiteDatabase_beginTransaction(JNIEnv *env, jobject object, int sqliteHandle) { - sqlite3 *handle = (sqlite3 *)sqliteHandle; - sqlite3_exec(handle, "BEGIN", 0, 0, 0); -} - -void Java_org_telegram_SQLite_SQLiteDatabase_commitTransaction(JNIEnv *env, jobject object, int sqliteHandle) { - sqlite3 *handle = (sqlite3 *)sqliteHandle; - sqlite3_exec(handle, "COMMIT", 0, 0, 0); -} - -int Java_org_telegram_SQLite_SQLiteDatabase_opendb(JNIEnv *env, jobject object, jstring fileName, jstring tempDir) { - char const *fileNameStr = (*env)->GetStringUTFChars(env, fileName, 0); - char const *tempDirStr = (*env)->GetStringUTFChars(env, tempDir, 0); - - if (sqlite3_temp_directory != 0) { - sqlite3_free(sqlite3_temp_directory); - } - sqlite3_temp_directory = sqlite3_mprintf("%s", tempDirStr); - - sqlite3 *handle = 0; - int err = sqlite3_open(fileNameStr, &handle); - if (SQLITE_OK != err) { - throw_sqlite3_exception(env, handle, err); - } - if (fileNameStr != 0) { - (*env)->ReleaseStringUTFChars(env, fileName, fileNameStr); - } - if (tempDirStr != 0) { - (*env)->ReleaseStringUTFChars(env, tempDir, tempDirStr); - } - return (int)handle; -} diff --git a/TMessagesProj/jni/sqlite_statement.c b/TMessagesProj/jni/sqlite_statement.c deleted file mode 100755 index 4957f790b..000000000 --- a/TMessagesProj/jni/sqlite_statement.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "sqlite.h" - -jfieldID queryArgsCountField; - -jint sqliteOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env) { - jclass class = (*env)->FindClass(env, "org/telegram/SQLite/SQLitePreparedStatement"); - queryArgsCountField = (*env)->GetFieldID(env, class, "queryArgsCount", "I"); - return JNI_VERSION_1_6; -} - -int Java_org_telegram_SQLite_SQLitePreparedStatement_step(JNIEnv *env, jobject object, int statementHandle) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_step(handle); - if (errcode == SQLITE_ROW) { - return 0; - } else if(errcode == SQLITE_DONE) { - return 1; - } else if(errcode == SQLITE_BUSY) { - return -1; - } - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); -} - -int Java_org_telegram_SQLite_SQLitePreparedStatement_prepare(JNIEnv *env, jobject object, int sqliteHandle, jstring sql) { - sqlite3 *handle = (sqlite3 *) sqliteHandle; - - char const *sqlStr = (*env)->GetStringUTFChars(env, sql, 0); - - sqlite3_stmt *stmt_handle; - - int errcode = sqlite3_prepare_v2(handle, sqlStr, -1, &stmt_handle, 0); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, handle, errcode); - } else { - int argsCount = sqlite3_bind_parameter_count(stmt_handle); - (*env)->SetIntField(env, object, queryArgsCountField, argsCount); - } - - if (sqlStr != 0) { - (*env)->ReleaseStringUTFChars(env, sql, sqlStr); - } - - return (int) stmt_handle; -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_reset(JNIEnv *env, jobject object, int statementHandle) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_reset(handle); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_finalize(JNIEnv *env, jobject object, int statementHandle) { - sqlite3_finalize((sqlite3_stmt *) statementHandle); -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindByteBuffer(JNIEnv *env, jobject object, int statementHandle, int index, jobject value, int length) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - jbyte *buf = (*env)->GetDirectBufferAddress(env, value); - - int errcode = sqlite3_bind_blob(handle, index, buf, length, SQLITE_STATIC); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindString(JNIEnv *env, jobject object, int statementHandle, int index, jstring value) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - char const *valueStr = (*env)->GetStringUTFChars(env, value, 0); - - int errcode = sqlite3_bind_text(handle, index, valueStr, -1, SQLITE_TRANSIENT); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } - - if (valueStr != 0) { - (*env)->ReleaseStringUTFChars(env, value, valueStr); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindInt(JNIEnv *env, jobject object, int statementHandle, int index, int value) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_bind_int(handle, index, value); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindLong(JNIEnv *env, jobject object, int statementHandle, int index, long long value) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_bind_int64(handle, index, value); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindDouble(JNIEnv *env, jobject object, int statementHandle, int index, double value) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_bind_double(handle, index, value); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindNull(JNIEnv *env, jobject object, int statementHandle, int index) { - sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; - - int errcode = sqlite3_bind_null(handle, index); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } -} - diff --git a/TMessagesProj/jni/tgnet/Datacenter.cpp b/TMessagesProj/jni/tgnet/Datacenter.cpp index d5256199f..faa320309 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.cpp +++ b/TMessagesProj/jni/tgnet/Datacenter.cpp @@ -1378,7 +1378,7 @@ NativeByteBuffer *Datacenter::createRequestsData(std::vectorgetConnectionType(), (uint64_t) connection->getSissionId(), networkMessage->message->seqno, (uint64_t) networkMessage->message->msg_id, typeid(*messageBody).name(), messageBody); int64_t messageTime = (int64_t) (networkMessage->message->msg_id / 4294967296.0 * 1000); - int64_t currentTime = ConnectionsManager::getInstance().getCurrentTimeMillis() + (int64_t) timeDifference * 1000; + int64_t currentTime = ConnectionsManager::getInstance().getCurrentTimeMillis() + (int64_t) ConnectionsManager::getInstance().getTimeDifference() * 1000; if (messageTime < currentTime - 30000 || messageTime > currentTime + 25000) { DEBUG_D("wrap message in container"); diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp index 7921031dc..efe399897 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.cpp +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.cpp @@ -947,6 +947,7 @@ void TL_config::readParams(NativeByteBuffer *stream, bool &error) { push_chat_limit = stream->readInt32(&error); saved_gifs_limit = stream->readInt32(&error); edit_time_limit = stream->readInt32(&error); + rating_e_decay = stream->readInt32(&error); magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; @@ -989,6 +990,7 @@ void TL_config::serializeToStream(NativeByteBuffer *stream) { stream->writeInt32(push_chat_limit); stream->writeInt32(saved_gifs_limit); stream->writeInt32(edit_time_limit); + stream->writeInt32(rating_e_decay); stream->writeInt32(0x1cb5c415); count = (uint32_t) disabled_features.size(); stream->writeInt32(count); diff --git a/TMessagesProj/jni/tgnet/MTProtoScheme.h b/TMessagesProj/jni/tgnet/MTProtoScheme.h index 75922fefc..cb6b92148 100644 --- a/TMessagesProj/jni/tgnet/MTProtoScheme.h +++ b/TMessagesProj/jni/tgnet/MTProtoScheme.h @@ -657,7 +657,7 @@ public: class TL_config : public TLObject { public: - static const uint32_t constructor = 0x317ceef4; + static const uint32_t constructor = 0xc9411388; int32_t date; int32_t expires; @@ -678,6 +678,7 @@ public: int32_t push_chat_limit; int32_t saved_gifs_limit; int32_t edit_time_limit; + int32_t rating_e_decay; std::vector> disabled_features; static TL_config *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error); diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index f16acf716..e88b0da7b 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -15,6 +15,8 @@ + + @@ -37,6 +39,10 @@ + + + + + + + + + + + @@ -222,6 +238,7 @@ + diff --git a/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java b/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java index 63081ef8b..89066116d 100644 --- a/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java +++ b/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java @@ -182,41 +182,47 @@ public class PhoneFormat { if (!initialzed) { return orig; } - String str = strip(orig); + try { + String str = strip(orig); - if (str.startsWith("+")) { - String rest = str.substring(1); - CallingCodeInfo info = findCallingCodeInfo(rest); - if (info != null) { - String phone = info.format(rest); - return "+" + phone; - } else { - return orig; - } - } else { - CallingCodeInfo info = callingCodeInfo(defaultCallingCode); - if (info == null) { - return orig; - } - - String accessCode = info.matchingAccessCode(str); - if (accessCode != null) { - String rest = str.substring(accessCode.length()); - String phone = rest; - CallingCodeInfo info2 = findCallingCodeInfo(rest); - if (info2 != null) { - phone = info2.format(rest); - } - - if (phone.length() == 0) { - return accessCode; + if (str.startsWith("+")) { + String rest = str.substring(1); + CallingCodeInfo info = findCallingCodeInfo(rest); + if (info != null) { + String phone = info.format(rest); + return "+" + phone; } else { - return String.format("%s %s", accessCode, phone); + return orig; } } else { - return info.format(str); + CallingCodeInfo info = callingCodeInfo(defaultCallingCode); + if (info == null) { + return orig; + } + + String accessCode = info.matchingAccessCode(str); + if (accessCode != null) { + String rest = str.substring(accessCode.length()); + String phone = rest; + CallingCodeInfo info2 = findCallingCodeInfo(rest); + if (info2 != null) { + phone = info2.format(rest); + } + + if (phone.length() == 0) { + return accessCode; + } else { + return String.format("%s %s", accessCode, phone); + } + } else { + return info.format(str); + } } + } catch (Exception e) { + FileLog.e("tmessages", e); + return orig; } + } public boolean isPhoneNumberValid(String phoneNumber) { diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java index b667c7fca..49a74fa0a 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java @@ -11,8 +11,6 @@ package org.telegram.SQLite; import org.telegram.messenger.FileLog; import org.telegram.tgnet.NativeByteBuffer; -import java.nio.ByteBuffer; - public class SQLiteCursor { public static final int FIELD_TYPE_INT = 1; @@ -58,19 +56,13 @@ public class SQLiteCursor { return columnByteArrayValue(preparedStatement.getStatementHandle(), columnIndex); } - public int byteArrayLength(int columnIndex) throws SQLiteException { - checkRow(); - return columnByteArrayLength(preparedStatement.getStatementHandle(), columnIndex); - } - - public int byteBufferValue(int columnIndex, ByteBuffer buffer) throws SQLiteException { - checkRow(); - return columnByteBufferValue(preparedStatement.getStatementHandle(), columnIndex, buffer); - } - - public int byteBufferValue(int columnIndex, NativeByteBuffer buffer) throws SQLiteException { + public NativeByteBuffer byteBufferValue(int columnIndex) throws SQLiteException { checkRow(); - return columnByteBufferValue(preparedStatement.getStatementHandle(), columnIndex, buffer.buffer); + int ptr = columnByteBufferValue(preparedStatement.getStatementHandle(), columnIndex); + if (ptr != 0) { + return NativeByteBuffer.wrap(ptr); + } + return null; } public int getTypeOf(int columnIndex) throws SQLiteException { @@ -123,6 +115,5 @@ public class SQLiteCursor { native double columnDoubleValue(int statementHandle, int columnIndex); native String columnStringValue(int statementHandle, int columnIndex); native byte[] columnByteArrayValue(int statementHandle, int columnIndex); - native int columnByteArrayLength(int statementHandle, int columnIndex); - native int columnByteBufferValue(int statementHandle, int columnIndex, ByteBuffer buffer); + native int columnByteBufferValue(int statementHandle, int columnIndex); } diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java index 6135e037f..cae02b41f 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java @@ -12,16 +12,14 @@ import org.telegram.messenger.FileLog; import org.telegram.tgnet.NativeByteBuffer; import java.nio.ByteBuffer; -import java.util.HashMap; public class SQLitePreparedStatement { + private boolean isFinalized = false; private int sqliteStatementHandle; - - private int queryArgsCount; private boolean finalizeAfterQuery = false; - private static HashMap hashMap; + //private static HashMap hashMap; public int getStatementHandle() { return sqliteStatementHandle; @@ -43,7 +41,7 @@ public class SQLitePreparedStatement { public SQLiteCursor query(Object[] args) throws SQLiteException { - if (args == null || args.length != queryArgsCount) { + if (args == null) { throw new IllegalArgumentException(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 133433214..2ecda7417 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -20,9 +20,15 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; @@ -99,6 +105,9 @@ public class AndroidUtilities { private static Boolean isTablet = null; private static int adjustOwnerClassGuid = 0; + private static Paint roundPaint; + private static RectF bitmapRect; + public static Pattern WEB_URL = null; static { try { @@ -277,6 +286,21 @@ public class AndroidUtilities { } } + public static boolean isInternalUri(Uri uri) { + String pathString = uri.getPath(); + if (pathString == null) { + return false; + } + while (true) { + String newPath = Utilities.readlink(pathString); + if (newPath == null || newPath.equals(pathString)) { + break; + } + pathString = newPath; + } + return pathString != null && pathString.toLowerCase().contains("/data/data/" + ApplicationLoader.applicationContext.getPackageName() + "/files"); + } + public static void lockOrientation(Activity activity) { if (activity == null || prevOrientation != -10) { return; @@ -380,27 +404,40 @@ public class AndroidUtilities { if (view == null) { return; } - InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + try { + InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } catch (Exception e) { + FileLog.e("tmessages", e); + } } public static boolean isKeyboardShowed(View view) { if (view == null) { return false; } - InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - return inputManager.isActive(view); + try { + InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + return inputManager.isActive(view); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + return false; } public static void hideKeyboard(View view) { if (view == null) { return; } - InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - if (!imm.isActive()) { - return; + try { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (!imm.isActive()) { + return; + } + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } catch (Exception e) { + FileLog.e("tmessages", e); } - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } public static File getCacheDir() { @@ -673,6 +710,137 @@ public class AndroidUtilities { } } + private static Intent createShortcutIntent(long did, boolean forDelete) { + Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); + + int lower_id = (int) did; + int high_id = (int) (did >> 32); + + TLRPC.User user = null; + TLRPC.Chat chat = null; + if (lower_id == 0) { + shortcutIntent.putExtra("encId", high_id); + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + if (encryptedChat == null) { + return null; + } + user = MessagesController.getInstance().getUser(encryptedChat.user_id); + } else if (lower_id > 0) { + shortcutIntent.putExtra("userId", lower_id); + user = MessagesController.getInstance().getUser(lower_id); + } else if (lower_id < 0) { + chat = MessagesController.getInstance().getChat(-lower_id); + shortcutIntent.putExtra("chatId", -lower_id); + } else { + return null; + } + if (user == null && chat == null) { + return null; + } + + String name; + TLRPC.FileLocation photo = null; + + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + if (user.photo != null) { + photo = user.photo.photo_small; + } + } else { + name = chat.title; + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + } + + shortcutIntent.setAction("com.tmessages.openchat" + did); + shortcutIntent.addFlags(0x4000000); + + Intent addIntent = new Intent(); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + addIntent.putExtra("duplicate", false); + if (!forDelete) { + Bitmap bitmap = null; + if (photo != null) { + try { + File path = FileLoader.getPathToAttach(photo, true); + bitmap = BitmapFactory.decodeFile(path.toString()); + if (bitmap != null) { + int size = AndroidUtilities.dp(58); + Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + result.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(result); + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (roundPaint == null) { + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapRect = new RectF(); + } + float scale = size / (float) bitmap.getWidth(); + canvas.save(); + canvas.scale(scale, scale); + roundPaint.setShader(shader); + bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); + canvas.restore(); + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.book_logo); + int w = AndroidUtilities.dp(15); + int left = size - w - AndroidUtilities.dp(2); + int top = size - w - AndroidUtilities.dp(2); + drawable.setBounds(left, top, left + w, top + w); + drawable.draw(canvas); + try { + canvas.setBitmap(null); + } catch (Exception e) { + //don't promt, this will crash on 2.x + } + bitmap = result; + } + } catch (Throwable e) { + FileLog.e("tmessages", e); + } + } + if (bitmap != null) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); + } else { + if (user != null) { + if (user.bot) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_bot)); + } else { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_user)); + } + } else if (chat != null) { + if (ChatObject.isChannel(chat) && !chat.megagroup) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_channel)); + } else { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_group)); + } + } + } + } + return addIntent; + } + + public static void installShortcut(long did) { + try { + Intent addIntent = createShortcutIntent(did, false); + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + ApplicationLoader.applicationContext.sendBroadcast(addIntent); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + + public static void uninstallShortcut(long did) { + try { + Intent addIntent = createShortcutIntent(did, true); + addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); + ApplicationLoader.applicationContext.sendBroadcast(addIntent); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + public static int getViewInset(View view) { if (view == null || Build.VERSION.SDK_INT < 21 || view.getHeight() == AndroidUtilities.displaySize.y || view.getHeight() == AndroidUtilities.displaySize.y - statusBarHeight) { return 0; @@ -924,6 +1092,21 @@ public class AndroidUtilities { } } + public static void addToClipboard(CharSequence str) { + try { + if (Build.VERSION.SDK_INT < 11) { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setText(str); + } else { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", str); + clipboard.setPrimaryClip(clip); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + public static void addMediaToGallery(String fromPath) { if (fromPath == null) { return; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java index 6ed4862d6..ffa92478b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java @@ -20,9 +20,6 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; @@ -373,8 +370,14 @@ public class ApplicationLoader extends Application { } private boolean checkPlayServices() { - int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); - return resultCode == ConnectionResult.SUCCESS; + try { + int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); + return resultCode == ConnectionResult.SUCCESS; + } catch (Exception e) { + FileLog.e("tmessages", e); + } + return true; + /*if (resultCode != ConnectionResult.SUCCESS) { if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) { GooglePlayServicesUtil.getErrorDialog(resultCode, this, PLAY_SERVICES_RESOLUTION_REQUEST).show(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 347eef541..ac30c6286 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -10,8 +10,8 @@ package org.telegram.messenger; public class BuildVars { public static boolean DEBUG_VERSION = false; - public static int BUILD_VERSION = 787; - public static String BUILD_VERSION_STRING = "3.8"; + public static int BUILD_VERSION = 803; + public static String BUILD_VERSION_STRING = "3.9"; public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index 2521e1d22..0fefae69e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -43,6 +43,7 @@ public class FileLoadOperation { private volatile int state = stateIdle; private int downloadedBytes; private int totalBytesCount; + private int bytesCountPadding; private FileLoadOperationDelegate delegate; private byte[] key; private byte[] iv; @@ -110,8 +111,13 @@ public class FileLoadOperation { location.access_hash = documentLocation.access_hash; datacenter_id = documentLocation.dc_id; } - if (totalBytesCount <= 0) { - totalBytesCount = documentLocation.size; + totalBytesCount = documentLocation.size; + if (key != null) { + int toAdd = 0; + if (totalBytesCount % 16 != 0) { + bytesCountPadding = 16 - totalBytesCount % 16; + totalBytesCount += bytesCountPadding; + } } ext = FileLoader.getDocumentFileName(documentLocation); int idx; @@ -405,8 +411,15 @@ public class FileLoadOperation { onFinishLoadingFile(); return; } + int currentBytesSize = requestInfo.response.bytes.limit(); + downloadedBytes += currentBytesSize; + boolean finishedDownloading = currentBytesSize != currentDownloadChunkSize || (totalBytesCount == downloadedBytes || downloadedBytes % currentDownloadChunkSize != 0) && (totalBytesCount <= 0 || totalBytesCount <= downloadedBytes); + if (key != null) { Utilities.aesIgeEncryption(requestInfo.response.bytes.buffer, key, iv, false, true, 0, requestInfo.response.bytes.limit()); + if (finishedDownloading && bytesCountPadding != 0) { + requestInfo.response.bytes.limit(requestInfo.response.bytes.limit() - bytesCountPadding); + } } if (fileOutputStream != null) { FileChannel channel = fileOutputStream.getChannel(); @@ -416,10 +429,8 @@ public class FileLoadOperation { fiv.seek(0); fiv.write(iv); } - int currentBytesSize = requestInfo.response.bytes.limit(); - downloadedBytes += currentBytesSize; if (totalBytesCount > 0 && state == stateDownloading) { - delegate.didChangedLoadProgress(FileLoadOperation.this, Math.min(1.0f, (float)downloadedBytes / (float)totalBytesCount)); + delegate.didChangedLoadProgress(FileLoadOperation.this, Math.min(1.0f, (float) downloadedBytes / (float) totalBytesCount)); } for (int a = 0; a < delayedRequestInfos.size(); a++) { @@ -433,14 +444,10 @@ public class FileLoadOperation { } } - if (currentBytesSize != currentDownloadChunkSize) { + if (finishedDownloading) { onFinishLoadingFile(); } else { - if (totalBytesCount != downloadedBytes && downloadedBytes % currentDownloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) { - startDownloadRequest(); - } else { - onFinishLoadingFile(); - } + startDownloadRequest(); } } catch (Exception e) { cleanup(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index b7d156e08..da2b80703 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -481,16 +481,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg } } - if (bitmapDrawable instanceof AnimatedFileDrawable) { - drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); + if (bitmapW / scaleH > imageW) { + bitmapW /= scaleH; + drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH); } else { - if (bitmapW / scaleH > imageW) { - bitmapW /= scaleH; - drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH); - } else { - bitmapH /= scaleW; - drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2); - } + bitmapH /= scaleW; + drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2); } if (orientation % 360 == 90 || orientation % 360 == 270) { int width = (drawRegion.right - drawRegion.left) / 2; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index a34e0a076..05ed68d5b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -374,7 +374,7 @@ public class LocaleController { if (languageNameInEnglish.contains("&") || languageNameInEnglish.contains("|")) { return false; } - if (languageCode.contains("&") || languageCode.contains("|")) { + if (languageCode.contains("&") || languageCode.contains("|") || languageCode.contains("/") || languageCode.contains("\\")) { return false; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index ef7b89218..ea42737a3 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -239,6 +239,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private int hasAudioFocus; private boolean callInProgress; + private int audioFocus = AUDIO_NO_FOCUS_NO_DUCK; + private boolean resumeAudioOnFocusGain; + + private static final float VOLUME_DUCK = 0.2f; + private static final float VOLUME_NORMAL = 1.0f; + private static final int AUDIO_NO_FOCUS_NO_DUCK = 0; + private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1; + private static final int AUDIO_FOCUSED = 2; private ArrayList videoConvertQueue = new ArrayList<>(); private final Object videoQueueSync = new Object(); @@ -303,6 +311,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private ArrayList playlist = new ArrayList<>(); private ArrayList shuffledPlaylist = new ArrayList<>(); private int currentPlaylistNum; + private boolean forceLoopCurrentPlaylist; private boolean downloadingCurrentMessage; private boolean playMusicAgain; private AudioInfo audioInfo; @@ -369,7 +378,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, sampleStep = (float) len / 2 / (float) newPart; for (int i = 0; i < len / 2; i++) { short peak = buffer.getShort(); - sum += peak * peak; + if (peak > 2500) { + sum += peak * peak; + } if (i == (int) nextNum && currentNum < recordSamples.length) { recordSamples[currentNum] = peak; nextNum += sampleStep; @@ -668,8 +679,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, @Override public void onCallStateChanged(int state, String incomingNumber) { if (state == TelephonyManager.CALL_STATE_RINGING) { - if (MediaController.getInstance().isPlayingAudio(MediaController.getInstance().getPlayingMessageObject()) && !MediaController.getInstance().isAudioPaused()) { - MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); + if (isPlayingAudio(getPlayingMessageObject()) && !isAudioPaused()) { + pauseAudio(getPlayingMessageObject()); } else if (recordStartRunnable != null || recordingAudio != null) { stopRecording(2); } @@ -693,12 +704,46 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, @Override public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { - if (MediaController.getInstance().isPlayingAudio(MediaController.getInstance().getPlayingMessageObject()) && !MediaController.getInstance().isAudioPaused()) { - MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); + if (isPlayingAudio(getPlayingMessageObject()) && !isAudioPaused()) { + pauseAudio(getPlayingMessageObject()); } hasAudioFocus = 0; + audioFocus = AUDIO_NO_FOCUS_NO_DUCK; } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { - //MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject()); + audioFocus = AUDIO_FOCUSED; + if (resumeAudioOnFocusGain) { + resumeAudioOnFocusGain = false; + if (isPlayingAudio(getPlayingMessageObject()) && isAudioPaused()) { + playAudio(getPlayingMessageObject()); + } + } + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { + audioFocus = AUDIO_NO_FOCUS_CAN_DUCK; + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { + audioFocus = AUDIO_NO_FOCUS_NO_DUCK; + if (isPlayingAudio(getPlayingMessageObject()) && !isAudioPaused()) { + pauseAudio(getPlayingMessageObject()); + resumeAudioOnFocusGain = true; + } + } + setPlayerVolume(); + } + + private void setPlayerVolume() { + try { + float volume; + if (audioFocus != AUDIO_NO_FOCUS_CAN_DUCK) { + volume = VOLUME_NORMAL; + } else { + volume = VOLUME_DUCK; + } + if (audioPlayer != null) { + audioPlayer.setVolume(volume, volume); + } else if (audioTrackPlayer != null) { + audioTrackPlayer.setStereoVolume(volume, volume); + } + } catch (Exception e) { + FileLog.e("tmessages", e); } } @@ -1936,6 +1981,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return playingMessageObject; } + public int getPlayingMessageObjectNum() { + return currentPlaylistNum; + } + private void buildShuffledPlayList() { if (playlist.isEmpty()) { return; @@ -1956,9 +2005,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } public boolean setPlaylist(ArrayList messageObjects, MessageObject current) { + return setPlaylist(messageObjects, current, true); + } + + public boolean setPlaylist(ArrayList messageObjects, MessageObject current, boolean loadMusic) { if (playingMessageObject == current) { return playAudio(current); } + forceLoopCurrentPlaylist = !loadMusic; playMusicAgain = !playlist.isEmpty(); playlist.clear(); for (int a = messageObjects.size() - 1; a >= 0; a--) { @@ -1978,7 +2032,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, buildShuffledPlayList(); currentPlaylistNum = 0; } - SharedMediaQuery.loadMusic(current.getDialogId(), playlist.get(0).getId()); + if (loadMusic) { + SharedMediaQuery.loadMusic(current.getDialogId(), playlist.get(0).getId()); + } } return playAudio(current); } @@ -1987,10 +2043,19 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, playNextMessage(false); } + public void playMessageAtIndex(int index) { + if (currentPlaylistNum < 0 || currentPlaylistNum >= playlist.size()) { + return; + } + currentPlaylistNum = index; + playMusicAgain = true; + playAudio(playlist.get(currentPlaylistNum)); + } + private void playNextMessage(boolean byStop) { ArrayList currentPlayList = shuffleMusic ? shuffledPlaylist : playlist; - if (byStop && repeatMode == 2) { + if (byStop && repeatMode == 2 && !forceLoopCurrentPlaylist) { cleanupPlayer(false, false); playAudio(currentPlayList.get(currentPlaylistNum)); return; @@ -1998,7 +2063,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, currentPlaylistNum++; if (currentPlaylistNum >= currentPlayList.size()) { currentPlaylistNum = 0; - if (byStop && repeatMode == 0) { + if (byStop && repeatMode == 0 && !forceLoopCurrentPlaylist) { if (audioPlayer != null || audioTrackPlayer != null) { if (audioPlayer != null) { try { @@ -2118,10 +2183,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } if (hasAudioFocus != neededAudioFocus) { hasAudioFocus = neededAudioFocus; + int result; if (neededAudioFocus == 3) { - NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); + result = NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); } else { - NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, neededAudioFocus == 2 ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK : AudioManager.AUDIOFOCUS_GAIN); + result = NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, neededAudioFocus == 2 ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK : AudioManager.AUDIOFOCUS_GAIN); + } + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + audioFocus = AUDIO_FOCUSED; } } } @@ -2270,6 +2339,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } } checkAudioFocus(messageObject); + setPlayerVolume(); isPaused = false; lastProgress = 0; @@ -2880,8 +2950,9 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, public static String getFileName(Uri uri) { String result = null; if (uri.getScheme().equals("content")) { - Cursor cursor = ApplicationLoader.applicationContext.getContentResolver().query(uri, null, null, null, null); + Cursor cursor = null; try { + cursor = ApplicationLoader.applicationContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); if (cursor.moveToFirst()) { result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index 0bff8975a..8dc531f0f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -29,6 +29,7 @@ import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.URLSpanNoUnderlineBold; import org.telegram.ui.Components.URLSpanReplacement; +import org.telegram.ui.Components.URLSpanUserMention; import java.io.File; import java.util.AbstractMap; @@ -461,6 +462,7 @@ public class MessageObject { } public void setType() { + int oldType = type; if (messageOwner instanceof TLRPC.TL_message || messageOwner instanceof TLRPC.TL_messageForwarded_old2) { if (isMediaEmpty()) { type = 0; @@ -513,6 +515,9 @@ public class MessageObject { type = 10; } } + if (oldType != 1000 && oldType != type) { + generateThumbs(false); + } } public void checkLayout() { @@ -879,7 +884,20 @@ public class MessageObject { generateLinkDescription(); textLayoutBlocks = new ArrayList<>(); - boolean useManualParse = messageOwner.entities.isEmpty() && ( + boolean hasEntities; + if (messageOwner.send_state != MESSAGE_SEND_STATE_SENT) { + hasEntities = false; + for (int a = 0; a < messageOwner.entities.size(); a++) { + if (!(messageOwner.entities.get(a) instanceof TLRPC.TL_inputMessageEntityMentionName)) { + hasEntities = true; + break; + } + } + } else { + hasEntities = !messageOwner.entities.isEmpty(); + } + + boolean useManualParse = !hasEntities && ( messageOwner instanceof TLRPC.TL_message_old || messageOwner instanceof TLRPC.TL_message_old2 || messageOwner instanceof TLRPC.TL_message_old3 || @@ -932,6 +950,10 @@ public class MessageObject { spannable.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { spannable.setSpan(new TypefaceSpan(Typeface.MONOSPACE, AndroidUtilities.dp(MessagesController.getInstance().fontSize - 1)), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (!useManualParse) { String url = messageOwner.message.substring(entity.offset, entity.offset + entity.length); if (entity instanceof TLRPC.TL_messageEntityBotCommand) { @@ -967,7 +989,7 @@ public class MessageObject { maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); } } - if (fromUser != null && fromUser.bot) { + if (fromUser != null && fromUser.bot || (isMegagroup() || messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) && !isOut()) { maxWidth -= AndroidUtilities.dp(20); } @@ -1467,6 +1489,16 @@ public class MessageObject { } } + public String getStickerEmoji() { + for (int a = 0; a < messageOwner.media.document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = messageOwner.media.document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeSticker) { + return attribute.alt != null && attribute.alt.length() > 0 ? attribute.alt : null; + } + } + return null; + } + public boolean isSticker() { if (type != 1000) { return type == 13; @@ -1524,6 +1556,22 @@ public class MessageObject { return ""; } + public int getDuration() { + TLRPC.Document document; + if (type == 0) { + document = messageOwner.media.webpage.document; + } else { + document = messageOwner.media.document; + } + for (int a = 0; a < document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeAudio) { + return attribute.duration; + } + } + return 0; + } + public String getMusicAuthor() { TLRPC.Document document; if (type == 0) { @@ -1599,7 +1647,7 @@ public class MessageObject { } if (message.to_id.channel_id == 0) { return message.out && (message.media instanceof TLRPC.TL_messageMediaPhoto || - message.media instanceof TLRPC.TL_messageMediaDocument && (isVideoMessage(message) || isGifDocument(message.media.document)) || + message.media instanceof TLRPC.TL_messageMediaDocument && !isStickerMessage(message) || message.media instanceof TLRPC.TL_messageMediaEmpty || message.media instanceof TLRPC.TL_messageMediaWebPage || message.media == null); @@ -1612,7 +1660,7 @@ public class MessageObject { } if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && isImportant(message)) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || - message.media instanceof TLRPC.TL_messageMediaDocument && (isVideoMessage(message) || isGifDocument(message.media.document)) || + message.media instanceof TLRPC.TL_messageMediaDocument && !isStickerMessage(message) || message.media instanceof TLRPC.TL_messageMediaEmpty || message.media instanceof TLRPC.TL_messageMediaWebPage || message.media == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 4c01cc31a..51a2bbc89 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -25,6 +25,7 @@ import android.widget.Toast; import org.telegram.SQLite.SQLiteCursor; import org.telegram.messenger.query.BotQuery; import org.telegram.messenger.query.MessagesQuery; +import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; @@ -138,6 +139,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public int minGroupConvertSize = 200; public int maxEditTime = 172800; public int groupBigSize; + public int ratingDecay; private ArrayList disabledFeatures = new ArrayList<>(); private class UserActionUpdatesSeq extends TLRPC.Updates { @@ -204,6 +206,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter maxMegagroupCount = preferences.getInt("maxMegagroupCount", 1000); maxEditTime = preferences.getInt("maxEditTime", 3600); groupBigSize = preferences.getInt("groupBigSize", 10); + ratingDecay = preferences.getInt("ratingDecay", 2419200); fontSize = preferences.getInt("fons_size", AndroidUtilities.isTablet() ? 18 : 16); String disabledFeaturesString = preferences.getString("disabledFeatures", null); if (disabledFeaturesString != null && disabledFeaturesString.length() != 0) { @@ -235,6 +238,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter groupBigSize = config.chat_big_size; disabledFeatures = config.disabled_features; maxEditTime = config.edit_time_limit; + ratingDecay = config.rating_e_decay; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); @@ -243,6 +247,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter editor.putInt("maxMegagroupCount", maxMegagroupCount); editor.putInt("groupBigSize", groupBigSize); editor.putInt("maxEditTime", maxEditTime); + editor.putInt("ratingDecay", ratingDecay); try { SerializedData data = new SerializedData(); data.writeInt32(disabledFeatures.size()); @@ -468,6 +473,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter SendMessagesHelper.getInstance().cleanUp(); SecretChatHelper.getInstance().cleanUp(); StickersQuery.cleanup(); + SearchQuery.cleanUp(); reloadingWebpages.clear(); reloadingWebpagesPending.clear(); @@ -1341,6 +1347,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter return; } blockedUsers.add(user_id); + if (user.bot) { + SearchQuery.removeInline(user_id); + } else { + SearchQuery.removePeer(user_id); + } NotificationCenter.getInstance().postNotificationName(NotificationCenter.blockedUsersDidLoaded); TLRPC.TL_contacts_block req = new TLRPC.TL_contacts_block(); req.id = getInputUser(user); @@ -1626,6 +1637,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessagesStorage.getInstance().deleteDialog(did, onlyHistory); return; } + if (onlyHistory == 0) { + AndroidUtilities.uninstallShortcut(did); + } if (first) { TLRPC.Dialog dialog = dialogs_dict.get(did); @@ -2235,7 +2249,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int reqId = ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { + if (response != null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; if (res.messages.size() > count) { res.messages.remove(0); @@ -4147,7 +4161,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } public void generateUpdateMessage() { - if (UserConfig.lastUpdateVersion == null || UserConfig.lastUpdateVersion.equals(BuildVars.BUILD_VERSION_STRING)) { + if (BuildVars.DEBUG_VERSION || UserConfig.lastUpdateVersion == null || UserConfig.lastUpdateVersion.equals(BuildVars.BUILD_VERSION_STRING)) { return; } TLRPC.TL_help_getAppChangelog req = new TLRPC.TL_help_getAppChangelog(); @@ -4628,7 +4642,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter long dialog_id = -channelId; Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance().getChannelReadInboxMax(channelId); + value = MessagesStorage.getInstance().getDialogReadInboxMax(dialog_id); } MessageObject obj = new MessageObject(message, usersDict, createdDialogIds.contains(dialog_id)); @@ -4683,6 +4697,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter processChannelsUpdatesQueue(channelId, 1); MessagesStorage.getInstance().saveChannelPts(channelId, res.pts); } else if (res instanceof TLRPC.TL_updates_channelDifferenceTooLong) { + long dialog_id = -channelId; + Integer value = dialogs_read_inbox_max.get(dialog_id); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadInboxMax(dialog_id); + } + value = Math.max(value, res.read_inbox_max_id); + dialogs_read_inbox_max.put(dialog_id, value); for (int a = 0; a < res.messages.size(); a++) { TLRPC.Message message = res.messages.get(a); message.dialog_id = -channelId; @@ -4696,11 +4717,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.unread = false; message.media_unread = false; } - if (channelFinal != null && channelFinal.megagroup) { - message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; - } - - if (channelFinal != null && channelFinal.left || res.read_inbox_max_id >= message.id) { + if (channelFinal != null && channelFinal.left || value >= message.id) { message.unread = false; message.media_unread = false; } @@ -4724,7 +4741,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter getChannelDifference(channelId); } FileLog.e("tmessages", "received channel difference with pts = " + res.pts + " channelId = " + channelId); - FileLog.e("tmessages", "messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); + FileLog.e("tmessages", "new_messages = " + res.new_messages.size() + " messages = " + res.messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); } }); } @@ -5151,7 +5168,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.User user3 = null; TLRPC.Chat channel = null; - if (user == null || user.min) { //TODO + if (user == null || user.min) { user = MessagesStorage.getInstance().getUserSync(user_id); if (user != null && user.min) { user = null; @@ -5200,6 +5217,26 @@ public class MessagesController implements NotificationCenter.NotificationCenter } missingData = chat == null || user == null || needFwdUser && user2 == null && channel == null || needBotUser && user3 == null; } + if (!missingData && !updates.entities.isEmpty()) { + for (int a = 0; a < updates.entities.size(); a++) { + TLRPC.MessageEntity entity = updates.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityMentionName) { + int uid = ((TLRPC.TL_messageEntityMentionName) entity).user_id; + TLRPC.User entityUser = getUser(uid); + if (entityUser == null || entityUser.min) { + entityUser = MessagesStorage.getInstance().getUserSync(uid); + if (entityUser != null && entityUser.min) { + entityUser = null; + } + if (entityUser == null) { + missingData = true; + break; + } + putUser(user, true); + } + } + } + } if (user != null && user.status != null && user.status.expires <= 0) { onlinePrivacy.put(user.id, ConnectionsManager.getInstance().getCurrentTime()); updateStatus = true; @@ -5697,9 +5734,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } - for (int a = 0; a < 3; a++) { + int count = 3 + message.entities.size(); + for (int a = 0; a < count; a++) { if (a != 0) { - user_id = a == 1 ? message.from_id : (message.fwd_from != null ? message.fwd_from.from_id : 0); + if (a == 1) { + user_id = message.from_id; + } else if (a == 2) { + user_id = message.fwd_from != null ? message.fwd_from.from_id : 0; + } else { + TLRPC.MessageEntity entity = message.entities.get(a - 3); + user_id = entity instanceof TLRPC.TL_messageEntityMentionName ? ((TLRPC.TL_messageEntityMentionName) entity).user_id : 0; + } } if (user_id > 0) { TLRPC.User user = usersDict.get(user_id); @@ -5745,7 +5790,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter long dialog_id = -update.channel_id; Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance().getChannelReadInboxMax(update.channel_id); + value = MessagesStorage.getInstance().getDialogReadInboxMax(dialog_id); } if (value >= message.id || ChatObject.isNotInChat(chat)) { message.unread = false; @@ -6094,7 +6139,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter long dialog_id = -update.channel_id; Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance().getChannelReadInboxMax(update.channel_id); + value = MessagesStorage.getInstance().getDialogReadInboxMax(dialog_id); } dialogs_read_inbox_max.put(dialog_id, Math.max(value, update.max_id)); } else if (update instanceof TLRPC.TL_updateDeleteChannelMessages) { @@ -6130,30 +6175,46 @@ public class MessagesController implements NotificationCenter.NotificationCenter TLRPC.Message message; if (update instanceof TLRPC.TL_updateEditChannelMessage) { message = ((TLRPC.TL_updateEditChannelMessage) update).message; + TLRPC.Chat chat = chatsDict.get(message.to_id.channel_id); + if (chat == null) { + chat = getChat(message.to_id.channel_id); + } + if (chat == null) { + chat = MessagesStorage.getInstance().getChatSync(message.to_id.channel_id); + putChat(chat, true); + } + if (chat != null && chat.megagroup) { + message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } else { message = ((TLRPC.TL_updateEditMessage) update).message; } - if (message.to_id.channel_id != 0 && !message.out) { - message.unread = true; - if (message.post || (message.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0) { - message.media_unread = true; + if (message.out && (message.message == null || message.message.length() == 0)) { + message.message = "-1"; + message.attachPath = ""; + } + int count = message.entities.size(); + for (int a = 0; a < count; a++) { + TLRPC.MessageEntity entity = message.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityMentionName) { + int user_id = ((TLRPC.TL_messageEntityMentionName) entity).user_id; + TLRPC.User user = usersDict.get(user_id); + if (user == null || user.min) { + user = getUser(user_id); + } + if (user == null || user.min) { + user = MessagesStorage.getInstance().getUserSync(user_id); + if (user != null && user.min) { + user = null; + } + putUser(user, true); + } + if (user == null) { + return false; + } } } - if (update instanceof TLRPC.TL_updateEditChannelMessage) { - long dialog_id = -update.channel_id; - Integer value = dialogs_read_inbox_max.get(dialog_id); - if (value == null) { - value = MessagesStorage.getInstance().getChannelReadInboxMax(update.channel_id); - } - if (value >= message.id) { - message.unread = false; - message.media_unread = false; - } //TODO unread for updateEditMessage? - } - - ImageLoader.saveMessageThumbs(message); - if (message.to_id.chat_id != 0) { message.dialog_id = -message.to_id.chat_id; } else if (message.to_id.channel_id != 0) { @@ -6164,6 +6225,27 @@ public class MessagesController implements NotificationCenter.NotificationCenter } message.dialog_id = message.to_id.user_id; } + + if (!message.out) { + if (message.to_id.channel_id != 0) { + message.unread = true; + if (message.post || (message.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0) { + message.media_unread = true; + } + } + Integer value = dialogs_read_inbox_max.get(message.dialog_id); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadInboxMax(message.dialog_id); + } + if (value >= message.id) { + message.unread = message.media_unread = false; + } else { + message.unread = message.media_unread = true; + } + } + + ImageLoader.saveMessageThumbs(message); + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(message.dialog_id)); ArrayList arr = editingMessages.get(message.dialog_id); @@ -6417,6 +6499,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter } boolean updateDialogs = false; + if (!messages.isEmpty()) { + for (HashMap.Entry> entry : messages.entrySet()) { + Long key = entry.getKey(); + ArrayList value = entry.getValue(); + updateInterfaceWithMessages(key, value); + } + updateDialogs = true; + } if (!editingMessages.isEmpty()) { for (HashMap.Entry> pair : editingMessages.entrySet()) { Long dialog_id = pair.getKey(); @@ -6427,6 +6517,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessageObject newMessage = arrayList.get(a); if (oldObject.getId() == newMessage.getId()) { dialogMessage.put(dialog_id, newMessage); + if (newMessage.messageOwner.to_id != null && newMessage.messageOwner.to_id.channel_id == 0) { + dialogMessagesByIds.put(newMessage.getId(), newMessage); + } updateDialogs = true; break; } @@ -6435,14 +6528,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter NotificationCenter.getInstance().postNotificationName(NotificationCenter.replaceMessagesObjects, dialog_id, arrayList); } } - if (!messages.isEmpty()) { - for (HashMap.Entry> entry : messages.entrySet()) { - Long key = entry.getKey(); - ArrayList value = entry.getValue(); - updateInterfaceWithMessages(key, value); - } - updateDialogs = true; - } if (updateDialogs) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); } @@ -6667,6 +6752,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter boolean isEncryptedChat = ((int) uid) == 0; MessageObject lastMessage = null; int channelId = 0; + boolean updateRating = false; for (int a = 0; a < messages.size(); a++) { MessageObject message = messages.get(a); if (lastMessage == null || (!isEncryptedChat && message.getId() > lastMessage.getId() || (isEncryptedChat || message.getId() < 0 && lastMessage.getId() < 0) && message.getId() < lastMessage.getId()) || message.messageOwner.date > lastMessage.messageOwner.date) { @@ -6680,6 +6766,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter if (message.isOut() && message.isNewGif() && !message.isSending() && !message.isForwarded()) { addNewGifToRecent(message.messageOwner.media.document, message.messageOwner.date); } + if (message.isOut() && message.isSent()) { + updateRating = true; + } } MessagesQuery.loadReplyMessagesForMessages(messages, uid); NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceivedNewMessages, uid, messages); @@ -6794,6 +6883,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } } + + if (updateRating) { + SearchQuery.increasePeerRaiting(uid); + } } private static String getRestrictionReason(String reason) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 11ec02096..bfa0d16f2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -148,6 +148,9 @@ public class MessagesStorage { database.executeFast("CREATE TABLE chat_pinned(uid INTEGER PRIMARY KEY, pinned INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_pinned_mid_idx ON chat_pinned(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); + database.executeFast("CREATE TABLE chat_hints(did INTEGER, type INTEGER, rating REAL, date INTEGER, PRIMARY KEY(did, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_hints_rating_idx ON chat_hints(rating);").stepThis().dispose(); + database.executeFast("CREATE TABLE users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE users(uid INTEGER PRIMARY KEY, name TEXT, status INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE chats(uid INTEGER PRIMARY KEY, name TEXT, data BLOB)").stepThis().dispose(); @@ -160,7 +163,6 @@ public class MessagesStorage { database.executeFast("CREATE TABLE blocked_users(uid INTEGER PRIMARY KEY)").stepThis().dispose(); database.executeFast("CREATE TABLE dialog_settings(did INTEGER PRIMARY KEY, flags INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE web_recent_v3(id TEXT, type INTEGER, image_url TEXT, thumb_url TEXT, local_url TEXT, width INTEGER, height INTEGER, size INTEGER, date INTEGER, document BLOB, PRIMARY KEY (id, type));").stepThis().dispose(); - database.executeFast("CREATE TABLE bot_recent(id INTEGER PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE stickers_v2(id INTEGER PRIMARY KEY, data BLOB, date INTEGER, hash TEXT);").stepThis().dispose(); database.executeFast("CREATE TABLE hashtag_recent_v2(id TEXT PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE webpage_pending(id INTEGER, mid INTEGER, PRIMARY KEY (id, mid));").stepThis().dispose(); @@ -172,7 +174,7 @@ public class MessagesStorage { database.executeFast("CREATE TABLE bot_info(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); //version - database.executeFast("PRAGMA user_version = 31").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 32").stepThis().dispose(); //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); @@ -206,7 +208,7 @@ public class MessagesStorage { } } int version = database.executeInt("PRAGMA user_version"); - if (version < 31) { + if (version < 32) { updateDbToLastVersion(version); } } @@ -301,17 +303,17 @@ public class MessagesStorage { SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?)"); if (cursor.next()) { int date = cursor.intValue(0); - int length; - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if ((length = cursor.byteBufferValue(1, data)) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + int length = data.limit(); for (int a = 0; a < length / 4; a++) { state.requery(); state.bindInteger(1, data.readInt32(false)); state.bindInteger(2, date); state.step(); } + data.reuse(); } - data.reuse(); } state.dispose(); cursor.dispose(); @@ -422,9 +424,10 @@ public class MessagesStorage { SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?)"); while (cursor.next()) { int chat_id = cursor.intValue(0); - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.ChatParticipants participants = TLRPC.ChatParticipants.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (participants != null) { TLRPC.TL_chatFull chatFull = new TLRPC.TL_chatFull(); chatFull.id = chat_id; @@ -441,7 +444,6 @@ public class MessagesStorage { data2.reuse(); } } - data.reuse(); } state.dispose(); cursor.dispose(); @@ -486,7 +488,6 @@ public class MessagesStorage { version = 28; } if (version == 28) { - database.executeFast("CREATE TABLE IF NOT EXISTS bot_recent(id INTEGER PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 29").stepThis().dispose(); version = 29; } @@ -503,7 +504,14 @@ public class MessagesStorage { database.executeFast("CREATE INDEX IF NOT EXISTS chat_pinned_mid_idx ON chat_pinned(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 31").stepThis().dispose(); - //version = 31; + version = 31; + } + if (version == 31) { + database.executeFast("DROP TABLE IF EXISTS bot_recent;").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS chat_hints(did INTEGER, type INTEGER, rating REAL, date INTEGER, PRIMARY KEY(did, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_hints_rating_idx ON chat_hints(rating);").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 32").stepThis().dispose(); + //version = 32; } } catch (Exception e) { FileLog.e("tmessages", e); @@ -662,9 +670,10 @@ public class MessagesStorage { cursor = database.queryFinalized("SELECT read_state, data, send_state, mid, date, uid FROM messages WHERE uid IN (" + ids.toString() + ") AND out = 0 AND read_state IN(0,2) ORDER BY date DESC LIMIT 50"); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); message.date = cursor.intValue(4); @@ -698,7 +707,6 @@ public class MessagesStorage { message.random_id = cursor.longValue(5); } } - data.reuse(); } cursor.dispose(); @@ -792,11 +800,11 @@ public class MessagesStorage { searchImage.size = cursor.intValue(6); searchImage.date = cursor.intValue(7); if (!cursor.isNull(8)) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(8)); - if (cursor.byteBufferValue(8, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(8); + if (data != null) { searchImage.document = TLRPC.Document.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); } - data.reuse(); } searchImage.type = type; arrayList.add(searchImage); @@ -936,12 +944,12 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized("SELECT data FROM wallpapers WHERE 1"); final ArrayList wallPapers = new ArrayList<>(); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.WallPaper wallPaper = TLRPC.WallPaper.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); wallPapers.add(wallPaper); } - data.reuse(); } cursor.dispose(); AndroidUtilities.runOnUIThread(new Runnable() { @@ -1039,9 +1047,10 @@ public class MessagesStorage { ArrayList filesToDelete = new ArrayList<>(); try { while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message != null && message.from_id == uid && message.id != 1) { mids.add(message.id); if (message.media instanceof TLRPC.TL_messageMediaPhoto) { @@ -1063,7 +1072,6 @@ public class MessagesStorage { } } } - data.reuse(); } } catch (Exception e) { FileLog.e("tmessages", e); @@ -1103,9 +1111,10 @@ public class MessagesStorage { ArrayList filesToDelete = new ArrayList<>(); try { while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message != null && message.media != null) { if (message.media instanceof TLRPC.TL_messageMediaPhoto) { for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { @@ -1126,7 +1135,6 @@ public class MessagesStorage { } } } - data.reuse(); } } catch (Exception e) { FileLog.e("tmessages", e); @@ -1162,14 +1170,14 @@ public class MessagesStorage { SQLiteCursor cursor2 = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did + " AND mid IN (" + last_mid_i + "," + last_mid + ")"); try { while (cursor2.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor2.byteArrayLength(0)); - if (cursor2.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor2.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message != null) { arrayList.add(message); } } - data.reuse(); } } catch (Exception e) { FileLog.e("tmessages", e); @@ -1239,12 +1247,12 @@ public class MessagesStorage { final TLRPC.photos_Photos res = new TLRPC.photos_Photos(); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Photo photo = TLRPC.Photo.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); res.photos.add(photo); } - data.reuse(); } cursor.dispose(); @@ -1505,6 +1513,9 @@ public class MessagesStorage { } public void updateChatParticipants(final TLRPC.ChatParticipants participants) { + if (participants == null) { + return; + } storageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -1513,12 +1524,12 @@ public class MessagesStorage { TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); info.pinned_msg_id = cursor.intValue(1); } - data.reuse(); } cursor.dispose(); if (info instanceof TLRPC.TL_chatFull) { @@ -1588,13 +1599,10 @@ public class MessagesStorage { public void run() { try { if (ifExist) { - boolean dontExist = true; SQLiteCursor cursor = database.queryFinalized("SELECT uid FROM chat_settings_v2 WHERE uid = " + info.id); - if (cursor.next()) { - dontExist = false; - } + boolean exist = cursor.next(); cursor.dispose(); - if (dontExist) { + if (!exist) { return; } } @@ -1609,27 +1617,34 @@ public class MessagesStorage { data.reuse(); if (info instanceof TLRPC.TL_channelFull) { - SQLiteCursor cursor = database.queryFinalized("SELECT date, last_mid_i, pts, date_i, last_mid FROM dialogs WHERE did = " + (-info.id)); + SQLiteCursor cursor = database.queryFinalized("SELECT date, last_mid_i, pts, date_i, last_mid, inbox_max FROM dialogs WHERE did = " + (-info.id)); if (cursor.next()) { - int dialog_date = cursor.intValue(0); - long last_mid_i = cursor.longValue(1); - int pts = cursor.intValue(2); - int dialog_date_i = cursor.intValue(3); - long last_mid = cursor.longValue(4); + int inbox_max = cursor.intValue(5); + if (inbox_max < info.read_inbox_max_id) { + int inbox_diff = info.read_inbox_max_id - inbox_max; + if (inbox_diff < info.unread_important_count) { + info.unread_important_count = inbox_diff; + } + int dialog_date = cursor.intValue(0); + long last_mid_i = cursor.longValue(1); + int pts = cursor.intValue(2); + int dialog_date_i = cursor.intValue(3); + long last_mid = cursor.longValue(4); - state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - state.bindLong(1, -info.id); - state.bindInteger(2, dialog_date); - state.bindInteger(3, info.unread_important_count); - state.bindLong(4, last_mid); - state.bindInteger(5, info.read_inbox_max_id); - state.bindInteger(6, 0); - state.bindLong(7, last_mid_i); - state.bindInteger(8, info.unread_count); - state.bindInteger(9, pts); - state.bindInteger(10, dialog_date_i); - state.step(); - state.dispose(); + state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state.bindLong(1, -info.id); + state.bindInteger(2, dialog_date); + state.bindInteger(3, info.unread_important_count); + state.bindLong(4, last_mid); + state.bindInteger(5, info.read_inbox_max_id); + state.bindInteger(6, 0); + state.bindLong(7, last_mid_i); + state.bindInteger(8, info.unread_count); + state.bindInteger(9, pts); + state.bindInteger(10, dialog_date_i); + state.step(); + state.dispose(); + } } cursor.dispose(); } @@ -1649,12 +1664,12 @@ public class MessagesStorage { TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); info.pinned_msg_id = cursor.intValue(1); } - data.reuse(); } cursor.dispose(); if (info instanceof TLRPC.TL_channelFull) { @@ -1695,12 +1710,12 @@ public class MessagesStorage { TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); info.pinned_msg_id = cursor.intValue(1); } - data.reuse(); } cursor.dispose(); if (info instanceof TLRPC.TL_chatFull) { @@ -1782,11 +1797,11 @@ public class MessagesStorage { TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); } - data.reuse(); } cursor.dispose(); result[0] = info instanceof TLRPC.TL_channelFull && info.migrated_from_chat_id != 0; @@ -1820,12 +1835,12 @@ public class MessagesStorage { try { SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM chat_settings_v2 WHERE uid = " + chat_id); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); info.pinned_msg_id = cursor.intValue(1); } - data.reuse(); } cursor.dispose(); @@ -1846,27 +1861,31 @@ public class MessagesStorage { info.participants = new TLRPC.TL_chatParticipants(); while (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(2)); - if (cursor.byteBufferValue(0, data) != 0 && cursor.byteBufferValue(2, data2) != 0) { - TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); - TLRPC.ChannelParticipant participant = TLRPC.ChannelParticipant.TLdeserialize(data2, data2.readInt32(false), false); - if (user != null && participant != null) { - if (user.status != null) { - user.status.expires = cursor.intValue(1); - } - loadedUsers.add(user); - participant.date = cursor.intValue(3); - TLRPC.TL_chatChannelParticipant chatChannelParticipant = new TLRPC.TL_chatChannelParticipant(); - chatChannelParticipant.user_id = participant.user_id; - chatChannelParticipant.date = participant.date; - chatChannelParticipant.inviter_id = participant.inviter_id; - chatChannelParticipant.channelParticipant = participant; - info.participants.participants.add(chatChannelParticipant); - } + TLRPC.User user = null; + TLRPC.ChannelParticipant participant = null; + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + } + data = cursor.byteBufferValue(2); + if (data != null) { + participant = TLRPC.ChannelParticipant.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + } + if (user != null && participant != null) { + if (user.status != null) { + user.status.expires = cursor.intValue(1); + } + loadedUsers.add(user); + participant.date = cursor.intValue(3); + TLRPC.TL_chatChannelParticipant chatChannelParticipant = new TLRPC.TL_chatChannelParticipant(); + chatChannelParticipant.user_id = participant.user_id; + chatChannelParticipant.date = participant.date; + chatChannelParticipant.inviter_id = participant.inviter_id; + chatChannelParticipant.channelParticipant = participant; + info.participants.participants.add(chatChannelParticipant); } - data.reuse(); - data2.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -2160,9 +2179,10 @@ public class MessagesStorage { ArrayList encryptedChatIds = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized("SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.uid, s.seq_in, s.seq_out, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid LEFT JOIN messages_seq as s ON m.mid = s.mid WHERE m.mid < 0 AND m.send_state = 1 ORDER BY m.mid DESC LIMIT " + count); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (!messageHashMap.containsKey(message.id)) { MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); @@ -2213,7 +2233,6 @@ public class MessagesStorage { } } } - data.reuse(); } cursor.dispose(); @@ -2357,6 +2376,24 @@ public class MessagesStorage { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0" + imp, dialog_id)); if (cursor.next()) { messageMaxId = max_id_query = min_unread_id = cursor.intValue(0); + if (messageMaxId != 0 && channelId != 0) { + messageMaxId |= ((long) channelId) << 32; + } + } + cursor.dispose(); + } + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM " + holesTable + " WHERE uid = %d AND start < %d AND end > %d", dialog_id, max_id_query, max_id_query)); + boolean containMessage = !cursor.next(); + cursor.dispose(); + + if (containMessage) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > %d" + imp, dialog_id, max_id_query)); + if (cursor.next()) { + messageMaxId = max_id_query = cursor.intValue(0); + if (messageMaxId != 0 && channelId != 0) { + messageMaxId |= ((long) channelId) << 32; + } } cursor.dispose(); } @@ -2560,9 +2597,10 @@ public class MessagesStorage { } if (cursor != null) { while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); message.date = cursor.intValue(4); @@ -2570,21 +2608,23 @@ public class MessagesStorage { if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { message.views = cursor.intValue(7); } - message.ttl = cursor.intValue(8); + if (lower_id != 0) { + message.ttl = cursor.intValue(8); + } res.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.reply_to_msg_id != 0 || message.reply_to_random_id != 0) { if (!cursor.isNull(6)) { - NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(6)); - if (cursor.byteBufferValue(6, data2) != 0) { - message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + data = cursor.byteBufferValue(6); + if (data != null) { + message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } - data2.reuse(); } if (message.replyMessage == null) { if (message.reply_to_msg_id != 0) { @@ -2633,7 +2673,6 @@ public class MessagesStorage { } } } - data.reuse(); } cursor.dispose(); } @@ -2701,9 +2740,10 @@ public class MessagesStorage { cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); } while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialog_id; @@ -2728,7 +2768,6 @@ public class MessagesStorage { } } } - data.reuse(); } cursor.dispose(); if (!replyMessageRandomOwners.isEmpty()) { @@ -2816,16 +2855,16 @@ public class MessagesStorage { if (id != null) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM sent_files_v2 WHERE uid = '%s' AND type = %d", id, type)); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLObject file = TLRPC.MessageMedia.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (file instanceof TLRPC.TL_messageMediaDocument) { result.add(((TLRPC.TL_messageMediaDocument) file).document); } else if (file instanceof TLRPC.TL_messageMediaPhoto) { result.add(((TLRPC.TL_messageMediaDocument) file).photo); } } - data.reuse(); } cursor.dispose(); } @@ -3186,9 +3225,10 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM users WHERE uid = %d", user.id)); if (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User oldUser = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (oldUser != null) { if (user.username != null) { oldUser.username = user.username; @@ -3207,11 +3247,11 @@ public class MessagesStorage { user = oldUser; } } - data.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } } + cursor.dispose(); } state.requery(); NativeByteBuffer data = new NativeByteBuffer(user.getObjectSize()); @@ -3248,9 +3288,10 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid = %d", chat.id)); if (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Chat oldChat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (oldChat != null) { oldChat.title = chat.title; oldChat.photo = chat.photo; @@ -3268,7 +3309,6 @@ public class MessagesStorage { chat = oldChat; } } - data.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -3298,9 +3338,10 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, status FROM users WHERE uid IN(%s)", usersToLoad)); while (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (user != null) { if (user.status != null) { user.status.expires = cursor.intValue(1); @@ -3308,7 +3349,6 @@ public class MessagesStorage { result.add(user); } } - data.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -3323,14 +3363,14 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid IN(%s)", chatsToLoad)); while (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (chat != null) { result.add(chat); } } - data.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -3345,9 +3385,10 @@ public class MessagesStorage { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, user, g, authkey, ttl, layer, seq_in, seq_out, use_count, exchange_id, key_date, fprint, fauthkey, khash FROM enc_chats WHERE uid IN(%s)", chatsToLoad)); while (cursor.next()) { try { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.EncryptedChat chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (chat != null) { chat.user_id = cursor.intValue(1); if (usersToLoad != null && !usersToLoad.contains(chat.user_id)) { @@ -3370,7 +3411,6 @@ public class MessagesStorage { result.add(chat); } } - data.reuse(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -3462,16 +3502,16 @@ public class MessagesStorage { DownloadObject downloadObject = new DownloadObject(); downloadObject.type = cursor.intValue(1); downloadObject.id = cursor.longValue(0); - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(2)); - if (cursor.byteBufferValue(2, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(2); + if (data != null) { TLRPC.MessageMedia messageMedia = TLRPC.MessageMedia.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (messageMedia.document != null) { downloadObject.object = messageMedia.document; } else if (messageMedia.photo != null) { downloadObject.object = FileLoader.getClosestPhotoSizeWithSize(messageMedia.photo.sizes, AndroidUtilities.getPhotoSize()); } } - data.reuse(); objects.add(downloadObject); } cursor.dispose(); @@ -3524,16 +3564,16 @@ public class MessagesStorage { cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages WHERE mid IN (%s)", TextUtils.join(",", mids))); while (cursor.next()) { int mid = cursor.intValue(0); - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message.media instanceof TLRPC.TL_messageMediaWebPage) { message.id = mid; message.media.webpage = webPages.get(message.media.webpage.id); messages.add(message); } } - data.reuse(); } cursor.dispose(); @@ -3547,7 +3587,8 @@ public class MessagesStorage { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET data = ? WHERE mid = ?"); SQLitePreparedStatement state2 = database.executeFast("UPDATE media_v2 SET data = ? WHERE mid = ?"); - for (TLRPC.Message message : messages) { + for (int a = 0; a < messages.size(); a++) { + TLRPC.Message message = messages.get(a); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); @@ -4562,10 +4603,11 @@ public class MessagesStorage { if ((int) did != 0) { continue; } - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); - if (cursor.byteBufferValue(1, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message != null && message.media != null) { + data.reuse(); + if (message != null) { if (message.media instanceof TLRPC.TL_messageMediaPhoto) { for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { File file = FileLoader.getPathToAttach(photoSize); @@ -4585,7 +4627,6 @@ public class MessagesStorage { } } } - data.reuse(); } } catch (Exception e) { FileLog.e("tmessages", e); @@ -4677,9 +4718,10 @@ public class MessagesStorage { dialogs.dialogs.add(dialog); - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(4)); - if (cursor.byteBufferValue(4, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(4); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); message.send_state = cursor.intValue(7); @@ -4692,7 +4734,6 @@ public class MessagesStorage { addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); } - data.reuse(); int lower_id = (int)dialog.id; int high_id = (int)(dialog.id >> 32); @@ -5067,6 +5108,7 @@ public class MessagesStorage { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); + SQLitePreparedStatement state5 = null; TLRPC.Message botKeyboard = null; int countBeforeImportant = 0; int countAfterImportant = 0; @@ -5086,8 +5128,18 @@ public class MessagesStorage { } if (load_type == -2) { - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM messages WHERE mid = %d", messageId)); - boolean exist = cursor.next(); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages WHERE mid = %d", messageId)); + boolean exist; + if (exist = cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message oldMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + if (oldMessage != null) { + message.attachPath = oldMessage.attachPath; + } + } + } cursor.dispose(); if (!exist) { continue; @@ -5163,6 +5215,15 @@ public class MessagesStorage { state2.step(); } data.reuse(); + if (message.media instanceof TLRPC.TL_messageMediaWebPage && message.media.webpage instanceof TLRPC.TL_webPagePending) { + if (state5 == null) { + state5 = database.executeFast("REPLACE INTO webpage_pending VALUES(?, ?)"); + } + state5.requery(); + state5.bindLong(1, message.media.webpage.id); + state5.bindLong(2, messageId); + state5.step(); + } if (load_type == 0 && isValidKeyboardToSave(message)) { if (botKeyboard == null || botKeyboard.id < message.id) { @@ -5172,6 +5233,9 @@ public class MessagesStorage { } state.dispose(); state2.dispose(); + if (state5 != null) { + state5.dispose(); + } if (botKeyboard != null) { BotQuery.putBotKeyboard(dialog_id, botKeyboard); } @@ -5277,6 +5341,16 @@ public class MessagesStorage { } } } + if (!message.entities.isEmpty()) { + for (int a = 0; a < message.entities.size(); a++) { + TLRPC.MessageEntity entity = message.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityMentionName) { + usersToLoad.add(((TLRPC.TL_messageEntityMentionName) entity).user_id); + } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { + usersToLoad.add(((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id); + } + } + } if (message.media != null) { if (message.media.user_id != 0 && !usersToLoad.contains(message.media.user_id)) { usersToLoad.add(message.media.user_id); @@ -5344,9 +5418,10 @@ public class MessagesStorage { } dialogs.dialogs.add(dialog); - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(4)); - if (cursor.byteBufferValue(4, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(4); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message != null) { MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); @@ -5363,14 +5438,14 @@ public class MessagesStorage { try { if (message.reply_to_msg_id != 0 && message.action instanceof TLRPC.TL_messageActionPinMessage) { if (!cursor.isNull(15)) { - NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(15)); - if (cursor.byteBufferValue(15, data2) != 0) { - message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + data = cursor.byteBufferValue(15); + if (data != null) { + message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } - data2.reuse(); } if (message.replyMessage == null) { long messageId = message.reply_to_msg_id; @@ -5388,7 +5463,6 @@ public class MessagesStorage { } } } - data.reuse(); int lower_id = (int)dialog.id; int high_id = (int)(dialog.id >> 32); @@ -5419,9 +5493,10 @@ public class MessagesStorage { if (!replyMessages.isEmpty()) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = cursor.longValue(3); @@ -5434,7 +5509,6 @@ public class MessagesStorage { message.dialog_id = owner.dialog_id; } } - data.reuse(); } cursor.dispose(); } @@ -5709,7 +5783,7 @@ public class MessagesStorage { }); } - public int getChannelReadInboxMax(final int channelId) { + public int getDialogReadInboxMax(final long dialog_id) { final Semaphore semaphore = new Semaphore(0); final Integer[] max = new Integer[] {0}; MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @@ -5717,7 +5791,7 @@ public class MessagesStorage { public void run() { SQLiteCursor cursor = null; try { - cursor = database.queryFinalized("SELECT inbox_max FROM dialogs WHERE did = " + (-channelId)); + cursor = database.queryFinalized("SELECT inbox_max FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { max[0] = cursor.intValue(0); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java new file mode 100644 index 000000000..0c2163bee --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java @@ -0,0 +1,593 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.messenger; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import android.media.browse.MediaBrowser; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.service.media.MediaBrowserService; +import android.text.TextUtils; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.messenger.audioinfo.AudioInfo; +import org.telegram.messenger.query.SharedMediaQuery; +import org.telegram.tgnet.NativeByteBuffer; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.LaunchActivity; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class MusicBrowserService extends MediaBrowserService implements NotificationCenter.NotificationCenterDelegate { + + private static final String AUTO_APP_PACKAGE_NAME = "com.google.android.projection.gearhead"; + private static final String SLOT_RESERVATION_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; + private static final String SLOT_RESERVATION_SKIP_TO_PREV = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; + private static final String SLOT_RESERVATION_QUEUE = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"; + + private MediaSession mediaSession; + private static final String MEDIA_ID_ROOT = "__ROOT__"; + + private boolean chatsLoaded; + private boolean loadingChats; + private ArrayList dialogs = new ArrayList<>(); + private HashMap users = new HashMap<>(); + private HashMap chats = new HashMap<>(); + private HashMap> musicObjects = new HashMap<>(); + private HashMap> musicQueues = new HashMap<>(); + + public static final String ACTION_CMD = "com.example.android.mediabrowserservice.ACTION_CMD"; + public static final String CMD_NAME = "CMD_NAME"; + public static final String CMD_PAUSE = "CMD_PAUSE"; + + private Paint roundPaint; + private RectF bitmapRect; + + private boolean serviceStarted; + + private int lastSelectedDialog; + + private static final int STOP_DELAY = 30000; + + private DelayedStopHandler delayedStopHandler = new DelayedStopHandler(this); + + @Override + public void onCreate() { + super.onCreate(); + ApplicationLoader.postInitApplication(); + + lastSelectedDialog = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getInt("auto_lastSelectedDialog", 0); + + mediaSession = new MediaSession(this, "MusicService"); + setSessionToken(mediaSession.getSessionToken()); + mediaSession.setCallback(new MediaSessionCallback()); + mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + + Context context = getApplicationContext(); + Intent intent = new Intent(context, LaunchActivity.class); + PendingIntent pi = PendingIntent.getActivity(context, 99, intent, PendingIntent.FLAG_UPDATE_CURRENT); + mediaSession.setSessionActivity(pi); + + Bundle extras = new Bundle(); + extras.putBoolean(SLOT_RESERVATION_QUEUE, true); + extras.putBoolean(SLOT_RESERVATION_SKIP_TO_PREV, true); + extras.putBoolean(SLOT_RESERVATION_SKIP_TO_NEXT, true); + mediaSession.setExtras(extras); + + updatePlaybackState(null); + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset); + } + + @Override + public int onStartCommand(Intent startIntent, int flags, int startId) { + /*if (startIntent != null) { + String action = startIntent.getAction(); + String command = startIntent.getStringExtra(CMD_NAME); + if (ACTION_CMD.equals(action)) { + if (CMD_PAUSE.equals(command)) { + if (mPlayback != null && mPlayback.isPlaying()) { + handlePauseRequest(); + } + } + } + }*/ + return START_STICKY; + } + + @Override + public void onDestroy() { + handleStopRequest(null); + delayedStopHandler.removeCallbacksAndMessages(null); + mediaSession.release(); + } + + @Override + public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { + if (clientPackageName == null || Process.SYSTEM_UID != clientUid && Process.myUid() != clientUid && !clientPackageName.equals("com.google.android.mediasimulator") && !clientPackageName.equals("com.google.android.projection.gearhead")) { + return null; + } + return new BrowserRoot(MEDIA_ID_ROOT, null); + } + + @Override + public void onLoadChildren(final String parentMediaId, final Result> result) { + if (!chatsLoaded) { + result.detach(); + if (loadingChats) { + return; + } + loadingChats = true; + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + ArrayList usersToLoad = new ArrayList<>(); + ArrayList chatsToLoad = new ArrayList<>(); + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT DISTINCT uid FROM media_v2 WHERE uid != 0 AND mid > 0 AND type = %d", SharedMediaQuery.MEDIA_MUSIC)); + while (cursor.next()) { + int lower_part = (int) cursor.longValue(0); + if (lower_part == 0) { + continue; + } + dialogs.add(lower_part); + if (lower_part > 0) { + usersToLoad.add(lower_part); + } else { + chatsToLoad.add(-lower_part); + } + } + cursor.dispose(); + if (!dialogs.isEmpty()) { + String ids = TextUtils.join(",", dialogs); + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid, data, mid FROM media_v2 WHERE uid IN (%s) AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC", ids, SharedMediaQuery.MEDIA_MUSIC)); + while (cursor.next()) { + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + if (MessageObject.isMusicMessage(message)) { + int did = cursor.intValue(0); + message.id = cursor.intValue(2); + message.dialog_id = did; + ArrayList arrayList = musicObjects.get(did); + ArrayList arrayList1 = musicQueues.get(did); + if (arrayList == null) { + arrayList = new ArrayList<>(); + musicObjects.put(did, arrayList); + arrayList1 = new ArrayList<>(); + musicQueues.put(did, arrayList1); + } + MessageObject messageObject = new MessageObject(message, null, false); + arrayList.add(0, messageObject); + MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId(did + "_" + arrayList.size()); + builder.setTitle(messageObject.getMusicTitle()); + builder.setSubtitle(messageObject.getMusicAuthor()); + arrayList1.add(0, new MediaSession.QueueItem(builder.build(), arrayList1.size())); + } + } + } + cursor.dispose(); + if (!usersToLoad.isEmpty()) { + ArrayList usersArrayList = new ArrayList<>(); + MessagesStorage.getInstance().getUsersInternal(TextUtils.join(",", usersToLoad), usersArrayList); + for (int a = 0; a < usersArrayList.size(); a++) { + TLRPC.User user = usersArrayList.get(a); + users.put(user.id, user); + } + } + if (!chatsToLoad.isEmpty()) { + ArrayList chatsArrayList = new ArrayList<>(); + MessagesStorage.getInstance().getChatsInternal(TextUtils.join(",", chatsToLoad), chatsArrayList); + for (int a = 0; a < chatsArrayList.size(); a++) { + TLRPC.Chat chat = chatsArrayList.get(a); + chats.put(chat.id, chat); + } + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + chatsLoaded = true; + loadingChats = false; + loadChildrenImpl(parentMediaId, result); + if (lastSelectedDialog == 0 && !dialogs.isEmpty()) { + lastSelectedDialog = dialogs.get(0); + } + if (lastSelectedDialog != 0) { + ArrayList arrayList = musicObjects.get(lastSelectedDialog); + ArrayList arrayList1 = musicQueues.get(lastSelectedDialog); + if (arrayList != null && !arrayList.isEmpty()) { + mediaSession.setQueue(arrayList1); + if (lastSelectedDialog > 0) { + TLRPC.User user = users.get(lastSelectedDialog); + if (user != null) { + mediaSession.setQueueTitle(ContactsController.formatName(user.first_name, user.last_name)); + } else { + mediaSession.setQueueTitle("DELETED USER"); + } + } else { + TLRPC.Chat chat = chats.get(-lastSelectedDialog); + if (chat != null) { + mediaSession.setQueueTitle(chat.title); + } else { + mediaSession.setQueueTitle("DELETED CHAT"); + } + } + MessageObject messageObject = arrayList.get(0); + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putLong(MediaMetadata.METADATA_KEY_DURATION, messageObject.getDuration() * 1000); + builder.putString(MediaMetadata.METADATA_KEY_ARTIST, messageObject.getMusicAuthor()); + builder.putString(MediaMetadata.METADATA_KEY_TITLE, messageObject.getMusicTitle()); + mediaSession.setMetadata(builder.build()); + } + } + updatePlaybackState(null); + } + }); + } + }); + } else { + loadChildrenImpl(parentMediaId, result); + } + } + + private void loadChildrenImpl(final String parentMediaId, final Result> result) { + List mediaItems = new ArrayList<>(); + + if (MEDIA_ID_ROOT.equals(parentMediaId)) { + for (int a = 0; a < dialogs.size(); a++) { + int did = dialogs.get(a); + MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId("__CHAT_" + did); + TLRPC.FileLocation avatar = null; + if (did > 0) { + TLRPC.User user = users.get(did); + if (user != null) { + builder.setTitle(ContactsController.formatName(user.first_name, user.last_name)); + if (user.photo != null && user.photo.photo_small instanceof TLRPC.TL_fileLocation) { + avatar = user.photo.photo_small; + } + } else { + builder.setTitle("DELETED USER"); + } + } else { + TLRPC.Chat chat = chats.get(-did); + if (chat != null) { + builder.setTitle(chat.title); + if (chat.photo != null && chat.photo.photo_small instanceof TLRPC.TL_fileLocation) { + avatar = chat.photo.photo_small; + } + } else { + builder.setTitle("DELETED CHAT"); + } + } + Bitmap bitmap = null; + if (avatar != null) { + bitmap = createRoundBitmap(FileLoader.getPathToAttach(avatar, true)); + if (bitmap != null) { + builder.setIconBitmap(bitmap); + } + } + if (avatar == null || bitmap == null) { + builder.setIconUri(Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/drawable/contact_blue")); + } + mediaItems.add(new MediaBrowser.MediaItem(builder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)); + } + } else if (parentMediaId != null && parentMediaId.startsWith("__CHAT_")) { + int did = 0; + try { + did = Integer.parseInt(parentMediaId.replace("__CHAT_", "")); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + ArrayList arrayList = musicObjects.get(did); + if (arrayList != null) { + for (int a = 0; a < arrayList.size(); a++) { + MessageObject messageObject = arrayList.get(a); + MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId(did + "_" + a); + builder.setTitle(messageObject.getMusicTitle()); + builder.setSubtitle(messageObject.getMusicAuthor()); + mediaItems.add(new MediaBrowser.MediaItem(builder.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE)); + } + } + } + result.sendResult(mediaItems); + } + + private Bitmap createRoundBitmap(File path) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 2; + Bitmap bitmap = BitmapFactory.decodeFile(path.toString(), options); + if (bitmap != null) { + Bitmap result = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + result.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(result); + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (roundPaint == null) { + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapRect = new RectF(); + } + roundPaint.setShader(shader); + bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); + return result; + } + } catch (Throwable e) { + FileLog.e("tmessages", e); + } + return null; + } + + private final class MediaSessionCallback extends MediaSession.Callback { + @Override + public void onPlay() { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject == null) { + onPlayFromMediaId(lastSelectedDialog + "_" + 0, null); + } else { + MediaController.getInstance().playAudio(messageObject); + } + } + + @Override + public void onSkipToQueueItem(long queueId) { + MediaController.getInstance().playMessageAtIndex((int) queueId); + handlePlayRequest(); + } + + @Override + public void onSeekTo(long position) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null) { + MediaController.getInstance().seekToProgress(messageObject, position / 1000 / (float) messageObject.getDuration()); + } + } + + @Override + public void onPlayFromMediaId(String mediaId, Bundle extras) { + String args[] = mediaId.split("_"); + if (args.length != 2) { + return; + } + try { + int did = Integer.parseInt(args[0]); + int id = Integer.parseInt(args[1]); + ArrayList arrayList = musicObjects.get(did); + ArrayList arrayList1 = musicQueues.get(did); + if (arrayList == null || id < 0 || id >= arrayList.size()) { + return; + } + lastSelectedDialog = did; + ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit().putInt("auto_lastSelectedDialog", did).commit(); + MediaController.getInstance().setPlaylist(arrayList, arrayList.get(id), false); + mediaSession.setQueue(arrayList1); + if (did > 0) { + TLRPC.User user = users.get(did); + if (user != null) { + mediaSession.setQueueTitle(ContactsController.formatName(user.first_name, user.last_name)); + } else { + mediaSession.setQueueTitle("DELETED USER"); + } + } else { + TLRPC.Chat chat = chats.get(-did); + if (chat != null) { + mediaSession.setQueueTitle(chat.title); + } else { + mediaSession.setQueueTitle("DELETED CHAT"); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + handlePlayRequest(); + } + + @Override + public void onPause() { + handlePauseRequest(); + } + + @Override + public void onStop() { + handleStopRequest(null); + } + + @Override + public void onSkipToNext() { + MediaController.getInstance().playNextMessage(); + } + + @Override + public void onSkipToPrevious() { + MediaController.getInstance().playPreviousMessage(); + } + + @Override + public void onPlayFromSearch(String query, Bundle extras) { + if (query == null || query.length() == 0) { + return; + } + query = query.toLowerCase(); + for (int a = 0; a < dialogs.size(); a++) { + int did = dialogs.get(a); + if (did > 0) { + TLRPC.User user = users.get(did); + if (user == null) { + continue; + } + if (user.first_name != null && user.first_name.startsWith(query) || user.last_name != null && user.last_name.startsWith(query)) { + onPlayFromMediaId(did + "_" + 0, null); + break; + } + } else { + TLRPC.Chat chat = chats.get(-did); + if (chat == null) { + continue; + } + if (chat.title != null && chat.title.toLowerCase().contains(query)) { + onPlayFromMediaId(did + "_" + 0, null); + break; + } + } + } + } + } + + private void updatePlaybackState(String error) { + long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN; + MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject(); + if (playingMessageObject != null) { + position = playingMessageObject.audioProgressSec * 1000; + } + + PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(getAvailableActions()); + int state; + if (playingMessageObject == null) { + state = PlaybackState.STATE_STOPPED; + } else { + if (MediaController.getInstance().isDownloadingCurrentMessage()) { + state = PlaybackState.STATE_BUFFERING; + } else { + state = MediaController.getInstance().isAudioPaused() ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING; + } + } + + if (error != null) { + stateBuilder.setErrorMessage(error); + state = PlaybackState.STATE_ERROR; + } + stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime()); + if (playingMessageObject != null) { + stateBuilder.setActiveQueueItemId(MediaController.getInstance().getPlayingMessageObjectNum()); + } else { + stateBuilder.setActiveQueueItemId(0); + } + + mediaSession.setPlaybackState(stateBuilder.build()); + } + + private long getAvailableActions() { + long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_PLAY_FROM_SEARCH; + MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject(); + if (playingMessageObject != null) { + if (!MediaController.getInstance().isAudioPaused()) { + actions |= PlaybackState.ACTION_PAUSE; + } + actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS; + actions |= PlaybackState.ACTION_SKIP_TO_NEXT; + } + return actions; + } + + private void handleStopRequest(String withError) { + delayedStopHandler.removeCallbacksAndMessages(null); + delayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); + updatePlaybackState(withError); + stopSelf(); + serviceStarted = false; + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset); + } + + private void handlePlayRequest() { + delayedStopHandler.removeCallbacksAndMessages(null); + if (!serviceStarted) { + startService(new Intent(getApplicationContext(), MusicBrowserService.class)); + serviceStarted = true; + } + + if (!mediaSession.isActive()) { + mediaSession.setActive(true); + } + + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject == null) { + return; + } + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putLong(MediaMetadata.METADATA_KEY_DURATION, messageObject.getDuration() * 1000); + builder.putString(MediaMetadata.METADATA_KEY_ARTIST, messageObject.getMusicAuthor()); + builder.putString(MediaMetadata.METADATA_KEY_TITLE, messageObject.getMusicTitle()); + AudioInfo audioInfo = MediaController.getInstance().getAudioInfo(); + if (audioInfo != null) { + Bitmap bitmap = audioInfo.getCover(); + if (bitmap != null) { + builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap); + } + } + mediaSession.setMetadata(builder.build()); + } + + private void handlePauseRequest() { + MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); + delayedStopHandler.removeCallbacksAndMessages(null); + delayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); + } + + @Override + public void didReceivedNotification(int id, Object... args) { + updatePlaybackState(null); + handlePlayRequest(); + } + + private static class DelayedStopHandler extends Handler { + private final WeakReference mWeakReference; + + private DelayedStopHandler(MusicBrowserService service) { + mWeakReference = new WeakReference<>(service); + } + + @Override + public void handleMessage(Message msg) { + MusicBrowserService service = mWeakReference.get(); + if (service != null) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null && !MediaController.getInstance().isAudioPaused()) { + return; + } + service.stopSelf(); + service.serviceStarted = false; + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java index 1e10edb6f..263195693 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java @@ -37,7 +37,6 @@ public class NativeCrashManager { try { String filename = UUID.randomUUID().toString(); String path = Constants.FILES_PATH + "/" + filename + ".faketrace"; - Log.d(Constants.TAG, "Writing unhandled exception to: " + path); BufferedWriter write = new BufferedWriter(new FileWriter(path)); write.write("Package: " + Constants.APP_PACKAGE + "\n"); write.write("Version Code: " + Constants.APP_VERSION + "\n"); @@ -114,7 +113,6 @@ public class NativeCrashManager { }; return dir.list(filter); } else { - FileLog.d(Constants.TAG, "Can't search for exception as file path is null."); return new String[0]; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java index 48baf9503..b73b083f3 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java @@ -23,7 +23,7 @@ import java.util.zip.ZipFile; public class NativeLoader { - private final static int LIB_VERSION = 21; + private final static int LIB_VERSION = 22; private final static String LIB_NAME = "tmessages." + LIB_VERSION; private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so"; private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.so"; @@ -77,11 +77,9 @@ public class NativeLoader { } out.close(); - if (Build.VERSION.SDK_INT >= 9) { - destLocalFile.setReadable(true, false); - destLocalFile.setExecutable(true, false); - destLocalFile.setWritable(true); - } + destLocalFile.setReadable(true, false); + destLocalFile.setExecutable(true, false); + destLocalFile.setWritable(true); try { System.load(destLocalFile.getAbsolutePath()); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index 70c73aff6..81028939d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -71,6 +71,8 @@ public class NotificationCenter { public static final int locationPermissionGranted = totalEvents++; public static final int peerSettingsDidLoaded = totalEvents++; public static final int wasUnableToFindCurrentLocation = totalEvents++; + public static final int reloadHints = totalEvents++; + public static final int reloadInlineHints = totalEvents++; public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; @@ -162,6 +164,10 @@ public class NotificationCenter { } } + public boolean isAnimationInProgress() { + return animationInProgress; + } + public void postNotificationName(int id, Object... args) { boolean allowDuringAnimation = false; if (allowedNotifications != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index d911f513c..fd4d004ba 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -769,7 +769,12 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationMessageMap", R.string.NotificationMessageMap, name); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (messageObject.isSticker()) { - msg = LocaleController.formatString("NotificationMessageSticker", R.string.NotificationMessageSticker, name); + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("NotificationMessageStickerEmoji", R.string.NotificationMessageStickerEmoji, name, emoji); + } else { + msg = LocaleController.formatString("NotificationMessageSticker", R.string.NotificationMessageSticker, name); + } } else if (messageObject.isGif()) { msg = LocaleController.formatString("NotificationMessageGif", R.string.NotificationMessageGif, name); } else { @@ -889,10 +894,19 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title); } } else if (object.isSticker()) { - if (!ChatObject.isChannel(chat) || chat.megagroup) { - msg = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, name, chat.title); + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedStickerEmoji", R.string.NotificationActionPinnedStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("NotificationActionPinnedStickerEmojiChannel", R.string.NotificationActionPinnedStickerEmojiChannel, chat.title, emoji); + } } else { - msg = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, chat.title); + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, chat.title); + } } } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (!ChatObject.isChannel(chat) || chat.megagroup) { @@ -960,7 +974,12 @@ public class NotificationsController { msg = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (messageObject.isSticker()) { - msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name, chat.title); + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name, chat.title); + } } else if (messageObject.isGif()) { msg = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, name, chat.title); } else { @@ -988,7 +1007,12 @@ public class NotificationsController { msg = LocaleController.formatString("ChannelMessageGroupMap", R.string.ChannelMessageGroupMap, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (messageObject.isSticker()) { - msg = LocaleController.formatString("ChannelMessageGroupSticker", R.string.ChannelMessageGroupSticker, name, chat.title); + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("ChannelMessageGroupStickerEmoji", R.string.ChannelMessageGroupStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("ChannelMessageGroupSticker", R.string.ChannelMessageGroupSticker, name, chat.title); + } } else if (messageObject.isGif()) { msg = LocaleController.formatString("ChannelMessageGroupGif", R.string.ChannelMessageGroupGif, name, chat.title); } else { @@ -1017,7 +1041,12 @@ public class NotificationsController { msg = LocaleController.formatString("NotificationMessageGroupMap", R.string.NotificationMessageGroupMap, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (messageObject.isSticker()) { - msg = LocaleController.formatString("NotificationMessageGroupSticker", R.string.NotificationMessageGroupSticker, name, chat.title); + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("NotificationMessageGroupStickerEmoji", R.string.NotificationMessageGroupStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("NotificationMessageGroupSticker", R.string.NotificationMessageGroupSticker, name, chat.title); + } } else if (messageObject.isGif()) { msg = LocaleController.formatString("NotificationMessageGroupGif", R.string.NotificationMessageGroupGif, name, chat.title); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java new file mode 100644 index 000000000..2f50ed963 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java @@ -0,0 +1,36 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.messenger; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import org.telegram.ui.LaunchActivity; + +public class OpenChatReceiver extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + if (intent == null) { + finish(); + } + if (intent.getAction() == null || !intent.getAction().startsWith("com.tmessages.openchat")) { + finish(); + return; + } + Intent intent2 = new Intent(this, LaunchActivity.class); + intent2.setAction(intent.getAction()); + intent2.putExtras(intent); + startActivity(intent2); + finish(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index 1420bf6aa..5cddafc16 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -176,7 +176,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } protected void processUpdateEncryption(TLRPC.TL_updateEncryption update, ConcurrentHashMap usersDict) { @@ -281,7 +281,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendClearHistoryMessage(TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -308,7 +308,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendNotifyLayerMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -340,7 +340,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendRequestKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -371,7 +371,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendAcceptKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -403,7 +403,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendCommitKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -434,7 +434,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendAbortKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage, long excange_id) { @@ -464,7 +464,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendNoopMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -492,7 +492,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendTTLMessage(TLRPC.EncryptedChat encryptedChat, TLRPC.Message resendMessage) { @@ -528,7 +528,7 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } public void sendScreenshotMessage(TLRPC.EncryptedChat encryptedChat, ArrayList random_ids, TLRPC.Message resendMessage) { @@ -564,10 +564,11 @@ public class SecretChatHelper { } reqSend.random_id = message.random_id; - performSendEncryptedRequest(reqSend, message, encryptedChat, null, null); + performSendEncryptedRequest(reqSend, message, encryptedChat, null, null, null); } - private void processSentMessage(TLRPC.Message newMsg, TLRPC.EncryptedFile file, TLRPC.DecryptedMessage decryptedMessage, String originalPath) { + private void updateMediaPaths(MessageObject newMsgObj, TLRPC.EncryptedFile file, TLRPC.DecryptedMessage decryptedMessage, String originalPath) { + TLRPC.Message newMsg = newMsgObj.messageOwner; if (file != null) { if (newMsg.media instanceof TLRPC.TL_messageMediaPhoto && newMsg.media.photo != null) { TLRPC.PhotoSize size = newMsg.media.photo.sizes.get(newMsg.media.photo.sizes.size() - 1); @@ -608,6 +609,8 @@ public class SecretChatHelper { File cacheFile = new File(newMsg.attachPath); File cacheFile2 = FileLoader.getPathToAttach(newMsg.media.document); if (cacheFile.renameTo(cacheFile2)) { + newMsgObj.mediaExists = newMsgObj.attachPathExists; + newMsgObj.attachPathExists = false; newMsg.attachPath = ""; } } @@ -640,7 +643,7 @@ public class SecretChatHelper { return message.action instanceof TLRPC.TL_messageEncryptedAction && !(message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages || message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL); } - protected void performSendEncryptedRequest(final TLRPC.DecryptedMessage req, final TLRPC.Message newMsgObj, final TLRPC.EncryptedChat chat, final TLRPC.InputEncryptedFile encryptedFile, final String originalPath) { + protected void performSendEncryptedRequest(final TLRPC.DecryptedMessage req, final TLRPC.Message newMsgObj, final TLRPC.EncryptedChat chat, final TLRPC.InputEncryptedFile encryptedFile, final String originalPath, final MessageObject newMsg) { if (req == null || chat.auth_key == null || chat instanceof TLRPC.TL_encryptedChatRequested || chat instanceof TLRPC.TL_encryptedChatWaiting) { return; } @@ -800,8 +803,8 @@ public class SecretChatHelper { if (isSecretVisibleMessage(newMsgObj)) { newMsgObj.date = res.date; } - if (res.file instanceof TLRPC.TL_encryptedFile) { - processSentMessage(newMsgObj, res.file, req, originalPath); + if (newMsg != null && res.file instanceof TLRPC.TL_encryptedFile) { + updateMediaPaths(newMsg, res.file, req, originalPath); } MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override @@ -1042,7 +1045,7 @@ public class SecretChatHelper { newMessage.media.document.attributes = decryptedMessage.media.attributes; } newMessage.media.document.mime_type = decryptedMessage.media.mime_type; - newMessage.media.document.size = file.size; + newMessage.media.document.size = decryptedMessage.media.size != 0 ? Math.min(decryptedMessage.media.size, file.size) : file.size; newMessage.media.document.key = decryptedMessage.media.key; newMessage.media.document.iv = decryptedMessage.media.iv; if (newMessage.media.document.mime_type == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index 1af40c081..da6f09697 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -10,9 +10,7 @@ package org.telegram.messenger; import android.app.Activity; import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; -import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.location.Location; @@ -29,6 +27,7 @@ import android.webkit.MimeTypeMap; import android.widget.Toast; import org.telegram.messenger.audioinfo.AudioInfo; +import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.QuickAckDelegate; @@ -306,7 +305,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } message.sendEncryptedRequest.media.key = (byte[]) args[3]; message.sendEncryptedRequest.media.iv = (byte[]) args[4]; - SecretChatHelper.getInstance().performSendEncryptedRequest(message.sendEncryptedRequest, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath); + SecretChatHelper.getInstance().performSendEncryptedRequest(message.sendEncryptedRequest, message.obj.messageOwner, message.encryptedChat, encryptedFile, message.originalPath, message.obj); arr.remove(a); a--; } @@ -892,6 +891,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter @Override public void run() { newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + SearchQuery.increasePeerRaiting(peer); NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, newMsgObj.id, newMsgObj, peer); processSentMessage(oldId); removeFromSendingMessages(oldId); @@ -945,14 +945,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } - public void editMessage(MessageObject messageObject, String message, boolean searchLinks, final BaseFragment fragment) { - if (fragment == null || fragment.getParentActivity() == null) { - return; + public int editMessage(MessageObject messageObject, String message, boolean searchLinks, final BaseFragment fragment, ArrayList entities, final Runnable callback) { + if (fragment == null || fragment.getParentActivity() == null || callback == null) { + return 0; } - final ProgressDialog progressDialog = new ProgressDialog(fragment.getParentActivity()); - progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); - progressDialog.setCanceledOnTouchOutside(false); - progressDialog.setCancelable(false); TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); req.peer = MessagesController.getInputPeer((int) messageObject.getDialogId()); @@ -960,19 +956,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter req.flags |= 2048; req.id = messageObject.getId(); req.no_webpage = !searchLinks; - final int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + if (entities != null) { + req.entities = entities; + req.flags |= 8; + } + return ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - try { - if (!fragment.getParentActivity().isFinishing()) { - progressDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } + callback.run(); } }); if (error == null) { @@ -993,22 +987,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } }); - progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - try { - dialog.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - try { - progressDialog.show(); - } catch (Exception e) { - //don't promt - } } private void sendLocation(Location location) { @@ -1451,9 +1429,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter newMsg.media_unread = true; } + newMsg.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; newMsgObj = new MessageObject(newMsg, null, true); newMsgObj.replyMessageObject = reply_to_msg; - newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; + if (!newMsgObj.isForwarded() && newMsgObj.type == 3) { + newMsgObj.attachPathExists = true; + } ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); @@ -1500,6 +1481,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (!searchLinks) { reqSend.no_webpage = true; } + if (entities != null && !entities.isEmpty()) { + reqSend.entities = entities; + reqSend.flags |= 8; + } performSendMessageRequest(reqSend, newMsgObj, null); } } else { @@ -1536,7 +1521,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { reqSend.media = new TLRPC.TL_decryptedMessageMediaEmpty(); } - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } } else if (type >= 1 && type <= 3 || type >= 5 && type <= 8 || type == 9 && encryptedChat != null) { if (encryptedChat == null) { @@ -1764,7 +1749,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } reqSend.media.lat = location.geo.lat; reqSend.media._long = location.geo._long; - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else if (type == 2 || type == 9 && photo != null) { TLRPC.PhotoSize small = photo.sizes.get(0); TLRPC.PhotoSize big = photo.sizes.get(photo.sizes.size() - 1); @@ -1809,7 +1794,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = big.location.secret; reqSend.media.key = big.location.key; reqSend.media.iv = big.location.iv; - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } else if (type == 3) { ImageLoader.fillPhotoSizeWithBytes(document.thumb); @@ -1865,7 +1850,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = document.access_hash; reqSend.media.key = document.key; reqSend.media.iv = document.iv; - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } else if (type == 6) { reqSend.media = new TLRPC.TL_decryptedMessageMediaContact(); @@ -1873,7 +1858,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter reqSend.media.first_name = user.first_name; reqSend.media.last_name = user.last_name; reqSend.media.user_id = user.id; - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else if (type == 7 || type == 9 && document != null) { if (MessageObject.isStickerDocument(document)) { reqSend.media = new TLRPC.TL_decryptedMessageMediaExternalDocument(); @@ -1890,7 +1875,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { ((TLRPC.TL_decryptedMessageMediaExternalDocument) reqSend.media).thumb = document.thumb; } - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else { ImageLoader.fillPhotoSizeWithBytes(document.thumb); if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { @@ -1940,7 +1925,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter encryptedFile.access_hash = document.access_hash; reqSend.media.key = document.key; reqSend.media.iv = document.iv; - SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null); + SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, encryptedFile, null, newMsgObj); } } } else if (type == 8) { @@ -2287,6 +2272,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); } + SearchQuery.increasePeerRaiting(newMsgObj.dialog_id); NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id); processSentMessage(oldId); removeFromSendingMessages(oldId); @@ -2402,7 +2388,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } else if (size2 != null && MessageObject.isStickerMessage(sentMessage) && size2.location != null) { size.location = size2.location; - } else if (size2 != null && size2.location instanceof TLRPC.TL_fileLocationUnavailable) { + } else if (size2 != null && size2.location instanceof TLRPC.TL_fileLocationUnavailable || size2 instanceof TLRPC.TL_photoSizeEmpty) { newMsg.media.document.thumb = sentMessage.media.document.thumb; } @@ -2414,6 +2400,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter TLRPC.DocumentAttribute attribute = newMsg.media.document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { oldWaveform = attribute.waveform; + break; } } newMsg.media.document.attributes = sentMessage.media.document.attributes; @@ -2439,12 +2426,16 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (!cacheFile.renameTo(cacheFile2)) { sentMessage.attachPath = newMsg.attachPath; sentMessage.message = newMsg.message; - } else if (!MessageObject.isVideoMessage(sentMessage)) { - newMsgObj.mediaExists = newMsgObj.attachPathExists; - newMsgObj.attachPathExists = false; - newMsg.attachPath = ""; - if (originalPath != null && originalPath.startsWith("http")) { - MessagesStorage.getInstance().addRecentLocalFile(originalPath, cacheFile2.toString(), newMsg.media.document); + } else { + if (MessageObject.isVideoMessage(sentMessage)) { + newMsgObj.attachPathExists = true; + } else { + newMsgObj.mediaExists = newMsgObj.attachPathExists; + newMsgObj.attachPathExists = false; + newMsg.attachPath = ""; + if (originalPath != null && originalPath.startsWith("http")) { + MessagesStorage.getInstance().addRecentLocalFile(originalPath, cacheFile2.toString(), newMsg.media.document); + } } } } else { @@ -2531,6 +2522,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if ((path == null || path.length() == 0) && uri == null) { return false; } + if (uri != null && AndroidUtilities.isInternalUri(uri)) { + return false; + } + if (path != null && AndroidUtilities.isInternalUri(Uri.fromFile(new File(path)))) { + return false; + } MimeTypeMap myMime = MimeTypeMap.getSingleton(); TLRPC.TL_documentAttributeAudio attributeAudio = null; if (uri != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java index 7f89fcb80..4e53ecf2b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java @@ -180,5 +180,4 @@ public class TgChooserTargetService extends ChooserTargetService { } return null; } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java index ea10ee13e..a13000ce9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java @@ -41,6 +41,7 @@ public class UserConfig { public static boolean useFingerprint = true; public static String lastUpdateVersion; public static int lastContactsSyncTime; + public static int lastHintsSyncTime; public static int migrateOffsetId = -1; public static int migrateOffsetDate = -1; @@ -86,6 +87,7 @@ public class UserConfig { editor.putString("lastUpdateVersion2", lastUpdateVersion); editor.putInt("lastContactsSyncTime", lastContactsSyncTime); editor.putBoolean("useFingerprint", useFingerprint); + editor.putInt("lastHintsSyncTime", lastHintsSyncTime); editor.putInt("migrateOffsetId", migrateOffsetId); if (migrateOffsetId != -1) { @@ -226,6 +228,7 @@ public class UserConfig { useFingerprint = preferences.getBoolean("useFingerprint", true); lastUpdateVersion = preferences.getString("lastUpdateVersion2", "3.5"); lastContactsSyncTime = preferences.getInt("lastContactsSyncTime", (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60); + lastHintsSyncTime = preferences.getInt("lastHintsSyncTime", (int) (System.currentTimeMillis() / 1000) - 25 * 60 * 60); migrateOffsetId = preferences.getInt("migrateOffsetId", 0); if (migrateOffsetId != -1) { @@ -316,6 +319,7 @@ public class UserConfig { isWaitingForPasscodeEnter = false; lastUpdateVersion = BuildVars.BUILD_VERSION_STRING; lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60; + lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000) - 25 * 60 * 60; saveConfig(true); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java index ae01ce9a2..70b7807eb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java @@ -89,7 +89,11 @@ public class Browser { public void onServiceConnected(CustomTabsClient client) { customTabsClient = client; if (customTabsClient != null) { - customTabsClient.warmup(0); + try { + customTabsClient.warmup(0); + } catch (Exception e) { + FileLog.e("tmessages", e); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java index 03646523b..a3cf4adee 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java @@ -29,6 +29,8 @@ public class BotQuery { public static void cleanup() { botInfos.clear(); + botKeyboards.clear(); + botKeyboardsByMids.clear(); } public static void clearBotKeyboard(final long did, final ArrayList messages) { @@ -68,11 +70,11 @@ public class BotQuery { NativeByteBuffer data; if (!cursor.isNull(0)) { - data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + data = cursor.byteBufferValue(0); + if (data != null) { botKeyboard = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); } - data.reuse(); } } cursor.dispose(); @@ -111,11 +113,11 @@ public class BotQuery { NativeByteBuffer data; if (!cursor.isNull(0)) { - data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + data = cursor.byteBufferValue(0); + if (data != null) { botInfo = TLRPC.BotInfo.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); } - data.reuse(); } } cursor.dispose(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java index d7e767ab3..c9291a278 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java @@ -57,24 +57,25 @@ public class MessagesQuery { SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid = %d", messageId)); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); result.id = cursor.intValue(1); result.date = cursor.intValue(2); result.dialog_id = -channelId; MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); } - data.reuse(); } cursor.dispose(); if (result == null) { cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM chat_pinned WHERE uid = %d", channelId)); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (result.id != mid) { result = null; } else { @@ -82,7 +83,6 @@ public class MessagesQuery { MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); } } - data.reuse(); } cursor.dispose(); } @@ -213,14 +213,14 @@ public class MessagesQuery { try { SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialogId; - ArrayList arrayList = replyMessageRandomOwners.remove(cursor.longValue(3)); if (arrayList != null) { MessageObject messageObject = new MessageObject(message, null, null, false); @@ -231,7 +231,6 @@ public class MessagesQuery { } } } - data.reuse(); } cursor.dispose(); if (!replyMessageRandomOwners.isEmpty()) { @@ -299,9 +298,10 @@ public class MessagesQuery { SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", stringBuilder.toString())); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialogId; @@ -309,7 +309,6 @@ public class MessagesQuery { result.add(message); replyMessages.remove((Integer) message.id); } - data.reuse(); } cursor.dispose(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java new file mode 100644 index 000000000..6f4982c1b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java @@ -0,0 +1,410 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.messenger.query; + +import android.text.TextUtils; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Locale; + +public class SearchQuery { + + public static ArrayList hints = new ArrayList<>(); + public static ArrayList inlineBots = new ArrayList<>(); + private static HashMap inlineDates = new HashMap<>(); + private static boolean loaded; + private static boolean loading; + + public static void cleanUp() { + loading = false; + loaded = false; + hints.clear(); + inlineBots.clear(); + inlineDates.clear(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); + } + + public static void loadHints(boolean cache) { + if (loading) { + return; + } + if (cache) { + if (loaded) { + return; + } + loading = true; + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + final ArrayList hintsNew = new ArrayList<>(); + final ArrayList inlineBotsNew = new ArrayList<>(); + final HashMap inlineDatesNew = new HashMap<>(); + final ArrayList users = new ArrayList<>(); + final ArrayList chats = new ArrayList<>(); + try { + ArrayList usersToLoad = new ArrayList<>(); + ArrayList chatsToLoad = new ArrayList<>(); + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT did, type, rating, date FROM chat_hints WHERE 1 ORDER BY rating DESC"); + while (cursor.next()) { + int did = cursor.intValue(0); + int type = cursor.intValue(1); + TLRPC.TL_topPeer peer = new TLRPC.TL_topPeer(); + peer.rating = cursor.doubleValue(2); + if (did > 0) { + peer.peer = new TLRPC.TL_peerUser(); + peer.peer.user_id = did; + usersToLoad.add(did); + } else { + peer.peer = new TLRPC.TL_peerChat(); + peer.peer.chat_id = -did; + chatsToLoad.add(-did); + } + if (type == 0) { + hintsNew.add(peer); + } else if (type == 1) { + inlineBotsNew.add(peer); + inlineDatesNew.put(did, cursor.intValue(3)); + } + } + cursor.dispose(); + if (!usersToLoad.isEmpty()) { + MessagesStorage.getInstance().getUsersInternal(TextUtils.join(",", usersToLoad), users); + } + + if (!chatsToLoad.isEmpty()) { + MessagesStorage.getInstance().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().putUsers(users, true); + MessagesController.getInstance().putChats(chats, true); + loading = false; + loaded = true; + hints = hintsNew; + inlineBots = inlineBotsNew; + inlineDates = inlineDatesNew; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); + if (Math.abs(UserConfig.lastHintsSyncTime - (int) (System.currentTimeMillis() / 1000)) >= 24 * 60 * 60) { + loadHints(false); + } + } + }); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + loaded = true; + } else { + loading = true; + TLRPC.TL_contacts_getTopPeers req = new TLRPC.TL_contacts_getTopPeers(); + req.hash = 0; + req.bots_pm = false; + req.correspondents = true; + req.groups = false; + req.channels = false; + req.bots_inline = true; + req.offset = 0; + req.limit = 20; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_contacts_topPeers) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + final TLRPC.TL_contacts_topPeers topPeers = (TLRPC.TL_contacts_topPeers) response; + MessagesController.getInstance().putUsers(topPeers.users, false); + MessagesController.getInstance().putChats(topPeers.chats, false); + for (int a = 0; a < topPeers.categories.size(); a++) { + TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); + if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { + inlineBots = category.peers; + } else { + hints = category.peers; + } + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); + final HashMap inlineDatesCopy = new HashMap<>(inlineDates); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); + MessagesStorage.getInstance().getDatabase().beginTransaction(); + MessagesStorage.getInstance().putUsersAndChats(topPeers.users, topPeers.chats, false, false); + + SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); + for (int a = 0; a < topPeers.categories.size(); a++) { + int type; + TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); + if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { + type = 1; + } else { + type = 0; + } + for (int b = 0; b < category.peers.size(); b++) { + TLRPC.TL_topPeer peer = category.peers.get(b); + int did; + if (peer.peer instanceof TLRPC.TL_peerUser) { + did = peer.peer.user_id; + } else if (peer.peer instanceof TLRPC.TL_peerChat) { + did = -peer.peer.chat_id; + } else { + did = -peer.peer.channel_id; + } + Integer date = inlineDatesCopy.get(did); + state.requery(); + state.bindInteger(1, did); + state.bindInteger(2, type); + state.bindDouble(3, peer.rating); + state.bindInteger(4, date != null ? date : 0); + state.step(); + } + } + + state.dispose(); + + MessagesStorage.getInstance().getDatabase().commitTransaction(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + UserConfig.lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); + UserConfig.saveConfig(false); + } + }); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + }); + } + } + }); + } + } + + public static void increaseInlineRaiting(final int uid) { + Integer time = inlineDates.get(uid); + int dt; + if (time != null) { + dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - time); + } else { + dt = 60; + } + + TLRPC.TL_topPeer peer = null; + for (int a = 0; a < inlineBots.size(); a++) { + TLRPC.TL_topPeer p = inlineBots.get(a); + if (p.peer.user_id == uid) { + peer = p; + break; + } + } + if (peer == null) { + peer = new TLRPC.TL_topPeer(); + peer.peer = new TLRPC.TL_peerUser(); + peer.peer.user_id = uid; + inlineBots.add(peer); + } + peer.rating += Math.exp(dt / MessagesController.getInstance().ratingDecay); + Collections.sort(inlineBots, new Comparator() { + @Override + public int compare(TLRPC.TL_topPeer lhs, TLRPC.TL_topPeer rhs) { + if (lhs.rating > rhs.rating) { + return -1; + } else if (lhs.rating < rhs.rating) { + return 1; + } + return 0; + } + }); + if (inlineBots.size() > 20) { + inlineBots.remove(inlineBots.size() - 1); + } + savePeer(uid, 1, peer.rating); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); + } + + public static void removeInline(final int uid) { + TLRPC.TL_topPeerCategoryPeers category = null; + for (int a = 0; a < inlineBots.size(); a++) { + if (inlineBots.get(a).peer.user_id == uid) { + inlineBots.remove(a); + TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); + req.category = new TLRPC.TL_topPeerCategoryBotsInline(); + req.peer = MessagesController.getInputPeer(uid); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + deletePeer(uid, 1); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); + return; + } + } + } + + public static void removePeer(final int uid) { + TLRPC.TL_topPeerCategoryPeers category = null; + for (int a = 0; a < hints.size(); a++) { + if (hints.get(a).peer.user_id == uid) { + hints.remove(a); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); + TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); + req.category = new TLRPC.TL_topPeerCategoryCorrespondents(); + req.peer = MessagesController.getInputPeer(uid); + deletePeer(uid, 0); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + return; + } + } + } + + public static void increasePeerRaiting(final long did) { + final int lower_id = (int) did; + if (lower_id <= 0) { + return; + } + //remove chats and bots for now + final TLRPC.User user = lower_id > 0 ? MessagesController.getInstance().getUser(lower_id) : null; + //final TLRPC.Chat chat = lower_id < 0 ? MessagesController.getInstance().getChat(-lower_id) : null; + if (user == null || user.bot/*&& chat == null || ChatObject.isChannel(chat) && !chat.megagroup*/) { + return; + } + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + double dt = 0; + try { + int lastTime = 0; + int lastMid = 0; + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT MAX(mid), MAX(date) FROM messages WHERE uid = %d AND out = 1", did)); + if (cursor.next()) { + lastMid = cursor.intValue(0); + lastTime = cursor.intValue(1); + } + cursor.dispose(); + if (lastMid > 0) { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT date FROM messages WHERE uid = %d AND mid < %d AND out = 1 ORDER BY date DESC", did, lastMid)); + if (cursor.next()) { + dt = (lastTime - cursor.intValue(0)); + } + cursor.dispose(); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + final double dtFinal = dt; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + TLRPC.TL_topPeer peer = null; + for (int a = 0; a < hints.size(); a++) { + TLRPC.TL_topPeer p = hints.get(a); + if (lower_id < 0 && (p.peer.chat_id == -lower_id || p.peer.channel_id == -lower_id) || lower_id > 0 && p.peer.user_id == lower_id) { + peer = p; + break; + } + } + if (peer == null) { + peer = new TLRPC.TL_topPeer(); + if (lower_id > 0) { + peer.peer = new TLRPC.TL_peerUser(); + peer.peer.user_id = lower_id; + } else { + peer.peer = new TLRPC.TL_peerChat(); + peer.peer.chat_id = -lower_id; + } + hints.add(peer); + } + peer.rating += Math.exp(dtFinal / MessagesController.getInstance().ratingDecay); + Collections.sort(hints, new Comparator() { + @Override + public int compare(TLRPC.TL_topPeer lhs, TLRPC.TL_topPeer rhs) { + if (lhs.rating > rhs.rating) { + return -1; + } else if (lhs.rating < rhs.rating) { + return 1; + } + return 0; + } + }); + + savePeer((int) did, 0, peer.rating); + + NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); + } + }); + } + }); + } + + private static void savePeer(final int did, final int type, final double rating) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); + state.requery(); + state.bindInteger(1, did); + state.bindInteger(2, type); + state.bindDouble(3, rating); + state.bindInteger(4, (int) System.currentTimeMillis() / 1000); + state.step(); + state.dispose(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + + private static void deletePeer(final int did, final int type) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + MessagesStorage.getInstance().getDatabase().executeFast(String.format(Locale.US, "DELETE FROM chat_hints WHERE did = %d AND type = %d", did, type)).stepThis().dispose(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java index 2a1841784..8f2ccdc3e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java @@ -375,9 +375,10 @@ public class SharedMediaQuery { } while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); message.id = cursor.intValue(1); message.dialog_id = uid; if ((int) uid == 0) { @@ -394,7 +395,6 @@ public class SharedMediaQuery { } } } - data.reuse(); } cursor.dispose(); @@ -481,16 +481,16 @@ public class SharedMediaQuery { SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_MUSIC)); while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (MessageObject.isMusicMessage(message)) { message.id = cursor.intValue(1); message.dialog_id = uid; arrayList.add(0, new MessageObject(message, null, false)); } } - data.reuse(); } cursor.dispose(); } catch (Exception e) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java index bbc794bb2..922eee48d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java @@ -179,18 +179,18 @@ public class StickersQuery { try { cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT data, date, hash FROM stickers_v2 WHERE 1"); if (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { newStickerArray = new ArrayList<>(); int count = data.readInt32(false); for (int a = 0; a < count; a++) { TLRPC.TL_messages_stickerSet stickerSet = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false); newStickerArray.add(stickerSet); } + data.reuse(); } date = cursor.intValue(1); hash = calcStickersHash(newStickerArray); - data.reuse(); } } catch (Throwable e) { FileLog.e("tmessages", e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java index 5db54056b..c3e33d5ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java @@ -18,7 +18,6 @@ package org.telegram.messenger.support.widget; import android.content.Context; -import android.content.res.TypedArray; import android.database.Observable; import android.graphics.Canvas; import android.graphics.PointF; @@ -59,7 +58,6 @@ import android.view.FocusFinder; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.View.MeasureSpec; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; @@ -153,9 +151,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro private static final boolean DEBUG = false; - private static final int[] NESTED_SCROLLING_ATTRS - = {16843830 /* android.R.attr.nestedScrollingEnabled */}; - /** * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by @@ -859,6 +854,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ public void setAdapter(Adapter adapter) { // bail out if layout is frozen + stopScroll(); setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index e6a5f08b0..1f4d26f16 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -133,6 +133,7 @@ public class ConnectionsManager { TLRPC.TL_error error = null; if (response != 0) { NativeByteBuffer buff = NativeByteBuffer.wrap(response); + buff.reused = true; resp = object.deserializeResponse(buff, buff.readInt32(true), true); } else if (errorText != null) { error = new TLRPC.TL_error(); @@ -260,6 +261,7 @@ public class ConnectionsManager { public static void onUnparsedMessageReceived(int address) { try { NativeByteBuffer buff = NativeByteBuffer.wrap(address); + buff.reused = true; final TLObject message = TLClassStore.Instance().TLdeserialize(buff, buff.readInt32(true), true); if (message instanceof TLRPC.Updates) { FileLog.d("tmessages", "java received " + message); @@ -327,6 +329,7 @@ public class ConnectionsManager { public static void onUpdateConfig(int address) { try { NativeByteBuffer buff = NativeByteBuffer.wrap(address); + buff.reused = true; final TLRPC.TL_config message = TLRPC.TL_config.TLdeserialize(buff, buff.readInt32(true), true); if (message != null) { Utilities.stageQueue.postRunnable(new Runnable() { diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java index eddea361a..91185ea06 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java @@ -9,8 +9,9 @@ public class NativeByteBuffer extends AbstractSerializedData { protected int address; public ByteBuffer buffer; - private boolean justCalc = false; - private int len = 0; + private boolean justCalc; + private int len; + public boolean reused = true; private static final ThreadLocal addressWrapper = new ThreadLocal() { @Override @@ -22,14 +23,16 @@ public class NativeByteBuffer extends AbstractSerializedData { public static NativeByteBuffer wrap(int address) { NativeByteBuffer result = addressWrapper.get(); if (address != 0) { + if (!result.reused) { + FileLog.e("tmessages", "forgot to reuse?"); + } result.address = address; + result.reused = false; result.buffer = native_getJavaByteBuffer(address); result.buffer.limit(native_limit(address)); int position = native_position(address); if (position <= result.buffer.limit()) { result.buffer.position(position); - } else { - FileLog.e("tmessages", "what with position " + position); } result.buffer.order(ByteOrder.LITTLE_ENDIAN); } @@ -494,6 +497,7 @@ public class NativeByteBuffer extends AbstractSerializedData { public void reuse() { if (address != 0) { + reused = true; native_reuse(address); } } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index 4418191aa..efc3a381d 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -54,9 +54,10 @@ public class TLRPC { public static final int MESSAGE_FLAG_HAS_MEDIA = 0x00000200; public static final int MESSAGE_FLAG_HAS_VIEWS = 0x00000400; public static final int MESSAGE_FLAG_HAS_BOT_ID = 0x00000800; + public static final int MESSAGE_FLAG_EDITED = 0x00008000; public static final int MESSAGE_FLAG_MEGAGROUP = 0x80000000; - public static final int LAYER = 51; + public static final int LAYER = 52; public static class ChatPhoto extends TLObject { public FileLocation photo_small; @@ -4422,6 +4423,59 @@ public class TLRPC { } } + public static class TL_topPeerCategoryPeers extends TLObject { + public static int constructor = 0xfb834291; + + public TopPeerCategory category; + public int count; + public ArrayList peers = new ArrayList<>(); + + public static TL_topPeerCategoryPeers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_topPeerCategoryPeers.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_topPeerCategoryPeers", constructor)); + } else { + return null; + } + } + TL_topPeerCategoryPeers result = new TL_topPeerCategoryPeers(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + category = TopPeerCategory.TLdeserialize(stream, stream.readInt32(exception), exception); + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_topPeer object = TL_topPeer.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + peers.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + category.serializeToStream(stream); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); + int count = peers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + peers.get(a).serializeToStream(stream); + } + } + } + public static class InputUser extends TLObject { public int user_id; public long access_hash; @@ -5213,9 +5267,9 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { email_pattern = stream.readString(exception); - } + } - public void serializeToStream(AbstractSerializedData stream) { + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeString(email_pattern); } @@ -7246,6 +7300,82 @@ public class TLRPC { } } + public static class TopPeerCategory extends TLObject { + + public static TopPeerCategory TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + TopPeerCategory result = null; + switch(constructor) { + case 0x637b7ed: + result = new TL_topPeerCategoryCorrespondents(); + break; + case 0xbd17a14a: + result = new TL_topPeerCategoryGroups(); + break; + case 0x148677e2: + result = new TL_topPeerCategoryBotsInline(); + break; + case 0x161d9628: + result = new TL_topPeerCategoryChannels(); + break; + case 0xab661b5b: + result = new TL_topPeerCategoryBotsPM(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in TopPeerCategory", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_topPeerCategoryCorrespondents extends TopPeerCategory { + public static int constructor = 0x637b7ed; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_topPeerCategoryGroups extends TopPeerCategory { + public static int constructor = 0xbd17a14a; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_topPeerCategoryBotsInline extends TopPeerCategory { + public static int constructor = 0x148677e2; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_topPeerCategoryChannels extends TopPeerCategory { + public static int constructor = 0x161d9628; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_topPeerCategoryBotsPM extends TopPeerCategory { + public static int constructor = 0xab661b5b; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_contactBlocked extends TLObject { public static int constructor = 0x561bc879; @@ -10509,21 +10639,18 @@ public class TLRPC { public static class MessageEntity extends TLObject { public int offset; - public int length; - public String language; + public int length; public String url; + public String language; public static MessageEntity TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - MessageEntity result = null; + MessageEntity result = null; switch(constructor) { - case 0x6ed02538: - result = new TL_messageEntityUrl(); + case 0x76a6d327: + result = new TL_messageEntityTextUrl(); break; - case 0xbd610bc9: - result = new TL_messageEntityBold(); - break; - case 0x826f8b60: - result = new TL_messageEntityItalic(); + case 0x6cef8ac7: + result = new TL_messageEntityBotCommand(); break; case 0x64e475c2: result = new TL_messageEntityEmail(); @@ -10531,24 +10658,33 @@ public class TLRPC { case 0x73924be0: result = new TL_messageEntityPre(); break; - case 0x76a6d327: - result = new TL_messageEntityTextUrl(); - break; case 0xbb92ba95: result = new TL_messageEntityUnknown(); break; + case 0x6ed02538: + result = new TL_messageEntityUrl(); + break; + case 0x826f8b60: + result = new TL_messageEntityItalic(); + break; + case 0xfa04579d: + result = new TL_messageEntityMention(); + break; + case 0x352dca58: + result = new TL_messageEntityMentionName(); + break; + case 0x208e68c9: + result = new TL_inputMessageEntityMentionName(); + break; + case 0xbd610bc9: + result = new TL_messageEntityBold(); + break; case 0x6f635b0d: result = new TL_messageEntityHashtag(); break; - case 0x6cef8ac7: - result = new TL_messageEntityBotCommand(); - break; case 0x28a20571: result = new TL_messageEntityCode(); break; - case 0xfa04579d: - result = new TL_messageEntityMention(); - break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in MessageEntity", constructor)); @@ -10560,8 +10696,26 @@ public class TLRPC { } } - public static class TL_messageEntityUrl extends MessageEntity { - public static int constructor = 0x6ed02538; + public static class TL_messageEntityTextUrl extends MessageEntity { + public static int constructor = 0x76a6d327; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + url = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + stream.writeString(url); + } + } + + public static class TL_messageEntityBotCommand extends MessageEntity { + public static int constructor = 0x6cef8ac7; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -10576,8 +10730,58 @@ public class TLRPC { } } - public static class TL_messageEntityBold extends MessageEntity { - public static int constructor = 0xbd610bc9; + public static class TL_messageEntityEmail extends MessageEntity { + public static int constructor = 0x64e475c2; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + } + } + + public static class TL_messageEntityPre extends MessageEntity { + public static int constructor = 0x73924be0; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + language = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + stream.writeString(language); + } + } + + public static class TL_messageEntityUnknown extends MessageEntity { + public static int constructor = 0xbb92ba95; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + } + } + + public static class TL_messageEntityUrl extends MessageEntity { + public static int constructor = 0x6ed02538; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -10608,68 +10812,70 @@ public class TLRPC { } } - public static class TL_messageEntityEmail extends MessageEntity { - public static int constructor = 0x64e475c2; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - offset = stream.readInt32(exception); - length = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(offset); - stream.writeInt32(length); - } - } - - public static class TL_messageEntityPre extends MessageEntity { - public static int constructor = 0x73924be0; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - offset = stream.readInt32(exception); - length = stream.readInt32(exception); - language = stream.readString(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(offset); - stream.writeInt32(length); - stream.writeString(language); - } - } - - public static class TL_messageEntityTextUrl extends MessageEntity { - public static int constructor = 0x76a6d327; + public static class TL_messageEntityMention extends MessageEntity { + public static int constructor = 0xfa04579d; public void readParams(AbstractSerializedData stream, boolean exception) { offset = stream.readInt32(exception); - length = stream.readInt32(exception); - url = stream.readString(exception); + length = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(offset); stream.writeInt32(length); - stream.writeString(url); } } - public static class TL_messageEntityUnknown extends MessageEntity { - public static int constructor = 0xbb92ba95; + public static class TL_messageEntityMentionName extends MessageEntity { + public static int constructor = 0x352dca58; + + public int user_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + user_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + stream.writeInt32(user_id); + } + } + + public static class TL_inputMessageEntityMentionName extends MessageEntity { + public static int constructor = 0x208e68c9; + + public InputUser user_id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + offset = stream.readInt32(exception); + length = stream.readInt32(exception); + user_id = InputUser.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(offset); + stream.writeInt32(length); + user_id.serializeToStream(stream); + } + } + + public static class TL_messageEntityBold extends MessageEntity { + public static int constructor = 0xbd610bc9; public void readParams(AbstractSerializedData stream, boolean exception) { - offset = stream.readInt32(exception); + offset = stream.readInt32(exception); length = stream.readInt32(exception); - } + } - public void serializeToStream(AbstractSerializedData stream) { + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(offset); stream.writeInt32(length); @@ -10692,42 +10898,10 @@ public class TLRPC { } } - public static class TL_messageEntityBotCommand extends MessageEntity { - public static int constructor = 0x6cef8ac7; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - offset = stream.readInt32(exception); - length = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(offset); - stream.writeInt32(length); - } - } - public static class TL_messageEntityCode extends MessageEntity { public static int constructor = 0x28a20571; - public void readParams(AbstractSerializedData stream, boolean exception) { - offset = stream.readInt32(exception); - length = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(offset); - stream.writeInt32(length); - } - } - - public static class TL_messageEntityMention extends MessageEntity { - public static int constructor = 0xfa04579d; - - public void readParams(AbstractSerializedData stream, boolean exception) { offset = stream.readInt32(exception); length = stream.readInt32(exception); @@ -11177,7 +11351,7 @@ public class TLRPC { } public static class TL_config extends TLObject { - public static int constructor = 0x317ceef4; + public static int constructor = 0xc9411388; public int date; public int expires; @@ -11198,6 +11372,7 @@ public class TLRPC { public int push_chat_limit; public int saved_gifs_limit; public int edit_time_limit; + public int rating_e_decay; public ArrayList disabled_features = new ArrayList<>(); public static TL_config TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -11247,6 +11422,7 @@ public class TLRPC { push_chat_limit = stream.readInt32(exception); saved_gifs_limit = stream.readInt32(exception); edit_time_limit = stream.readInt32(exception); + rating_e_decay = stream.readInt32(exception); magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -11290,6 +11466,7 @@ public class TLRPC { stream.writeInt32(push_chat_limit); stream.writeInt32(saved_gifs_limit); stream.writeInt32(edit_time_limit); + stream.writeInt32(rating_e_decay); stream.writeInt32(0x1cb5c415); count = disabled_features.size(); stream.writeInt32(count); @@ -11299,6 +11476,115 @@ public class TLRPC { } } + public static class contacts_TopPeers extends TLObject { + public ArrayList categories = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + + public static contacts_TopPeers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + contacts_TopPeers result = null; + switch(constructor) { + case 0x70b772a8: + result = new TL_contacts_topPeers(); + break; + case 0xde266ef5: + result = new TL_contacts_topPeersNotModified(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in contacts_TopPeers", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_contacts_topPeers extends contacts_TopPeers { + public static int constructor = 0x70b772a8; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_topPeerCategoryPeers object = TL_topPeerCategoryPeers.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + categories.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = categories.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + categories.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static class TL_contacts_topPeersNotModified extends contacts_TopPeers { + public static int constructor = 0xde266ef5; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_help_support extends TLObject { public static int constructor = 0x17c6b5f6; @@ -15671,6 +15957,153 @@ public class TLRPC { } } + public static class TL_messages_peerDialogs extends TLObject { + public static int constructor = 0x3371c354; + + public ArrayList dialogs = new ArrayList<>(); + public ArrayList messages = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + public TL_updates_state state; + + public static TL_messages_peerDialogs TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_messages_peerDialogs.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_messages_peerDialogs", constructor)); + } else { + return null; + } + } + TL_messages_peerDialogs result = new TL_messages_peerDialogs(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Dialog object = Dialog.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + dialogs.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Message object = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + messages.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + state = TL_updates_state.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = dialogs.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + dialogs.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + messages.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + state.serializeToStream(stream); + } + } + + public static class TL_topPeer extends TLObject { + public static int constructor = 0xedcdc05b; + + public Peer peer; + public double rating; + + public static TL_topPeer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_topPeer.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_topPeer", constructor)); + } else { + return null; + } + } + TL_topPeer result = new TL_topPeer(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + rating = stream.readDouble(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeDouble(rating); + } + } + public static class InputPhotoCrop extends TLObject { public double crop_left; public double crop_top; @@ -17948,6 +18381,54 @@ public class TLRPC { } } + public static class TL_contacts_getTopPeers extends TLObject { + public static int constructor = 0xd4982db5; + + public int flags; + public boolean correspondents; + public boolean bots_pm; + public boolean bots_inline; + public boolean groups; + public boolean channels; + public int offset; + public int limit; + public int hash; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return contacts_TopPeers.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + flags = correspondents ? (flags | 1) : (flags &~ 1); + flags = bots_pm ? (flags | 2) : (flags &~ 2); + flags = bots_inline ? (flags | 4) : (flags &~ 4); + flags = groups ? (flags | 1024) : (flags &~ 1024); + flags = channels ? (flags | 32768) : (flags &~ 32768); + stream.writeInt32(offset); + stream.writeInt32(limit); + stream.writeInt32(hash); + } + } + + public static class TL_contacts_resetTopPeerRating extends TLObject { + public static int constructor = 0x1ae373ac; + + public TopPeerCategory category; + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + category.serializeToStream(stream); + peer.serializeToStream(stream); + } + } + public static class TL_account_sendChangePhoneCode extends TLObject { public static int constructor = 0x8e57deb; @@ -18550,6 +19031,26 @@ public class TLRPC { } } + public static class TL_messages_getPeerDialogs extends TLObject { + public static int constructor = 0x2d9776b9; + + public ArrayList peers = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_peerDialogs.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = peers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + peers.get(a).serializeToStream(stream); + } + } + } + public static class TL_messages_editInlineBotMessage extends TLObject { public static int constructor = 0x130c2c85; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java index 692259feb..6f52388af 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java @@ -50,6 +50,7 @@ public class ActionBar extends FrameLayout { private boolean addToContainer = true; private boolean interceptTouches = true; private int extraHeight; + private AnimatorSetProxy actionModeAnimation; private boolean allowOverlayTitle; private CharSequence lastTitle; @@ -227,10 +228,13 @@ public class ActionBar extends FrameLayout { if (occupyStatusBar && actionModeTop != null) { animators.add(ObjectAnimatorProxy.ofFloat(actionModeTop, "alpha", 0.0f, 1.0f)); } - AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); - animatorSetProxy.playTogether(animators); - animatorSetProxy.setDuration(200); - animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { + if (actionModeAnimation != null) { + actionModeAnimation.cancel(); + } + actionModeAnimation = new AnimatorSetProxy(); + actionModeAnimation.playTogether(animators); + actionModeAnimation.setDuration(200); + actionModeAnimation.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationStart(Object animation) { actionMode.setVisibility(VISIBLE); @@ -241,18 +245,28 @@ public class ActionBar extends FrameLayout { @Override public void onAnimationEnd(Object animation) { - if (titleTextView != null) { - titleTextView.setVisibility(INVISIBLE); + if (actionModeAnimation != null && actionModeAnimation.equals(animation)) { + actionModeAnimation = null; + if (titleTextView != null) { + titleTextView.setVisibility(INVISIBLE); + } + if (subtitleTextView != null) { + subtitleTextView.setVisibility(INVISIBLE); + } + if (menu != null) { + menu.setVisibility(INVISIBLE); + } } - if (subtitleTextView != null) { - subtitleTextView.setVisibility(INVISIBLE); - } - if (menu != null) { - menu.setVisibility(INVISIBLE); + } + + @Override + public void onAnimationCancel(Object animation) { + if (actionModeAnimation != null && actionModeAnimation.equals(animation)) { + actionModeAnimation = null; } } }); - animatorSetProxy.start(); + actionModeAnimation.start(); } else { actionMode.setVisibility(VISIBLE); if (occupyStatusBar && actionModeTop != null) { @@ -273,7 +287,7 @@ public class ActionBar extends FrameLayout { if (drawable instanceof BackDrawable) { ((BackDrawable) drawable).setRotation(1, true); } - backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); + backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_MODE_SELECTOR_COLOR)); } } @@ -288,19 +302,32 @@ public class ActionBar extends FrameLayout { if (occupyStatusBar && actionModeTop != null) { animators.add(ObjectAnimatorProxy.ofFloat(actionModeTop, "alpha", 0.0f)); } - AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); - animatorSetProxy.playTogether(animators); - animatorSetProxy.setDuration(200); - animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { + if (actionModeAnimation != null) { + actionModeAnimation.cancel(); + } + actionModeAnimation = new AnimatorSetProxy(); + actionModeAnimation.playTogether(animators); + actionModeAnimation.setDuration(200); + actionModeAnimation.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animation) { - actionMode.setVisibility(INVISIBLE); - if (occupyStatusBar && actionModeTop != null) { - actionModeTop.setVisibility(INVISIBLE); + if (actionModeAnimation != null && actionModeAnimation.equals(animation)) { + actionModeAnimation = null; + actionMode.setVisibility(INVISIBLE); + if (occupyStatusBar && actionModeTop != null) { + actionModeTop.setVisibility(INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (actionModeAnimation != null && actionModeAnimation.equals(animation)) { + actionModeAnimation = null; } } }); - animatorSetProxy.start(); + actionModeAnimation.start(); } else { actionMode.setVisibility(INVISIBLE); if (occupyStatusBar && actionModeTop != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java index 057635c5b..f6cd41e01 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -29,8 +29,6 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.BuildVars; -import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.AnimationCompat.AnimatorListenerAdapterProxy; import org.telegram.messenger.AnimationCompat.AnimatorSetProxy; @@ -101,9 +99,6 @@ public class ActionBarLayout extends FrameLayout { getWindowVisibleDisplayFrame(rect); int usableViewHeight = rootView.getHeight() - (rect.top != 0 ? AndroidUtilities.statusBarHeight : 0) - AndroidUtilities.getViewInset(rootView); isKeyboardVisible = usableViewHeight - (rect.bottom - rect.top) > 0; - if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "keyboard visible = " + isKeyboardVisible + " for " + this); - } if (waitingForKeyboardCloseRunnable != null && !containerView.isKeyboardVisible && !containerViewBack.isKeyboardVisible) { AndroidUtilities.cancelRunOnUIThread(waitingForKeyboardCloseRunnable); waitingForKeyboardCloseRunnable.run(); @@ -117,6 +112,7 @@ public class ActionBarLayout extends FrameLayout { private static Paint scrimPaint; private Runnable waitingForKeyboardCloseRunnable; + private Runnable delayedOpenAnimationRunnable; private LinearLayoutContainer containerView; private LinearLayoutContainer containerViewBack; @@ -629,6 +625,15 @@ public class ActionBarLayout extends FrameLayout { }); } + public void resumeDelayedFragmentAnimation() { + if (delayedOpenAnimationRunnable == null) { + return; + } + AndroidUtilities.cancelRunOnUIThread(delayedOpenAnimationRunnable); + delayedOpenAnimationRunnable.run(); + delayedOpenAnimationRunnable = null; + } + public boolean presentFragment(final BaseFragment fragment, final boolean removeLast, boolean forceWithoutAnimation, boolean check) { if (checkTransitionAnimation() || delegate != null && check && !delegate.needPresentFragment(fragment, removeLast, forceWithoutAnimation, this) || !fragment.onFragmentCreate()) { return false; @@ -736,7 +741,6 @@ public class ActionBarLayout extends FrameLayout { ViewProxy.setTranslationX(containerView, 0); } }; - FileLog.e("tmessages", "onOpenAnimationsStart"); fragment.onTransitionAnimationStart(true, false); AnimatorSetProxy animation = fragment.onCustomTransitionAnimation(true, new Runnable() { @Override @@ -751,7 +755,6 @@ public class ActionBarLayout extends FrameLayout { waitingForKeyboardCloseRunnable = new Runnable() { @Override public void run() { - FileLog.e("tmessages", "start delayed by keyboard open animation"); if (waitingForKeyboardCloseRunnable != this) { return; } @@ -759,6 +762,18 @@ public class ActionBarLayout extends FrameLayout { } }; AndroidUtilities.runOnUIThread(waitingForKeyboardCloseRunnable, 200); + } else if (fragment.needDelayOpenAnimation()) { + delayedOpenAnimationRunnable = new Runnable() { + @Override + public void run() { + if (delayedOpenAnimationRunnable != this) { + return; + } + delayedOpenAnimationRunnable = null; + startLayoutAnimation(true, true); + } + }; + AndroidUtilities.runOnUIThread(delayedOpenAnimationRunnable, 200); } else { startLayoutAnimation(true, true); } @@ -873,7 +888,6 @@ public class ActionBarLayout extends FrameLayout { layoutParams.width = LayoutHelper.MATCH_PARENT; layoutParams.height = LayoutHelper.MATCH_PARENT; fragmentView.setLayoutParams(layoutParams); - FileLog.e("tmessages", "onCloseAnimationStart"); previousFragment.onTransitionAnimationStart(true, true); currentFragment.onTransitionAnimationStart(false, false); previousFragment.onResume(); @@ -918,7 +932,6 @@ public class ActionBarLayout extends FrameLayout { if (waitingForKeyboardCloseRunnable != this) { return; } - FileLog.e("tmessages", "start delayed by keyboard close animation"); startLayoutAnimation(false, true); } }; @@ -978,7 +991,6 @@ public class ActionBarLayout extends FrameLayout { onAnimationEndCheck(false); } }); - FileLog.e("tmessages", "onCloseAnimationsStart"); currentAnimation.start(); } else { removeFragmentFromStackInternal(currentFragment); @@ -1104,13 +1116,11 @@ public class ActionBarLayout extends FrameLayout { if (post) { new Handler().post(new Runnable() { public void run() { - FileLog.e("tmessages", "onCloseAnimationEnd"); onCloseAnimationEndRunnable.run(); onCloseAnimationEndRunnable = null; } }); } else { - FileLog.e("tmessages", "onCloseAnimationEnd"); onCloseAnimationEndRunnable.run(); onCloseAnimationEndRunnable = null; } @@ -1124,13 +1134,11 @@ public class ActionBarLayout extends FrameLayout { if (post) { new Handler().post(new Runnable() { public void run() { - FileLog.e("tmessages", "onOpenAnimationEnd"); onOpenAnimationEndRunnable.run(); onOpenAnimationEndRunnable = null; } }); } else { - FileLog.e("tmessages", "onOpenAnimationEnd"); onOpenAnimationEndRunnable.run(); onOpenAnimationEndRunnable = null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index 350d9a89e..ae135b33d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -368,6 +368,10 @@ public class ActionBarMenuItem extends FrameLayoutFixed { iconView.setImageResource(resId); } + public ImageView getImageView() { + return iconView; + } + public EditText getSearchField() { return searchField; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java index 126612e3d..e898fa7bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java @@ -158,6 +158,10 @@ public class BaseFragment { } } + public boolean needDelayOpenAnimation() { + return false; + } + public void onResume() { } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 98eef547f..f4ae2f34e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -8,27 +8,27 @@ package org.telegram.ui.ActionBar; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.NestedScrollingParentHelper; +import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.View; -import android.view.ViewAnimationUtils; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; @@ -37,7 +37,6 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -48,21 +47,23 @@ import org.telegram.messenger.AnimationCompat.ViewProxy; import org.telegram.messenger.LocaleController; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.ui.Components.FrameLayoutFixed; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; public class BottomSheet extends Dialog { - private LinearLayout containerView; - private FrameLayout container; + protected ViewGroup containerView; + private ContainerView container; private WindowInsets lastInsets; + private Runnable startAnimationRunnable; + private int layoutCount; + private boolean dismissed; private int tag; - private boolean disableBackground; - private DialogInterface.OnClickListener onClickListener; private CharSequence[] items; @@ -70,21 +71,21 @@ public class BottomSheet extends Dialog { private View customView; private CharSequence title; private boolean fullWidth; - private boolean isGrid; - private ColorDrawable backgroundDrawable = new ColorDrawable(0xff000000); + protected ColorDrawable backDrawable = new ColorDrawable(0xff000000); + + private boolean allowCustomAnimation = true; + + private int touchSlop; + private boolean useFastDismiss; private boolean focusable; - private Paint ciclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + protected Paint ciclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Drawable shadowDrawable; protected static int backgroundPaddingTop; protected static int backgroundPaddingLeft; - private boolean useRevealAnimation; - private float revealRadius; - private int revealX; - private int revealY; private boolean applyTopPadding = true; private boolean applyBottomPadding = true; @@ -95,18 +96,326 @@ public class BottomSheet extends Dialog { private BottomSheetDelegateInterface delegate; + protected Object currentSheetAnimation; + + private class ContainerView extends FrameLayout implements NestedScrollingParent { + + private VelocityTracker velocityTracker = null; + private int startedTrackingX; + private int startedTrackingY; + private int startedTrackingPointerId; + private boolean maybeStartTracking = false; + private boolean startedTracking = false; + private AnimatorSetProxy currentAnimation = null; + private NestedScrollingParentHelper nestedScrollingParentHelper; + + public ContainerView(Context context) { + super(context); + nestedScrollingParentHelper = new NestedScrollingParentHelper(this); + } + + @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return !dismissed && nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL && !canDismissWithSwipe(); + } + + @Override + public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { + nestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); + if (dismissed) { + return; + } + cancelCurrentAnimation(); + } + + @Override + public void onStopNestedScroll(View target) { + nestedScrollingParentHelper.onStopNestedScroll(target); + if (dismissed) { + return; + } + float currentTranslation = ViewProxy.getTranslationY(containerView); + checkDismiss(0, 0); + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + if (dismissed) { + return; + } + cancelCurrentAnimation(); + if (dyUnconsumed != 0) { + float currentTranslation = ViewProxy.getTranslationY(containerView); + currentTranslation -= dyUnconsumed; + if (currentTranslation < 0) { + currentTranslation = 0; + } + ViewProxy.setTranslationY(containerView, currentTranslation); + } + } + + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + if (dismissed) { + return; + } + cancelCurrentAnimation(); + float currentTranslation = ViewProxy.getTranslationY(containerView); + if (currentTranslation > 0 && dy > 0) { + currentTranslation -= dy; + consumed[1] = dy; + if (currentTranslation < 0) { + currentTranslation = 0; + consumed[1] += currentTranslation; + } + ViewProxy.setTranslationY(containerView, currentTranslation); + } + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + return false; + } + + @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + return false; + } + + @Override + public int getNestedScrollAxes() { + return nestedScrollingParentHelper.getNestedScrollAxes(); + } + + private void checkDismiss(float velX, float velY) { + float translationY = ViewProxy.getTranslationY(containerView); + boolean backAnimation = translationY < AndroidUtilities.getPixelsInCM(0.8f, false) && (velY < 3500 || Math.abs(velY) < Math.abs(velX)) || velY < 0 && Math.abs(velY) >= 3500; + if (!backAnimation) { + boolean allowOld = allowCustomAnimation; + allowCustomAnimation = false; + useFastDismiss = true; + dismiss(); + allowCustomAnimation = allowOld; + } else { + currentAnimation = new AnimatorSetProxy(); + currentAnimation.playTogether(ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0)); + currentAnimation.setDuration((int) (150 * (translationY / AndroidUtilities.getPixelsInCM(0.8f, false)))); + currentAnimation.setInterpolator(new DecelerateInterpolator()); + currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (currentAnimation != null && currentAnimation.equals(animation)) { + currentAnimation = null; + } + } + }); + currentAnimation.start(); + } + } + + private void cancelCurrentAnimation() { + if (currentAnimation != null) { + currentAnimation.cancel(); + currentAnimation = null; + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (dismissed) { + return false; + } + if (ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && !startedTracking && !maybeStartTracking) { + startedTrackingX = (int) ev.getX(); + startedTrackingY = (int) ev.getY(); + if (startedTrackingY < containerView.getTop() || startedTrackingX < containerView.getLeft() || startedTrackingX > containerView.getRight()) { + dismiss(); + return true; + } + startedTrackingPointerId = ev.getPointerId(0); + maybeStartTracking = true; + cancelCurrentAnimation(); + if (velocityTracker != null) { + velocityTracker.clear(); + } + } else if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && ev.getPointerId(0) == startedTrackingPointerId) { + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + float dx = Math.abs((int) (ev.getX() - startedTrackingX)); + float dy = (int) ev.getY() - startedTrackingY; + velocityTracker.addMovement(ev); + if (maybeStartTracking && !startedTracking && (dy > 0 && dy / 3.0f > Math.abs(dx) && Math.abs(dy) >= touchSlop)) { + startedTrackingY = (int) ev.getY(); + maybeStartTracking = false; + startedTracking = true; + requestDisallowInterceptTouchEvent(true); + } else if (startedTracking) { + float translationY = ViewProxy.getTranslationY(containerView); + translationY += dy; + if (translationY < 0) { + translationY = 0; + } + ViewProxy.setTranslationY(containerView, translationY); + startedTrackingY = (int) ev.getY(); + } + } else if (ev == null || ev != null && ev.getPointerId(0) == startedTrackingPointerId && (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) { + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + velocityTracker.computeCurrentVelocity(1000); + float translationY = ViewProxy.getTranslationY(containerView); + if (startedTracking || translationY != 0) { + checkDismiss(velocityTracker.getXVelocity(), velocityTracker.getYVelocity()); + startedTracking = false; + } else { + maybeStartTracking = false; + startedTracking = false; + } + if (velocityTracker != null) { + velocityTracker.recycle(); + velocityTracker = null; + } + } + return startedTracking; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + width -= lastInsets.getSystemWindowInsetRight() + lastInsets.getSystemWindowInsetLeft(); + } + + setMeasuredDimension(width, height); + boolean isPortrait = width < height; + + if (containerView != null) { + if (!fullWidth) { + int widthSpec; + if (AndroidUtilities.isTablet()) { + widthSpec = MeasureSpec.makeMeasureSpec((int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.8f) + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY); + } else { + widthSpec = MeasureSpec.makeMeasureSpec(isPortrait ? width + backgroundPaddingLeft * 2 : (int) Math.max(width * 0.8f, Math.min(AndroidUtilities.dp(480), width)) + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY); + } + if (lastInsets != null && Build.VERSION.SDK_INT >= 21 && focusable) { + containerView.getLayoutParams(); + containerView.measure(widthSpec, MeasureSpec.makeMeasureSpec(height - lastInsets.getSystemWindowInsetBottom(), MeasureSpec.AT_MOST)); + } else { + containerView.measure(widthSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + } else { + containerView.measure(MeasureSpec.makeMeasureSpec(width + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + } + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() == GONE || child == containerView) { + continue; + } + measureChildWithMargins(child, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 0, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), 0); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + layoutCount--; + if (containerView != null) { + int t = (bottom - top) - containerView.getMeasuredHeight(); + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + left += lastInsets.getSystemWindowInsetLeft(); + right += lastInsets.getSystemWindowInsetLeft(); + if (focusable) { + t -= lastInsets.getSystemWindowInsetBottom(); + } + } + int l = ((right - left) - containerView.getMeasuredWidth()) / 2; + containerView.layout(l, t, l + containerView.getMeasuredWidth(), t + containerView.getMeasuredHeight()); + } + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE || child == containerView) { + continue; + } + final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + int childLeft; + int childTop; + + int gravity = lp.gravity; + if (gravity == -1) { + gravity = Gravity.TOP | Gravity.LEFT; + } + + final int absoluteGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + childLeft = (right - left - width) / 2 + lp.leftMargin - lp.rightMargin; + break; + case Gravity.RIGHT: + childLeft = right - width - lp.rightMargin; + break; + case Gravity.LEFT: + default: + childLeft = lp.leftMargin; + } + + switch (verticalGravity) { + case Gravity.TOP: + childTop = lp.topMargin; + break; + case Gravity.CENTER_VERTICAL: + childTop = (bottom - top - height) / 2 + lp.topMargin - lp.bottomMargin; + break; + case Gravity.BOTTOM: + childTop = (bottom - top) - height - lp.bottomMargin; + break; + default: + childTop = lp.topMargin; + } + child.layout(childLeft, childTop, childLeft + width, childTop + height); + } + if (layoutCount == 0 && startAnimationRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(startAnimationRunnable); + startAnimationRunnable.run(); + startAnimationRunnable = null; + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (canDismissWithSwipe()) { + return onTouchEvent(event); + } + return super.onInterceptTouchEvent(event); + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (maybeStartTracking && !startedTracking) { + onTouchEvent(null); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + } + public interface BottomSheetDelegateInterface { void onOpenAnimationStart(); - void onOpenAnimationEnd(); - - void onRevealAnimationStart(boolean open); - - void onRevealAnimationEnd(boolean open); - - void onRevealAnimationProgress(boolean open, float radius, int x, int y); - - View getRevealView(); } public static class BottomSheetDelegate implements BottomSheetDelegateInterface { @@ -119,65 +428,33 @@ public class BottomSheet extends Dialog { public void onOpenAnimationEnd() { } - - @Override - public void onRevealAnimationStart(boolean open) { - - } - - @Override - public void onRevealAnimationEnd(boolean open) { - - } - - @Override - public void onRevealAnimationProgress(boolean open, float radius, int x, int y) { - - } - - @Override - public View getRevealView() { - return null; - } } public static class BottomSheetCell extends FrameLayout { private TextView textView; private ImageView imageView; - private boolean isGrid; public BottomSheetCell(Context context, int type) { super(context); - isGrid = type == 1; setBackgroundResource(R.drawable.list_selector); - if (type != 1) { - setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); - } + setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); - if (type == 1) { - addView(imageView, LayoutHelper.createFrame(48, 48, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 8, 0, 0)); - } else { - addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); - } + addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); textView = new TextView(context); textView.setLines(1); textView.setSingleLine(true); textView.setGravity(Gravity.CENTER_HORIZONTAL); textView.setEllipsize(TextUtils.TruncateAt.END); - if (type == 1) { - textView.setTextColor(0xff757575); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 60, 0, 0)); - } else if (type == 0) { + if (type == 0) { textView.setTextColor(0xff212121); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL)); - } else if (type == 2) { + } else if (type == 1) { textView.setGravity(Gravity.CENTER); textView.setTextColor(0xff212121); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); @@ -188,7 +465,7 @@ public class BottomSheet extends Dialog { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(isGrid ? MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(96), MeasureSpec.EXACTLY) : widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(isGrid ? 80 : 48), MeasureSpec.EXACTLY)); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); } public void setTextColor(int color) { @@ -204,9 +481,7 @@ public class BottomSheet extends Dialog { if (icon != 0) { imageView.setImageResource(icon); imageView.setVisibility(VISIBLE); - if (!isGrid) { - textView.setPadding(LocaleController.isRTL ? 0 : AndroidUtilities.dp(56), 0, LocaleController.isRTL ? AndroidUtilities.dp(56) : 0, 0); - } + textView.setPadding(LocaleController.isRTL ? 0 : AndroidUtilities.dp(56), 0, LocaleController.isRTL ? AndroidUtilities.dp(56) : 0, 0); } else { imageView.setVisibility(INVISIBLE); textView.setPadding(0, 0, 0, 0); @@ -214,12 +489,19 @@ public class BottomSheet extends Dialog { } } + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + } + public BottomSheet(Context context, boolean needFocus) { super(context, R.style.TransparentDialog); if (Build.VERSION.SDK_INT >= 21 && !"N".equals(Build.VERSION.CODENAME)) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); } + ViewConfiguration vc = ViewConfiguration.get(context); + touchSlop = vc.getScaledTouchSlop(); Rect padding = new Rect(); shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); @@ -227,122 +509,8 @@ public class BottomSheet extends Dialog { backgroundPaddingLeft = padding.left; backgroundPaddingTop = padding.top; - container = new FrameLayout(getContext()) { - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { - width -= lastInsets.getSystemWindowInsetRight() + lastInsets.getSystemWindowInsetLeft(); - } - - setMeasuredDimension(width, height); - boolean isPortrait = width < height; - - if (containerView != null) { - int left = useRevealAnimation && Build.VERSION.SDK_INT <= 19 ? 0 : backgroundPaddingLeft; - if (!fullWidth) { - int widthSpec; - if (AndroidUtilities.isTablet()) { - widthSpec = MeasureSpec.makeMeasureSpec((int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.8f) + left * 2, MeasureSpec.EXACTLY); - } else { - widthSpec = MeasureSpec.makeMeasureSpec(isPortrait ? width + left * 2 : (int) Math.max(width * 0.8f, Math.min(AndroidUtilities.dp(480), width)) + left * 2, MeasureSpec.EXACTLY); - } - if (lastInsets != null && Build.VERSION.SDK_INT >= 21 && focusable) { - containerView.getLayoutParams(); - containerView.measure(widthSpec, MeasureSpec.makeMeasureSpec(height - lastInsets.getSystemWindowInsetBottom(), MeasureSpec.AT_MOST)); - } else { - containerView.measure(widthSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } - } else { - containerView.measure(MeasureSpec.makeMeasureSpec(width + left * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); - } - } - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child.getVisibility() == GONE || child == containerView) { - continue; - } - measureChildWithMargins(child, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 0, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), 0); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (containerView != null) { - int t = (bottom - top) - containerView.getMeasuredHeight(); - if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { - left += lastInsets.getSystemWindowInsetLeft(); - right += lastInsets.getSystemWindowInsetLeft(); - if (focusable) { - t -= lastInsets.getSystemWindowInsetBottom(); - } - } - int l = ((right - left) - containerView.getMeasuredWidth()) / 2; - containerView.layout(l, t, l + containerView.getMeasuredWidth(), t + getMeasuredHeight()); - } - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE || child == containerView) { - continue; - } - final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); - - final int width = child.getMeasuredWidth(); - final int height = child.getMeasuredHeight(); - - int childLeft; - int childTop; - - int gravity = lp.gravity; - if (gravity == -1) { - gravity = Gravity.TOP | Gravity.LEFT; - } - - final int absoluteGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; - final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; - - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - childLeft = (right - left - width) / 2 + lp.leftMargin - lp.rightMargin; - break; - case Gravity.RIGHT: - childLeft = right - width - lp.rightMargin; - break; - case Gravity.LEFT: - default: - childLeft = lp.leftMargin; - } - - switch (verticalGravity) { - case Gravity.TOP: - childTop = lp.topMargin; - break; - case Gravity.CENTER_VERTICAL: - childTop = (bottom - top - height) / 2 + lp.topMargin - lp.bottomMargin; - break; - case Gravity.BOTTOM: - childTop = (bottom - top) - height - lp.bottomMargin; - break; - default: - childTop = lp.topMargin; - } - child.layout(childLeft, childTop, childLeft + width, childTop + height); - } - } - }; - container.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - dismiss(); - return false; - } - }); - container.setBackgroundDrawable(backgroundDrawable); + container = new ContainerView(getContext()); + container.setBackgroundDrawable(backDrawable); focusable = needFocus; if (Build.VERSION.SDK_INT >= 21 && !"N".equals(Build.VERSION.CODENAME)) { container.setFitsSystemWindows(true); @@ -357,6 +525,9 @@ public class BottomSheet extends Dialog { }); container.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } + + ciclePaint.setColor(0xffffffff); + backDrawable.setAlpha(Build.VERSION.SDK_INT >= 11 ? 0 : 51); } @Override @@ -367,113 +538,66 @@ public class BottomSheet extends Dialog { window.setWindowAnimations(R.style.DialogNoAnimation); setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - ciclePaint.setColor(0xffffffff); - - containerView = new LinearLayout(getContext()) { - - @Override - protected void onDraw(Canvas canvas) { - if (useRevealAnimation && Build.VERSION.SDK_INT <= 19) { - canvas.drawCircle(revealX, revealY, revealRadius, ciclePaint); + if (containerView == null) { + containerView = new FrameLayoutFixed(getContext()) { + @Override + public boolean hasOverlappingRendering() { + return false; } - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - return super.drawChild(canvas, child, drawingTime); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - FileLog.e("tmessages", "container on layout"); - } - }; + }; + containerView.setBackgroundDrawable(shadowDrawable); + containerView.setPadding(backgroundPaddingLeft, (applyTopPadding ? AndroidUtilities.dp(8) : 0) + backgroundPaddingTop, backgroundPaddingLeft, (applyBottomPadding ? AndroidUtilities.dp(8) : 0)); + } if (Build.VERSION.SDK_INT >= 21) { containerView.setFitsSystemWindows(true); } containerView.setVisibility(View.INVISIBLE); - backgroundDrawable.setAlpha(0); - containerView.setWillNotDraw(false); - containerView.setOrientation(LinearLayout.VERTICAL); container.addView(containerView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM)); - if (title != null) { - TextView titleView = new TextView(getContext()); - titleView.setLines(1); - titleView.setSingleLine(true); - titleView.setText(title); - titleView.setTextColor(0xff757575); - titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - titleView.setEllipsize(TextUtils.TruncateAt.MIDDLE); - titleView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), AndroidUtilities.dp(8)); - titleView.setGravity(Gravity.CENTER_VERTICAL); - containerView.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - titleView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - } - if (customView != null) { if (customView.getParent() != null) { ViewGroup viewGroup = (ViewGroup) customView.getParent(); viewGroup.removeView(customView); } - containerView.addView(customView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - } - - if (items != null) { - if (customView != null) { - FrameLayout frameLayout = new FrameLayout(getContext()); - frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); - containerView.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 16)); - - View lineView = new View(getContext()); - lineView.setBackgroundColor(0xffd2d2d2); - frameLayout.addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - } - FrameLayout rowLayout = null; - int lastRowLayoutNum = 0; - for (int a = 0; a < items.length; a++) { - BottomSheetCell cell = new BottomSheetCell(getContext(), isGrid ? 1 : 0); - cell.setTextAndIcon(items[a], itemIcons != null ? itemIcons[a] : 0); - if (isGrid) { - int row = a / 3; - if (rowLayout == null || lastRowLayoutNum != row) { - rowLayout = new FrameLayout(getContext()); - lastRowLayoutNum = row; - containerView.addView(rowLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 80, 0, lastRowLayoutNum != 0 ? 8 : 0, 0, 0)); - rowLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - } - int col = a % 3; - int gravity; - if (col == 0) { - gravity = Gravity.LEFT | Gravity.TOP; - } else if (col == 1) { - gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - } else { - gravity = Gravity.RIGHT | Gravity.TOP; - } - rowLayout.addView(cell, LayoutHelper.createFrame(96, 80, gravity)); - } else { - containerView.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - } - cell.setTag(a); - cell.setOnClickListener(new View.OnClickListener() { + containerView.addView(customView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); + } else { + int topOffset = 0; + if (title != null) { + TextView titleView = new TextView(getContext()); + titleView.setLines(1); + titleView.setSingleLine(true); + titleView.setText(title); + titleView.setTextColor(0xff757575); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleView.setEllipsize(TextUtils.TruncateAt.MIDDLE); + titleView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), AndroidUtilities.dp(8)); + titleView.setGravity(Gravity.CENTER_VERTICAL); + containerView.addView(titleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48)); + titleView.setOnTouchListener(new View.OnTouchListener() { @Override - public void onClick(View v) { - dismissWithButtonClick((Integer) v.getTag()); + public boolean onTouch(View v, MotionEvent event) { + return true; } }); - itemViews.add(cell); + topOffset += 48; + } + if (items != null) { + FrameLayout rowLayout = null; + int lastRowLayoutNum = 0; + for (int a = 0; a < items.length; a++) { + BottomSheetCell cell = new BottomSheetCell(getContext(), 0); + cell.setTextAndIcon(items[a], itemIcons != null ? itemIcons[a] : 0); + containerView.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP, 0, topOffset, 0, 0)); + topOffset += 48; + cell.setTag(a); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismissWithButtonClick((Integer) v.getTag()); + } + }); + itemViews.add(cell); + } } } @@ -498,26 +622,34 @@ public class BottomSheet extends Dialog { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } dismissed = false; - if ((Build.VERSION.SDK_INT >= 21 || !useRevealAnimation) && !disableBackground) { - containerView.setBackgroundDrawable(shadowDrawable); - } else { - containerView.setBackgroundDrawable(null); + cancelSheetAnimation(); + if (containerView.getMeasuredHeight() == 0) { + containerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, View.MeasureSpec.AT_MOST)); } - int left = useRevealAnimation && Build.VERSION.SDK_INT <= 19 || disableBackground ? 0 : backgroundPaddingLeft; - int top = useRevealAnimation && Build.VERSION.SDK_INT <= 19 || disableBackground ? 0 : backgroundPaddingTop; - containerView.setPadding(left, (applyTopPadding ? AndroidUtilities.dp(8) : 0) + top, left, (applyBottomPadding ? AndroidUtilities.dp(isGrid ? 16 : 8) : 0)); - if (Build.VERSION.SDK_INT >= 21) { - AndroidUtilities.runOnUIThread(new Runnable() { + if (Build.VERSION.SDK_INT >= 11) { + backDrawable.setAlpha(0); + } + if (Build.VERSION.SDK_INT >= 18) { + layoutCount = 2; + AndroidUtilities.runOnUIThread(startAnimationRunnable = new Runnable() { @Override public void run() { + if (startAnimationRunnable != this) { + return; + } + startAnimationRunnable = null; startOpenAnimation(); } - }); + }, 150); } else { startOpenAnimation(); } } + protected boolean canDismissWithSwipe() { + return true; + } + public void setCustomView(View view) { customView = view; } @@ -534,168 +666,70 @@ public class BottomSheet extends Dialog { applyBottomPadding = value; } - public void setDisableBackground(boolean value) { - disableBackground = value; - } - - protected void setRevealRadius(float radius) { - revealRadius = radius; - delegate.onRevealAnimationProgress(!dismissed, radius, revealX, revealY); - if (Build.VERSION.SDK_INT <= 19) { - containerView.invalidate(); + private void cancelSheetAnimation() { + if (currentSheetAnimation instanceof AnimatorSetProxy) { + ((AnimatorSetProxy) currentSheetAnimation).cancel(); + currentSheetAnimation = null; + } else if (Build.VERSION.SDK_INT >= 11 && currentSheetAnimation instanceof AnimatorSet) { + ((AnimatorSet) currentSheetAnimation).cancel(); + currentSheetAnimation = null; } } - protected float getRevealRadius() { - return revealRadius; - } - - @SuppressLint("NewApi") - private void startRevealAnimation(final boolean open) { - ViewProxy.setTranslationY(containerView, 0); - - final AnimatorSet animatorSet = new AnimatorSet(); - - View view = delegate.getRevealView(); - if (view.getVisibility() == View.VISIBLE && ((ViewGroup) view.getParent()).getVisibility() == View.VISIBLE) { - final int coords[] = new int[2]; - view.getLocationInWindow(coords); - float top; - if (Build.VERSION.SDK_INT <= 19) { - top = AndroidUtilities.displaySize.y - containerView.getMeasuredHeight() - AndroidUtilities.statusBarHeight; - } else { - top = containerView.getY(); - } - revealX = coords[0] + view.getMeasuredWidth() / 2; - revealY = (int) (coords[1] + view.getMeasuredHeight() / 2 - top); - if (Build.VERSION.SDK_INT <= 19) { - revealY -= AndroidUtilities.statusBarHeight; - } - } else { - revealX = AndroidUtilities.displaySize.x / 2 + backgroundPaddingLeft; - revealY = (int) (AndroidUtilities.displaySize.y - containerView.getY()); - } - - int corners[][] = new int[][]{ - {0, 0}, - {0, containerView.getMeasuredHeight()}, - {containerView.getMeasuredWidth(), 0}, - {containerView.getMeasuredWidth(), containerView.getMeasuredHeight()} - }; - int finalRevealRadius = 0; - for (int a = 0; a < 4; a++) { - finalRevealRadius = Math.max(finalRevealRadius, (int) Math.ceil(Math.sqrt((revealX - corners[a][0]) * (revealX - corners[a][0]) + (revealY - corners[a][1]) * (revealY - corners[a][1])))); - } - int finalRevealX = revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth(); - - ArrayList animators = new ArrayList<>(3); - animators.add(ObjectAnimator.ofFloat(this, "revealRadius", open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0)); - animators.add(ObjectAnimator.ofInt(backgroundDrawable, "alpha", open ? 51 : 0)); - if (Build.VERSION.SDK_INT >= 21) { - containerView.setElevation(AndroidUtilities.dp(10)); - try { - animators.add(ViewAnimationUtils.createCircularReveal(containerView, finalRevealX, revealY, open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0)); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - animatorSet.setDuration(300); - } else { - if (!open) { - animatorSet.setDuration(200); - containerView.setPivotX(revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth()); - containerView.setPivotY(revealY); - animators.add(ObjectAnimator.ofFloat(containerView, "scaleX", 0.0f)); - animators.add(ObjectAnimator.ofFloat(containerView, "scaleY", 0.0f)); - animators.add(ObjectAnimator.ofFloat(containerView, "alpha", 0.0f)); - } else { - animatorSet.setDuration(250); - containerView.setScaleX(1); - containerView.setScaleY(1); - containerView.setAlpha(1); - if (Build.VERSION.SDK_INT <= 19) { - animatorSet.setStartDelay(20); - } - } - } - animatorSet.playTogether(animators); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (delegate != null) { - delegate.onRevealAnimationStart(open); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - if (delegate != null) { - delegate.onRevealAnimationEnd(open); - } - containerView.invalidate(); - if (Build.VERSION.SDK_INT >= 11) { - containerView.setLayerType(View.LAYER_TYPE_NONE, null); - } - if (!open) { - containerView.setVisibility(View.INVISIBLE); - try { - BottomSheet.super.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - }); - animatorSet.start(); - } - private void startOpenAnimation() { - if (containerView.getMeasuredHeight() == 0) { - containerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, View.MeasureSpec.AT_MOST)); - } - backgroundDrawable.setAlpha(0); containerView.setVisibility(View.VISIBLE); - if (useRevealAnimation) { - if (Build.VERSION.SDK_INT >= 20) { - containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - startRevealAnimation(true); - } else { + + if (!onCustomOpenAnimation()) { if (Build.VERSION.SDK_INT >= 20) { container.setLayerType(View.LAYER_TYPE_HARDWARE, null); } ViewProxy.setTranslationY(containerView, containerView.getMeasuredHeight()); - backgroundDrawable.setAlpha(0); AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); - animatorSetProxy.playTogether( - ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0), - ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 51)); + if (Build.VERSION.SDK_INT < 11) { + animatorSetProxy.playTogether(ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0)); + } else { + animatorSetProxy.playTogether( + ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0), + ObjectAnimatorProxy.ofInt(backDrawable, "alpha", 51)); + } animatorSetProxy.setDuration(200); animatorSetProxy.setStartDelay(20); animatorSetProxy.setInterpolator(new DecelerateInterpolator()); animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animation) { - if (delegate != null) { - delegate.onOpenAnimationEnd(); + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + if (delegate != null) { + delegate.onOpenAnimationEnd(); + } + if (Build.VERSION.SDK_INT >= 11) { + container.setLayerType(View.LAYER_TYPE_NONE, null); + } } - if (Build.VERSION.SDK_INT >= 11) { - container.setLayerType(View.LAYER_TYPE_NONE, null); + } + + @Override + public void onAnimationCancel(Object animation) { + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; } } }); animatorSetProxy.start(); + currentSheetAnimation = animatorSetProxy; } } - public void setDelegate(BottomSheetDelegate delegate) { - this.delegate = delegate; + public void setDelegate(BottomSheetDelegateInterface bottomSheetDelegate) { + delegate = bottomSheetDelegate; } public FrameLayout getContainer() { return container; } - public LinearLayout getSheetContainer() { + public ViewGroup getSheetContainer() { return containerView; } @@ -711,59 +745,35 @@ public class BottomSheet extends Dialog { cell.textView.setText(text); } + public boolean isDismissed() { + return dismissed; + } + public void dismissWithButtonClick(final int item) { if (dismissed) { return; } dismissed = true; + cancelSheetAnimation(); AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); - animatorSetProxy.playTogether( - ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)), - ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0) - ); + if (Build.VERSION.SDK_INT < 11) { + animatorSetProxy.playTogether(ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10))); + } else { + animatorSetProxy.playTogether( + ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)), + ObjectAnimatorProxy.ofInt(backDrawable, "alpha", 0) + ); + } animatorSetProxy.setDuration(180); animatorSetProxy.setInterpolator(new AccelerateInterpolator()); animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animation) { - if (onClickListener != null) { - onClickListener.onClick(BottomSheet.this, item); - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - try { - BottomSheet.super.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + if (onClickListener != null) { + onClickListener.onClick(BottomSheet.this, item); } - }); - } - }); - animatorSetProxy.start(); - } - - @Override - public void dismiss() { - if (dismissed) { - return; - } - dismissed = true; - if (useRevealAnimation) { - backgroundDrawable.setAlpha(51); - startRevealAnimation(false); - } else { - AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); - animatorSetProxy.playTogether( - ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)), - ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0) - ); - animatorSetProxy.setDuration(180); - animatorSetProxy.setInterpolator(new AccelerateInterpolator()); - animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Object animation) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -775,11 +785,86 @@ public class BottomSheet extends Dialog { } }); } + } + + @Override + public void onAnimationCancel(Object animation) { + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + } + } + }); + animatorSetProxy.start(); + currentSheetAnimation = animatorSetProxy; + } + + @Override + public void dismiss() { + if (dismissed) { + return; + } + dismissed = true; + cancelSheetAnimation(); + if (!allowCustomAnimation || !onCustomCloseAnimation()) { + AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); + if (Build.VERSION.SDK_INT < 11) { + animatorSetProxy.playTogether(ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10))); + } else { + animatorSetProxy.playTogether( + ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)), + ObjectAnimatorProxy.ofInt(backDrawable, "alpha", 0) + ); + } + if (useFastDismiss) { + int height = containerView.getMeasuredHeight(); + animatorSetProxy.setDuration(Math.max(60, (int) (180 * (height - ViewProxy.getTranslationY(containerView)) / (float) height))); + useFastDismiss = false; + } else { + animatorSetProxy.setDuration(180); + } + animatorSetProxy.setInterpolator(new AccelerateInterpolator()); + animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + dismissInternal(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + } + } }); animatorSetProxy.start(); + currentSheetAnimation = animatorSetProxy; } } + protected void dismissInternal() { + super.dismiss(); + } + + protected boolean onCustomCloseAnimation() { + return false; + } + + protected boolean onCustomOpenAnimation() { + return false; + } + public static class Builder { private BottomSheet bottomSheet; @@ -829,23 +914,11 @@ public class BottomSheet extends Dialog { return this; } - public Builder setUseRevealAnimation() { - if (Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet()) { - bottomSheet.useRevealAnimation = true; - } - return this; - } - public Builder setDelegate(BottomSheetDelegate delegate) { bottomSheet.setDelegate(delegate); return this; } - public Builder setIsGrid(boolean value) { - bottomSheet.isGrid = value; - return this; - } - public Builder setApplyTopPadding(boolean value) { bottomSheet.applyTopPadding = value; return this; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java index f70a67e1a..7a7d21947 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java @@ -365,7 +365,6 @@ public class DrawerLayoutContainer extends FrameLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { inLayout = true; - FileLog.w("tmessages", "onLayout"); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java index cdda2c80c..82f219832 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java @@ -123,7 +123,7 @@ public class SimpleTextView extends View implements Drawable.Callback { } else if (layout.getLineLeft(0) == 0) { offsetX = width - textWidth; } else { - offsetX = 0; + offsetX = -AndroidUtilities.dp(8); } } } catch (Exception e) { @@ -134,6 +134,7 @@ public class SimpleTextView extends View implements Drawable.Callback { textWidth = 0; textHeight = 0; } + invalidate(); } @Override @@ -153,9 +154,7 @@ public class SimpleTextView extends View implements Drawable.Callback { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (changed) { - wasLayout = true; - } + wasLayout = true; } public int getTextWidth() { @@ -229,7 +228,6 @@ public class SimpleTextView extends View implements Drawable.Callback { private void recreateLayoutMaybe() { if (wasLayout) { createLayout(getMeasuredWidth()); - invalidate(); } else { requestLayout(); } @@ -276,6 +274,15 @@ public class SimpleTextView extends View implements Drawable.Callback { @Override public void invalidateDrawable(Drawable who) { - invalidate(); + if (who == leftDrawable) { + invalidate(leftDrawable.getBounds()); + } else if (who == rightDrawable) { + invalidate(rightDrawable.getBounds()); + } + } + + @Override + public boolean hasOverlappingRendering() { + return false; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index 4e69a1ecc..52e5dcf31 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -55,7 +55,12 @@ public class Theme { public static final int ACTION_BAR_VIOLET_SELECTOR_COLOR = 0xff735fbe; public static final int ACTION_BAR_YELLOW_SELECTOR_COLOR = 0xffef9f09; + public static final int ATTACH_SHEET_TEXT_COLOR = 0xff757575; + public static final int DIALOGS_MESSAGE_TEXT_COLOR = 0xff8f8f8f; + public static final int DIALOGS_NAME_TEXT_COLOR = 0xff4d83b3; + public static final int DIALOGS_ATTACH_TEXT_COLOR = 0xff4d83b3; + public static final int DIALOGS_PRINTING_TEXT_COLOR = 0xff4d83b3; public static final int CHAT_UNREAD_TEXT_COLOR = 0xff5695cc; public static final int CHAT_ADD_CONTACT_TEXT_COLOR = 0xff4a82b5; @@ -95,6 +100,7 @@ public class Theme { public static final int SECRET_CHAT_INFO_TEXT_COLOR = 0xffffffff; + public static final int MSG_SELECTED_BACKGROUND_COLOR = 0x6633b5e5; public static final int MSG_WEB_PREVIEW_DURATION_TEXT_COLOR = 0xffffffff; public static final int MSG_SECRET_TIME_TEXT_COLOR = 0xffe4e2e0; public static final int MSG_STICKER_NAME_TEXT_COLOR = 0xffffffff; @@ -331,15 +337,6 @@ public class Theme { geoInDrawable = context.getResources().getDrawable(R.drawable.location_b); geoOutDrawable = context.getResources().getDrawable(R.drawable.location_g); - attachButtonDrawables[0] = context.getResources().getDrawable(R.drawable.attach_camera_states); - attachButtonDrawables[1] = context.getResources().getDrawable(R.drawable.attach_gallery_states); - attachButtonDrawables[2] = context.getResources().getDrawable(R.drawable.attach_video_states); - attachButtonDrawables[3] = context.getResources().getDrawable(R.drawable.attach_audio_states); - attachButtonDrawables[4] = context.getResources().getDrawable(R.drawable.attach_file_states); - attachButtonDrawables[5] = context.getResources().getDrawable(R.drawable.attach_contact_states); - attachButtonDrawables[6] = context.getResources().getDrawable(R.drawable.attach_location_states); - attachButtonDrawables[7] = context.getResources().getDrawable(R.drawable.attach_hide_states); - cornerOuter[0] = context.getResources().getDrawable(R.drawable.corner_out_tl); cornerOuter[1] = context.getResources().getDrawable(R.drawable.corner_out_tr); cornerOuter[2] = context.getResources().getDrawable(R.drawable.corner_out_br); @@ -368,6 +365,19 @@ public class Theme { } } + public static void loadChatResources(Context context) { + if (attachButtonDrawables[0] == null) { + attachButtonDrawables[0] = context.getResources().getDrawable(R.drawable.attach_camera_states); + attachButtonDrawables[1] = context.getResources().getDrawable(R.drawable.attach_gallery_states); + attachButtonDrawables[2] = context.getResources().getDrawable(R.drawable.attach_video_states); + attachButtonDrawables[3] = context.getResources().getDrawable(R.drawable.attach_audio_states); + attachButtonDrawables[4] = context.getResources().getDrawable(R.drawable.attach_file_states); + attachButtonDrawables[5] = context.getResources().getDrawable(R.drawable.attach_contact_states); + attachButtonDrawables[6] = context.getResources().getDrawable(R.drawable.attach_location_states); + attachButtonDrawables[7] = context.getResources().getDrawable(R.drawable.attach_hide_states); + } + } + public static Drawable createBarSelectorDrawable(int color) { return createBarSelectorDrawable(color, true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java index c5c0912ea..9aa9b0fcb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java @@ -127,7 +127,7 @@ public abstract class BaseSearchAdapterRecycler extends RecyclerView.Adapter { }); } - public void addHashtagsFromMessage(String message) { + public void addHashtagsFromMessage(CharSequence message) { if (message == null) { return; } @@ -140,7 +140,7 @@ public abstract class BaseSearchAdapterRecycler extends RecyclerView.Adapter { if (message.charAt(start) != '@' && message.charAt(start) != '#') { start++; } - String hashtag = message.substring(start, end); + String hashtag = message.subSequence(start, end).toString(); if (hashtagsByText == null) { hashtagsByText = new HashMap<>(); hashtags = new ArrayList<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ChatActivityAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ChatActivityAdapter.java deleted file mode 100644 index f01a9184e..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ChatActivityAdapter.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -public class ChatActivityAdapter { - - /*private Context mContext; - - public ChatAdapter(Context context) { - mContext = context; - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { - return true; - } - - @Override - public int getCount() { - int count = messages.size(); - if (count != 0) { - if (!endReached) { - count++; - } - if (!forward_end_reached) { - count++; - } - } - return count; - } - - @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int offset = 1; - if ((!endReached || !forward_end_reached) && messages.size() != 0) { - if (!endReached) { - offset = 0; - } - if (i == 0 && !endReached || !forward_end_reached && i == (messages.size() + 1 - offset)) { - View progressBar = null; - if (view == null) { - LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = li.inflate(R.layout.chat_loading_layout, viewGroup, false); - progressBar = view.findViewById(R.id.progressLayout); - if (ApplicationLoader.isCustomTheme()) { - progressBar.setBackgroundResource(R.drawable.system_loader2); - } else { - progressBar.setBackgroundResource(R.drawable.system_loader1); - } - } else { - progressBar = view.findViewById(R.id.progressLayout); - } - progressBar.setVisibility(loadsCount > 1 ? View.VISIBLE : View.INVISIBLE); - - return view; - } - } - final MessageObject message = messages.get(messages.size() - i - offset); - int type = message.contentType; - if (view == null) { - if (type == 0) { - view = new ChatMessageCell(mContext); - } - if (type == 1) { - view = new ChatMediaCell(mContext); - } else if (type == 2) { - view = new ChatAudioCell(mContext); - } else if (type == 3) { - view = new ChatContactCell(mContext); - } else if (type == 6) { - LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = li.inflate(R.layout.chat_unread_layout, viewGroup, false); - } else if (type == 4) { - view = new ChatActionCell(mContext); - } - - if (view instanceof ChatBaseCell) { - ((ChatBaseCell) view).setDelegate(new ChatBaseCell.ChatBaseCellDelegate() { - @Override - public void didPressedUserAvatar(ChatBaseCell cell, TLRPC.User user) { - if (actionBar.isActionModeShowed()) { - processRowSelect(cell); - return; - } - if (user != null && user.id != UserConfig.getClientUserId()) { - Bundle args = new Bundle(); - args.putInt("user_id", user.id); - presentFragment(new ProfileActivity(args)); - } - } - - @Override - public void didPressedCancelSendButton(ChatBaseCell cell) { - MessageObject message = cell.getMessageObject(); - if (message.messageOwner.send_state != 0) { - SendMessagesHelper.getInstance().cancelSendingMessage(message); - } - } - - @Override - public void didLongPressed(ChatBaseCell cell) { - createMenu(cell, false); - } - - @Override - public boolean canPerformActions() { - return actionBar != null && !actionBar.isActionModeShowed(); - } - - @Override - public void didPressUrl(String url) { - if (url.startsWith("@")) { - openProfileWithUsername(url.substring(1)); - } else if (url.startsWith("#")) { - MessagesActivity fragment = new MessagesActivity(null); - fragment.setSearchString(url); - presentFragment(fragment); - } - } - - @Override - public void didPressReplyMessage(ChatBaseCell cell, int id) { - scrollToMessageId(id, cell.getMessageObject().getId(), true); - } - }); - if (view instanceof ChatMediaCell) { - ((ChatMediaCell) view).setAllowedToSetPhoto(openAnimationEnded); - ((ChatMediaCell) view).setMediaDelegate(new ChatMediaCell.ChatMediaCellDelegate() { - @Override - public void didClickedImage(ChatMediaCell cell) { - MessageObject message = cell.getMessageObject(); - if (message.isSendError()) { - createMenu(cell, false); - return; - } else if (message.isSending()) { - return; - } - if (message.type == 1) { - PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(message, ChatActivity.this); - } else if (message.type == 3) { - sendSecretMessageRead(message); - try { - File f = null; - if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { - f = new File(message.messageOwner.attachPath); - } - if (f == null || f != null && !f.exists()) { - f = FileLoader.getPathToMessage(message.messageOwner); - } - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.fromFile(f), "video/mp4"); - getParentActivity().startActivityForResult(intent, 500); - } catch (Exception e) { - alertUserOpenError(message); - } - } else if (message.type == 4) { - if (!isGoogleMapsInstalled()) { - return; - } - LocationActivity fragment = new LocationActivity(); - fragment.setMessageObject(message); - presentFragment(fragment); - } else if (message.type == 9) { - File f = null; - String fileName = message.getFileName(); - if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { - f = new File(message.messageOwner.attachPath); - } - if (f == null || f != null && !f.exists()) { - f = FileLoader.getPathToMessage(message.messageOwner); - } - if (f != null && f.exists()) { - String realMimeType = null; - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - if (message.type == 8 || message.type == 9) { - MimeTypeMap myMime = MimeTypeMap.getSingleton(); - int idx = fileName.lastIndexOf('.'); - if (idx != -1) { - String ext = fileName.substring(idx + 1); - realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); - if (realMimeType == null) { - realMimeType = message.messageOwner.media.document.mime_type; - if (realMimeType == null || realMimeType.length() == 0) { - realMimeType = null; - } - } - if (realMimeType != null) { - intent.setDataAndType(Uri.fromFile(f), realMimeType); - } else { - intent.setDataAndType(Uri.fromFile(f), "text/plain"); - } - } else { - intent.setDataAndType(Uri.fromFile(f), "text/plain"); - } - } - if (realMimeType != null) { - try { - getParentActivity().startActivityForResult(intent, 500); - } catch (Exception e) { - intent.setDataAndType(Uri.fromFile(f), "text/plain"); - getParentActivity().startActivityForResult(intent, 500); - } - } else { - getParentActivity().startActivityForResult(intent, 500); - } - } catch (Exception e) { - alertUserOpenError(message); - } - } - } - } - - @Override - public void didPressedOther(ChatMediaCell cell) { - createMenu(cell, true); - } - }); - } else if (view instanceof ChatContactCell) { - ((ChatContactCell) view).setContactDelegate(new ChatContactCell.ChatContactCellDelegate() { - @Override - public void didClickAddButton(ChatContactCell cell, TLRPC.User user) { - if (actionBar.isActionModeShowed()) { - processRowSelect(cell); - return; - } - MessageObject messageObject = cell.getMessageObject(); - Bundle args = new Bundle(); - args.putInt("user_id", messageObject.messageOwner.media.user_id); - args.putString("phone", messageObject.messageOwner.media.phone_number); - args.putBoolean("addContact", true); - presentFragment(new ContactAddActivity(args)); - } - - @Override - public void didClickPhone(ChatContactCell cell) { - if (actionBar.isActionModeShowed()) { - processRowSelect(cell); - return; - } - final MessageObject messageObject = cell.getMessageObject(); - if (getParentActivity() == null || messageObject.messageOwner.media.phone_number == null || messageObject.messageOwner.media.phone_number.length() == 0) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setItems(new CharSequence[]{LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("Call", R.string.Call)}, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 1) { - try { - Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + messageObject.messageOwner.media.phone_number)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getParentActivity().startActivityForResult(intent, 500); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else if (i == 0) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(messageObject.messageOwner.media.phone_number); - } else { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", messageObject.messageOwner.media.phone_number); - clipboard.setPrimaryClip(clip); - } - } - } - } - ); - showDialog(builder.create()); - } - }); - } - } else if (view instanceof ChatActionCell) { - ((ChatActionCell) view).setDelegate(new ChatActionCell.ChatActionCellDelegate() { - @Override - public void didClickedImage(ChatActionCell cell) { - MessageObject message = cell.getMessageObject(); - PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(message, ChatActivity.this); - } - - @Override - public void didLongPressed(ChatActionCell cell) { - createMenu(cell, false); - } - - @Override - public void needOpenUserProfile(int uid) { - if (uid != UserConfig.getClientUserId()) { - Bundle args = new Bundle(); - args.putInt("user_id", uid); - presentFragment(new ProfileActivity(args)); - } - } - }); - } - } - - boolean selected = false; - boolean disableSelection = false; - if (actionBar.isActionModeShowed()) { - if (selectedMessagesIds.containsKey(message.getId())) { - view.setBackgroundColor(0x6633b5e5); - selected = true; - } else { - view.setBackgroundColor(0); - } - disableSelection = true; - } else { - view.setBackgroundColor(0); - } - - if (view instanceof ChatBaseCell) { - ChatBaseCell baseCell = (ChatBaseCell) view; - baseCell.isChat = currentChat != null; - baseCell.setMessageObject(message); - baseCell.setCheckPressed(!disableSelection, disableSelection && selected); - if (view instanceof ChatAudioCell && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_AUDIO)) { - ((ChatAudioCell) view).downloadAudioIfNeed(); - } - baseCell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && message.getId() == highlightMessageId); - } else if (view instanceof ChatActionCell) { - ChatActionCell actionCell = (ChatActionCell) view; - actionCell.setMessageObject(message); - } - if (type == 6) { - TextView messageTextView = (TextView) view.findViewById(R.id.chat_message_text); - messageTextView.setText(LocaleController.formatPluralString("NewMessages", unread_to_load)); - } - - return view; - } - - @Override - public int getItemViewType(int i) { - int offset = 1; - if (!endReached && messages.size() != 0) { - offset = 0; - if (i == 0) { - return 5; - } - } - if (!forward_end_reached && i == (messages.size() + 1 - offset)) { - return 5; - } - MessageObject message = messages.get(messages.size() - i - offset); - return message.contentType; - } - - @Override - public int getViewTypeCount() { - return 7; - } - - @Override - public boolean isEmpty() { - int count = messages.size(); - if (count != 0) { - if (!endReached) { - count++; - } - if (!forward_end_reached) { - count++; - } - } - return count == 0; - }*/ -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index 5f738ff28..c98cf45ae 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -10,6 +10,7 @@ package org.telegram.ui.Adapters; import android.content.Context; import android.text.TextUtils; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,8 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.query.SearchQuery; +import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; @@ -33,8 +36,10 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.DialogCell; import org.telegram.ui.Cells.GreySectionCell; import org.telegram.ui.Cells.HashtagSearchCell; +import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.Collections; @@ -55,7 +60,7 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { private String lastSearchText; private int reqId = 0; private int lastReqId; - private MessagesActivitySearchAdapterDelegate delegate; + private DialogsSearchAdapterDelegate delegate; private int needMessagesSearch; private boolean messagesSearchEndReached; private String lastMessagesSearchString; @@ -84,8 +89,58 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { long did; } - public interface MessagesActivitySearchAdapterDelegate { + public interface DialogsSearchAdapterDelegate { void searchStateChanged(boolean searching); + void didPressedOnSubDialog(int did); + void needRemoveHint(int did); + } + + private class CategoryAdapterRecycler extends RecyclerView.Adapter { + + public void setIndex(int value) { + notifyDataSetChanged(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = new HintDialogCell(mContext); + view.setLayoutParams(new RecyclerView.LayoutParams(AndroidUtilities.dp(80), AndroidUtilities.dp(100))); + return new Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + HintDialogCell cell = (HintDialogCell) holder.itemView; + + TLRPC.TL_topPeer peer = SearchQuery.hints.get(position); + TLRPC.Dialog dialog = new TLRPC.Dialog(); + TLRPC.Chat chat = null; + TLRPC.User user = null; + int did = 0; + if (peer.peer.user_id != 0) { + did = peer.peer.user_id; + user = MessagesController.getInstance().getUser(peer.peer.user_id); + } else if (peer.peer.channel_id != 0) { + did = -peer.peer.channel_id; + chat = MessagesController.getInstance().getChat(peer.peer.channel_id); + } else if (peer.peer.chat_id != 0) { + did = -peer.peer.chat_id; + chat = MessagesController.getInstance().getChat(peer.peer.chat_id); + } + cell.setTag(did); + String name = ""; + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + } else if (chat != null) { + name = chat.title; + } + cell.setDialog(did, false, name); + } + + @Override + public int getItemCount() { + return SearchQuery.hints.size(); + } } public DialogsSearchAdapter(Context context, int messagesSearch, int type) { @@ -93,9 +148,10 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { needMessagesSearch = messagesSearch; dialogsType = type; loadRecentSearch(); + SearchQuery.loadHints(true); } - public void setDelegate(MessagesActivitySearchAdapterDelegate delegate) { + public void setDelegate(DialogsSearchAdapterDelegate delegate) { this.delegate = delegate; } @@ -189,11 +245,11 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } public boolean hasRecentRearch() { - return !recentSearchObjects.isEmpty(); + return !recentSearchObjects.isEmpty() || !SearchQuery.hints.isEmpty(); } public boolean isRecentSearchDisplayed() { - return needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty(); + return needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && (!recentSearchObjects.isEmpty() || !SearchQuery.hints.isEmpty()); } public void loadRecentSearch() { @@ -320,6 +376,8 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { }); } + + public void putRecentSearch(final long did, TLObject object) { RecentSearchObject recentSearchObject = recentSearchObjectsById.get(did); if (recentSearchObject == null) { @@ -467,9 +525,10 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { found = 2; } if (found != 0) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); DialogSearchResult dialogSearchResult = dialogsResult.get((long) user.id); if (user.status != null) { user.status.expires = cursor.intValue(1); @@ -482,7 +541,6 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { dialogSearchResult.object = user; resultCount++; } - data.reuse(); break; } } @@ -500,9 +558,10 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (!(chat == null || chat.deactivated || ChatObject.isChannel(chat) && ChatObject.isNotInChat(chat))) { long dialog_id; if (chat.id > 0) { @@ -516,7 +575,6 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { resultCount++; } } - data.reuse(); break; } } @@ -539,7 +597,8 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { username = name.substring(usernamePos + 2); } int found = 0; - for (String q : search) { + for (int a = 0; a < search.length; a++) { + String q = search[a]; if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (username != null && username.startsWith(q)) { @@ -547,12 +606,20 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } if (found != 0) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(6)); - if (data != null && cursor.byteBufferValue(0, data) != 0 && cursor.byteBufferValue(6, data2) != 0) { - TLRPC.EncryptedChat chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false); + TLRPC.EncryptedChat chat = null; + TLRPC.User user = null; + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + } + data = cursor.byteBufferValue(6); + if (data != null) { + user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + } + if (chat != null && user != null) { DialogSearchResult dialogSearchResult = dialogsResult.get((long) chat.id << 32); - chat.user_id = cursor.intValue(2); chat.a_or_b = cursor.byteArrayValue(3); chat.auth_key = cursor.byteArrayValue(4); @@ -569,7 +636,6 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { chat.future_auth_key = cursor.byteArrayValue(15); chat.key_hash = cursor.byteArrayValue(16); - TLRPC.User user = TLRPC.User.TLdeserialize(data2, data2.readInt32(false), false); if (user.status != null) { user.status.expires = cursor.intValue(7); } @@ -582,8 +648,6 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { encUsers.add(user); resultCount++; } - data.reuse(); - data2.reuse(); break; } } @@ -644,9 +708,10 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { found = 2; } if (found != 0) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (user.status != null) { user.status.expires = cursor.intValue(1); } @@ -657,7 +722,6 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } resultArray.add(user); } - data.reuse(); break; } } @@ -680,7 +744,8 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { if (searchId != lastSearchId) { return; } - for (TLObject obj : result) { + for (int a = 0; a < result.size(); a++) { + TLObject obj = result.get(a); if (obj instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) obj; MessagesController.getInstance().putUser(user, true); @@ -692,9 +757,7 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { MessagesController.getInstance().putEncryptedChat(chat, true); } } - for (TLRPC.User user : encUsers) { - MessagesController.getInstance().putUser(user, true); - } + MessagesController.getInstance().putUsers(encUsers, true); searchResult = result; searchResultNames = names; notifyDataSetChanged(); @@ -801,8 +864,8 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { @Override public int getItemCount() { - if (needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty()) { - return recentSearchObjects.size() + 1; + if (isRecentSearchDisplayed()) { + return (!recentSearchObjects.isEmpty() ? recentSearchObjects.size() + 1 : 0) + (!SearchQuery.hints.isEmpty() ? 2 : 0); } if (!searchResultHashtags.isEmpty()) { return searchResultHashtags.size() + 1; @@ -820,9 +883,10 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } public Object getItem(int i) { - if (needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty()) { - if (i > 0 && i - 1 < recentSearchObjects.size()) { - TLObject object = recentSearchObjects.get(i - 1).object; + if (isRecentSearchDisplayed()) { + int offset = (!SearchQuery.hints.isEmpty() ? 2 : 0); + if (i > offset && i - 1 - offset < recentSearchObjects.size()) { + TLObject object = recentSearchObjects.get(i - 1 - offset).object; if (object instanceof TLRPC.User) { TLRPC.User user = MessagesController.getInstance().getUser(((TLRPC.User) object).id); if (user != null) { @@ -884,8 +948,52 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { case 4: view = new HashtagSearchCell(mContext); break; + case 5: + RecyclerListView horizontalListView = new RecyclerListView(mContext) { + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + if (getParent() != null && getParent().getParent() != null) { + getParent().getParent().requestDisallowInterceptTouchEvent(true); + } + return super.onInterceptTouchEvent(e); + } + }; + horizontalListView.setItemAnimator(null); + horizontalListView.setLayoutAnimation(null); + LinearLayoutManager layoutManager = new LinearLayoutManager(mContext) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }; + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + horizontalListView.setLayoutManager(layoutManager); + //horizontalListView.setDisallowInterceptTouchEvents(true); + horizontalListView.setAdapter(new CategoryAdapterRecycler()); + horizontalListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (delegate != null) { + delegate.didPressedOnSubDialog((Integer) view.getTag()); + } + } + }); + horizontalListView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { + @Override + public boolean onItemClick(View view, int position) { + if (delegate != null) { + delegate.needRemoveHint((Integer) view.getTag()); + } + return true; + } + }); + view = horizontalListView; + } + if (viewType == 5) { + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(100))); + } else { + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); } - view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } @@ -918,7 +1026,7 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { user = MessagesController.getInstance().getUser(encryptedChat.user_id); } - if (needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty()) { + if (isRecentSearchDisplayed()) { isRecent = true; cell.useSeparator = position != getItemCount() - 1; } else { @@ -952,8 +1060,13 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { } case 1: { GreySectionCell cell = (GreySectionCell) holder.itemView; - if (needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty()) { - cell.setText(LocaleController.getString("Recent", R.string.Recent).toUpperCase()); + if (isRecentSearchDisplayed()) { + int offset = (!SearchQuery.hints.isEmpty() ? 2 : 0); + if (position < offset) { + cell.setText(LocaleController.getString("ChatHints", R.string.ChatHints).toUpperCase()); + } else { + cell.setText(LocaleController.getString("Recent", R.string.Recent).toUpperCase()); + } } else if (!searchResultHashtags.isEmpty()) { cell.setText(LocaleController.getString("Hashtags", R.string.Hashtags).toUpperCase()); } else if (!globalSearch.isEmpty() && position == searchResult.size()) { @@ -979,13 +1092,26 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { cell.setNeedDivider(position != searchResultHashtags.size()); break; } + case 5: { + RecyclerListView recyclerListView = (RecyclerListView) holder.itemView; + ((CategoryAdapterRecycler) recyclerListView.getAdapter()).setIndex(position / 2); + break; + } } } @Override public int getItemViewType(int i) { - if (needMessagesSearch != 2 && (lastSearchText == null || lastSearchText.length() == 0) && !recentSearchObjects.isEmpty()) { - return i == 0 ? 1 : 0; + if (isRecentSearchDisplayed()) { + int offset = (!SearchQuery.hints.isEmpty() ? 2 : 0); + if (i <= offset) { + if (i == offset || i % 2 == 0) { + return 1; + } else { + return 5; + } + } + return 0; } if (!searchResultHashtags.isEmpty()) { return i == 0 ? 1 : 4; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java index dc0e10b6b..185fa140e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -20,11 +20,8 @@ import android.os.Build; import android.view.View; import android.view.ViewGroup; -import org.telegram.SQLite.SQLiteCursor; -import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -32,7 +29,7 @@ import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserObject; -import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; @@ -66,7 +63,6 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { private Context mContext; private long dialog_id; private TLRPC.ChatFull info; - private ArrayList botRecent; private ArrayList searchResultUsernames; private ArrayList searchResultHashtags; private ArrayList searchResultCommands; @@ -78,6 +74,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { private MentionsAdapterDelegate delegate; private HashMap botInfo; private int resultStartPosition; + private boolean allowNewMentions = true; private int resultLength; private String lastText; private int lastPosition; @@ -86,8 +83,6 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { private boolean needBotContext = true; private boolean isDarkTheme; private int botsCount; - private boolean loadingBotRecent; - private boolean botRecentLoaded; private String searchingContextUsername; private String searchingContextQuery; @@ -123,10 +118,10 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { } }; - public MentionsAdapter(Context context, boolean isDarkTheme, long did, MentionsAdapterDelegate delegate) { + public MentionsAdapter(Context context, boolean darkTheme, long did, MentionsAdapterDelegate mentionsAdapterDelegate) { mContext = context; - this.delegate = delegate; - this.isDarkTheme = isDarkTheme; + delegate = mentionsAdapterDelegate; + isDarkTheme = darkTheme; dialog_id = did; } @@ -152,6 +147,10 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { noUserName = false; } + public void setAllowNewMentions(boolean value) { + allowNewMentions = value; + } + public void setParentFragment(BaseFragment fragment) { parentFragment = fragment; } @@ -163,136 +162,12 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { } } - private void loadBotRecent() { - if (loadingBotRecent || botRecentLoaded) { - return; - } - loadingBotRecent = true; - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT id FROM bot_recent ORDER BY date DESC"); - ArrayList uids = null; - while (cursor.next()) { - if (uids == null) { - uids = new ArrayList<>(); - } - uids.add(cursor.intValue(0)); - } - cursor.dispose(); - if (uids != null) { - final ArrayList uidsFinal = uids; - final ArrayList users = MessagesStorage.getInstance().getUsers(uids); - Collections.sort(users, new Comparator() { - @Override - public int compare(TLRPC.User lhs, TLRPC.User rhs) { - int idx1 = uidsFinal.indexOf(lhs.id); - int idx2 = uidsFinal.indexOf(rhs.id); - if (idx1 > idx2) { - return 1; - } else if (idx1 < idx2) { - return -1; - } - return 0; - } - }); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - botRecent = users; - loadingBotRecent = false; - botRecentLoaded = true; - if (lastText != null) { - searchUsernameOrHashtag(lastText, lastPosition, messages); - } - } - }); - } else { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - loadingBotRecent = false; - botRecentLoaded = true; - } - }); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void addRecentBot() { - if (foundContextBot == null) { - return; - } - if (botRecent == null) { - botRecent = new ArrayList<>(); - } else { - for (int a = 0; a < botRecent.size(); a++) { - TLRPC.User user = botRecent.get(a); - if (user.id == foundContextBot.id) { - botRecent.remove(a); - break; - } - } - } - botRecent.add(0, foundContextBot); - final int uid = foundContextBot.id; - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO bot_recent VALUES(?, ?)"); - state.requery(); - state.bindInteger(1, uid); - state.bindInteger(2, (int) (System.currentTimeMillis() / 1000)); - state.step(); - state.dispose(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void removeRecentBot(final TLRPC.User bot) { - if (botRecent == null || bot == null) { - return; - } - for (int a = 0; a < botRecent.size(); a++) { - TLRPC.User user = botRecent.get(a); - if (user.id == bot.id) { - botRecent.remove(a); - if (botRecent.isEmpty()) { - botRecent = null; - } - break; - } - } - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM bot_recent WHERE id = " + bot.id).stepThis().dispose(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - public void setNeedUsernames(boolean value) { needUsernames = value; } public void setNeedBotContext(boolean value) { needBotContext = value; - if (needBotContext) { - loadBotRecent(); - } } public void setBotInfo(HashMap info) { @@ -498,10 +373,6 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { } } - public int getOrientation() { - return searchResultBotContext != null && !searchResultBotContext.isEmpty() && contextMedia ? LinearLayoutManager.HORIZONTAL : LinearLayoutManager.VERTICAL; - } - public String getBotCaption() { if (foundContextBot != null) { return foundContextBot.bot_inline_placeholder; @@ -593,7 +464,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { searchResultCommandsHelp = null; searchResultCommandsUsers = null; if (added) { - boolean hasTop = getOrientation() == LinearLayoutManager.VERTICAL && searchResultBotContextSwitch != null; + boolean hasTop = searchResultBotContextSwitch != null; notifyItemChanged(searchResultBotContext.size() - res.results.size() + (hasTop ? 1 : 0) - 1); notifyItemRangeInserted(searchResultBotContext.size() - res.results.size() + (hasTop ? 1 : 0), res.results.size()); } else { @@ -657,32 +528,23 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { char ch = text.charAt(a); if (a == 0 || text.charAt(a - 1) == ' ' || text.charAt(a - 1) == '\n') { if (ch == '@') { - if (needUsernames || needBotContext && botRecent != null && a == 0) { + if (needUsernames || needBotContext && a == 0) { if (hasIllegalUsernameCharacters) { delegate.needChangePanelVisibility(false); return; } - if (info == null && (botRecent == null || a != 0)) { + if (info == null && a != 0) { lastText = text; lastPosition = position; messages = messageObjects; delegate.needChangePanelVisibility(false); return; } - if (loadingBotRecent) { - lastText = text; - lastPosition = position; - messages = messageObjects; - } dogPostion = a; foundType = 0; resultStartPosition = a; resultLength = result.length() + 1; break; - } else if (loadingBotRecent) { - lastText = text; - lastPosition = position; - messages = messageObjects; } } else if (ch == '#') { if (!hashtagsLoadedFromDb) { @@ -725,12 +587,20 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { String usernameString = result.toString().toLowerCase(); ArrayList newResult = new ArrayList<>(); final HashMap newResultsHashMap = new HashMap<>(); - if (needBotContext && dogPostion == 0 && botRecent != null) { - for (int a = 0; a < botRecent.size(); a++) { - TLRPC.User user = botRecent.get(a); + if (needBotContext && dogPostion == 0 && !SearchQuery.inlineBots.isEmpty()) { + int count = 0; + for (int a = 0; a < SearchQuery.inlineBots.size(); a++) { + TLRPC.User user = MessagesController.getInstance().getUser(SearchQuery.inlineBots.get(a).peer.user_id); + if (user == null) { + continue; + } if (user.username != null && user.username.length() > 0 && (usernameString.length() > 0 && user.username.toLowerCase().startsWith(usernameString) || usernameString.length() == 0)) { newResult.add(user); newResultsHashMap.put(user.id, user); + count++; + } + if (count == 5) { + break; } } } @@ -741,8 +611,23 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { if (user == null || UserObject.isUserSelf(user) || newResultsHashMap.containsKey(user.id)) { continue; } - if (user.username != null && user.username.length() > 0 && (usernameString.length() > 0 && user.username.toLowerCase().startsWith(usernameString) || usernameString.length() == 0)) { - newResult.add(user); + if (usernameString.length() == 0) { + if (!user.deleted && (allowNewMentions || !allowNewMentions && user.username != null && user.username.length() != 0)) { + newResult.add(user); + } + } else { + if (user.username != null && user.username.length() > 0 && user.username.toLowerCase().startsWith(usernameString)) { + newResult.add(user); + } else { + if (!allowNewMentions && (user.username == null || user.username.length() == 0)) { + continue; + } + if (user.first_name != null && user.first_name.length() > 0 && user.first_name.toLowerCase().startsWith(usernameString)) { + newResult.add(user); + } else if (user.last_name != null && user.last_name.length() > 0 && user.last_name.toLowerCase().startsWith(usernameString)) { + newResult.add(user); + } + } } } } @@ -828,7 +713,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { @Override public int getItemCount() { if (searchResultBotContext != null) { - return searchResultBotContext.size() + (getOrientation() == LinearLayoutManager.VERTICAL && searchResultBotContextSwitch != null ? 1 : 0); + return searchResultBotContext.size() + (searchResultBotContextSwitch != null ? 1 : 0); } else if (searchResultUsernames != null) { return searchResultUsernames.size(); } else if (searchResultHashtags != null) { @@ -842,7 +727,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { @Override public int getItemViewType(int position) { if (searchResultBotContext != null) { - if (position == 0 && getOrientation() == LinearLayoutManager.VERTICAL && searchResultBotContextSwitch != null) { + if (position == 0 && searchResultBotContextSwitch != null) { return 2; } return 1; @@ -853,8 +738,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { public Object getItem(int i) { if (searchResultBotContext != null) { - boolean hasTop = getOrientation() == LinearLayoutManager.VERTICAL && searchResultBotContextSwitch != null; - if (hasTop) { + if (searchResultBotContextSwitch != null) { if (i == 0) { return searchResultBotContextSwitch; } else { @@ -903,6 +787,10 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { return searchResultBotContext != null; } + public boolean isMediaLayout() { + return contextMedia; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; @@ -926,7 +814,7 @@ public class MentionsAdapter extends BaseSearchAdapterRecycler { @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (searchResultBotContext != null) { - boolean hasTop = getOrientation() == LinearLayoutManager.VERTICAL && searchResultBotContextSwitch != null; + boolean hasTop = searchResultBotContextSwitch != null; if (holder.getItemViewType() == 2) { if (hasTop) { ((BotSwitchCell) holder.itemView).setText(searchResultBotContextSwitch.text); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java index 22f692da3..18e466c32 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java @@ -8,12 +8,15 @@ package org.telegram.ui.Adapters; +import android.app.Activity; import android.content.Context; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import org.telegram.messenger.FileLog; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.Utilities; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.FileLoader; @@ -22,6 +25,8 @@ import org.telegram.ui.Cells.StickerCell; import java.io.File; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; public class StickersAdapter extends RecyclerView.Adapter implements NotificationCenter.NotificationCenterDelegate { @@ -32,6 +37,8 @@ public class StickersAdapter extends RecyclerView.Adapter implements Notificatio private StickersAdapterDelegate delegate; private String lastSticker; private boolean visible; + private ArrayList newRecentStickers = new ArrayList<>(); + private long recentLoadDate; public interface StickersAdapterDelegate { void needChangePanelVisibility(boolean show); @@ -110,7 +117,42 @@ public class StickersAdapter extends RecyclerView.Adapter implements Notificatio visible = false; } } else { - stickers = newStickers; + stickers = newStickers != null && !newStickers.isEmpty() ? new ArrayList<>(newStickers) : null; + if (stickers != null) { + if (Math.abs(recentLoadDate - System.currentTimeMillis()) > 10 * 1000) { + recentLoadDate = System.currentTimeMillis(); + try { + String str = mContext.getSharedPreferences("emoji", Activity.MODE_PRIVATE).getString("stickers2", ""); + String[] args = str.split(","); + for (int a = 0; a < args.length; a++) { + if (args[a].length() == 0) { + continue; + } + long id = Utilities.parseLong(args[a]); + if (id != 0) { + newRecentStickers.add(id); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + if (!newRecentStickers.isEmpty()) { + Collections.sort(stickers, new Comparator() { + @Override + public int compare(TLRPC.Document lhs, TLRPC.Document rhs) { + int idx1 = newRecentStickers.indexOf(lhs.id); + int idx2 = newRecentStickers.indexOf(rhs.id); + if (idx1 > idx2) { + return -1; + } else if (idx1 < idx2) { + return 1; + } + return 0; + } + }); + } + } checkStickerFilesExistAndDownload(); delegate.needChangePanelVisibility(stickers != null && !stickers.isEmpty() && stickersToLoad.isEmpty()); notifyDataSetChanged(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index ccd898b41..032203b8d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -410,14 +410,14 @@ public class CacheControlActivity extends BaseFragment { SQLiteCursor cursor2 = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did + " AND mid IN (" + last_mid_i + "," + last_mid + ")"); try { while (cursor2.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor2.byteArrayLength(0)); - if (data != null && cursor2.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor2.byteBufferValue(0); + if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (message != null) { arrayList.add(message); } } - data.reuse(); } } catch (Exception e) { FileLog.e("tmessages", e); @@ -518,7 +518,7 @@ public class CacheControlActivity extends BaseFragment { clear[a] = false; } } - BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 2); + BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); cell.setBackgroundResource(R.drawable.list_selector); cell.setTextAndIcon(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache).toUpperCase(), 0); cell.setTextColor(0xffcd5a5a); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java deleted file mode 100644 index a3cf79580..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java +++ /dev/null @@ -1,1234 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Cells; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.text.Layout; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.ClickableSpan; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ContactsController; -import org.telegram.messenger.Emoji; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.MediaController; -import org.telegram.messenger.UserObject; -import org.telegram.messenger.FileLoader; -import org.telegram.messenger.FileLog; -import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.MessagesController; -import org.telegram.messenger.R; -import org.telegram.messenger.MessageObject; -import org.telegram.messenger.ImageReceiver; -import org.telegram.ui.Components.AvatarDrawable; -import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Components.TypefaceSpan; - -public class ChatBaseCell extends BaseCell implements MediaController.FileDownloadProgressListener { - - public interface ChatBaseCellDelegate { - void didPressedUserAvatar(ChatBaseCell cell, TLRPC.User user); - void didPressedViaBot(ChatBaseCell cell, String username); - void didPressedChannelAvatar(ChatBaseCell cell, TLRPC.Chat chat, int postId); - void didPressedCancelSendButton(ChatBaseCell cell); - void didLongPressed(ChatBaseCell cell); - void didPressedReplyMessage(ChatBaseCell cell, int id); - void didPressedUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); - void needOpenWebView(String url, String title, String description, String originalUrl, int w, int h); - void didPressedImage(ChatBaseCell cell); - void didPressedShare(ChatBaseCell cell); - void didPressedOther(ChatBaseCell cell); - void didPressedBotButton(ChatBaseCell cell, TLRPC.KeyboardButton button); - boolean needPlayAudio(MessageObject messageObject); - boolean canPerformActions(); - } - - private int TAG; - - public boolean isChat; - protected boolean isPressed; - protected boolean forwardName; - protected boolean isHighlighted; - protected boolean mediaBackground; - protected boolean isCheckPressed = true; - private boolean wasLayout; - protected boolean isAvatarVisible; - protected boolean drawBackground = true; - protected int substractBackgroundHeight; - protected boolean allowAssistant; - protected Drawable currentBackgroundDrawable; - protected MessageObject currentMessageObject; - private int viaWidth; - private int viaNameWidth; - protected int availableTimeWidth; - - protected static TextPaint timePaint; - private static TextPaint namePaint; - private static TextPaint forwardNamePaint; - protected static TextPaint replyNamePaint; - protected static TextPaint replyTextPaint; - protected static Paint replyLinePaint; - - protected int backgroundWidth = 100; - - protected int layoutWidth; - protected int layoutHeight; - - private ImageReceiver avatarImage; - private AvatarDrawable avatarDrawable; - private boolean avatarPressed; - private boolean forwardNamePressed; - private boolean forwardBotPressed; - - private StaticLayout replyNameLayout; - private StaticLayout replyTextLayout; - private ImageReceiver replyImageReceiver; - private int replyStartX; - private int replyStartY; - protected int replyNameWidth; - private float replyNameOffset; - protected int replyTextWidth; - private float replyTextOffset; - private boolean needReplyImage; - private boolean replyPressed; - private TLRPC.FileLocation currentReplyPhoto; - - protected boolean drawShareButton; - private boolean sharePressed; - private int shareStartX; - private int shareStartY; - - private StaticLayout nameLayout; - protected int nameWidth; - private float nameOffsetX; - private float nameX; - private float nameY; - protected boolean drawName; - protected boolean drawNameLayout; - - private StaticLayout[] forwardedNameLayout = new StaticLayout[2]; - protected int forwardedNameWidth; - protected boolean drawForwardedName; - private int forwardNameX; - private int forwardNameY; - private float forwardNameOffsetX[] = new float[2]; - - private StaticLayout timeLayout; - protected int timeWidth; - private int timeTextWidth; - private int timeX; - private String currentTimeString; - protected boolean drawTime = true; - - private StaticLayout viewsLayout; - private int viewsTextWidth; - private String currentViewsString; - - private TLRPC.User currentUser; - private TLRPC.Chat currentChat; - private TLRPC.FileLocation currentPhoto; - private String currentNameString; - - private TLRPC.User currentForwardUser; - private TLRPC.User currentViaBotUser; - private TLRPC.Chat currentForwardChannel; - private String currentForwardNameString; - - protected ChatBaseCellDelegate delegate; - - protected int namesOffset; - - private int lastSendState; - private int lastDeleteDate; - private int lastViewsCount; - - public ChatBaseCell(Context context) { - super(context); - if (timePaint == null) { - timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - timePaint.setTextSize(AndroidUtilities.dp(12)); - - namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - namePaint.setTextSize(AndroidUtilities.dp(14)); - - forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - forwardNamePaint.setTextSize(AndroidUtilities.dp(14)); - - replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - replyNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - replyNamePaint.setTextSize(AndroidUtilities.dp(14)); - - replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - replyTextPaint.setTextSize(AndroidUtilities.dp(14)); - replyTextPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - - replyLinePaint = new Paint(); - } - avatarImage = new ImageReceiver(this); - avatarImage.setRoundRadius(AndroidUtilities.dp(21)); - avatarDrawable = new AvatarDrawable(); - replyImageReceiver = new ImageReceiver(this); - TAG = MediaController.getInstance().generateObserverTag(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - avatarImage.onDetachedFromWindow(); - replyImageReceiver.onDetachedFromWindow(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - avatarImage.onAttachedToWindow(); - replyImageReceiver.onAttachedToWindow(); - } - - @Override - public void setPressed(boolean pressed) { - super.setPressed(pressed); - invalidate(); - } - - public void setDelegate(ChatBaseCellDelegate delegate) { - this.delegate = delegate; - } - - public void setHighlighted(boolean value) { - if (isHighlighted == value) { - return; - } - isHighlighted = value; - invalidate(); - } - - public void setCheckPressed(boolean value, boolean pressed) { - isCheckPressed = value; - isPressed = pressed; - invalidate(); - } - - public void setAllowAssistant(boolean value) { - allowAssistant = value; - } - - protected boolean isUserDataChanged() { - if (currentMessageObject == null || currentUser == null && currentChat == null) { - return false; - } - if (lastSendState != currentMessageObject.messageOwner.send_state) { - return true; - } - if (lastDeleteDate != currentMessageObject.messageOwner.destroyTime) { - return true; - } - if (lastViewsCount != currentMessageObject.messageOwner.views) { - return true; - } - - TLRPC.User newUser = null; - TLRPC.Chat newChat = null; - if (currentMessageObject.isFromUser()) { - newUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.from_id < 0) { - newChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.post) { - newChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); - } - TLRPC.FileLocation newPhoto = null; - - if (isAvatarVisible) { - if (newUser != null && newUser.photo != null){ - newPhoto = newUser.photo.photo_small; - } else if (newChat != null && newChat.photo != null) { - newPhoto = newChat.photo.photo_small; - } - } - - if (replyTextLayout == null && currentMessageObject.replyMessageObject != null) { - return true; - } - - if (currentPhoto == null && newPhoto != null || currentPhoto != null && newPhoto == null || currentPhoto != null && newPhoto != null && (currentPhoto.local_id != newPhoto.local_id || currentPhoto.volume_id != newPhoto.volume_id)) { - return true; - } - - TLRPC.FileLocation newReplyPhoto = null; - - if (currentMessageObject.replyMessageObject != null) { - TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(currentMessageObject.replyMessageObject.photoThumbs, 80); - if (photoSize != null && currentMessageObject.replyMessageObject.type != 13) { - newReplyPhoto = photoSize.location; - } - } - - if (currentReplyPhoto == null && newReplyPhoto != null) { - return true; - } - - String newNameString = null; - if (drawName && isChat && !currentMessageObject.isOutOwner()) { - if (newUser != null) { - newNameString = UserObject.getUserName(newUser); - } else if (newChat != null) { - newNameString = newChat.title; - } - } - - if (currentNameString == null && newNameString != null || currentNameString != null && newNameString == null || currentNameString != null && newNameString != null && !currentNameString.equals(newNameString)) { - return true; - } - - if (drawForwardedName) { - newNameString = currentMessageObject.getForwardedName(); - return currentForwardNameString == null && newNameString != null || currentForwardNameString != null && newNameString == null || currentForwardNameString != null && newNameString != null && !currentForwardNameString.equals(newNameString); - } - return false; - } - - protected void measureTime(MessageObject messageObject) { - boolean hasSign = !messageObject.isOutOwner() && messageObject.messageOwner.from_id > 0 && messageObject.messageOwner.post; - TLRPC.User signUser = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); - if (hasSign && signUser == null) { - hasSign = false; - } - if (hasSign) { - currentTimeString = ", " + LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); - } else { - currentTimeString = LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); - } - timeTextWidth = timeWidth = (int) Math.ceil(timePaint.measureText(currentTimeString)); - if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - currentViewsString = String.format("%s", LocaleController.formatShortNumber(Math.max(1, messageObject.messageOwner.views), null)); - viewsTextWidth = (int) Math.ceil(timePaint.measureText(currentViewsString)); - timeWidth += viewsTextWidth + Theme.viewsCountDrawable[0].getIntrinsicWidth() + AndroidUtilities.dp(10); - } - if (hasSign) { - if (availableTimeWidth == 0) { - availableTimeWidth = AndroidUtilities.dp(1000); - } - CharSequence name = ContactsController.formatName(signUser.first_name, signUser.last_name).replace('\n', ' '); - int widthForSign = availableTimeWidth - timeWidth; - int width = (int) Math.ceil(timePaint.measureText(name, 0, name.length())); - if (width > widthForSign) { - name = TextUtils.ellipsize(name, timePaint, widthForSign, TextUtils.TruncateAt.END); - width = widthForSign; - } - currentTimeString = name + currentTimeString; - timeTextWidth += width; - timeWidth += width; - } - } - - protected boolean checkNeedDrawShareButton(MessageObject messageObject) { - if (messageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); - if (user != null && user.bot && messageObject.type != 13 && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty || messageObject.messageOwner.media == null - || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && !(messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage))) { - return true; - } - } else if (messageObject.messageOwner.from_id < 0 || messageObject.messageOwner.post) { - if (messageObject.messageOwner.to_id.channel_id != 0 && (messageObject.messageOwner.via_bot_id == 0 && messageObject.messageOwner.reply_to_msg_id == 0 || messageObject.type != 13)) { - return true; - } - } - return false; - } - - public void setMessageObject(MessageObject messageObject) { - currentMessageObject = messageObject; - lastSendState = messageObject.messageOwner.send_state; - lastDeleteDate = messageObject.messageOwner.destroyTime; - lastViewsCount = messageObject.messageOwner.views; - isPressed = false; - isCheckPressed = true; - isAvatarVisible = false; - wasLayout = false; - drawShareButton = checkNeedDrawShareButton(messageObject); - replyNameLayout = null; - replyTextLayout = null; - replyNameWidth = 0; - replyTextWidth = 0; - viaWidth = 0; - viaNameWidth = 0; - currentReplyPhoto = null; - currentUser = null; - currentChat = null; - currentViaBotUser = null; - drawNameLayout = false; - - if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - if (currentMessageObject.isContentUnread() && !currentMessageObject.isOut()) { - MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, false); - currentMessageObject.setContentIsRead(); - } else if (!currentMessageObject.viewsReloaded) { - MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, true); - currentMessageObject.viewsReloaded = true; - } - } - - if (currentMessageObject.isFromUser()) { - currentUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.from_id < 0) { - currentChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); - } else if (currentMessageObject.messageOwner.post) { - currentChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); - } - - if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - isAvatarVisible = true; - if (currentUser != null) { - if (currentUser.photo != null) { - currentPhoto = currentUser.photo.photo_small; - } else { - currentPhoto = null; - } - avatarDrawable.setInfo(currentUser); - } else if (currentChat != null) { - if (currentChat.photo != null) { - currentPhoto = currentChat.photo.photo_small; - } else { - currentPhoto = null; - } - avatarDrawable.setInfo(currentChat); - } else { - currentPhoto = null; - avatarDrawable.setInfo(messageObject.messageOwner.from_id, null, null, false); - } - avatarImage.setImage(currentPhoto, "50_50", avatarDrawable, null, false); - } - - - measureTime(messageObject); - - namesOffset = 0; - - String viaUsername = null; - CharSequence viaString = null; - if (messageObject.messageOwner.via_bot_id != 0) { - TLRPC.User botUser = MessagesController.getInstance().getUser(messageObject.messageOwner.via_bot_id); - if (botUser != null && botUser.username != null && botUser.username.length() > 0) { - viaUsername = "@" + botUser.username; - viaString = AndroidUtilities.replaceTags(String.format(" via %s", viaUsername)); - viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); - currentViaBotUser = botUser; - } - } else if (messageObject.messageOwner.via_bot_name != null && messageObject.messageOwner.via_bot_name.length() > 0) { - viaUsername = "@" + messageObject.messageOwner.via_bot_name; - viaString = AndroidUtilities.replaceTags(String.format(" via %s", viaUsername)); - viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); - } - - boolean authorName = drawName && isChat && !currentMessageObject.isOutOwner(); - boolean viaBot = (messageObject.messageOwner.fwd_from == null || messageObject.type == 14) && viaUsername != null; - if (authorName || viaBot) { - drawNameLayout = true; - nameWidth = getMaxNameWidth(); - if (nameWidth < 0) { - nameWidth = AndroidUtilities.dp(100); - } - - if (authorName) { - if (currentUser != null) { - currentNameString = UserObject.getUserName(currentUser); - } else if (currentChat != null) { - currentNameString = currentChat.title; - } else { - currentNameString = "DELETED"; - } - } else { - currentNameString = ""; - } - CharSequence nameStringFinal = TextUtils.ellipsize(currentNameString.replace('\n', ' '), namePaint, nameWidth - (viaBot ? viaWidth : 0), TextUtils.TruncateAt.END); - if (viaBot) { - viaNameWidth = (int) Math.ceil(namePaint.measureText(nameStringFinal, 0, nameStringFinal.length())); - if (viaNameWidth != 0) { - viaNameWidth += AndroidUtilities.dp(4); - } - int color; - if (currentMessageObject.type == 13) { - color = Theme.MSG_STICKER_VIA_BOT_NAME_TEXT_COLOR; - } else { - color = currentMessageObject.isOutOwner() ? Theme.MSG_OUT_VIA_BOT_NAME_TEXT_COLOR : Theme.MSG_IN_VIA_BOT_NAME_TEXT_COLOR; - } - if (currentNameString.length() > 0) { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s via %s", nameStringFinal, viaUsername)); - stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), nameStringFinal.length() + 1, nameStringFinal.length() + 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 5, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - nameStringFinal = stringBuilder; - } else { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("via %s", viaUsername)); - stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), 4, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - nameStringFinal = stringBuilder; - } - nameStringFinal = TextUtils.ellipsize(nameStringFinal, namePaint, nameWidth, TextUtils.TruncateAt.END); - } - try { - nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (nameLayout != null && nameLayout.getLineCount() > 0) { - nameWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); - if (messageObject.type != 13) { - namesOffset += AndroidUtilities.dp(19); - } - nameOffsetX = nameLayout.getLineLeft(0); - } else { - nameWidth = 0; - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - if (currentNameString.length() == 0) { - currentNameString = null; - } - } else { - currentNameString = null; - nameLayout = null; - nameWidth = 0; - } - - currentForwardUser = null; - currentForwardNameString = null; - forwardedNameLayout[0] = null; - forwardedNameLayout[1] = null; - forwardedNameWidth = 0; - if (drawForwardedName && messageObject.isForwarded()) { - currentForwardChannel = null; - if (messageObject.messageOwner.fwd_from.channel_id != 0) { - currentForwardChannel = MessagesController.getInstance().getChat(messageObject.messageOwner.fwd_from.channel_id); - } - if (messageObject.messageOwner.fwd_from.from_id != 0) { - currentForwardUser = MessagesController.getInstance().getUser(messageObject.messageOwner.fwd_from.from_id); - } - - if (currentForwardUser != null || currentForwardChannel != null) { - if (currentForwardChannel != null) { - if (currentForwardUser != null) { - currentForwardNameString = String.format("%s (%s)", currentForwardChannel.title, UserObject.getUserName(currentForwardUser)); - } else { - currentForwardNameString = currentForwardChannel.title; - } - } else if (currentForwardUser != null) { - currentForwardNameString = UserObject.getUserName(currentForwardUser); - } - - forwardedNameWidth = getMaxNameWidth(); - int fromWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " ")); - CharSequence name = TextUtils.ellipsize(currentForwardNameString.replace('\n', ' '), replyNamePaint, forwardedNameWidth - fromWidth - viaWidth, TextUtils.TruncateAt.END); - CharSequence lastLine; - if (viaString != null) { - viaNameWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " " + name)); - lastLine = AndroidUtilities.replaceTags(String.format("%s %s via %s", LocaleController.getString("From", R.string.From), name, viaUsername)); - } else { - lastLine = AndroidUtilities.replaceTags(String.format("%s %s", LocaleController.getString("From", R.string.From), name)); - } - lastLine = TextUtils.ellipsize(lastLine, forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); - try { - forwardedNameLayout[1] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - lastLine = TextUtils.ellipsize(AndroidUtilities.replaceTags(LocaleController.getString("ForwardedMessage", R.string.ForwardedMessage)), forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); - forwardedNameLayout[0] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - forwardedNameWidth = Math.max((int) Math.ceil(forwardedNameLayout[0].getLineWidth(0)), (int) Math.ceil(forwardedNameLayout[1].getLineWidth(0))); - forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0); - forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0); - namesOffset += AndroidUtilities.dp(36); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - - if (messageObject.isReply()) { - namesOffset += AndroidUtilities.dp(42); - if (messageObject.type != 0) { - if (messageObject.type == 13) { - namesOffset -= AndroidUtilities.dp(42); - } else { - namesOffset += AndroidUtilities.dp(5); - } - } - - int maxWidth = getMaxNameWidth(); - if (messageObject.type != 13) { - maxWidth -= AndroidUtilities.dp(10); - } - - CharSequence stringFinalName = null; - CharSequence stringFinalText = null; - if (messageObject.replyMessageObject != null) { - TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs, 80); - if (photoSize == null || messageObject.replyMessageObject.type == 13 || messageObject.type == 13 && !AndroidUtilities.isTablet() || messageObject.replyMessageObject.isSecretMedia()) { - replyImageReceiver.setImageBitmap((Drawable) null); - needReplyImage = false; - } else { - currentReplyPhoto = photoSize.location; - replyImageReceiver.setImage(photoSize.location, "50_50", null, null, true); - needReplyImage = true; - maxWidth -= AndroidUtilities.dp(44); - } - - String name = null; - if (messageObject.replyMessageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(messageObject.replyMessageObject.messageOwner.from_id); - if (user != null) { - name = UserObject.getUserName(user); - } - } else if (messageObject.replyMessageObject.messageOwner.from_id < 0) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-messageObject.replyMessageObject.messageOwner.from_id); - if (chat != null) { - name = chat.title; - } - } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.replyMessageObject.messageOwner.to_id.channel_id); - if (chat != null) { - name = chat.title; - } - } - - if (name != null) { - stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), replyNamePaint, maxWidth, TextUtils.TruncateAt.END); - } - if (messageObject.replyMessageObject.messageText != null && messageObject.replyMessageObject.messageText.length() > 0) { - String mess = messageObject.replyMessageObject.messageText.toString(); - if (mess.length() > 150) { - mess = mess.substring(0, 150); - } - mess = mess.replace('\n', ' '); - stringFinalText = Emoji.replaceEmoji(mess, replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); - stringFinalText = TextUtils.ellipsize(stringFinalText, replyTextPaint, maxWidth, TextUtils.TruncateAt.END); - } - } - if (stringFinalName == null) { - stringFinalName = LocaleController.getString("Loading", R.string.Loading); - } - try { - replyNameLayout = new StaticLayout(stringFinalName, replyNamePaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (replyNameLayout.getLineCount() > 0) { - replyNameWidth = (int)Math.ceil(replyNameLayout.getLineWidth(0)) + AndroidUtilities.dp(12 + (needReplyImage ? 44 : 0)); - replyNameOffset = replyNameLayout.getLineLeft(0); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - try { - if (stringFinalText != null) { - replyTextLayout = new StaticLayout(stringFinalText, replyTextPaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (replyTextLayout.getLineCount() > 0) { - replyTextWidth = (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(12 + (needReplyImage ? 44 : 0)); - replyTextOffset = replyTextLayout.getLineLeft(0); - } - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - - requestLayout(); - } - - public final MessageObject getMessageObject() { - return currentMessageObject; - } - - protected int getMaxNameWidth() { - return backgroundWidth - AndroidUtilities.dp(mediaBackground ? 22 : 31); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = false; - float x = event.getX(); - float y = event.getY(); - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (delegate == null || delegate.canPerformActions()) { - if (isAvatarVisible && avatarImage.isInsideImage(x, y)) { - avatarPressed = true; - result = true; - } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32)) { - if (viaWidth != 0 && x >= forwardNameX + viaNameWidth + AndroidUtilities.dp(4)) { - forwardBotPressed = true; - } else { - forwardNamePressed = true; - } - result = true; - } else if (drawNameLayout && nameLayout != null && viaWidth != 0 && x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - AndroidUtilities.dp(4) && y <= nameY + AndroidUtilities.dp(20)) { - forwardBotPressed = true; - result = true; - } else if (currentMessageObject.isReply() && x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35)) { - replyPressed = true; - result = true; - } else if (drawShareButton && x >= shareStartX && x <= shareStartX + AndroidUtilities.dp(40) && y >= shareStartY && y <= shareStartY + AndroidUtilities.dp(32)) { - sharePressed = true; - result = true; - invalidate(); - } - if (result) { - startCheckLongPress(); - } - } - } else { - if (event.getAction() != MotionEvent.ACTION_MOVE) { - cancelCheckLongPress(); - } - if (avatarPressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - avatarPressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (delegate != null) { - if (currentUser != null) { - delegate.didPressedUserAvatar(this, currentUser); - } else if (currentChat != null) { - delegate.didPressedChannelAvatar(this, currentChat, 0); - } - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - avatarPressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (isAvatarVisible && !avatarImage.isInsideImage(x, y)) { - avatarPressed = false; - } - } - } else if (forwardNamePressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - forwardNamePressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (delegate != null) { - if (currentForwardChannel != null) { - delegate.didPressedChannelAvatar(this, currentForwardChannel, currentMessageObject.messageOwner.fwd_from.channel_post); - } else if (currentForwardUser != null) { - delegate.didPressedUserAvatar(this, currentForwardUser); - } - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - forwardNamePressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { - forwardNamePressed = false; - } - } - } else if (forwardBotPressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - forwardBotPressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (delegate != null) { - delegate.didPressedViaBot(this, currentViaBotUser != null ? currentViaBotUser.username : currentMessageObject.messageOwner.via_bot_name); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - forwardBotPressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (drawForwardedName && forwardedNameLayout[0] != null) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { - forwardBotPressed = false; - } - } else { - if (!(x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - AndroidUtilities.dp(4) && y <= nameY + AndroidUtilities.dp(20))) { - forwardBotPressed = false; - } - } - } - } else if (replyPressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - replyPressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (delegate != null) { - delegate.didPressedReplyMessage(this, currentMessageObject.messageOwner.reply_to_msg_id); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - replyPressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35))) { - replyPressed = false; - } - } - } else if (sharePressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - sharePressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (delegate != null) { - delegate.didPressedShare(this); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - sharePressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= shareStartX && x <= shareStartX + AndroidUtilities.dp(40) && y >= shareStartY && y <= shareStartY + AndroidUtilities.dp(32))) { - sharePressed = false; - } - } - invalidate(); - } - } - return result; - } - - @SuppressLint("DrawAllocation") - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (currentMessageObject == null) { - super.onLayout(changed, left, top, right, bottom); - return; - } - - if (changed || !wasLayout) { - layoutWidth = getMeasuredWidth(); - layoutHeight = getMeasuredHeight() - substractBackgroundHeight; - if (timeTextWidth < 0) { - timeTextWidth = AndroidUtilities.dp(10); - } - timeLayout = new StaticLayout(currentTimeString, timePaint, timeTextWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (!mediaBackground) { - if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - AndroidUtilities.dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(48) : 0); - } else { - timeX = layoutWidth - timeWidth - AndroidUtilities.dp(38.5f); - } - } else { - if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - AndroidUtilities.dp(4) - timeWidth + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(48) : 0); - } else { - timeX = layoutWidth - timeWidth - AndroidUtilities.dp(42.0f); - } - } - - if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - viewsLayout = new StaticLayout(currentViewsString, timePaint, viewsTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - } else { - viewsLayout = null; - } - - if (isAvatarVisible) { - avatarImage.setImageCoords(AndroidUtilities.dp(6), layoutHeight - AndroidUtilities.dp(44), AndroidUtilities.dp(42), AndroidUtilities.dp(42)); - } - - wasLayout = true; - } - } - - protected void drawContent(Canvas canvas) { - - } - - public ImageReceiver getPhotoImage() { - return null; - } - - @Override - protected void onLongPress() { - if (delegate != null) { - delegate.didLongPressed(this); - } - } - - protected boolean isDrawSelectedBackground() { - return isPressed() && isCheckPressed || !isCheckPressed && isPressed || isHighlighted; - } - - @Override - protected void onDraw(Canvas canvas) { - if (currentMessageObject == null) { - return; - } - - if (!wasLayout) { - requestLayout(); - return; - } - - if (isAvatarVisible) { - avatarImage.draw(canvas); - } - - if (mediaBackground) { - timePaint.setColor(Theme.MSG_MEDIA_TIME_TEXT_COLOR); - } else { - if (currentMessageObject.isOutOwner()) { - timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_TIME_SELECTED_TEXT_COLOR : Theme.MSG_OUT_TIME_TEXT_COLOR); - } else { - timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_TIME_SELECTED_TEXT_COLOR : Theme.MSG_IN_TIME_TEXT_COLOR); - } - } - - if (currentMessageObject.isOutOwner()) { - if (isDrawSelectedBackground()) { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableOutSelected; - } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableOutSelected; - } - } else { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableOut; - } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableOut; - } - } - setDrawableBounds(currentBackgroundDrawable, layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)), layoutHeight - AndroidUtilities.dp(2)); - } else { - if (isDrawSelectedBackground()) { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableInSelected; - } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableInSelected; - } - } else { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableIn; - } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableIn; - } - } - if (isChat && currentMessageObject.isFromUser()) { - setDrawableBounds(currentBackgroundDrawable, AndroidUtilities.dp(48 + (!mediaBackground ? 3 : 9)), AndroidUtilities.dp(1), backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)), layoutHeight - AndroidUtilities.dp(2)); - } else { - setDrawableBounds(currentBackgroundDrawable, (!mediaBackground ? AndroidUtilities.dp(3) : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)), layoutHeight - AndroidUtilities.dp(2)); - } - } - if (drawBackground && currentBackgroundDrawable != null) { - currentBackgroundDrawable.draw(canvas); - } - - drawContent(canvas); - - if (drawShareButton) { - Theme.shareDrawable.setColorFilter(sharePressed ? Theme.colorPressedFilter : Theme.colorFilter); - setDrawableBounds(Theme.shareDrawable, shareStartX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(8), shareStartY = layoutHeight - AndroidUtilities.dp(41)); - Theme.shareDrawable.draw(canvas); - setDrawableBounds(Theme.shareIconDrawable, shareStartX + AndroidUtilities.dp(9), shareStartY + AndroidUtilities.dp(9)); - Theme.shareIconDrawable.draw(canvas); - } - - if (drawNameLayout && nameLayout != null) { - canvas.save(); - - if (currentMessageObject.type == 13) { - namePaint.setColor(Theme.MSG_STICKER_NAME_TEXT_COLOR); - int backWidth; - if (currentMessageObject.isOutOwner()) { - nameX = AndroidUtilities.dp(28); - } else { - nameX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(22); - } - nameY = layoutHeight - AndroidUtilities.dp(38); - Theme.systemDrawable.setColorFilter(Theme.colorFilter); - Theme.systemDrawable.setBounds((int) nameX - AndroidUtilities.dp(12), (int) nameY - AndroidUtilities.dp(5), (int) nameX + AndroidUtilities.dp(12) + nameWidth, (int) nameY + AndroidUtilities.dp(22)); - Theme.systemDrawable.draw(canvas); - } else { - if (mediaBackground || currentMessageObject.isOutOwner()) { - nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11) - nameOffsetX; - } else { - nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(17) - nameOffsetX; - } - if (currentUser != null) { - namePaint.setColor(AvatarDrawable.getNameColorForId(currentUser.id)); - } else if (currentChat != null) { - namePaint.setColor(AvatarDrawable.getNameColorForId(currentChat.id)); - } else { - namePaint.setColor(AvatarDrawable.getNameColorForId(0)); - } - nameY = AndroidUtilities.dp(10); - } - canvas.translate(nameX, nameY); - nameLayout.draw(canvas); - canvas.restore(); - } - - if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null) { - forwardNameY = AndroidUtilities.dp(10 + (drawNameLayout ? 19 : 0)); - if (currentMessageObject.isOutOwner()) { - forwardNamePaint.setColor(Theme.MSG_OUT_FORDWARDED_NAME_TEXT_COLOR); - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); - } else { - forwardNamePaint.setColor(Theme.MSG_IN_FORDWARDED_NAME_TEXT_COLOR); - if (mediaBackground) { - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); - } else { - forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(17); - } - } - for (int a = 0; a < 2; a++) { - canvas.save(); - canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + AndroidUtilities.dp(16) * a); - forwardedNameLayout[a].draw(canvas); - canvas.restore(); - } - } - - if (currentMessageObject.isReply()) { - if (currentMessageObject.type == 13) { - replyLinePaint.setColor(Theme.MSG_STICKER_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_STICKER_REPLY_NAME_TEXT_COLOR); - replyTextPaint.setColor(Theme.MSG_STICKER_REPLY_MESSAGE_TEXT_COLOR); - if (currentMessageObject.isOutOwner()) { - replyStartX = AndroidUtilities.dp(23); - } else { - replyStartX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(17); - } - replyStartY = layoutHeight - AndroidUtilities.dp(58); - if (nameLayout != null) { - replyStartY -= AndroidUtilities.dp(25 + 6); - } - int backWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14 + (needReplyImage ? 44 : 0)); - Theme.systemDrawable.setColorFilter(Theme.colorFilter); - Theme.systemDrawable.setBounds(replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(41)); - Theme.systemDrawable.draw(canvas); - } else { - if (currentMessageObject.isOutOwner()) { - replyLinePaint.setColor(Theme.MSG_OUT_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_OUT_REPLY_NAME_TEXT_COLOR); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0) { - replyTextPaint.setColor(Theme.MSG_OUT_REPLY_MESSAGE_TEXT_COLOR); - } else { - replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_TEXT_COLOR); - } - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); - } else { - replyLinePaint.setColor(Theme.MSG_IN_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_IN_REPLY_NAME_TEXT_COLOR); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0) { - replyTextPaint.setColor(Theme.MSG_IN_REPLY_MESSAGE_TEXT_COLOR); - } else { - replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_IN_REPLY_MEDIA_MESSAGE_TEXT_COLOR); - } - if (mediaBackground) { - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); - } else { - replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(18); - } - } - replyStartY = AndroidUtilities.dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); - } - canvas.drawRect(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35), replyLinePaint); - if (needReplyImage) { - replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); - replyImageReceiver.draw(canvas); - } - - if (replyNameLayout != null) { - canvas.save(); - canvas.translate(replyStartX - replyNameOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY); - replyNameLayout.draw(canvas); - canvas.restore(); - } - if (replyTextLayout != null) { - canvas.save(); - canvas.translate(replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY + AndroidUtilities.dp(19)); - replyTextLayout.draw(canvas); - canvas.restore(); - } - } - - if (drawTime || !mediaBackground) { - if (mediaBackground) { - Drawable drawable; - if (currentMessageObject.type == 13) { - drawable = Theme.timeStickerBackgroundDrawable; - } else { - drawable = Theme.timeBackgroundDrawable; - } - setDrawableBounds(drawable, timeX - AndroidUtilities.dp(4), layoutHeight - AndroidUtilities.dp(27), timeWidth + AndroidUtilities.dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), AndroidUtilities.dp(17)); - drawable.draw(canvas); - - int additionalX = 0; - if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - additionalX = (int) (timeWidth - timeLayout.getLineWidth(0)); - - if (currentMessageObject.isSending()) { - if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.clockMediaDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(13.0f) - Theme.clockMediaDrawable.getIntrinsicHeight()); - Theme.clockMediaDrawable.draw(canvas); - } - } else if (currentMessageObject.isSendError()) { - if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.errorDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); - } - } else { - Drawable countDrawable = Theme.viewsMediaCountDrawable; - setDrawableBounds(countDrawable, timeX, layoutHeight - AndroidUtilities.dp(9.5f) - timeLayout.getHeight()); - countDrawable.draw(canvas); - - if (viewsLayout != null) { - canvas.save(); - canvas.translate(timeX + countDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(11.3f) - timeLayout.getHeight()); - viewsLayout.draw(canvas); - canvas.restore(); - } - } - } - - canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(11.3f) - timeLayout.getHeight()); - timeLayout.draw(canvas); - canvas.restore(); - } else { - int additionalX = 0; - if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - additionalX = (int) (timeWidth - timeLayout.getLineWidth(0)); - - if (currentMessageObject.isSending()) { - if (!currentMessageObject.isOutOwner()) { - Drawable clockDrawable = Theme.clockChannelDrawable[isDrawSelectedBackground() ? 1 : 0]; - setDrawableBounds(clockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(8.5f) - clockDrawable.getIntrinsicHeight()); - clockDrawable.draw(canvas); - } - } else if (currentMessageObject.isSendError()) { - if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.errorDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(6.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); - } - } else { - if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0], timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); - Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0].draw(canvas); - } else { - setDrawableBounds(Theme.viewsOutCountDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); - Theme.viewsOutCountDrawable.draw(canvas); - } - - if (viewsLayout != null) { - canvas.save(); - canvas.translate(timeX + Theme.viewsOutCountDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); - viewsLayout.draw(canvas); - canvas.restore(); - } - } - } - - canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); - timeLayout.draw(canvas); - canvas.restore(); - //canvas.drawRect(timeX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight(), timeX + availableTimeWidth, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight(), timePaint); - } - - if (currentMessageObject.isOutOwner()) { - boolean drawCheck1 = false; - boolean drawCheck2 = false; - boolean drawClock = false; - boolean drawError = false; - boolean isBroadcast = (int)(currentMessageObject.getDialogId() >> 32) == 1; - - if (currentMessageObject.isSending()) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = true; - drawError = false; - } else if (currentMessageObject.isSendError()) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = false; - drawError = true; - } else if (currentMessageObject.isSent()) { - if (!currentMessageObject.isUnread()) { - drawCheck1 = true; - drawCheck2 = true; - } else { - drawCheck1 = false; - drawCheck2 = true; - } - drawClock = false; - drawError = false; - } - - if (drawClock) { - if (!mediaBackground) { - setDrawableBounds(Theme.clockDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - Theme.clockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.5f) - Theme.clockDrawable.getIntrinsicHeight()); - Theme.clockDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.clockMediaDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.clockMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.clockMediaDrawable.getIntrinsicHeight()); - Theme.clockMediaDrawable.draw(canvas); - } - } - if (isBroadcast) { - if (drawCheck1 || drawCheck2) { - if (!mediaBackground) { - setDrawableBounds(Theme.broadcastDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - Theme.broadcastDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.broadcastDrawable.getIntrinsicHeight()); - Theme.broadcastDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.broadcastMediaDrawable, layoutWidth - AndroidUtilities.dp(24.0f) - Theme.broadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.0f) - Theme.broadcastMediaDrawable.getIntrinsicHeight()); - Theme.broadcastMediaDrawable.draw(canvas); - } - } - } else { - if (drawCheck2) { - if (!mediaBackground) { - if (drawCheck1) { - setDrawableBounds(Theme.checkDrawable, layoutWidth - AndroidUtilities.dp(22.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); - } else { - setDrawableBounds(Theme.checkDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); - } - Theme.checkDrawable.draw(canvas); - } else { - if (drawCheck1) { - setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); - } else { - setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); - } - Theme.checkMediaDrawable.draw(canvas); - } - } - if (drawCheck1) { - if (!mediaBackground) { - setDrawableBounds(Theme.halfCheckDrawable, layoutWidth - AndroidUtilities.dp(18) - Theme.halfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.halfCheckDrawable.getIntrinsicHeight()); - Theme.halfCheckDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.halfCheckMediaDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.halfCheckMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.halfCheckMediaDrawable.getIntrinsicHeight()); - Theme.halfCheckMediaDrawable.draw(canvas); - } - } - } - if (drawError) { - if (!mediaBackground) { - setDrawableBounds(Theme.errorDrawable, layoutWidth - AndroidUtilities.dp(18) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(7) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); - } else { - setDrawableBounds(Theme.errorDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(11.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); - } - } - } - } - } - - @Override - public void onFailedDownload(String fileName) { - - } - - @Override - public void onSuccessDownload(String fileName) { - - } - - @Override - public void onProgressDownload(String fileName, float progress) { - - } - - @Override - public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { - - } - - @Override - public int getObserverTag() { - return TAG; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index 16253ff43..f59a1621e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -8,17 +8,20 @@ package org.telegram.ui.Cells; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; @@ -42,6 +45,7 @@ import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; +import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.ConnectionsManager; @@ -53,6 +57,7 @@ import org.telegram.ui.Components.SeekBar; import org.telegram.ui.Components.SeekBarWaveform; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.PhotoViewer; @@ -62,7 +67,26 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; -public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDelegate, ImageReceiver.ImageReceiverDelegate { +import static org.telegram.messenger.AndroidUtilities.*; + +public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate, ImageReceiver.ImageReceiverDelegate, MediaController.FileDownloadProgressListener { + + public interface ChatMessageCellDelegate { + void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user); + void didPressedViaBot(ChatMessageCell cell, String username); + void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int postId); + void didPressedCancelSendButton(ChatMessageCell cell); + void didLongPressed(ChatMessageCell cell); + void didPressedReplyMessage(ChatMessageCell cell, int id); + void didPressedUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); + void needOpenWebView(String url, String title, String description, String originalUrl, int w, int h); + void didPressedImage(ChatMessageCell cell); + void didPressedShare(ChatMessageCell cell); + void didPressedOther(ChatMessageCell cell); + void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button); + boolean needPlayAudio(MessageObject messageObject); + boolean canPerformActions(); + } private final static int DOCUMENT_ATTACH_TYPE_NONE = 0; private final static int DOCUMENT_ATTACH_TYPE_DOCUMENT = 1; @@ -77,7 +101,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private int y; private int width; private int height; - private StaticLayout caption; + private StaticLayout title; private TLRPC.KeyboardButton button; private int angle; private float progressAlpha; @@ -99,7 +123,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private RadialProgress radialProgress; private ImageReceiver photoImage; - private AvatarDrawable avatarDrawable; + private AvatarDrawable contactAvatarDrawable; private boolean disallowLongPress; @@ -116,25 +140,25 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private int descriptionX; private int titleX; private int authorX; - private StaticLayout siteCaptionLayout; + private StaticLayout siteNameLayout; private StaticLayout titleLayout; private StaticLayout descriptionLayout; - private StaticLayout durationLayout; + private StaticLayout videoInfoLayout; private StaticLayout authorLayout; + private StaticLayout docTitleLayout; + private int docTitleOffsetX; + private StaticLayout captionLayout; private int captionX; private int captionY; private int captionHeight; - private int nameOffsetX; private StaticLayout infoLayout; private int infoWidth; private String currentUrl; - private boolean allowedToSetPhoto = true; - private int buttonX; private int buttonY; private int buttonState; @@ -167,7 +191,6 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private boolean linkPreviewPressed; private ArrayList urlPathCache = new ArrayList<>(); private ArrayList urlPath = new ArrayList<>(); - //private LinkPath urlPath = new LinkPath(); private boolean useSeekBarWaweform; private SeekBar seekBar; @@ -175,7 +198,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private int seekBarX; private int seekBarY; - private StaticLayout timeLayout; + private StaticLayout durationLayout; private String lastTimeString; private int timeWidthAudio; private int timeAudioX; @@ -198,25 +221,115 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele private int widthForButtons; private int pressedBotButton; + // + private int TAG; + + public boolean isChat; + private boolean isPressed; + private boolean forwardName; + private boolean isHighlighted; + private boolean mediaBackground; + private boolean isCheckPressed = true; + private boolean wasLayout; + private boolean isAvatarVisible; + private boolean drawBackground = true; + private int substractBackgroundHeight; + private boolean allowAssistant; + private Drawable currentBackgroundDrawable; + private MessageObject currentMessageObject; + private int viaWidth; + private int viaNameWidth; + private int availableTimeWidth; + + private static TextPaint timePaint; + private static TextPaint namePaint; + private static TextPaint forwardNamePaint; + private static TextPaint replyNamePaint; + private static TextPaint replyTextPaint; + private static Paint replyLinePaint; + + private int backgroundWidth = 100; + + private int layoutWidth; + private int layoutHeight; + + private ImageReceiver avatarImage; + private AvatarDrawable avatarDrawable; + private boolean avatarPressed; + private boolean forwardNamePressed; + private boolean forwardBotPressed; + + private StaticLayout replyNameLayout; + private StaticLayout replyTextLayout; + private ImageReceiver replyImageReceiver; + private int replyStartX; + private int replyStartY; + private int replyNameWidth; + private float replyNameOffset; + private int replyTextWidth; + private float replyTextOffset; + private boolean needReplyImage; + private boolean replyPressed; + private TLRPC.FileLocation currentReplyPhoto; + + private boolean drawShareButton; + private boolean sharePressed; + private int shareStartX; + private int shareStartY; + + private StaticLayout nameLayout; + private int nameWidth; + private float nameOffsetX; + private float nameX; + private float nameY; + private boolean drawName; + private boolean drawNameLayout; + + private StaticLayout[] forwardedNameLayout = new StaticLayout[2]; + private int forwardedNameWidth; + private boolean drawForwardedName; + private int forwardNameX; + private int forwardNameY; + private float forwardNameOffsetX[] = new float[2]; + + private StaticLayout timeLayout; + private int timeWidth; + private int timeTextWidth; + private int timeX; + private String currentTimeString; + private boolean drawTime = true; + + private StaticLayout viewsLayout; + private int viewsTextWidth; + private String currentViewsString; + + private TLRPC.User currentUser; + private TLRPC.Chat currentChat; + private TLRPC.FileLocation currentPhoto; + private String currentNameString; + + private TLRPC.User currentForwardUser; + private TLRPC.User currentViaBotUser; + private TLRPC.Chat currentForwardChannel; + private String currentForwardNameString; + + private ChatMessageCellDelegate delegate; + + private int namesOffset; + + private int lastSendState; + private int lastDeleteDate; + private int lastViewsCount; + public ChatMessageCell(Context context) { super(context); - avatarDrawable = new AvatarDrawable(); - photoImage = new ImageReceiver(this); - photoImage.setDelegate(this); - radialProgress = new RadialProgress(this); - seekBar = new SeekBar(context); - seekBar.setDelegate(this); - seekBarWaveform = new SeekBarWaveform(context); - seekBarWaveform.setDelegate(this); - seekBarWaveform.setParentView(this); - if (infoPaint == null) { infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - infoPaint.setTextSize(AndroidUtilities.dp(12)); + infoPaint.setTextSize(dp(12)); docNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - docNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - docNamePaint.setTextSize(AndroidUtilities.dp(15)); + docNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); + docNamePaint.setTextSize(dp(15)); docBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -227,44 +340,79 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele botProgressPaint.setColor(Theme.MSG_BOT_PROGRESS_COLOR); botProgressPaint.setStrokeCap(Paint.Cap.ROUND); botProgressPaint.setStyle(Paint.Style.STROKE); - botProgressPaint.setStrokeWidth(AndroidUtilities.dp(2)); + botProgressPaint.setStrokeWidth(dp(2)); locationTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - locationTitlePaint.setTextSize(AndroidUtilities.dp(15)); - locationTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + locationTitlePaint.setTextSize(dp(15)); + locationTitlePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - locationAddressPaint.setTextSize(AndroidUtilities.dp(13)); + locationAddressPaint.setTextSize(dp(13)); urlPaint = new Paint(); urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); audioTimePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - audioTimePaint.setTextSize(AndroidUtilities.dp(12)); + audioTimePaint.setTextSize(dp(12)); audioTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - audioTitlePaint.setTextSize(AndroidUtilities.dp(16)); - audioTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + audioTitlePaint.setTextSize(dp(16)); + audioTitlePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); audioPerformerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - audioPerformerPaint.setTextSize(AndroidUtilities.dp(15)); + audioPerformerPaint.setTextSize(dp(15)); botButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - botButtonPaint.setTextSize(AndroidUtilities.dp(15)); + botButtonPaint.setTextSize(dp(15)); botButtonPaint.setColor(Theme.MSG_BOT_BUTTON_TEXT_COLOR); - botButtonPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + botButtonPaint.setTypeface(getTypeface("fonts/rmedium.ttf")); contactNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - contactNamePaint.setTextSize(AndroidUtilities.dp(15)); - contactNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + contactNamePaint.setTextSize(dp(15)); + contactNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); contactPhonePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - contactPhonePaint.setTextSize(AndroidUtilities.dp(13)); + contactPhonePaint.setTextSize(dp(13)); durationPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - durationPaint.setTextSize(AndroidUtilities.dp(12)); + durationPaint.setTextSize(dp(12)); durationPaint.setColor(Theme.MSG_WEB_PREVIEW_DURATION_TEXT_COLOR); + + timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + timePaint.setTextSize(dp(12)); + + namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + namePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); + namePaint.setTextSize(dp(14)); + + forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + forwardNamePaint.setTextSize(dp(14)); + + replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + replyNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); + replyNamePaint.setTextSize(dp(14)); + + replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + replyTextPaint.setTextSize(dp(14)); + replyTextPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; + + replyLinePaint = new Paint(); } + avatarImage = new ImageReceiver(this); + avatarImage.setRoundRadius(dp(21)); + avatarDrawable = new AvatarDrawable(); + replyImageReceiver = new ImageReceiver(this); + TAG = MediaController.getInstance().generateObserverTag(); + + contactAvatarDrawable = new AvatarDrawable(); + photoImage = new ImageReceiver(this); + photoImage.setDelegate(this); + radialProgress = new RadialProgress(this); + seekBar = new SeekBar(context); + seekBar.setDelegate(this); + seekBarWaveform = new SeekBarWaveform(context); + seekBarWaveform.setDelegate(this); + seekBarWaveform.setParentView(this); radialProgress = new RadialProgress(this); } @@ -461,10 +609,10 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele int x = (int) event.getX(); int y = (int) event.getY(); - if (x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + AndroidUtilities.dp(8)) { + if (x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + dp(8)) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && drawPhotoImage && photoImage.isInsideImage(x, y)) { - if (drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { + if (drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + dp(48) && y >= buttonY && y <= buttonY + dp(48)) { buttonPressed = 1; return true; } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF && buttonState == -1 && MediaController.getInstance().canAutoplayGifs()) { @@ -476,7 +624,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } else if (descriptionLayout != null && y >= descriptionY) { try { - x -= textX + AndroidUtilities.dp(10) + descriptionX; + x -= textX + dp(10) + descriptionX; y -= descriptionY; final int line = descriptionLayout.getLineForVertical(y); final int off = descriptionLayout.getOffsetForHorizontal(line, x); @@ -579,7 +727,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (x >= otherX - AndroidUtilities.dp(20) && x <= otherX + AndroidUtilities.dp(20) && y >= otherY - AndroidUtilities.dp(4) && y <= otherY + AndroidUtilities.dp(30)) { + if (x >= otherX - dp(20) && x <= otherX + dp(20) && y >= otherY - dp(4) && y <= otherY + dp(30)) { otherPressed = true; result = true; } @@ -594,7 +742,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } return result; } - + private boolean checkPhotoImageMotionEvent(MotionEvent event) { if (!drawPhotoImage && documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT) { return false; @@ -605,13 +753,13 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { + if (buttonState != -1 && x >= buttonX && x <= buttonX + dp(48) && y >= buttonY && y <= buttonY + dp(48)) { buttonPressed = 1; invalidate(); result = true; } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { - if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { + if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { imagePressed = true; result = true; } @@ -670,7 +818,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele int y = (int) event.getY(); boolean result; if (useSeekBarWaweform) { - result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - AndroidUtilities.dp(13), event.getY() - seekBarY); + result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - dp(13), event.getY() - seekBarY); } else { result = seekBar.onTouch(event.getAction(), event.getX() - seekBarX, event.getY() - seekBarY); } @@ -683,10 +831,10 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele disallowLongPress = true; invalidate(); } else { - int side = AndroidUtilities.dp(36); + int side = dp(36); boolean area; if (buttonState == 0 || buttonState == 1 || buttonState == 2) { - area = x >= buttonX - AndroidUtilities.dp(12) && x <= buttonX - AndroidUtilities.dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; + area = x >= buttonX - dp(12) && x <= buttonX - dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; } else { area = x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side; } @@ -730,13 +878,13 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (event.getAction() == MotionEvent.ACTION_DOWN) { int addX; if (currentMessageObject.isOutOwner()) { - addX = getMeasuredWidth() - widthForButtons - AndroidUtilities.dp(10); + addX = getMeasuredWidth() - widthForButtons - dp(10); } else { - addX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(mediaBackground ? 1 : 7); + addX = currentBackgroundDrawable.getBounds().left + dp(mediaBackground ? 1 : 7); } for (int a = 0; a < botButtons.size(); a++) { BotButton button = botButtons.get(a); - int y2 = button.y + layoutHeight - AndroidUtilities.dp(2); + int y2 = button.y + layoutHeight - dp(2); if (x >= button.x + addX && x <= button.x + addX + button.width && y >= y2 && y <= y2 + button.height) { pressedBotButton = a; invalidate(); @@ -801,7 +949,129 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele cancelCheckLongPress(); } - return result || super.onTouchEvent(event); + if (!result) { + float x = event.getX(); + float y = event.getY(); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (delegate == null || delegate.canPerformActions()) { + if (isAvatarVisible && avatarImage.isInsideImage(x, y)) { + avatarPressed = true; + result = true; + } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32)) { + if (viaWidth != 0 && x >= forwardNameX + viaNameWidth + dp(4)) { + forwardBotPressed = true; + } else { + forwardNamePressed = true; + } + result = true; + } else if (drawNameLayout && nameLayout != null && viaWidth != 0 && x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - dp(4) && y <= nameY + dp(20)) { + forwardBotPressed = true; + result = true; + } else if (currentMessageObject.isReply() && x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + dp(35)) { + replyPressed = true; + result = true; + } else if (drawShareButton && x >= shareStartX && x <= shareStartX + dp(40) && y >= shareStartY && y <= shareStartY + dp(32)) { + sharePressed = true; + result = true; + invalidate(); + } + if (result) { + startCheckLongPress(); + } + } + } else { + if (event.getAction() != MotionEvent.ACTION_MOVE) { + cancelCheckLongPress(); + } + if (avatarPressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + avatarPressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + if (delegate != null) { + if (currentUser != null) { + delegate.didPressedUserAvatar(this, currentUser); + } else if (currentChat != null) { + delegate.didPressedChannelAvatar(this, currentChat, 0); + } + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + avatarPressed = false; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (isAvatarVisible && !avatarImage.isInsideImage(x, y)) { + avatarPressed = false; + } + } + } else if (forwardNamePressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + forwardNamePressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + if (delegate != null) { + if (currentForwardChannel != null) { + delegate.didPressedChannelAvatar(this, currentForwardChannel, currentMessageObject.messageOwner.fwd_from.channel_post); + } else if (currentForwardUser != null) { + delegate.didPressedUserAvatar(this, currentForwardUser); + } + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + forwardNamePressed = false; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32))) { + forwardNamePressed = false; + } + } + } else if (forwardBotPressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + forwardBotPressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + if (delegate != null) { + delegate.didPressedViaBot(this, currentViaBotUser != null ? currentViaBotUser.username : currentMessageObject.messageOwner.via_bot_name); + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + forwardBotPressed = false; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (drawForwardedName && forwardedNameLayout[0] != null) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32))) { + forwardBotPressed = false; + } + } else { + if (!(x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - dp(4) && y <= nameY + dp(20))) { + forwardBotPressed = false; + } + } + } + } else if (replyPressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + replyPressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + if (delegate != null) { + delegate.didPressedReplyMessage(this, currentMessageObject.messageOwner.reply_to_msg_id); + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + replyPressed = false; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (!(x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + dp(35))) { + replyPressed = false; + } + } + } else if (sharePressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + sharePressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + if (delegate != null) { + delegate.didPressedShare(this); + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + sharePressed = false; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (!(x >= shareStartX && x <= shareStartX + dp(40) && y >= shareStartY && y <= shareStartY + dp(32))) { + sharePressed = false; + } + } + invalidate(); + } + } + } + return result; } public void updateAudioProgress() { @@ -836,7 +1106,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { lastTimeString = timeString; timeWidthAudio = (int) Math.ceil(audioTimePaint.measureText(timeString)); - timeLayout = new StaticLayout(timeString, audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } else { int currentProgress = 0; @@ -854,7 +1124,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { lastTimeString = timeString; int timeWidth = (int) Math.ceil(audioTimePaint.measureText(timeString)); - timeLayout = new StaticLayout(timeString, audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } invalidate(); @@ -940,7 +1210,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele break; } } - return StaticLayoutEx.createStaticLayout(stringBuilder, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, maxWidth, maxLines); + return StaticLayoutEx.createStaticLayout(stringBuilder, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, maxWidth, maxLines); } private void didClickedImage() { @@ -1010,7 +1280,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } double lat = object.messageOwner.media.geo.lat; double lon = object.messageOwner.media.geo._long; - String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); if (!url.equals(currentUrl)) { return true; } @@ -1025,12 +1295,83 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele return false; } - @Override - protected boolean isUserDataChanged() { - return currentMessageObject != null && (!hasLinkPreview && currentMessageObject.messageOwner.media != null && currentMessageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage || super.isUserDataChanged()); + private boolean isUserDataChanged() { + if (currentMessageObject != null && (!hasLinkPreview && currentMessageObject.messageOwner.media != null && currentMessageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage)) { + return true; + } + if (currentMessageObject == null || currentUser == null && currentChat == null) { + return false; + } + if (lastSendState != currentMessageObject.messageOwner.send_state) { + return true; + } + if (lastDeleteDate != currentMessageObject.messageOwner.destroyTime) { + return true; + } + if (lastViewsCount != currentMessageObject.messageOwner.views) { + return true; + } + + TLRPC.User newUser = null; + TLRPC.Chat newChat = null; + if (currentMessageObject.isFromUser()) { + newUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); + } else if (currentMessageObject.messageOwner.from_id < 0) { + newChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); + } else if (currentMessageObject.messageOwner.post) { + newChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); + } + TLRPC.FileLocation newPhoto = null; + + if (isAvatarVisible) { + if (newUser != null && newUser.photo != null){ + newPhoto = newUser.photo.photo_small; + } else if (newChat != null && newChat.photo != null) { + newPhoto = newChat.photo.photo_small; + } + } + + if (replyTextLayout == null && currentMessageObject.replyMessageObject != null) { + return true; + } + + if (currentPhoto == null && newPhoto != null || currentPhoto != null && newPhoto == null || currentPhoto != null && newPhoto != null && (currentPhoto.local_id != newPhoto.local_id || currentPhoto.volume_id != newPhoto.volume_id)) { + return true; + } + + TLRPC.FileLocation newReplyPhoto = null; + + if (currentMessageObject.replyMessageObject != null) { + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(currentMessageObject.replyMessageObject.photoThumbs, 80); + if (photoSize != null && currentMessageObject.replyMessageObject.type != 13) { + newReplyPhoto = photoSize.location; + } + } + + if (currentReplyPhoto == null && newReplyPhoto != null) { + return true; + } + + String newNameString = null; + if (drawName && isChat && !currentMessageObject.isOutOwner()) { + if (newUser != null) { + newNameString = UserObject.getUserName(newUser); + } else if (newChat != null) { + newNameString = newChat.title; + } + } + + if (currentNameString == null && newNameString != null || currentNameString != null && newNameString == null || currentNameString != null && newNameString != null && !currentNameString.equals(newNameString)) { + return true; + } + + if (drawForwardedName) { + newNameString = currentMessageObject.getForwardedName(); + return currentForwardNameString == null && newNameString != null || currentForwardNameString != null && newNameString == null || currentForwardNameString != null && newNameString != null && !currentForwardNameString.equals(newNameString); + } + return false; } - @Override public ImageReceiver getPhotoImage() { return photoImage; } @@ -1038,6 +1379,8 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + avatarImage.onDetachedFromWindow(); + replyImageReceiver.onDetachedFromWindow(); photoImage.onDetachedFromWindow(); MediaController.getInstance().removeLoadingFileObserver(this); } @@ -1045,6 +1388,8 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + avatarImage.onAttachedToWindow(); + replyImageReceiver.onAttachedToWindow(); if (drawPhotoImage) { if (photoImage.onAttachedToWindow()) { updateButtonState(false); @@ -1072,46 +1417,47 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele pressedBotButton = -1; invalidate(); } - super.onLongPress(); + if (delegate != null) { + delegate.didLongPressed(this); + } } - @Override public void setCheckPressed(boolean value, boolean pressed) { - super.setCheckPressed(value, pressed); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); - } + isCheckPressed = value; + isPressed = pressed; + radialProgress.swapBackground(getDrawableForCurrentState()); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } + invalidate(); } - @Override public void setHighlighted(boolean value) { - super.setHighlighted(value); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); + if (isHighlighted == value) { + return; } + isHighlighted = value; + radialProgress.swapBackground(getDrawableForCurrentState()); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } + invalidate(); } @Override public void setPressed(boolean pressed) { super.setPressed(pressed); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); - } + radialProgress.swapBackground(getDrawableForCurrentState()); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } + invalidate(); } @Override @@ -1159,11 +1505,11 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele break; } } - availableTimeWidth = maxWidth - AndroidUtilities.dp(76 + 18) - (int) Math.ceil(audioTimePaint.measureText("00:00")); + availableTimeWidth = maxWidth - dp(76 + 18) - (int) Math.ceil(audioTimePaint.measureText("00:00")); measureTime(messageObject); - int minSize = AndroidUtilities.dp(40 + 14 + 20 + 90 + 10) + timeWidth; + int minSize = dp(40 + 14 + 20 + 90 + 10) + timeWidth; if (!hasLinkPreview) { - backgroundWidth = Math.min(maxWidth, minSize + duration * AndroidUtilities.dp(10)); + backgroundWidth = Math.min(maxWidth, minSize + duration * dp(10)); } if (messageObject.isOutOwner()) { @@ -1183,9 +1529,9 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele seekBar.setColors(Theme.MSG_IN_AUDIO_SEEKBAR_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_SELECTED_COLOR); } - maxWidth = maxWidth - AndroidUtilities.dp(86); + maxWidth = maxWidth - dp(86); - CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace('\n', ' '), audioTitlePaint, maxWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace('\n', ' '), audioTitlePaint, maxWidth - dp(12), TextUtils.TruncateAt.END); songLayout = new StaticLayout(stringFinal, audioTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (songLayout.getLineCount() > 0) { songX = -(int) Math.ceil(songLayout.getLineLeft(0)); @@ -1206,7 +1552,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } int durationWidth = (int) Math.ceil(audioTimePaint.measureText(String.format("%d:%02d / %d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); - availableTimeWidth = backgroundWidth - AndroidUtilities.dp(76 + 18) - durationWidth; + availableTimeWidth = backgroundWidth - dp(76 + 18) - durationWidth; return durationWidth; } else if (MessageObject.isVideoDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO; @@ -1220,7 +1566,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } int minutes = duration / 60; int seconds = duration - minutes * 60; - String str = String.format("%d:%02d, %s", minutes, seconds, AndroidUtilities.formatFileSize(documentAttach.size)); + String str = String.format("%d:%02d, %s", minutes, seconds, formatFileSize(documentAttach.size)); infoWidth = (int) Math.ceil(infoPaint.measureText(str)); infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); @@ -1228,34 +1574,34 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } else { drawPhotoImage = documentAttach.mime_type != null && documentAttach.mime_type.toLowerCase().startsWith("image/") || documentAttach.thumb instanceof TLRPC.TL_photoSize && !(documentAttach.thumb.location instanceof TLRPC.TL_fileLocationUnavailable); if (!drawPhotoImage) { - maxWidth += AndroidUtilities.dp(30); + maxWidth += dp(30); } documentAttachType = DOCUMENT_ATTACH_TYPE_DOCUMENT; String name = FileLoader.getDocumentFileName(documentAttach); if (name == null || name.length() == 0) { name = LocaleController.getString("AttachDocument", R.string.AttachDocument); } - captionLayout = StaticLayoutEx.createStaticLayout(name, docNamePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, drawPhotoImage ? 2 : 1); - nameOffsetX = Integer.MIN_VALUE; - int captionWidth; - if (captionLayout != null && captionLayout.getLineCount() > 0) { + docTitleLayout = StaticLayoutEx.createStaticLayout(name, docNamePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, drawPhotoImage ? 2 : 1); + docTitleOffsetX = Integer.MIN_VALUE; + int width; + if (docTitleLayout != null && docTitleLayout.getLineCount() > 0) { int maxLineWidth = 0; - for (int a = 0; a < captionLayout.getLineCount(); a++) { - maxLineWidth = Math.max(maxLineWidth, (int) Math.ceil(captionLayout.getLineWidth(a))); - nameOffsetX = Math.max(nameOffsetX, (int) Math.ceil(-captionLayout.getLineLeft(a))); + for (int a = 0; a < docTitleLayout.getLineCount(); a++) { + maxLineWidth = Math.max(maxLineWidth, (int) Math.ceil(docTitleLayout.getLineWidth(a))); + docTitleOffsetX = Math.max(docTitleOffsetX, (int) Math.ceil(-docTitleLayout.getLineLeft(a))); } - captionWidth = Math.min(maxWidth, maxLineWidth); + width = Math.min(maxWidth, maxLineWidth); } else { - captionWidth = maxWidth; - nameOffsetX = 0; + width = maxWidth; + docTitleOffsetX = 0; } - String str = AndroidUtilities.formatFileSize(documentAttach.size) + " " + FileLoader.getDocumentExtension(documentAttach); - infoWidth = Math.min(maxWidth, (int) Math.ceil(infoPaint.measureText(str))); + String str = formatFileSize(documentAttach.size) + " " + FileLoader.getDocumentExtension(documentAttach); + infoWidth = Math.min(maxWidth - AndroidUtilities.dp(30), (int) Math.ceil(infoPaint.measureText(str))); CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); try { if (infoWidth < 0) { - infoWidth = AndroidUtilities.dp(10); + infoWidth = dp(10); } infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { @@ -1263,7 +1609,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } if (drawPhotoImage) { - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); @@ -1274,31 +1620,51 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoImage.setImageBitmap((BitmapDrawable) null); } } - return captionWidth; + return width; } } private void calcBackgroundWidth(int maxWidth, int timeMore, int maxChildWidth) { if (hasLinkPreview || maxWidth - currentMessageObject.lastLineWidth < timeMore) { - totalHeight += AndroidUtilities.dp(14); - backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + AndroidUtilities.dp(31); - backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(31)); + totalHeight += dp(14); + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + dp(31); + backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); } else { int diff = maxChildWidth - currentMessageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { - backgroundWidth = maxChildWidth + timeMore - diff + AndroidUtilities.dp(31); + backgroundWidth = maxChildWidth + timeMore - diff + dp(31); } else { - backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(31); + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + dp(31); } } } - @Override public void setMessageObject(MessageObject messageObject) { boolean messageIdChanged = currentMessageObject == null || currentMessageObject.getId() != messageObject.getId(); boolean messageChanged = currentMessageObject != messageObject || messageObject.forceUpdate; boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); if (messageChanged || dataChanged || isPhotoDataChanged(messageObject)) { + currentMessageObject = messageObject; + lastSendState = messageObject.messageOwner.send_state; + lastDeleteDate = messageObject.messageOwner.destroyTime; + lastViewsCount = messageObject.messageOwner.views; + isPressed = false; + isCheckPressed = true; + isAvatarVisible = false; + wasLayout = false; + drawShareButton = checkNeedDrawShareButton(messageObject); + replyNameLayout = null; + replyTextLayout = null; + replyNameWidth = 0; + replyTextWidth = 0; + viaWidth = 0; + viaNameWidth = 0; + currentReplyPhoto = null; + currentUser = null; + currentChat = null; + currentViaBotUser = null; + drawNameLayout = false; + resetPressedLink(-1); messageObject.forceUpdate = false; drawPhotoImage = false; @@ -1308,14 +1674,15 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele pressedBotButton = -1; linkPreviewHeight = 0; mediaOffsetY = 0; - durationLayout = null; documentAttachType = DOCUMENT_ATTACH_TYPE_NONE; documentAttach = null; descriptionLayout = null; titleLayout = null; - siteCaptionLayout = null; + videoInfoLayout = null; + siteNameLayout = null; authorLayout = null; captionLayout = null; + docTitleLayout = null; drawImageButton = false; currentPhotoObject = null; currentPhotoObjectThumb = null; @@ -1334,7 +1701,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoImage.setNeedsQualityThumb(false); photoImage.setShouldGenerateQualityThumb(false); photoImage.setParentMessageObject(null); - photoImage.setRoundRadius(AndroidUtilities.dp(3)); + photoImage.setRoundRadius(dp(3)); if (messageChanged) { firstVisibleBlockNum = 0; @@ -1346,48 +1713,48 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele drawForwardedName = true; int maxWidth; - if (AndroidUtilities.isTablet()) { + if (isTablet()) { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + maxWidth = getMinTabletSide() - dp(122); drawName = true; } else { drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); + maxWidth = getMinTabletSide() - dp(80); } } else { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); + maxWidth = Math.min(displaySize.x, displaySize.y) - dp(122); drawName = true; } else { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + maxWidth = Math.min(displaySize.x, displaySize.y) - dp(80); drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); } } measureTime(messageObject); - int timeMore = timeWidth + AndroidUtilities.dp(6); + int timeMore = timeWidth + dp(6); if (messageObject.isOutOwner()) { - timeMore += AndroidUtilities.dp(20.5f); + timeMore += dp(20.5f); } hasLinkPreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage; backgroundWidth = maxWidth; if (hasLinkPreview || maxWidth - messageObject.lastLineWidth < timeMore) { - backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth) + AndroidUtilities.dp(31); - backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(31)); + backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth) + dp(31); + backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); } else { int diff = backgroundWidth - messageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { - backgroundWidth = backgroundWidth + timeMore - diff + AndroidUtilities.dp(31); + backgroundWidth = backgroundWidth + timeMore - diff + dp(31); } else { - backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(31); + backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth + timeMore) + dp(31); } } - availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31); + availableTimeWidth = backgroundWidth - dp(31); - super.setMessageObject(messageObject); + setMessageObjectInternal(messageObject); backgroundWidth = messageObject.textWidth; - totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; + totalHeight = messageObject.textHeight + dp(19.5f) + namesOffset; int maxChildWidth = Math.max(backgroundWidth, nameWidth); maxChildWidth = Math.max(maxChildWidth, forwardedNameWidth); @@ -1397,30 +1764,30 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (hasLinkPreview) { int linkPreviewMaxWidth; - if (AndroidUtilities.isTablet()) { + if (isTablet()) { if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOut()) { - linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + linkPreviewMaxWidth = getMinTabletSide() - dp(122); } else { - linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); + linkPreviewMaxWidth = getMinTabletSide() - dp(80); } } else { if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOutOwner()) { - linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); + linkPreviewMaxWidth = Math.min(displaySize.x, displaySize.y) - dp(122); } else { - linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + linkPreviewMaxWidth = Math.min(displaySize.x, displaySize.y) - dp(80); } } if (drawShareButton) { - linkPreviewMaxWidth -= AndroidUtilities.dp(20); + linkPreviewMaxWidth -= dp(20); } TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) messageObject.messageOwner.media.webpage; if (webPage.site_name != null && webPage.photo != null && webPage.site_name.toLowerCase().equals("instagram")) { - linkPreviewMaxWidth = Math.max(AndroidUtilities.displaySize.y / 3, currentMessageObject.textWidth); + linkPreviewMaxWidth = Math.max(displaySize.y / 3, currentMessageObject.textWidth); } - int additinalWidth = AndroidUtilities.dp(10); + int additinalWidth = dp(10); int restLinesCount = 3; int additionalHeight = 0; linkPreviewMaxWidth -= additinalWidth; @@ -1434,12 +1801,12 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (webPage.site_name != null) { try { int width = (int) Math.ceil(replyNamePaint.measureText(webPage.site_name)); - siteCaptionLayout = new StaticLayout(webPage.site_name, replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - int height = siteCaptionLayout.getLineBottom(siteCaptionLayout.getLineCount() - 1); + siteNameLayout = new StaticLayout(webPage.site_name, replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int height = siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); linkPreviewHeight += height; totalHeight += height; additionalHeight += height; - width = siteCaptionLayout.getWidth(); + width = siteNameLayout.getWidth(); maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); } catch (Exception e) { @@ -1452,15 +1819,15 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele try { titleX = 0; if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + linkPreviewHeight += dp(2); + totalHeight += dp(2); } int restLines = 0; if (!isSmallImage || webPage.description == null) { - titleLayout = StaticLayoutEx.createStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); + titleLayout = StaticLayoutEx.createStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); } else { restLines = restLinesCount; - titleLayout = generateStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 4); + titleLayout = generateStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 4); restLinesCount -= titleLayout.getLineCount(); } int height = titleLayout.getLineBottom(titleLayout.getLineCount() - 1); @@ -1483,7 +1850,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele width = (int) Math.ceil(titleLayout.getLineWidth(a)); } if (a < restLines || lineLeft != 0 && isSmallImage) { - width += AndroidUtilities.dp(48 + 4); + width += dp(48 + 4); } maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); @@ -1497,14 +1864,14 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (webPage.author != null) { try { if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + linkPreviewHeight += dp(2); + totalHeight += dp(2); } //int width = Math.min((int) Math.ceil(replyNamePaint.measureText(webPage.author)), linkPreviewMaxWidth); if (restLinesCount == 3 && (!isSmallImage || webPage.description == null)) { authorLayout = new StaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } else { - authorLayout = generateStaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 1); + authorLayout = generateStaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 1); restLinesCount -= authorLayout.getLineCount(); } int height = authorLayout.getLineBottom(authorLayout.getLineCount() - 1); @@ -1531,15 +1898,15 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele descriptionX = 0; currentMessageObject.generateLinkDescription(); if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + linkPreviewHeight += dp(2); + totalHeight += dp(2); } int restLines = 0; if (restLinesCount == 3 && !isSmallImage) { - descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); + descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); } else { restLines = restLinesCount; - descriptionLayout = generateStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 6); + descriptionLayout = generateStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 6); } int height = descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); linkPreviewHeight += height; @@ -1571,7 +1938,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele width = hasRTL ? descriptionLayout.getWidth() : (int) Math.ceil(descriptionLayout.getLineWidth(a)); } if (a < restLines || restLines != 0 && lineLeft != 0 && isSmallImage) { - width += AndroidUtilities.dp(48 + 4); + width += dp(48 + 4); } if (maxWebWidth < width + additinalWidth) { if (titleIsRTL) { @@ -1584,10 +1951,10 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } if (restLines == 0 || !isSmallImage) { if (titleIsRTL) { - titleX = -AndroidUtilities.dp(4); + titleX = -dp(4); } if (authorIsRTL) { - authorX = -AndroidUtilities.dp(4); + authorX = -dp(4); } } maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); @@ -1602,7 +1969,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele smallImage = false; isSmallImage = false; } - int maxPhotoWidth = smallImage ? AndroidUtilities.dp(48) : linkPreviewMaxWidth; + int maxPhotoWidth = smallImage ? dp(48) : linkPreviewMaxWidth; if (webPage.document != null) { TLRPC.Document document = webPage.document; @@ -1622,7 +1989,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); + currentPhotoObject.w = currentPhotoObject.h = dp(150); } } documentAttachType = DOCUMENT_ATTACH_TYPE_GIF; @@ -1638,7 +2005,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); + currentPhotoObject.w = currentPhotoObject.h = dp(150); } } createDocumentLayout(0, messageObject); @@ -1654,7 +2021,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); + currentPhotoObject.w = currentPhotoObject.h = dp(150); } } documentAttach = document; @@ -1662,50 +2029,50 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } else { calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); if (!MessageObject.isStickerDocument(document)) { - if (backgroundWidth < maxWidth + AndroidUtilities.dp(20)) { - backgroundWidth = maxWidth + AndroidUtilities.dp(20); + if (backgroundWidth < maxWidth + dp(20)) { + backgroundWidth = maxWidth + dp(20); } if (MessageObject.isVoiceDocument(document)) { - createDocumentLayout(backgroundWidth - AndroidUtilities.dp(10), messageObject); - mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; - totalHeight += AndroidUtilities.dp(30 + 14); - linkPreviewHeight += AndroidUtilities.dp(44); + createDocumentLayout(backgroundWidth - dp(10), messageObject); + mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; + totalHeight += dp(30 + 14); + linkPreviewHeight += dp(44); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } else if (MessageObject.isMusicDocument(document)) { - int durationWidth = createDocumentLayout(backgroundWidth - AndroidUtilities.dp(10), messageObject); - mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; - totalHeight += AndroidUtilities.dp(42 + 14); - linkPreviewHeight += AndroidUtilities.dp(56); + int durationWidth = createDocumentLayout(backgroundWidth - dp(10), messageObject); + mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; + totalHeight += dp(42 + 14); + linkPreviewHeight += dp(56); - maxWidth = maxWidth - AndroidUtilities.dp(86); - maxChildWidth = Math.max(maxChildWidth, durationWidth + additinalWidth + AndroidUtilities.dp(86 + 8)); + maxWidth = maxWidth - dp(86); + maxChildWidth = Math.max(maxChildWidth, durationWidth + additinalWidth + dp(86 + 8)); if (songLayout != null && songLayout.getLineCount() > 0) { - maxChildWidth = (int) Math.max(maxChildWidth, songLayout.getLineWidth(0) + additinalWidth + AndroidUtilities.dp(86)); + maxChildWidth = (int) Math.max(maxChildWidth, songLayout.getLineWidth(0) + additinalWidth + dp(86)); } if (performerLayout != null && performerLayout.getLineCount() > 0) { - maxChildWidth = (int) Math.max(maxChildWidth, performerLayout.getLineWidth(0) + additinalWidth + AndroidUtilities.dp(86)); + maxChildWidth = (int) Math.max(maxChildWidth, performerLayout.getLineWidth(0) + additinalWidth + dp(86)); } calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } else { - createDocumentLayout(backgroundWidth - AndroidUtilities.dp(86 + 24 + 58), messageObject); + createDocumentLayout(backgroundWidth - dp(86 + 24 + 58), messageObject); drawImageButton = true; if (drawPhotoImage) { - totalHeight += AndroidUtilities.dp(86 + 14); - linkPreviewHeight += AndroidUtilities.dp(86); - photoImage.setImageCoords(0, totalHeight + namesOffset, AndroidUtilities.dp(86), AndroidUtilities.dp(86)); + totalHeight += dp(86 + 14); + linkPreviewHeight += dp(86); + photoImage.setImageCoords(0, totalHeight + namesOffset, dp(86), dp(86)); } else { - mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; - photoImage.setImageCoords(0, totalHeight + namesOffset - AndroidUtilities.dp(14), AndroidUtilities.dp(56), AndroidUtilities.dp(56)); - totalHeight += AndroidUtilities.dp(50 + 14); - linkPreviewHeight += AndroidUtilities.dp(50); + mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; + photoImage.setImageCoords(0, totalHeight + namesOffset - dp(14), dp(56), dp(56)); + totalHeight += dp(50 + 14); + linkPreviewHeight += dp(50); } } } } } else if (webPage.photo != null) { drawImageButton = webPage.type != null && webPage.type.equals("photo"); - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? AndroidUtilities.getPhotoSize() : maxPhotoWidth, !drawImageButton); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? getPhotoSize() : maxPhotoWidth, !drawImageButton); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); if (currentPhotoObjectThumb == currentPhotoObject) { currentPhotoObjectThumb = null; @@ -1716,15 +2083,15 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (currentPhotoObject != null) { drawImageButton = webPage.type != null && (webPage.type.equals("photo") || webPage.type.equals("document") && documentAttachType != DOCUMENT_ATTACH_TYPE_STICKER || webPage.type.equals("gif") || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO); if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + linkPreviewHeight += dp(2); + totalHeight += dp(2); } if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { - if (AndroidUtilities.isTablet()) { - maxPhotoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); + if (isTablet()) { + maxPhotoWidth = (int) (getMinTabletSide() * 0.5f); } else { - maxPhotoWidth = (int) (AndroidUtilities.displaySize.x * 0.5f); + maxPhotoWidth = (int) (displaySize.x * 0.5f); } } @@ -1741,23 +2108,23 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } else { width = currentPhotoObject.w; height = currentPhotoObject.h; - float scale = width / (float) (maxPhotoWidth - AndroidUtilities.dp(2)); + float scale = width / (float) (maxPhotoWidth - dp(2)); width /= scale; height /= scale; if (webPage.site_name == null || webPage.site_name != null && !webPage.site_name.toLowerCase().equals("instagram") && documentAttachType == 0) { - if (height > AndroidUtilities.displaySize.y / 3) { - height = AndroidUtilities.displaySize.y / 3; + if (height > displaySize.y / 3) { + height = displaySize.y / 3; } } } if (isSmallImage) { - if (AndroidUtilities.dp(50) + additionalHeight > linkPreviewHeight) { - totalHeight += AndroidUtilities.dp(50) + additionalHeight - linkPreviewHeight + AndroidUtilities.dp(8); - linkPreviewHeight = AndroidUtilities.dp(50) + additionalHeight; + if (dp(50) + additionalHeight > linkPreviewHeight) { + totalHeight += dp(50) + additionalHeight - linkPreviewHeight + dp(8); + linkPreviewHeight = dp(50) + additionalHeight; } - linkPreviewHeight -= AndroidUtilities.dp(8); + linkPreviewHeight -= dp(8); } else { - totalHeight += height + AndroidUtilities.dp(12); + totalHeight += height + dp(12); linkPreviewHeight += height; } @@ -1802,12 +2169,12 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele int seconds = webPage.duration - minutes * 60; String str = String.format("%d:%02d", minutes, seconds); durationWidth = (int) Math.ceil(durationPaint.measureText(str)); - durationLayout = new StaticLayout(str, durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + videoInfoLayout = new StaticLayout(str, durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } else { photoImage.setImageBitmap((Drawable) null); - linkPreviewHeight -= AndroidUtilities.dp(6); - totalHeight += AndroidUtilities.dp(4); + linkPreviewHeight -= dp(6); + totalHeight += dp(4); } calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } @@ -1819,20 +2186,20 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele drawName = false; drawForwardedName = true; drawPhotoImage = true; - photoImage.setRoundRadius(AndroidUtilities.dp(22)); - if (AndroidUtilities.isTablet()) { - backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + photoImage.setRoundRadius(dp(22)); + if (isTablet()) { + backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { - backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } - availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31); + availableTimeWidth = backgroundWidth - dp(31); int uid = messageObject.messageOwner.media.user_id; TLRPC.User user = MessagesController.getInstance().getUser(uid); - int maxWidth = getMaxNameWidth() - AndroidUtilities.dp(110); + int maxWidth = getMaxNameWidth() - dp(110); if (maxWidth < 0) { - maxWidth = AndroidUtilities.dp(10); + maxWidth = dp(10); } TLRPC.FileLocation currentPhoto = null; @@ -1840,9 +2207,9 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (user.photo != null) { currentPhoto = user.photo.photo_small; } - avatarDrawable.setInfo(user); + contactAvatarDrawable.setInfo(user); } - photoImage.setImage(currentPhoto, "50_50", user != null ? avatarDrawable : Theme.contactDrawable[messageObject.isOutOwner() ? 1 : 0], null, false); + photoImage.setImage(currentPhoto, "50_50", user != null ? contactAvatarDrawable : Theme.contactDrawable[messageObject.isOutOwner() ? 1 : 0], null, false); String phone = messageObject.messageOwner.media.phone_number; if (phone != null && phone.length() != 0) { @@ -1858,42 +2225,48 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (currentNameString.length() == 0) { currentNameString = phone; } - titleLayout = new StaticLayout(TextUtils.ellipsize(currentNameString, contactNamePaint, maxWidth, TextUtils.TruncateAt.END), contactNamePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - captionLayout = new StaticLayout(TextUtils.ellipsize(phone.replace('\n', ' '), contactPhonePaint, maxWidth, TextUtils.TruncateAt.END), contactPhonePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + titleLayout = new StaticLayout(TextUtils.ellipsize(currentNameString, contactNamePaint, maxWidth, TextUtils.TruncateAt.END), contactNamePaint, maxWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + docTitleLayout = new StaticLayout(TextUtils.ellipsize(phone.replace('\n', ' '), contactPhonePaint, maxWidth, TextUtils.TruncateAt.END), contactPhonePaint, maxWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - super.setMessageObject(messageObject); + setMessageObjectInternal(messageObject); if (drawForwardedName && messageObject.isForwarded()) { - namesOffset += AndroidUtilities.dp(5); + namesOffset += dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { - namesOffset += AndroidUtilities.dp(7); + namesOffset += dp(7); } - totalHeight = AndroidUtilities.dp(70) + namesOffset; + totalHeight = dp(70) + namesOffset; + if (docTitleLayout.getLineCount() > 0) { + int timeLeft = backgroundWidth - AndroidUtilities.dp(40 + 18 + 44 + 8) - (int) Math.ceil(docTitleLayout.getLineWidth(0)); + if (timeLeft < timeWidth) { + totalHeight += AndroidUtilities.dp(8); + } + } } else if (messageObject.type == 2) { drawForwardedName = true; - if (AndroidUtilities.isTablet()) { - backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + if (isTablet()) { + backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { - backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } createDocumentLayout(backgroundWidth, messageObject); - super.setMessageObject(messageObject); + setMessageObjectInternal(messageObject); - totalHeight = AndroidUtilities.dp(70) + namesOffset; + totalHeight = dp(70) + namesOffset; } else if (messageObject.type == 14) { - if (AndroidUtilities.isTablet()) { - backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + if (isTablet()) { + backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { - backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } createDocumentLayout(backgroundWidth, messageObject); - super.setMessageObject(messageObject); + setMessageObjectInternal(messageObject); - totalHeight = AndroidUtilities.dp(82) + namesOffset; + totalHeight = dp(82) + namesOffset; } else { drawForwardedName = messageObject.messageOwner.fwd_from != null && messageObject.type != 13; mediaBackground = messageObject.type != 9; @@ -1912,23 +2285,23 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoImage.setForcePreview(messageObject.isSecretPhoto()); if (messageObject.type == 9) { - if (AndroidUtilities.isTablet()) { - backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + if (isTablet()) { + backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { - backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } if (checkNeedDrawShareButton(messageObject)) { - backgroundWidth -= AndroidUtilities.dp(20); + backgroundWidth -= dp(20); } - int maxWidth = backgroundWidth - AndroidUtilities.dp(86 + 52); + int maxWidth = backgroundWidth - dp(86 + 52); createDocumentLayout(maxWidth, messageObject); if (drawPhotoImage) { - photoWidth = AndroidUtilities.dp(86); - photoHeight = AndroidUtilities.dp(86); + photoWidth = dp(86); + photoHeight = dp(86); } else { - photoWidth = AndroidUtilities.dp(56); - photoHeight = AndroidUtilities.dp(56); + photoWidth = dp(56); + photoHeight = dp(56); } availableTimeWidth = maxWidth; } else if (messageObject.type == 4) { //geo @@ -1936,18 +2309,18 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele double lon = messageObject.messageOwner.media.geo._long; if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { - if (AndroidUtilities.isTablet()) { - backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + if (isTablet()) { + backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { - backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } if (checkNeedDrawShareButton(messageObject)) { - backgroundWidth -= AndroidUtilities.dp(20); + backgroundWidth -= dp(20); } - int maxWidth = backgroundWidth - AndroidUtilities.dp(86 + 37); + int maxWidth = backgroundWidth - dp(86 + 37); - captionLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, 2); - int lineCount = captionLayout.getLineCount(); + docTitleLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, 2); + int lineCount = docTitleLayout.getLineCount(); if (messageObject.messageOwner.media.address != null && messageObject.messageOwner.media.address.length() > 0) { infoLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.address, locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, Math.min(3, 3 - lineCount)); } else { @@ -1956,15 +2329,15 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele mediaBackground = false; availableTimeWidth = maxWidth; - photoWidth = AndroidUtilities.dp(86); - photoHeight = AndroidUtilities.dp(86); - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + photoWidth = dp(86); + photoHeight = dp(86); + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); } else { - availableTimeWidth = AndroidUtilities.dp(200 - 14); - photoWidth = AndroidUtilities.dp(200); - photoHeight = AndroidUtilities.dp(100); - backgroundWidth = photoWidth + AndroidUtilities.dp(12); - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + availableTimeWidth = dp(200 - 14); + photoWidth = dp(200); + photoHeight = dp(100); + backgroundWidth = photoWidth + dp(12); + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); } photoImage.setImage(currentUrl, null, messageObject.isOutOwner() ? Theme.geoOutDrawable : Theme.geoInDrawable, null, 0); } else if (messageObject.type == 13) { //webp @@ -1977,27 +2350,26 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele break; } } - float maxHeight = AndroidUtilities.displaySize.y * 0.4f; + float maxHeight; float maxWidth; - if (AndroidUtilities.isTablet()) { - maxWidth = AndroidUtilities.getMinTabletSide() * 0.5f; + if (isTablet()) { + maxHeight = maxWidth = getMinTabletSide() * 0.4f; } else { - maxWidth = AndroidUtilities.displaySize.x * 0.5f; + maxHeight = maxWidth = Math.min(displaySize.x, displaySize.y) * 0.5f; } if (photoWidth == 0) { photoHeight = (int) maxHeight; - photoWidth = photoHeight + AndroidUtilities.dp(100); + photoWidth = photoHeight + dp(100); } + photoHeight *= maxWidth / photoWidth; + photoWidth = (int) maxWidth; if (photoHeight > maxHeight) { photoWidth *= maxHeight / photoHeight; photoHeight = (int) maxHeight; - } else { - photoHeight *= maxWidth / photoWidth; - photoWidth = (int) maxWidth; } documentAttachType = DOCUMENT_ATTACH_TYPE_STICKER; - availableTimeWidth = photoWidth - AndroidUtilities.dp(14); - backgroundWidth = photoWidth + AndroidUtilities.dp(12); + availableTimeWidth = photoWidth - dp(14); + backgroundWidth = photoWidth + dp(12); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); if (messageObject.attachPathExists) { photoImage.setImage(null, messageObject.messageOwner.attachPath, @@ -2016,22 +2388,22 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } else { int maxPhotoWidth; - if (AndroidUtilities.isTablet()) { - maxPhotoWidth = photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); + if (isTablet()) { + maxPhotoWidth = photoWidth = (int) (getMinTabletSide() * 0.7f); } else { - maxPhotoWidth = photoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); + maxPhotoWidth = photoWidth = (int) (Math.min(displaySize.x, displaySize.y) * 0.7f); } - photoHeight = photoWidth + AndroidUtilities.dp(100); + photoHeight = photoWidth + dp(100); if (checkNeedDrawShareButton(messageObject)) { - maxPhotoWidth -= AndroidUtilities.dp(20); - photoWidth -= AndroidUtilities.dp(20); + maxPhotoWidth -= dp(20); + photoWidth -= dp(20); } - if (photoWidth > AndroidUtilities.getPhotoSize()) { - photoWidth = AndroidUtilities.getPhotoSize(); + if (photoWidth > getPhotoSize()) { + photoWidth = getPhotoSize(); } - if (photoHeight > AndroidUtilities.getPhotoSize()) { - photoHeight = AndroidUtilities.getPhotoSize(); + if (photoHeight > getPhotoSize()) { + photoHeight = getPhotoSize(); } if (messageObject.type == 1) { //photo @@ -2043,7 +2415,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); } else if (messageObject.type == 8) { //gif - String str = AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size); + String str = formatFileSize(messageObject.messageOwner.media.document.size); infoWidth = (int) Math.ceil(infoPaint.measureText(str)); infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); @@ -2056,7 +2428,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele mediaBackground = false; } - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); int w = 0; int h = 0; @@ -2070,18 +2442,18 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele w = (int) (currentPhotoObject.w / scale); h = (int) (currentPhotoObject.h / scale); if (w == 0) { - w = AndroidUtilities.dp(150); + w = dp(150); } if (h == 0) { - h = AndroidUtilities.dp(150); + h = dp(150); } if (h > photoHeight) { float scale2 = h; h = photoHeight; scale2 /= h; w = (int) (w / scale2); - } else if (h < AndroidUtilities.dp(120)) { - h = AndroidUtilities.dp(120); + } else if (h < dp(120)) { + h = dp(120); float hScale = (float) currentPhotoObject.h / h; if (currentPhotoObject.w / hScale < photoWidth) { w = (int) (currentPhotoObject.w / hScale); @@ -2101,8 +2473,8 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele h = photoHeight; scale2 /= h; w = (int) (w / scale2); - } else if (h < AndroidUtilities.dp(120)) { - h = AndroidUtilities.dp(120); + } else if (h < dp(120)) { + h = dp(120); float hScale = (float) attribute.h / h; if (attribute.w / hScale < photoWidth) { w = (int) (attribute.w / hScale); @@ -2115,44 +2487,44 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (w == 0 || h == 0) { - w = h = AndroidUtilities.dp(150); + w = h = dp(150); } if (messageObject.type == 3) { - if (w < infoWidth + AndroidUtilities.dp(16 + 24)) { - w = infoWidth + AndroidUtilities.dp(16 + 24); + if (w < infoWidth + dp(16 + 24)) { + w = infoWidth + dp(16 + 24); } } - availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); + availableTimeWidth = maxPhotoWidth - dp(14); measureTime(messageObject); - int timeWidthTotal = timeWidth + AndroidUtilities.dp(14 + (messageObject.isOutOwner() ? 20 : 0)); + int timeWidthTotal = timeWidth + dp(14 + (messageObject.isOutOwner() ? 20 : 0)); if (w < timeWidthTotal) { w = timeWidthTotal; } if (messageObject.isSecretPhoto()) { - if (AndroidUtilities.isTablet()) { - w = h = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); + if (isTablet()) { + w = h = (int) (getMinTabletSide() * 0.5f); } else { - w = h = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f); + w = h = (int) (Math.min(displaySize.x, displaySize.y) * 0.5f); } } photoWidth = w; photoHeight = h; - backgroundWidth = w + AndroidUtilities.dp(12); + backgroundWidth = w + dp(12); if (!mediaBackground) { - backgroundWidth += AndroidUtilities.dp(9); + backgroundWidth += dp(9); } if (messageObject.caption != null) { try { - captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), photoWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (captionLayout != null && captionLayout.getLineCount() > 0) { + captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), photoWidth - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (captionLayout.getLineCount() > 0) { captionHeight = captionLayout.getHeight(); - additionHeight += captionHeight + AndroidUtilities.dp(9); + additionHeight += captionHeight + dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); - if (photoWidth - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { - additionHeight += AndroidUtilities.dp(14); + if (photoWidth - dp(8) - lastLineWidth < timeWidthTotal) { + additionHeight += dp(14); } } } catch (Exception e) { @@ -2160,7 +2532,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } - currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); + currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / density), (int) (h / density)); if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == 3 || messageObject.type == 8) { if (messageObject.isSecretPhoto()) { currentPhotoFilter += "_b2"; @@ -2187,14 +2559,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoExist = false; } if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - if (allowedToSetPhoto || ImageLoader.getInstance().getImageFromMemory(currentPhotoObject.location, null, currentPhotoFilter) != null) { - allowedToSetPhoto = true; - photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); - } else if (currentPhotoObjectThumb != null) { - photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); - } else { - photoImage.setImageBitmap((Drawable) null); - } + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); } else { photoNotSet = true; if (currentPhotoObjectThumb != null) { @@ -2229,18 +2594,36 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); } } - super.setMessageObject(messageObject); + setMessageObjectInternal(messageObject); if (drawForwardedName) { - namesOffset += AndroidUtilities.dp(5); + namesOffset += dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { - namesOffset += AndroidUtilities.dp(7); + namesOffset += dp(7); } invalidate(); - photoImage.setImageCoords(0, AndroidUtilities.dp(7) + namesOffset, photoWidth, photoHeight); - totalHeight = photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight; + photoImage.setImageCoords(0, dp(7) + namesOffset, photoWidth, photoHeight); + totalHeight = photoHeight + dp(14) + namesOffset + additionHeight; + } + if (captionLayout == null && messageObject.caption != null && messageObject.type != 13) { + try { + int width = backgroundWidth - AndroidUtilities.dp(31); + captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), width - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (captionLayout.getLineCount() > 0) { + int timeWidthTotal = timeWidth + (messageObject.isOutOwner() ? dp(20) : 0); + captionHeight = captionLayout.getHeight(); + totalHeight += captionHeight + dp(9); + float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); + if (width - dp(8) - lastLineWidth < timeWidthTotal) { + totalHeight += dp(14); + captionHeight += dp(14); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } } botButtons.clear(); @@ -2249,16 +2632,16 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } if (messageObject.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) { int rows = messageObject.messageOwner.reply_markup.rows.size(); - substractBackgroundHeight = keyboardHeight = AndroidUtilities.dp(44 + 4) * rows + AndroidUtilities.dp(1); + substractBackgroundHeight = keyboardHeight = dp(44 + 4) * rows + dp(1); widthForButtons = backgroundWidth; boolean fullWidth = false; if (messageObject.wantedBotKeyboardWidth > widthForButtons) { - int maxButtonWidth = -AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 62 : 10); - if (AndroidUtilities.isTablet()) { - maxButtonWidth += AndroidUtilities.getMinTabletSide(); + int maxButtonWidth = -dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 62 : 10); + if (isTablet()) { + maxButtonWidth += getMinTabletSide(); } else { - maxButtonWidth += Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); + maxButtonWidth += Math.min(displaySize.x, displaySize.y); } widthForButtons = Math.max(backgroundWidth, Math.min(messageObject.wantedBotKeyboardWidth, maxButtonWidth)); fullWidth = true; @@ -2268,7 +2651,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele for (int a = 0; a < rows; a++) { TLRPC.TL_keyboardButtonRow row = messageObject.messageOwner.reply_markup.rows.get(a); int buttonsCount = row.buttons.size(); - int buttonWidth = (widthForButtons - (AndroidUtilities.dp(5) * (buttonsCount - 1)) - AndroidUtilities.dp(!fullWidth && mediaBackground ? 0 : 9) - AndroidUtilities.dp(2)) / buttonsCount; + int buttonWidth = (widthForButtons - (dp(5) * (buttonsCount - 1)) - dp(!fullWidth && mediaBackground ? 0 : 9) - dp(2)) / buttonsCount; for (int b = 0; b < row.buttons.size(); b++) { BotButton botButton = new BotButton(); botButton.button = row.buttons.get(b); @@ -2282,13 +2665,13 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele botButton.lastUpdateTime = System.currentTimeMillis(); } botButtonsByData.put(key, botButton); - botButton.x = b * (buttonWidth + AndroidUtilities.dp(5)); - botButton.y = a * AndroidUtilities.dp(44 + 4) + AndroidUtilities.dp(5); + botButton.x = b * (buttonWidth + dp(5)); + botButton.y = a * dp(44 + 4) + dp(5); botButton.width = buttonWidth; - botButton.height = AndroidUtilities.dp(44); - CharSequence caption = Emoji.replaceEmoji(botButton.button.text, botButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false); - caption = TextUtils.ellipsize(caption, botButtonPaint, buttonWidth - AndroidUtilities.dp(10), TextUtils.TruncateAt.END); - botButton.caption = new StaticLayout(caption, botButtonPaint, buttonWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + botButton.height = dp(44); + CharSequence buttonText = Emoji.replaceEmoji(botButton.button.text, botButtonPaint.getFontMetricsInt(), dp(15), false); + buttonText = TextUtils.ellipsize(buttonText, botButtonPaint, buttonWidth - dp(10), TextUtils.TruncateAt.END); + botButton.title = new StaticLayout(buttonText, botButtonPaint, buttonWidth - dp(10), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); botButtons.add(botButton); if (b == row.buttons.size() - 1) { maxButtonsWidth = Math.max(maxButtonsWidth, botButton.x + botButton.width); @@ -2310,122 +2693,159 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight + keyboardHeight); } + @SuppressLint("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); + if (currentMessageObject == null) { + super.onLayout(changed, left, top, right, bottom); + return; + } + + if (changed || !wasLayout) { + layoutWidth = getMeasuredWidth(); + layoutHeight = getMeasuredHeight() - substractBackgroundHeight; + if (timeTextWidth < 0) { + timeTextWidth = dp(10); + } + timeLayout = new StaticLayout(currentTimeString, timePaint, timeTextWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (!mediaBackground) { + if (!currentMessageObject.isOutOwner()) { + timeX = backgroundWidth - dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); + } else { + timeX = layoutWidth - timeWidth - dp(38.5f); + } + } else { + if (!currentMessageObject.isOutOwner()) { + timeX = backgroundWidth - dp(4) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); + } else { + timeX = layoutWidth - timeWidth - dp(42.0f); + } + } + + if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + viewsLayout = new StaticLayout(currentViewsString, timePaint, viewsTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } else { + viewsLayout = null; + } + + if (isAvatarVisible) { + avatarImage.setImageCoords(dp(6), layoutHeight - dp(44), dp(42), dp(42)); + } + + wasLayout = true; + } if (currentMessageObject.type == 0) { - textY = AndroidUtilities.dp(10) + namesOffset; + textY = dp(10) + namesOffset; } if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { if (currentMessageObject.isOutOwner()) { - seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(57); - buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); - timeAudioX = layoutWidth - backgroundWidth + AndroidUtilities.dp(67); + seekBarX = layoutWidth - backgroundWidth + dp(57); + buttonX = layoutWidth - backgroundWidth + dp(14); + timeAudioX = layoutWidth - backgroundWidth + dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { - seekBarX = AndroidUtilities.dp(114); - buttonX = AndroidUtilities.dp(71); - timeAudioX = AndroidUtilities.dp(124); + seekBarX = dp(114); + buttonX = dp(71); + timeAudioX = dp(124); } else { - seekBarX = AndroidUtilities.dp(66); - buttonX = AndroidUtilities.dp(23); - timeAudioX = AndroidUtilities.dp(76); + seekBarX = dp(66); + buttonX = dp(23); + timeAudioX = dp(76); } } if (hasLinkPreview) { - seekBarX += AndroidUtilities.dp(10); - buttonX += AndroidUtilities.dp(10); - timeAudioX += AndroidUtilities.dp(10); + seekBarX += dp(10); + buttonX += dp(10); + timeAudioX += dp(10); } - seekBarWaveform.setSize(backgroundWidth - AndroidUtilities.dp(92 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); - seekBar.setSize(backgroundWidth - AndroidUtilities.dp(72 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); - seekBarY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; - buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); + seekBarWaveform.setSize(backgroundWidth - dp(92 + (hasLinkPreview ? 10 : 0)), dp(30)); + seekBar.setSize(backgroundWidth - dp(72 + (hasLinkPreview ? 10 : 0)), dp(30)); + seekBarY = dp(13) + namesOffset + mediaOffsetY; + buttonY = dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { - seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(56); - buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); - timeAudioX = layoutWidth - backgroundWidth + AndroidUtilities.dp(67); + seekBarX = layoutWidth - backgroundWidth + dp(56); + buttonX = layoutWidth - backgroundWidth + dp(14); + timeAudioX = layoutWidth - backgroundWidth + dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { - seekBarX = AndroidUtilities.dp(113); - buttonX = AndroidUtilities.dp(71); - timeAudioX = AndroidUtilities.dp(124); + seekBarX = dp(113); + buttonX = dp(71); + timeAudioX = dp(124); } else { - seekBarX = AndroidUtilities.dp(65); - buttonX = AndroidUtilities.dp(23); - timeAudioX = AndroidUtilities.dp(76); + seekBarX = dp(65); + buttonX = dp(23); + timeAudioX = dp(76); } } if (hasLinkPreview) { - seekBarX += AndroidUtilities.dp(10); - buttonX += AndroidUtilities.dp(10); - timeAudioX += AndroidUtilities.dp(10); + seekBarX += dp(10); + buttonX += dp(10); + timeAudioX += dp(10); } - seekBar.setSize(backgroundWidth - AndroidUtilities.dp(65 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); - seekBarY = AndroidUtilities.dp(29) + namesOffset + mediaOffsetY; - buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); + seekBar.setSize(backgroundWidth - dp(65 + (hasLinkPreview ? 10 : 0)), dp(30)); + seekBarY = dp(29) + namesOffset + mediaOffsetY; + buttonY = dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT && !drawPhotoImage) { if (currentMessageObject.isOutOwner()) { - buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); + buttonX = layoutWidth - backgroundWidth + dp(14); } else { if (isChat && currentMessageObject.isFromUser()) { - buttonX = AndroidUtilities.dp(71); + buttonX = dp(71); } else { - buttonX = AndroidUtilities.dp(23); + buttonX = dp(23); } } if (hasLinkPreview) { - buttonX += AndroidUtilities.dp(10); + buttonX += dp(10); } - buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); - photoImage.setImageCoords(buttonX - AndroidUtilities.dp(10), buttonY - AndroidUtilities.dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); + buttonY = dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); + photoImage.setImageCoords(buttonX - dp(10), buttonY - dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); } else if (currentMessageObject.type == 12) { int x; if (currentMessageObject.isOutOwner()) { - x = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); + x = layoutWidth - backgroundWidth + dp(14); } else { if (isChat && currentMessageObject.isFromUser()) { - x = AndroidUtilities.dp(72); + x = dp(72); } else { - x = AndroidUtilities.dp(23); + x = dp(23); } } - photoImage.setImageCoords(x, AndroidUtilities.dp(13) + namesOffset, AndroidUtilities.dp(44), AndroidUtilities.dp(44)); + photoImage.setImageCoords(x, dp(13) + namesOffset, dp(44), dp(44)); } else { int x; if (currentMessageObject.isOutOwner()) { if (mediaBackground) { - x = layoutWidth - backgroundWidth - AndroidUtilities.dp(3); + x = layoutWidth - backgroundWidth - dp(3); } else { - x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); + x = layoutWidth - backgroundWidth + dp(6); } } else { if (isChat && currentMessageObject.isFromUser()) { - x = AndroidUtilities.dp(63); + x = dp(63); } else { - x = AndroidUtilities.dp(15); + x = dp(15); } } photoImage.setImageCoords(x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); - buttonX = (int) (x + (photoImage.getImageWidth() - AndroidUtilities.dp(48)) / 2.0f); - buttonY = (int) (AndroidUtilities.dp(7) + (photoImage.getImageHeight() - AndroidUtilities.dp(48)) / 2.0f) + namesOffset; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); - deleteProgressRect.set(buttonX + AndroidUtilities.dp(3), buttonY + AndroidUtilities.dp(3), buttonX + AndroidUtilities.dp(45), buttonY + AndroidUtilities.dp(45)); + buttonX = (int) (x + (photoImage.getImageWidth() - dp(48)) / 2.0f); + buttonY = (int) (dp(7) + (photoImage.getImageHeight() - dp(48)) / 2.0f) + namesOffset; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(48), buttonY + dp(48)); + deleteProgressRect.set(buttonX + dp(3), buttonY + dp(3), buttonX + dp(45), buttonY + dp(45)); } } - @Override - protected void drawContent(Canvas canvas) { + private void drawContent(Canvas canvas) { if (needNewVisiblePart && currentMessageObject.type == 0) { getLocalVisibleRect(scrollRect); @@ -2441,12 +2861,12 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele boolean imageDrawn = false; if (currentMessageObject.type == 0 && currentMessageObject.textLayoutBlocks != null && !currentMessageObject.textLayoutBlocks.isEmpty()) { if (currentMessageObject.isOutOwner()) { - textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); + textX = currentBackgroundDrawable.getBounds().left + dp(11); } else { - textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(17); + textX = currentBackgroundDrawable.getBounds().left + dp(17); } - textY = AndroidUtilities.dp(10) + namesOffset; + textY = dp(10) + namesOffset; if (firstVisibleBlockNum >= 0) { for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { @@ -2471,32 +2891,32 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } if (hasLinkPreview) { - int startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8); - int linkX = textX + AndroidUtilities.dp(1); + int startY = textY + currentMessageObject.textHeight + dp(8); + int linkX = textX + dp(1); int linkPreviewY = startY; int smallImageStartY = 0; replyLinePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_WEB_PREVIEW_LINE_COLOR : Theme.MSG_IN_WEB_PREVIEW_LINE_COLOR); - canvas.drawRect(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), replyLinePaint); + canvas.drawRect(linkX, linkPreviewY - dp(3), linkX + dp(2), linkPreviewY + linkPreviewHeight + dp(3), replyLinePaint); - if (siteCaptionLayout != null) { + if (siteNameLayout != null) { replyNamePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_SITE_NAME_TEXT_COLOR : Theme.MSG_IN_SITE_NAME_TEXT_COLOR); canvas.save(); - canvas.translate(linkX + AndroidUtilities.dp(10), linkPreviewY - AndroidUtilities.dp(3)); - siteCaptionLayout.draw(canvas); + canvas.translate(linkX + dp(10), linkPreviewY - dp(3)); + siteNameLayout.draw(canvas); canvas.restore(); - linkPreviewY += siteCaptionLayout.getLineBottom(siteCaptionLayout.getLineCount() - 1); + linkPreviewY += siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); } replyNamePaint.setColor(Theme.MSG_TEXT_COLOR); replyTextPaint.setColor(Theme.MSG_TEXT_COLOR); if (titleLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); + linkPreviewY += dp(2); } - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + smallImageStartY = linkPreviewY - dp(1); canvas.save(); - canvas.translate(linkX + AndroidUtilities.dp(10) + titleX, linkPreviewY - AndroidUtilities.dp(3)); + canvas.translate(linkX + dp(10) + titleX, linkPreviewY - dp(3)); titleLayout.draw(canvas); canvas.restore(); linkPreviewY += titleLayout.getLineBottom(titleLayout.getLineCount() - 1); @@ -2504,13 +2924,13 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (authorLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); + linkPreviewY += dp(2); } if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + smallImageStartY = linkPreviewY - dp(1); } canvas.save(); - canvas.translate(linkX + AndroidUtilities.dp(10) + authorX, linkPreviewY - AndroidUtilities.dp(3)); + canvas.translate(linkX + dp(10) + authorX, linkPreviewY - dp(3)); authorLayout.draw(canvas); canvas.restore(); linkPreviewY += authorLayout.getLineBottom(authorLayout.getLineCount() - 1); @@ -2518,14 +2938,14 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (descriptionLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); + linkPreviewY += dp(2); } if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + smallImageStartY = linkPreviewY - dp(1); } - descriptionY = linkPreviewY - AndroidUtilities.dp(3); + descriptionY = linkPreviewY - dp(3); canvas.save(); - canvas.translate(linkX + AndroidUtilities.dp(10) + descriptionX, descriptionY); + canvas.translate(linkX + dp(10) + descriptionX, descriptionY); if (pressedLink != null && linkBlockNum == -10) { for (int b = 0; b < urlPath.size(); b++) { canvas.drawPath(urlPath.get(b), urlPaint); @@ -2538,31 +2958,31 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (drawPhotoImage) { if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); + linkPreviewY += dp(2); } if (isSmallImage) { - photoImage.setImageCoords(linkX + backgroundWidth - AndroidUtilities.dp(81), smallImageStartY, photoImage.getImageWidth(), photoImage.getImageHeight()); + photoImage.setImageCoords(linkX + backgroundWidth - dp(81), smallImageStartY, photoImage.getImageWidth(), photoImage.getImageHeight()); } else { - photoImage.setImageCoords(linkX + AndroidUtilities.dp(10), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); + photoImage.setImageCoords(linkX + dp(10), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); if (drawImageButton) { - int size = AndroidUtilities.dp(48); + int size = dp(48); buttonX = (int) (photoImage.getImageX() + (photoImage.getImageWidth() - size) / 2.0f); buttonY = (int) (photoImage.getImageY() + (photoImage.getImageHeight() - size) / 2.0f); - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); + radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(48), buttonY + dp(48)); } } imageDrawn = photoImage.draw(canvas); - if (durationLayout != null) { - int x = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(8) - durationWidth; - int y = photoImage.getImageY() + photoImage.getImageHeight() - AndroidUtilities.dp(19); - Theme.timeBackgroundDrawable.setBounds(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + durationWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(14.5f)); + if (videoInfoLayout != null) { + int x = photoImage.getImageX() + photoImage.getImageWidth() - dp(8) - durationWidth; + int y = photoImage.getImageY() + photoImage.getImageHeight() - dp(19); + Theme.timeBackgroundDrawable.setBounds(x - dp(4), y - dp(1.5f), x + durationWidth + dp(4), y + dp(14.5f)); Theme.timeBackgroundDrawable.draw(canvas); canvas.save(); canvas.translate(x, y); - durationLayout.draw(canvas); + videoInfoLayout.draw(canvas); canvas.restore(); } } @@ -2590,7 +3010,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele float progress = Math.max(0, (long) currentMessageObject.messageOwner.destroyTime * 1000 - msTime) / (currentMessageObject.messageOwner.ttl * 1000.0f); canvas.drawArc(deleteProgressRect, -90, -360 * progress, true, deleteProgressPaint); if (progress != 0) { - int offset = AndroidUtilities.dp(2); + int offset = dp(2); invalidate((int) deleteProgressRect.left - offset, (int) deleteProgressRect.top - offset, (int) deleteProgressRect.right + offset * 2, (int) deleteProgressRect.bottom + offset * 2); } updateSecretTimeText(currentMessageObject); @@ -2612,7 +3032,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele radialProgress.draw(canvas); canvas.save(); - canvas.translate(timeAudioX + songX, AndroidUtilities.dp(13) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX + songX, dp(13) + namesOffset + mediaOffsetY); songLayout.draw(canvas); canvas.restore(); @@ -2621,14 +3041,14 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele canvas.translate(seekBarX, seekBarY); seekBar.draw(canvas); } else { - canvas.translate(timeAudioX + performerX, AndroidUtilities.dp(35) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX + performerX, dp(35) + namesOffset + mediaOffsetY); performerLayout.draw(canvas); } canvas.restore(); canvas.save(); - canvas.translate(timeAudioX, AndroidUtilities.dp(57) + namesOffset + mediaOffsetY); - timeLayout.draw(canvas); + canvas.translate(timeAudioX, dp(57) + namesOffset + mediaOffsetY); + durationLayout.draw(canvas); canvas.restore(); Drawable menuDrawable; @@ -2637,7 +3057,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } else { menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; } - setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - AndroidUtilities.dp(5)); + setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - dp(5)); menuDrawable.draw(canvas); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { if (currentMessageObject.isOutOwner()) { @@ -2651,7 +3071,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele canvas.save(); if (useSeekBarWaweform) { - canvas.translate(seekBarX + AndroidUtilities.dp(13), seekBarY); + canvas.translate(seekBarX + dp(13), seekBarY); seekBarWaveform.draw(canvas); } else { canvas.translate(seekBarX, seekBarY); @@ -2660,109 +3080,102 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele canvas.restore(); canvas.save(); - canvas.translate(timeAudioX, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY); - timeLayout.draw(canvas); + canvas.translate(timeAudioX, dp(44) + namesOffset + mediaOffsetY); + durationLayout.draw(canvas); canvas.restore(); if (currentMessageObject.type != 0 && currentMessageObject.messageOwner.to_id.channel_id == 0 && currentMessageObject.isContentUnread()) { docBackPaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_VOICE_SEEKBAR_FILL_COLOR : Theme.MSG_IN_VOICE_SEEKBAR_FILL_COLOR); - canvas.drawCircle(timeAudioX + timeWidthAudio + AndroidUtilities.dp(6), AndroidUtilities.dp(51) + namesOffset + mediaOffsetY, AndroidUtilities.dp(3), docBackPaint); + canvas.drawCircle(timeAudioX + timeWidthAudio + dp(6), dp(51) + namesOffset + mediaOffsetY, dp(3), docBackPaint); } } + if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { - setDrawableBounds(Theme.docMenuDrawable[3], otherX = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(14), otherY = photoImage.getImageY() + AndroidUtilities.dp(8.1f)); + setDrawableBounds(Theme.docMenuDrawable[3], otherX = photoImage.getImageX() + photoImage.getImageWidth() - dp(14), otherY = photoImage.getImageY() + dp(8.1f)); Theme.docMenuDrawable[3].draw(canvas); } - if (captionLayout != null) { - canvas.save(); - canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); - if (pressedLink != null) { - for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), urlPaint); - } - } - try { - captionLayout.draw(canvas); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - canvas.restore(); - } if (infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { infoPaint.setColor(Theme.MSG_MEDIA_INFO_TEXT_COLOR); - setDrawableBounds(Theme.timeBackgroundDrawable, photoImage.getImageX() + AndroidUtilities.dp(4), photoImage.getImageY() + AndroidUtilities.dp(4), infoWidth + AndroidUtilities.dp(8), AndroidUtilities.dp(16.5f)); + setDrawableBounds(Theme.timeBackgroundDrawable, photoImage.getImageX() + dp(4), photoImage.getImageY() + dp(4), infoWidth + dp(8), dp(16.5f)); Theme.timeBackgroundDrawable.draw(canvas); canvas.save(); - canvas.translate(photoImage.getImageX() + AndroidUtilities.dp(8), photoImage.getImageY() + AndroidUtilities.dp(5.5f)); + canvas.translate(photoImage.getImageX() + dp(8), photoImage.getImageY() + dp(5.5f)); infoLayout.draw(canvas); canvas.restore(); } - } else if (currentMessageObject.type == 4) { - if (captionLayout != null) { - if (currentMessageObject.isOutOwner()) { - locationTitlePaint.setColor(Theme.MSG_OUT_VENUE_NAME_TEXT_COLOR); - locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_OUT_VENUE_INFO_TEXT_COLOR); - } else { - locationTitlePaint.setColor(Theme.MSG_IN_VENUE_NAME_TEXT_COLOR); - locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_IN_VENUE_INFO_TEXT_COLOR); - } + } else { + if (currentMessageObject.type == 4) { + if (docTitleLayout != null) { + if (currentMessageObject.isOutOwner()) { + locationTitlePaint.setColor(Theme.MSG_OUT_VENUE_NAME_TEXT_COLOR); + locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_OUT_VENUE_INFO_TEXT_COLOR); + } else { + locationTitlePaint.setColor(Theme.MSG_IN_VENUE_NAME_TEXT_COLOR); + locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_IN_VENUE_INFO_TEXT_COLOR); + } - canvas.save(); - canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); - captionLayout.draw(canvas); - canvas.restore(); - - if (infoLayout != null) { canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + captionLayout.getLineBottom(captionLayout.getLineCount() - 1) + AndroidUtilities.dp(13)); - infoLayout.draw(canvas); + canvas.translate(docTitleOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + dp(10), photoImage.getImageY() + dp(8)); + docTitleLayout.draw(canvas); canvas.restore(); - } - } - } else if (currentMessageObject.type == 8) { - if (captionLayout != null) { - canvas.save(); - canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); - if (pressedLink != null) { - for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), urlPaint); + + if (infoLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(10), photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + dp(13)); + infoLayout.draw(canvas); + canvas.restore(); } } - try { - captionLayout.draw(canvas); - } catch (Exception e) { - FileLog.e("tmessages", e); + } else if (currentMessageObject.type == 12) { + contactNamePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_NAME_TEXT_COLOR : Theme.MSG_IN_CONTACT_NAME_TEXT_COLOR); + contactPhonePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_PHONE_TEXT_COLOR : Theme.MSG_IN_CONTACT_PHONE_TEXT_COLOR); + if (titleLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(9), dp(16) + namesOffset); + titleLayout.draw(canvas); + canvas.restore(); + } + if (docTitleLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(9), dp(39) + namesOffset); + docTitleLayout.draw(canvas); + canvas.restore(); } - canvas.restore(); - } - } else if (currentMessageObject.type == 12) { - contactNamePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_NAME_TEXT_COLOR : Theme.MSG_IN_CONTACT_NAME_TEXT_COLOR); - contactPhonePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_PHONE_TEXT_COLOR : Theme.MSG_IN_CONTACT_PHONE_TEXT_COLOR); - if (titleLayout != null) { - canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(9), AndroidUtilities.dp(16) + namesOffset); - titleLayout.draw(canvas); - canvas.restore(); - } - if (captionLayout != null) { - canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(9), AndroidUtilities.dp(39) + namesOffset); - captionLayout.draw(canvas); - canvas.restore(); - } - Drawable menuDrawable; - if (currentMessageObject.isOutOwner()) { - menuDrawable = Theme.docMenuDrawable[1]; - } else { - menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + Drawable menuDrawable; + if (currentMessageObject.isOutOwner()) { + menuDrawable = Theme.docMenuDrawable[1]; + } else { + menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + } + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(48), otherY = photoImage.getImageY() - dp(5)); + menuDrawable.draw(canvas); } - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(48), otherY = photoImage.getImageY() - AndroidUtilities.dp(5)); - menuDrawable.draw(canvas); } + + if (captionLayout != null) { + canvas.save(); + if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { + canvas.translate(captionX = photoImage.getImageX() + dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + dp(6)); + } else {//TODO + canvas.translate(captionX = currentBackgroundDrawable.getBounds().left + dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - dp(10)); + } + if (pressedLink != null) { + for (int b = 0; b < urlPath.size(); b++) { + canvas.drawPath(urlPath.get(b), urlPaint); + } + } + try { + captionLayout.draw(canvas); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + canvas.restore(); + } + if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { Drawable menuDrawable; if (currentMessageObject.isOutOwner()) { @@ -2782,14 +3195,14 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele int subtitleY; if (drawPhotoImage) { if (currentMessageObject.type == 0) { - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(56), otherY = photoImage.getImageY() + AndroidUtilities.dp(1)); + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(56), otherY = photoImage.getImageY() + dp(1)); } else { - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(40), otherY = photoImage.getImageY() + AndroidUtilities.dp(1)); + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(40), otherY = photoImage.getImageY() + dp(1)); } - x = photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10); - titleY = photoImage.getImageY() + AndroidUtilities.dp(8); - subtitleY = photoImage.getImageY() + captionLayout.getLineBottom(captionLayout.getLineCount() - 1) + AndroidUtilities.dp(13); + x = photoImage.getImageX() + photoImage.getImageWidth() + dp(10); + titleY = photoImage.getImageY() + dp(8); + subtitleY = photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + dp(13); if (buttonState >= 0 && buttonState < 4) { if (!imageDrawn) { int image = buttonState; @@ -2806,7 +3219,7 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (!imageDrawn) { rect.set(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX() + photoImage.getImageWidth(), photoImage.getImageY() + photoImage.getImageHeight()); - canvas.drawRoundRect(rect, AndroidUtilities.dp(3), AndroidUtilities.dp(3), docBackPaint); + canvas.drawRoundRect(rect, dp(3), dp(3), docBackPaint); if (currentMessageObject.isOutOwner()) { radialProgress.setProgressColor(isDrawSelectedBackground() ? Theme.MSG_OUT_FILE_PROGRESS_SELECTED_COLOR : Theme.MSG_OUT_FILE_PROGRESS_COLOR); } else { @@ -2819,10 +3232,10 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele radialProgress.setProgressColor(Theme.MSG_MEDIA_PROGRESS_COLOR); } } else { - setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - AndroidUtilities.dp(5)); - x = buttonX + AndroidUtilities.dp(53); - titleY = buttonY + AndroidUtilities.dp(4); - subtitleY = buttonY + AndroidUtilities.dp(27); + setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - dp(5)); + x = buttonX + dp(53); + titleY = buttonY + dp(4); + subtitleY = buttonY + dp(27); if (currentMessageObject.isOutOwner()) { radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_OUT_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_OUT_AUDIO_PROGRESS_COLOR); } else { @@ -2832,10 +3245,10 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele menuDrawable.draw(canvas); try { - if (captionLayout != null) { + if (docTitleLayout != null) { canvas.save(); - canvas.translate(x + nameOffsetX, titleY); - captionLayout.draw(canvas); + canvas.translate(x + docTitleOffsetX, titleY); + docTitleLayout.draw(canvas); canvas.restore(); } } catch (Exception e) { @@ -2860,37 +3273,37 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele if (!botButtons.isEmpty()) { int addX; if (currentMessageObject.isOutOwner()) { - addX = getMeasuredWidth() - widthForButtons - AndroidUtilities.dp(10); + addX = getMeasuredWidth() - widthForButtons - dp(10); } else { - addX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(mediaBackground ? 1 : 7); + addX = currentBackgroundDrawable.getBounds().left + dp(mediaBackground ? 1 : 7); } for (int a = 0; a < botButtons.size(); a++) { BotButton button = botButtons.get(a); - int y = button.y + layoutHeight - AndroidUtilities.dp(2); + int y = button.y + layoutHeight - dp(2); Theme.systemDrawable.setColorFilter(a == pressedBotButton ? Theme.colorPressedFilter : Theme.colorFilter); Theme.systemDrawable.setBounds(button.x + addX, y, button.x + addX + button.width, y + button.height); Theme.systemDrawable.draw(canvas); canvas.save(); - canvas.translate(button.x + addX + AndroidUtilities.dp(5), y + (AndroidUtilities.dp(44) - button.caption.getLineBottom(button.caption.getLineCount() - 1)) / 2); - button.caption.draw(canvas); + canvas.translate(button.x + addX + dp(5), y + (dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2); + button.title.draw(canvas); canvas.restore(); if (button.button instanceof TLRPC.TL_keyboardButtonUrl) { - int x = button.x + button.width - AndroidUtilities.dp(3) - Theme.botLink.getIntrinsicWidth() + addX; - setDrawableBounds(Theme.botLink, x, y + AndroidUtilities.dp(3)); + int x = button.x + button.width - dp(3) - Theme.botLink.getIntrinsicWidth() + addX; + setDrawableBounds(Theme.botLink, x, y + dp(3)); Theme.botLink.draw(canvas); } else if (button.button instanceof TLRPC.TL_keyboardButtonSwitchInline) { - int x = button.x + button.width - AndroidUtilities.dp(3) - Theme.botInline.getIntrinsicWidth() + addX; - setDrawableBounds(Theme.botInline, x, y + AndroidUtilities.dp(3)); + int x = button.x + button.width - dp(3) - Theme.botInline.getIntrinsicWidth() + addX; + setDrawableBounds(Theme.botInline, x, y + dp(3)); Theme.botInline.draw(canvas); } else if (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation) { boolean drawProgress = button.button instanceof TLRPC.TL_keyboardButtonCallback && SendMessagesHelper.getInstance().isSendingCallback(currentMessageObject, button.button) || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation && SendMessagesHelper.getInstance().isSendingCurrentLocation(currentMessageObject, button.button); if (drawProgress || !drawProgress && button.progressAlpha != 0) { botProgressPaint.setAlpha(Math.min(255, (int) (button.progressAlpha * 255))); - int x = button.x + button.width - AndroidUtilities.dp(9 + 3) + addX; - rect.set(x, y + AndroidUtilities.dp(4), x + AndroidUtilities.dp(8), y + AndroidUtilities.dp(8 + 4)); + int x = button.x + button.width - dp(9 + 3) + addX; + rect.set(x, y + dp(4), x + dp(8), y + dp(8 + 4)); canvas.drawArc(rect, button.angle, 220, false, botProgressPaint); - invalidate((int) rect.left - AndroidUtilities.dp(2), (int) rect.top - AndroidUtilities.dp(2), (int) rect.right + AndroidUtilities.dp(2), (int) rect.bottom + AndroidUtilities.dp(2)); + invalidate((int) rect.left - dp(2), (int) rect.top - dp(2), (int) rect.right + dp(2), (int) rect.bottom + dp(2)); long newTime = System.currentTimeMillis(); if (Math.abs(button.lastUpdateTime - System.currentTimeMillis()) < 1000) { long delta = (newTime - button.lastUpdateTime); @@ -2959,26 +3372,25 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele return null; } - @Override - protected int getMaxNameWidth() { + private int getMaxNameWidth() { if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { int maxWidth; - if (AndroidUtilities.isTablet()) { + if (isTablet()) { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(42); + maxWidth = getMinTabletSide() - dp(42); } else { - maxWidth = AndroidUtilities.getMinTabletSide(); + maxWidth = getMinTabletSide(); } } else { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(42); + maxWidth = Math.min(displaySize.x, displaySize.y) - dp(42); } else { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); + maxWidth = Math.min(displaySize.x, displaySize.y); } } - return maxWidth - backgroundWidth - AndroidUtilities.dp(57); + return maxWidth - backgroundWidth - dp(57); } - return super.getMaxNameWidth(); + return backgroundWidth - dp(mediaBackground ? 22 : 31); } public void updateButtonState(boolean animated) { @@ -3159,20 +3571,6 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } - public void setAllowedToSetPhoto(boolean value) { - if (allowedToSetPhoto == value) { - return; - } - if (currentMessageObject != null && currentMessageObject.type == 1) { - allowedToSetPhoto = value; - if (value) { - MessageObject temp = currentMessageObject; - currentMessageObject = null; - setMessageObject(temp); - } - } - } - private void didPressedButton(boolean animated) { if (buttonState == 0) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { @@ -3340,4 +3738,735 @@ public class ChatMessageCell extends ChatBaseCell implements SeekBar.SeekBarDele } } } + + public void setDelegate(ChatMessageCellDelegate chatMessageCellDelegate) { + delegate = chatMessageCellDelegate; + } + + public void setAllowAssistant(boolean value) { + allowAssistant = value; + } + + private void measureTime(MessageObject messageObject) { + boolean hasSign = !messageObject.isOutOwner() && messageObject.messageOwner.from_id > 0 && messageObject.messageOwner.post; + TLRPC.User signUser = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); + if (hasSign && signUser == null) { + hasSign = false; + } + String timeString; + TLRPC.User author = null; + if (currentMessageObject.isFromUser()) { + author = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); + } + if (messageObject.messageOwner.via_bot_id == 0 && messageObject.messageOwner.via_bot_name == null && (author == null || !author.bot) && (messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_EDITED) != 0) { + timeString = LocaleController.getString("EditedMessage", R.string.EditedMessage) + " " + LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); + } else { + timeString = LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); + } + if (hasSign) { + currentTimeString = ", " + timeString; + } else { + currentTimeString = timeString; + } + timeTextWidth = timeWidth = (int) Math.ceil(timePaint.measureText(currentTimeString)); + if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + currentViewsString = String.format("%s", LocaleController.formatShortNumber(Math.max(1, messageObject.messageOwner.views), null)); + viewsTextWidth = (int) Math.ceil(timePaint.measureText(currentViewsString)); + timeWidth += viewsTextWidth + Theme.viewsCountDrawable[0].getIntrinsicWidth() + dp(10); + } + if (hasSign) { + if (availableTimeWidth == 0) { + availableTimeWidth = dp(1000); + } + CharSequence name = ContactsController.formatName(signUser.first_name, signUser.last_name).replace('\n', ' '); + int widthForSign = availableTimeWidth - timeWidth; + int width = (int) Math.ceil(timePaint.measureText(name, 0, name.length())); + if (width > widthForSign) { + name = TextUtils.ellipsize(name, timePaint, widthForSign, TextUtils.TruncateAt.END); + width = widthForSign; + } + currentTimeString = name + currentTimeString; + timeTextWidth += width; + timeWidth += width; + } + } + + private boolean isDrawSelectedBackground() { + return isPressed() && isCheckPressed || !isCheckPressed && isPressed || isHighlighted; + } + + private boolean checkNeedDrawShareButton(MessageObject messageObject) { + if (messageObject.type == 13) { + return false; + } else if (messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.channel_id != 0 && !messageObject.isOut()) { + return true; + } else if (messageObject.isFromUser()) { + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty || messageObject.messageOwner.media == null || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && !(messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage)) { + return false; + } + TLRPC.User user = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); + if (user != null && user.bot) { + return true; + } + if (messageObject.isMegagroup() && !messageObject.isOut()) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.messageOwner.to_id.channel_id); + return chat != null && chat.username != null && chat.username.length() > 0 && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo); + } + } else if (messageObject.messageOwner.from_id < 0 || messageObject.messageOwner.post) { + if (messageObject.messageOwner.to_id.channel_id != 0 && (messageObject.messageOwner.via_bot_id == 0 && messageObject.messageOwner.reply_to_msg_id == 0 || messageObject.type != 13)) { + return true; + } + } + return false; + } + + private void setMessageObjectInternal(MessageObject messageObject) { + if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + if (currentMessageObject.isContentUnread() && !currentMessageObject.isOut()) { + MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, false); + currentMessageObject.setContentIsRead(); + } else if (!currentMessageObject.viewsReloaded) { + MessagesController.getInstance().addToViewsQueue(currentMessageObject.messageOwner, true); + currentMessageObject.viewsReloaded = true; + } + } + + if (currentMessageObject.isFromUser()) { + currentUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); + } else if (currentMessageObject.messageOwner.from_id < 0) { + currentChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); + } else if (currentMessageObject.messageOwner.post) { + currentChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); + } + + if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { + isAvatarVisible = true; + if (currentUser != null) { + if (currentUser.photo != null) { + currentPhoto = currentUser.photo.photo_small; + } else { + currentPhoto = null; + } + avatarDrawable.setInfo(currentUser); + } else if (currentChat != null) { + if (currentChat.photo != null) { + currentPhoto = currentChat.photo.photo_small; + } else { + currentPhoto = null; + } + avatarDrawable.setInfo(currentChat); + } else { + currentPhoto = null; + avatarDrawable.setInfo(messageObject.messageOwner.from_id, null, null, false); + } + avatarImage.setImage(currentPhoto, "50_50", avatarDrawable, null, false); + } + + + measureTime(messageObject); + + namesOffset = 0; + + String viaUsername = null; + CharSequence viaString = null; + if (messageObject.messageOwner.via_bot_id != 0) { + TLRPC.User botUser = MessagesController.getInstance().getUser(messageObject.messageOwner.via_bot_id); + if (botUser != null && botUser.username != null && botUser.username.length() > 0) { + viaUsername = "@" + botUser.username; + viaString = replaceTags(String.format(" via %s", viaUsername)); + viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); + currentViaBotUser = botUser; + } + } else if (messageObject.messageOwner.via_bot_name != null && messageObject.messageOwner.via_bot_name.length() > 0) { + viaUsername = "@" + messageObject.messageOwner.via_bot_name; + viaString = replaceTags(String.format(" via %s", viaUsername)); + viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); + } + + boolean authorName = drawName && isChat && !currentMessageObject.isOutOwner(); + boolean viaBot = (messageObject.messageOwner.fwd_from == null || messageObject.type == 14) && viaUsername != null; + if (authorName || viaBot) { + drawNameLayout = true; + nameWidth = getMaxNameWidth(); + if (nameWidth < 0) { + nameWidth = dp(100); + } + + if (authorName) { + if (currentUser != null) { + currentNameString = UserObject.getUserName(currentUser); + } else if (currentChat != null) { + currentNameString = currentChat.title; + } else { + currentNameString = "DELETED"; + } + } else { + currentNameString = ""; + } + CharSequence nameStringFinal = TextUtils.ellipsize(currentNameString.replace('\n', ' '), namePaint, nameWidth - (viaBot ? viaWidth : 0), TextUtils.TruncateAt.END); + if (viaBot) { + viaNameWidth = (int) Math.ceil(namePaint.measureText(nameStringFinal, 0, nameStringFinal.length())); + if (viaNameWidth != 0) { + viaNameWidth += dp(4); + } + int color; + if (currentMessageObject.type == 13) { + color = Theme.MSG_STICKER_VIA_BOT_NAME_TEXT_COLOR; + } else { + color = currentMessageObject.isOutOwner() ? Theme.MSG_OUT_VIA_BOT_NAME_TEXT_COLOR : Theme.MSG_IN_VIA_BOT_NAME_TEXT_COLOR; + } + if (currentNameString.length() > 0) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s via %s", nameStringFinal, viaUsername)); + stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), nameStringFinal.length() + 1, nameStringFinal.length() + 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new TypefaceSpan(getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 5, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + nameStringFinal = stringBuilder; + } else { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("via %s", viaUsername)); + stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new TypefaceSpan(getTypeface("fonts/rmedium.ttf"), 0, color), 4, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + nameStringFinal = stringBuilder; + } + nameStringFinal = TextUtils.ellipsize(nameStringFinal, namePaint, nameWidth, TextUtils.TruncateAt.END); + } + try { + nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (nameLayout != null && nameLayout.getLineCount() > 0) { + nameWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); + if (messageObject.type != 13) { + namesOffset += dp(19); + } + nameOffsetX = nameLayout.getLineLeft(0); + } else { + nameWidth = 0; + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + if (currentNameString.length() == 0) { + currentNameString = null; + } + } else { + currentNameString = null; + nameLayout = null; + nameWidth = 0; + } + + currentForwardUser = null; + currentForwardNameString = null; + forwardedNameLayout[0] = null; + forwardedNameLayout[1] = null; + forwardedNameWidth = 0; + if (drawForwardedName && messageObject.isForwarded()) { + currentForwardChannel = null; + if (messageObject.messageOwner.fwd_from.channel_id != 0) { + currentForwardChannel = MessagesController.getInstance().getChat(messageObject.messageOwner.fwd_from.channel_id); + } + if (messageObject.messageOwner.fwd_from.from_id != 0) { + currentForwardUser = MessagesController.getInstance().getUser(messageObject.messageOwner.fwd_from.from_id); + } + + if (currentForwardUser != null || currentForwardChannel != null) { + if (currentForwardChannel != null) { + if (currentForwardUser != null) { + currentForwardNameString = String.format("%s (%s)", currentForwardChannel.title, UserObject.getUserName(currentForwardUser)); + } else { + currentForwardNameString = currentForwardChannel.title; + } + } else if (currentForwardUser != null) { + currentForwardNameString = UserObject.getUserName(currentForwardUser); + } + + forwardedNameWidth = getMaxNameWidth(); + int fromWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " ")); + CharSequence name = TextUtils.ellipsize(currentForwardNameString.replace('\n', ' '), replyNamePaint, forwardedNameWidth - fromWidth - viaWidth, TextUtils.TruncateAt.END); + CharSequence lastLine; + if (viaString != null) { + viaNameWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " " + name)); + lastLine = replaceTags(String.format("%s %s via %s", LocaleController.getString("From", R.string.From), name, viaUsername)); + } else { + lastLine = replaceTags(String.format("%s %s", LocaleController.getString("From", R.string.From), name)); + } + lastLine = TextUtils.ellipsize(lastLine, forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); + try { + forwardedNameLayout[1] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + lastLine = TextUtils.ellipsize(replaceTags(LocaleController.getString("ForwardedMessage", R.string.ForwardedMessage)), forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); + forwardedNameLayout[0] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + forwardedNameWidth = Math.max((int) Math.ceil(forwardedNameLayout[0].getLineWidth(0)), (int) Math.ceil(forwardedNameLayout[1].getLineWidth(0))); + forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0); + forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0); + namesOffset += dp(36); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + } + + if (messageObject.isReply()) { + namesOffset += dp(42); + if (messageObject.type != 0) { + if (messageObject.type == 13) { + namesOffset -= dp(42); + } else { + namesOffset += dp(5); + } + } + + int maxWidth = getMaxNameWidth(); + if (messageObject.type != 13) { + maxWidth -= dp(10); + } + + CharSequence stringFinalName = null; + CharSequence stringFinalText = null; + if (messageObject.replyMessageObject != null) { + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs, 80); + if (photoSize == null || messageObject.replyMessageObject.type == 13 || messageObject.type == 13 && !isTablet() || messageObject.replyMessageObject.isSecretMedia()) { + replyImageReceiver.setImageBitmap((Drawable) null); + needReplyImage = false; + } else { + currentReplyPhoto = photoSize.location; + replyImageReceiver.setImage(photoSize.location, "50_50", null, null, true); + needReplyImage = true; + maxWidth -= dp(44); + } + + String name = null; + if (messageObject.replyMessageObject.isFromUser()) { + TLRPC.User user = MessagesController.getInstance().getUser(messageObject.replyMessageObject.messageOwner.from_id); + if (user != null) { + name = UserObject.getUserName(user); + } + } else if (messageObject.replyMessageObject.messageOwner.from_id < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-messageObject.replyMessageObject.messageOwner.from_id); + if (chat != null) { + name = chat.title; + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.replyMessageObject.messageOwner.to_id.channel_id); + if (chat != null) { + name = chat.title; + } + } + + if (name != null) { + stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), replyNamePaint, maxWidth, TextUtils.TruncateAt.END); + } + if (messageObject.replyMessageObject.messageText != null && messageObject.replyMessageObject.messageText.length() > 0) { + String mess = messageObject.replyMessageObject.messageText.toString(); + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + mess = mess.replace('\n', ' '); + stringFinalText = Emoji.replaceEmoji(mess, replyTextPaint.getFontMetricsInt(), dp(14), false); + stringFinalText = TextUtils.ellipsize(stringFinalText, replyTextPaint, maxWidth, TextUtils.TruncateAt.END); + } + } + if (stringFinalName == null) { + stringFinalName = LocaleController.getString("Loading", R.string.Loading); + } + try { + replyNameLayout = new StaticLayout(stringFinalName, replyNamePaint, maxWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (replyNameLayout.getLineCount() > 0) { + replyNameWidth = (int)Math.ceil(replyNameLayout.getLineWidth(0)) + dp(12 + (needReplyImage ? 44 : 0)); + replyNameOffset = replyNameLayout.getLineLeft(0); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + try { + if (stringFinalText != null) { + replyTextLayout = new StaticLayout(stringFinalText, replyTextPaint, maxWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (replyTextLayout.getLineCount() > 0) { + replyTextWidth = (int) Math.ceil(replyTextLayout.getLineWidth(0)) + dp(12 + (needReplyImage ? 44 : 0)); + replyTextOffset = replyTextLayout.getLineLeft(0); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + + requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentMessageObject == null) { + return; + } + + if (!wasLayout) { + requestLayout(); + return; + } + + if (isAvatarVisible) { + avatarImage.draw(canvas); + } + + if (mediaBackground) { + timePaint.setColor(Theme.MSG_MEDIA_TIME_TEXT_COLOR); + } else { + if (currentMessageObject.isOutOwner()) { + timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_TIME_SELECTED_TEXT_COLOR : Theme.MSG_OUT_TIME_TEXT_COLOR); + } else { + timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_TIME_SELECTED_TEXT_COLOR : Theme.MSG_IN_TIME_TEXT_COLOR); + } + } + + if (currentMessageObject.isOutOwner()) { + if (isDrawSelectedBackground()) { + if (!mediaBackground) { + currentBackgroundDrawable = Theme.backgroundDrawableOutSelected; + } else { + currentBackgroundDrawable = Theme.backgroundMediaDrawableOutSelected; + } + } else { + if (!mediaBackground) { + currentBackgroundDrawable = Theme.backgroundDrawableOut; + } else { + currentBackgroundDrawable = Theme.backgroundMediaDrawableOut; + } + } + setDrawableBounds(currentBackgroundDrawable, layoutWidth - backgroundWidth - (!mediaBackground ? 0 : dp(9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + } else { + if (isDrawSelectedBackground()) { + if (!mediaBackground) { + currentBackgroundDrawable = Theme.backgroundDrawableInSelected; + } else { + currentBackgroundDrawable = Theme.backgroundMediaDrawableInSelected; + } + } else { + if (!mediaBackground) { + currentBackgroundDrawable = Theme.backgroundDrawableIn; + } else { + currentBackgroundDrawable = Theme.backgroundMediaDrawableIn; + } + } + if (isChat && currentMessageObject.isFromUser()) { + setDrawableBounds(currentBackgroundDrawable, dp(48 + (!mediaBackground ? 3 : 9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + } else { + setDrawableBounds(currentBackgroundDrawable, (!mediaBackground ? dp(3) : dp(9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + } + } + if (drawBackground && currentBackgroundDrawable != null) { + currentBackgroundDrawable.draw(canvas); + } + + drawContent(canvas); + + if (drawShareButton) { + Theme.shareDrawable.setColorFilter(sharePressed ? Theme.colorPressedFilter : Theme.colorFilter); + if (currentMessageObject.isOutOwner()) { + shareStartX = currentBackgroundDrawable.getBounds().left - dp(8) - Theme.shareDrawable.getIntrinsicWidth(); + } else { + shareStartX = currentBackgroundDrawable.getBounds().right + dp(8); + } + setDrawableBounds(Theme.shareDrawable, shareStartX, shareStartY = layoutHeight - dp(41)); + Theme.shareDrawable.draw(canvas); + setDrawableBounds(Theme.shareIconDrawable, shareStartX + dp(9), shareStartY + dp(9)); + Theme.shareIconDrawable.draw(canvas); + } + + if (drawNameLayout && nameLayout != null) { + canvas.save(); + + if (currentMessageObject.type == 13) { + namePaint.setColor(Theme.MSG_STICKER_NAME_TEXT_COLOR); + int backWidth; + if (currentMessageObject.isOutOwner()) { + nameX = dp(28); + } else { + nameX = currentBackgroundDrawable.getBounds().right + dp(22); + } + nameY = layoutHeight - dp(38); + Theme.systemDrawable.setColorFilter(Theme.colorFilter); + Theme.systemDrawable.setBounds((int) nameX - dp(12), (int) nameY - dp(5), (int) nameX + dp(12) + nameWidth, (int) nameY + dp(22)); + Theme.systemDrawable.draw(canvas); + } else { + if (mediaBackground || currentMessageObject.isOutOwner()) { + nameX = currentBackgroundDrawable.getBounds().left + dp(11) - nameOffsetX; + } else { + nameX = currentBackgroundDrawable.getBounds().left + dp(17) - nameOffsetX; + } + if (currentUser != null) { + namePaint.setColor(AvatarDrawable.getNameColorForId(currentUser.id)); + } else if (currentChat != null) { + namePaint.setColor(AvatarDrawable.getNameColorForId(currentChat.id)); + } else { + namePaint.setColor(AvatarDrawable.getNameColorForId(0)); + } + nameY = dp(10); + } + canvas.translate(nameX, nameY); + nameLayout.draw(canvas); + canvas.restore(); + } + + if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null) { + forwardNameY = dp(10 + (drawNameLayout ? 19 : 0)); + if (currentMessageObject.isOutOwner()) { + forwardNamePaint.setColor(Theme.MSG_OUT_FORDWARDED_NAME_TEXT_COLOR); + forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); + } else { + forwardNamePaint.setColor(Theme.MSG_IN_FORDWARDED_NAME_TEXT_COLOR); + if (mediaBackground) { + forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); + } else { + forwardNameX = currentBackgroundDrawable.getBounds().left + dp(17); + } + } + for (int a = 0; a < 2; a++) { + canvas.save(); + canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + dp(16) * a); + forwardedNameLayout[a].draw(canvas); + canvas.restore(); + } + } + + if (currentMessageObject.isReply()) { + if (currentMessageObject.type == 13) { + replyLinePaint.setColor(Theme.MSG_STICKER_REPLY_LINE_COLOR); + replyNamePaint.setColor(Theme.MSG_STICKER_REPLY_NAME_TEXT_COLOR); + replyTextPaint.setColor(Theme.MSG_STICKER_REPLY_MESSAGE_TEXT_COLOR); + if (currentMessageObject.isOutOwner()) { + replyStartX = dp(23); + } else { + replyStartX = currentBackgroundDrawable.getBounds().right + dp(17); + } + replyStartY = layoutHeight - dp(58); + if (nameLayout != null) { + replyStartY -= dp(25 + 6); + } + int backWidth = Math.max(replyNameWidth, replyTextWidth) + dp(14 + (needReplyImage ? 44 : 0)); + Theme.systemDrawable.setColorFilter(Theme.colorFilter); + Theme.systemDrawable.setBounds(replyStartX - dp(7), replyStartY - dp(6), replyStartX - dp(7) + backWidth, replyStartY + dp(41)); + Theme.systemDrawable.draw(canvas); + } else { + if (currentMessageObject.isOutOwner()) { + replyLinePaint.setColor(Theme.MSG_OUT_REPLY_LINE_COLOR); + replyNamePaint.setColor(Theme.MSG_OUT_REPLY_NAME_TEXT_COLOR); + if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0) { + replyTextPaint.setColor(Theme.MSG_OUT_REPLY_MESSAGE_TEXT_COLOR); + } else { + replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_TEXT_COLOR); + } + replyStartX = currentBackgroundDrawable.getBounds().left + dp(12); + } else { + replyLinePaint.setColor(Theme.MSG_IN_REPLY_LINE_COLOR); + replyNamePaint.setColor(Theme.MSG_IN_REPLY_NAME_TEXT_COLOR); + if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0) { + replyTextPaint.setColor(Theme.MSG_IN_REPLY_MESSAGE_TEXT_COLOR); + } else { + replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_IN_REPLY_MEDIA_MESSAGE_TEXT_COLOR); + } + if (mediaBackground) { + replyStartX = currentBackgroundDrawable.getBounds().left + dp(12); + } else { + replyStartX = currentBackgroundDrawable.getBounds().left + dp(18); + } + } + replyStartY = dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); + } + canvas.drawRect(replyStartX, replyStartY, replyStartX + dp(2), replyStartY + dp(35), replyLinePaint); + if (needReplyImage) { + replyImageReceiver.setImageCoords(replyStartX + dp(10), replyStartY, dp(35), dp(35)); + replyImageReceiver.draw(canvas); + } + + if (replyNameLayout != null) { + canvas.save(); + canvas.translate(replyStartX - replyNameOffset + dp(10 + (needReplyImage ? 44 : 0)), replyStartY); + replyNameLayout.draw(canvas); + canvas.restore(); + } + if (replyTextLayout != null) { + canvas.save(); + canvas.translate(replyStartX - replyTextOffset + dp(10 + (needReplyImage ? 44 : 0)), replyStartY + dp(19)); + replyTextLayout.draw(canvas); + canvas.restore(); + } + } + + if (drawTime || !mediaBackground) { + if (mediaBackground) { + Drawable drawable; + if (currentMessageObject.type == 13) { + drawable = Theme.timeStickerBackgroundDrawable; + } else { + drawable = Theme.timeBackgroundDrawable; + } + setDrawableBounds(drawable, timeX - dp(4), layoutHeight - dp(27), timeWidth + dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), dp(17)); + drawable.draw(canvas); + + int additionalX = 0; + if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + additionalX = (int) (timeWidth - timeLayout.getLineWidth(0)); + + if (currentMessageObject.isSending()) { + if (!currentMessageObject.isOutOwner()) { + setDrawableBounds(Theme.clockMediaDrawable, timeX + dp(11), layoutHeight - dp(13.0f) - Theme.clockMediaDrawable.getIntrinsicHeight()); + Theme.clockMediaDrawable.draw(canvas); + } + } else if (currentMessageObject.isSendError()) { + if (!currentMessageObject.isOutOwner()) { + setDrawableBounds(Theme.errorDrawable, timeX + dp(11), layoutHeight - dp(12.5f) - Theme.errorDrawable.getIntrinsicHeight()); + Theme.errorDrawable.draw(canvas); + } + } else { + Drawable countDrawable = Theme.viewsMediaCountDrawable; + setDrawableBounds(countDrawable, timeX, layoutHeight - dp(9.5f) - timeLayout.getHeight()); + countDrawable.draw(canvas); + + if (viewsLayout != null) { + canvas.save(); + canvas.translate(timeX + countDrawable.getIntrinsicWidth() + dp(3), layoutHeight - dp(11.3f) - timeLayout.getHeight()); + viewsLayout.draw(canvas); + canvas.restore(); + } + } + } + + canvas.save(); + canvas.translate(timeX + additionalX, layoutHeight - dp(11.3f) - timeLayout.getHeight()); + timeLayout.draw(canvas); + canvas.restore(); + } else { + int additionalX = 0; + if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + additionalX = (int) (timeWidth - timeLayout.getLineWidth(0)); + + if (currentMessageObject.isSending()) { + if (!currentMessageObject.isOutOwner()) { + Drawable clockDrawable = Theme.clockChannelDrawable[isDrawSelectedBackground() ? 1 : 0]; + setDrawableBounds(clockDrawable, timeX + dp(11), layoutHeight - dp(8.5f) - clockDrawable.getIntrinsicHeight()); + clockDrawable.draw(canvas); + } + } else if (currentMessageObject.isSendError()) { + if (!currentMessageObject.isOutOwner()) { + setDrawableBounds(Theme.errorDrawable, timeX + dp(11), layoutHeight - dp(6.5f) - Theme.errorDrawable.getIntrinsicHeight()); + Theme.errorDrawable.draw(canvas); + } + } else { + if (!currentMessageObject.isOutOwner()) { + setDrawableBounds(Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0], timeX, layoutHeight - dp(4.5f) - timeLayout.getHeight()); + Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0].draw(canvas); + } else { + setDrawableBounds(Theme.viewsOutCountDrawable, timeX, layoutHeight - dp(4.5f) - timeLayout.getHeight()); + Theme.viewsOutCountDrawable.draw(canvas); + } + + if (viewsLayout != null) { + canvas.save(); + canvas.translate(timeX + Theme.viewsOutCountDrawable.getIntrinsicWidth() + dp(3), layoutHeight - dp(6.5f) - timeLayout.getHeight()); + viewsLayout.draw(canvas); + canvas.restore(); + } + } + } + + canvas.save(); + canvas.translate(timeX + additionalX, layoutHeight - dp(6.5f) - timeLayout.getHeight()); + timeLayout.draw(canvas); + canvas.restore(); + //canvas.drawRect(timeX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight(), timeX + availableTimeWidth, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight(), timePaint); + } + + if (currentMessageObject.isOutOwner()) { + boolean drawCheck1 = false; + boolean drawCheck2 = false; + boolean drawClock = false; + boolean drawError = false; + boolean isBroadcast = (int)(currentMessageObject.getDialogId() >> 32) == 1; + + if (currentMessageObject.isSending()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = true; + drawError = false; + } else if (currentMessageObject.isSendError()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = false; + drawError = true; + } else if (currentMessageObject.isSent()) { + if (!currentMessageObject.isUnread()) { + drawCheck1 = true; + drawCheck2 = true; + } else { + drawCheck1 = false; + drawCheck2 = true; + } + drawClock = false; + drawError = false; + } + + if (drawClock) { + if (!mediaBackground) { + setDrawableBounds(Theme.clockDrawable, layoutWidth - dp(18.5f) - Theme.clockDrawable.getIntrinsicWidth(), layoutHeight - dp(8.5f) - Theme.clockDrawable.getIntrinsicHeight()); + Theme.clockDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.clockMediaDrawable, layoutWidth - dp(22.0f) - Theme.clockMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.clockMediaDrawable.getIntrinsicHeight()); + Theme.clockMediaDrawable.draw(canvas); + } + } + if (isBroadcast) { + if (drawCheck1 || drawCheck2) { + if (!mediaBackground) { + setDrawableBounds(Theme.broadcastDrawable, layoutWidth - dp(20.5f) - Theme.broadcastDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.broadcastDrawable.getIntrinsicHeight()); + Theme.broadcastDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.broadcastMediaDrawable, layoutWidth - dp(24.0f) - Theme.broadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(13.0f) - Theme.broadcastMediaDrawable.getIntrinsicHeight()); + Theme.broadcastMediaDrawable.draw(canvas); + } + } + } else { + if (drawCheck2) { + if (!mediaBackground) { + if (drawCheck1) { + setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(22.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(18.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); + } + Theme.checkDrawable.draw(canvas); + } else { + if (drawCheck1) { + setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(26.3f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(21.5f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); + } + Theme.checkMediaDrawable.draw(canvas); + } + } + if (drawCheck1) { + if (!mediaBackground) { + setDrawableBounds(Theme.halfCheckDrawable, layoutWidth - dp(18) - Theme.halfCheckDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.halfCheckDrawable.getIntrinsicHeight()); + Theme.halfCheckDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.halfCheckMediaDrawable, layoutWidth - dp(21.5f) - Theme.halfCheckMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.halfCheckMediaDrawable.getIntrinsicHeight()); + Theme.halfCheckMediaDrawable.draw(canvas); + } + } + } + if (drawError) { + if (!mediaBackground) { + setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(18) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(7) - Theme.errorDrawable.getIntrinsicHeight()); + Theme.errorDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(20.5f) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(11.5f) - Theme.errorDrawable.getIntrinsicHeight()); + Theme.errorDrawable.draw(canvas); + } + } + } + } + } + + @Override + public int getObserverTag() { + return TAG; + } + + public MessageObject getMessageObject() { + return currentMessageObject; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java index 5c789c3dd..8418b0b63 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java @@ -286,7 +286,7 @@ public class ContextLinkCell extends View implements MediaController.FileDownloa } } else { if (currentPhotoObject != null) { - linkImageView.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, ext, false); + linkImageView.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, currentPhotoObject.size, ext, false); } else { linkImageView.setImage(null, url, currentPhotoFilter, null, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, -1, ext, true); } @@ -296,24 +296,24 @@ public class ContextLinkCell extends View implements MediaController.FileDownloa if (mediaWebpage) { setBackgroundDrawable(null); - if (inlineResult == null) { + //if (inlineResult == null) { width = viewWidth; int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); - if (needDivider) { - height -= AndroidUtilities.dp(2); + if (height == 0) { + height = AndroidUtilities.dp(100); } + setMeasuredDimension(width, height); int x = (width - AndroidUtilities.dp(24)) / 2; int y = (height - AndroidUtilities.dp(24)) / 2; radialProgress.setProgressRect(x, y, x + AndroidUtilities.dp(24), y + AndroidUtilities.dp(24)); linkImageView.setImageCoords(0, 0, width, height); - } else { + /*} else { setMeasuredDimension(width + AndroidUtilities.dp(5), AndroidUtilities.dp(90)); int x = AndroidUtilities.dp(5) + (width - AndroidUtilities.dp(24)) / 2; int y = (AndroidUtilities.dp(90) - AndroidUtilities.dp(24)) / 2; radialProgress.setProgressRect(x, y, x + AndroidUtilities.dp(24), y + AndroidUtilities.dp(24)); linkImageView.setImageCoords(AndroidUtilities.dp(5), AndroidUtilities.dp(5), width, AndroidUtilities.dp(80)); - } + }*/ } else { setBackgroundResource(R.drawable.list_selector); int height = 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index d7a67358f..496864ad6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -14,9 +14,12 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Layout; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.view.MotionEvent; import org.telegram.messenger.AndroidUtilities; @@ -33,6 +36,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.ImageReceiver; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import java.util.ArrayList; @@ -63,6 +67,7 @@ public class DialogCell extends BaseCell { private static Paint backPaint; private long currentDialogId; + private int currentEditDate; private boolean isDialogCell; private int lastMessageDate; private int unreadCount; @@ -140,8 +145,8 @@ public class DialogCell extends BaseCell { messagePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); messagePaint.setTextSize(AndroidUtilities.dp(16)); - messagePaint.setColor(0xff8f8f8f); - messagePaint.linkColor = 0xff8f8f8f; + messagePaint.setColor(Theme.DIALOGS_MESSAGE_TEXT_COLOR); + messagePaint.linkColor = Theme.DIALOGS_MESSAGE_TEXT_COLOR; linePaint = new Paint(); linePaint.setColor(0xffdcdcdc); @@ -151,7 +156,7 @@ public class DialogCell extends BaseCell { messagePrintingPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); messagePrintingPaint.setTextSize(AndroidUtilities.dp(16)); - messagePrintingPaint.setColor(0xff4d83b3); + messagePrintingPaint.setColor(Theme.DIALOGS_PRINTING_TEXT_COLOR); timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); timePaint.setTextSize(AndroidUtilities.dp(13)); @@ -196,6 +201,7 @@ public class DialogCell extends BaseCell { message = messageObject; isDialogCell = false; lastMessageDate = date; + currentEditDate = messageObject != null ? messageObject.messageOwner.edit_date : 0; unreadCount = 0; lastUnreadState = messageObject != null && messageObject.isUnread(); if (message != null) { @@ -383,35 +389,37 @@ public class DialogCell extends BaseCell { if (message.isOutOwner()) { name = LocaleController.getString("FromYou", R.string.FromYou); } else if (fromUser != null) { - name = UserObject.getFirstName(fromUser); + name = UserObject.getFirstName(fromUser).replace("\n", ""); } else if (fromChat != null) { - name = fromChat.title; + name = fromChat.title.replace("\n", ""); } else { name = "DELETED"; } checkMessage = false; + SpannableStringBuilder stringBuilder; if (message.caption != null) { String mess = message.caption.toString(); if (mess.length() > 150) { mess = mess.substring(0, 150); } - mess = mess.replace('\n', ' '); - messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("%s: %s", name.replace("\n", ""), mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - } else { - if (message.messageOwner.media != null && !message.isMediaEmpty()) { - currentMessagePaint = messagePrintingPaint; - messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("%s: %s", name.replace("\n", ""), message.messageText), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - } else { - if (message.messageOwner.message != null) { - String mess = message.messageOwner.message; - if (mess.length() > 150) { - mess = mess.substring(0, 150); - } - mess = mess.replace('\n', ' '); - messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("%s: %s", name.replace("\n", ""), mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - } + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + } else if (message.messageOwner.media != null && !message.isMediaEmpty()) { + currentMessagePaint = messagePrintingPaint; + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_ATTACH_TEXT_COLOR), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (message.messageOwner.message != null) { + String mess = message.messageOwner.message; + if (mess.length() > 150) { + mess = mess.substring(0, 150); } + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + } else { + stringBuilder = SpannableStringBuilder.valueOf(""); } + if (stringBuilder.length() > 0) { + stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_NAME_TEXT_COLOR), 0, name.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + messageString = Emoji.replaceEmoji(stringBuilder, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } else { if (message.caption != null) { messageString = message.caption; @@ -692,7 +700,12 @@ public class DialogCell extends BaseCell { public void checkCurrentDialogIndex() { if (index < getDialogsArray().size()) { TLRPC.Dialog dialog = getDialogsArray().get(index); - if (currentDialogId != dialog.id || message != null && message.getId() != dialog.top_message || unreadCount != dialog.unread_count || message == null && MessagesController.getInstance().dialogMessage.get(dialog.id) != null) { + MessageObject newMessageObject = MessagesController.getInstance().dialogMessage.get(dialog.id); + if (currentDialogId != dialog.id || + message != null && message.getId() != dialog.top_message || + newMessageObject != null && newMessageObject.messageOwner.edit_date != currentEditDate || + unreadCount != dialog.unread_count || + message == null && newMessageObject != null) { currentDialogId = dialog.id; update(0); } @@ -706,6 +719,7 @@ public class DialogCell extends BaseCell { message = MessagesController.getInstance().dialogMessage.get(dialog.id); lastUnreadState = message != null && message.isUnread(); unreadCount = dialog.unread_count; + currentEditDate = message != null ? message.messageOwner.edit_date : 0; lastMessageDate = dialog.last_message_date; if (message != null) { lastSendState = message.messageOwner.send_state; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java index cfbe8048f..0915c3f94 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java @@ -27,7 +27,7 @@ public class GreySectionCell extends FrameLayout { setBackgroundColor(0xfff2f2f2); textView = new TextView(getContext()); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setTextColor(0xff8a8a8a); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); @@ -36,7 +36,7 @@ public class GreySectionCell extends FrameLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(36), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); } public void setText(String text) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java new file mode 100644 index 000000000..e6ad8ce91 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java @@ -0,0 +1,175 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.LayoutHelper; + +public class HintDialogCell extends FrameLayout { + + private BackupImageView imageView; + private TextView nameTextView; + private AvatarDrawable avatarDrawable = new AvatarDrawable(); + + private static Drawable countDrawable; + private static Drawable countDrawableGrey; + private static TextPaint countPaint; + + private int lastUnreadCount; + private int countWidth; + private StaticLayout countLayout; + + private long dialog_id; + + public HintDialogCell(Context context) { + super(context); + setBackgroundResource(R.drawable.list_selector); + + imageView = new BackupImageView(context); + imageView.setRoundRadius(AndroidUtilities.dp(27)); + addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); + + nameTextView = new TextView(context); + nameTextView.setTextColor(0xff212121); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + nameTextView.setMaxLines(2); + nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + nameTextView.setLines(2); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, 64, 6, 0)); + + if (countDrawable == null) { + countDrawable = getResources().getDrawable(R.drawable.dialogs_badge); + countDrawableGrey = getResources().getDrawable(R.drawable.dialogs_badge2); + + countPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + countPaint.setTextSize(AndroidUtilities.dp(13)); + countPaint.setColor(0xffffffff); + countPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { + if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { + getBackground().setHotspot(event.getX(), event.getY()); + } + } + return super.onTouchEvent(event); + } + + public void checkUnreadCounter(int mask) { + if (mask != 0 && (mask & MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE) == 0 && (mask & MessagesController.UPDATE_MASK_NEW_MESSAGE) == 0) { + return; + } + TLRPC.Dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); + if (dialog != null && dialog.unread_count != 0) { + if (lastUnreadCount != dialog.unread_count) { + lastUnreadCount = dialog.unread_count; + String countString = String.format("%d", dialog.unread_count); + countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(countPaint.measureText(countString))); + countLayout = new StaticLayout(countString, countPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + if (mask != 0) { + invalidate(); + } + } + } else if (countLayout != null) { + if (mask != 0) { + invalidate(); + } + lastUnreadCount = 0; + countLayout = null; + } + } + + public void setDialog(int uid, boolean checked, CharSequence name) { + dialog_id = uid; + TLRPC.FileLocation photo = null; + if (uid > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(uid); + if (name != null) { + nameTextView.setText(name); + } else if (user != null) { + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + } else { + nameTextView.setText(""); + } + avatarDrawable.setInfo(user); + if (user != null && user.photo != null) { + photo = user.photo.photo_small; + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); + if (name != null) { + nameTextView.setText(name); + } else if (chat != null) { + nameTextView.setText(chat.title); + } else { + nameTextView.setText(""); + } + avatarDrawable.setInfo(chat); + if (chat != null && chat.photo != null) { + photo = chat.photo.photo_small; + } + } + imageView.setImage(photo, "50_50", avatarDrawable); + checkUnreadCounter(0); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == imageView) { + if (countLayout != null) { + int top = AndroidUtilities.dp(6); + int left = AndroidUtilities.dp(54); + int x = left - AndroidUtilities.dp(5.5f); + if (MessagesController.getInstance().isDialogMuted(dialog_id)) { + countDrawableGrey.setBounds(x, top, x + countWidth + AndroidUtilities.dp(11), top + countDrawableGrey.getIntrinsicHeight()); + countDrawableGrey.draw(canvas); + } else { + countDrawable.setBounds(x, top, x + countWidth + AndroidUtilities.dp(11), top + countDrawable.getIntrinsicHeight()); + countDrawable.draw(canvas); + } + canvas.save(); + canvas.translate(left, top + AndroidUtilities.dp(4)); + countLayout.draw(canvas); + canvas.restore(); + } + } + return result; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java index 94ffcd3c4..a15dcce7f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -18,6 +18,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; import org.telegram.messenger.R; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; @@ -92,7 +93,11 @@ public class MentionCell extends LinearLayout { imageView.setImageDrawable(avatarDrawable); } nameTextView.setText(UserObject.getUserName(user)); - usernameTextView.setText("@" + user.username); + if (user.username != null) { + usernameTextView.setText("@" + user.username); + } else { + usernameTextView.setText(""); + } imageView.setVisibility(VISIBLE); usernameTextView.setVisibility(VISIBLE); } @@ -117,7 +122,7 @@ public class MentionCell extends LinearLayout { } usernameTextView.setVisibility(VISIBLE); nameTextView.setText(command); - usernameTextView.setText(help); + usernameTextView.setText(Emoji.replaceEmoji(help, usernameTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false)); } public void setIsDarkTheme(boolean isDarkTheme) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java index 4d2fc2e67..7930fc4b2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java @@ -8,13 +8,16 @@ package org.telegram.ui.Cells; +import android.annotation.SuppressLint; import android.content.Context; import android.widget.FrameLayout; import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; import org.telegram.ui.Components.LayoutHelper; +@SuppressLint("NewApi") public class PhotoAttachCameraCell extends FrameLayout { public PhotoAttachCameraCell(Context context) { @@ -22,8 +25,7 @@ public class PhotoAttachCameraCell extends FrameLayout { ImageView imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); - //imageView.setImageResource(R.drawable.ic_attach_photobig); - imageView.setBackgroundColor(0xff777777); + imageView.setImageResource(R.drawable.instant_camera); addView(imageView, LayoutHelper.createFrame(80, 80)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java index b09451644..7fa7af144 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -350,7 +350,7 @@ public class ProfileSearchCell extends BaseCell { CharSequence onlineStringFinal = TextUtils.ellipsize(onlineString, currentOnlinePaint, onlineWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); onlineLayout = new StaticLayout(onlineStringFinal, currentOnlinePaint, onlineWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); nameTop = AndroidUtilities.dp(13); - if (subLabel != null && !drawNameBot) { + if (subLabel != null && chat != null) { nameLockTop -= AndroidUtilities.dp(12); } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java index d9793952e..1d5b16d01 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java @@ -74,11 +74,10 @@ public class ShareDialogCell extends FrameLayout { return super.onTouchEvent(event); } - public void setDialog(TLRPC.Dialog dialog, boolean checked, CharSequence name) { - int lower_id = (int) dialog.id; + public void setDialog(int uid, boolean checked, CharSequence name) { TLRPC.FileLocation photo = null; - if (lower_id > 0) { - TLRPC.User user = MessagesController.getInstance().getUser(lower_id); + if (uid > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(uid); if (name != null) { nameTextView.setText(name); } else if (user != null) { @@ -91,7 +90,7 @@ public class ShareDialogCell extends FrameLayout { photo = user.photo.photo_small; } } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); if (name != null) { nameTextView.setText(name); } else if (chat != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java index 257eb9c58..1da73b4a0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java @@ -40,15 +40,15 @@ public class UserCell extends FrameLayout { private ImageView adminImage; private AvatarDrawable avatarDrawable; - private TLObject currentObject = null; + private TLObject currentObject; private CharSequence currentName; private CharSequence currrntStatus; private int currentDrawable; - private String lastName = null; - private int lastStatus = 0; - private TLRPC.FileLocation lastAvatar = null; + private String lastName; + private int lastStatus; + private TLRPC.FileLocation lastAvatar; private int statusColor = 0xffa8a8a8; private int statusOnlineColor = 0xff3b84c0; @@ -232,7 +232,7 @@ public class UserCell extends FrameLayout { } else if (currentUser != null) { if (currentUser.bot) { statusTextView.setTextColor(statusColor); - if (currentUser.bot_chat_history) { + if (currentUser.bot_chat_history || adminImage != null && adminImage.getVisibility() == VISIBLE) { //TODO fix statusTextView.setText(LocaleController.getString("BotStatusRead", R.string.BotStatusRead)); } else { statusTextView.setText(LocaleController.getString("BotStatusCantRead", R.string.BotStatusCantRead)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index d0b556419..eb0b0fa07 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -12,7 +12,6 @@ import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -30,6 +29,9 @@ import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.MediaStore; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.URLSpan; @@ -70,7 +72,9 @@ import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.BotQuery; import org.telegram.messenger.query.MessagesSearchQuery; import org.telegram.messenger.query.MessagesQuery; +import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.query.StickersQuery; +import org.telegram.messenger.support.widget.GridLayoutManager; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.ApplicationLoader; @@ -97,9 +101,7 @@ import org.telegram.messenger.AnimationCompat.AnimatorListenerAdapterProxy; import org.telegram.messenger.AnimationCompat.AnimatorSetProxy; import org.telegram.messenger.AnimationCompat.ObjectAnimatorProxy; import org.telegram.messenger.AnimationCompat.ViewProxy; -import org.telegram.ui.Cells.BotSwitchCell; import org.telegram.ui.Cells.ChatActionCell; -import org.telegram.ui.Cells.ChatBaseCell; import org.telegram.ui.Cells.ChatLoadingCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; @@ -113,20 +115,24 @@ import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Cells.BotHelpCell; import org.telegram.ui.Components.ChatActivityEnterView; import org.telegram.messenger.ImageReceiver; -import org.telegram.ui.Components.ChatAttachView; +import org.telegram.ui.Components.ChatAttachAlert; import org.telegram.ui.Components.ChatAvatarContainer; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.ExtendedGridLayoutManager; import org.telegram.ui.Components.PlayerView; import org.telegram.ui.Components.FrameLayoutFixed; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ShareAlert; +import org.telegram.ui.Components.Size; import org.telegram.ui.Components.SizeNotifierFrameLayout; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.URLSpanReplacement; +import org.telegram.ui.Components.URLSpanUserMention; import org.telegram.ui.Components.WebFrameLayout; import java.io.File; @@ -159,6 +165,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ActionBarMenuItem searchItem; private ActionBarMenuItem searchUpItem; private ActionBarMenuItem searchDownItem; + private ActionBarMenuItem editDoneItem; + private ContextProgressView editDoneItemProgress; + private AnimatorSetProxy editDoneItemAnimation; private TextView addContactItem; private RecyclerListView chatListView; private LinearLayoutManager chatLayoutManager; @@ -171,7 +180,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TextView bottomOverlayText; private TextView secretViewStatusTextView; private NumberTextView selectedMessagesCountTextView; - private TextView actionModeTextView; + private FrameLayout actionModeTitleContainer; + private SimpleTextView actionModeTextView; + private SimpleTextView actionModeSubTextView; private RecyclerListView stickersListView; private RecyclerListView.OnItemClickListener stickersOnItemClickListener; private RecyclerListView.OnItemClickListener mentionsOnItemClickListener; @@ -180,18 +191,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TextView muteItem; private ImageView pagedownButton; private BackupImageView replyImageView; - private TextView replyNameTextView; - private TextView replyObjectTextView; + private SimpleTextView replyNameTextView; + private SimpleTextView replyObjectTextView; private ImageView replyIconImageView; private MentionsAdapter mentionsAdapter; - private BotSwitchCell botSwitchCell; - private View botSwitchShadow; private FrameLayout mentionContainer; private RecyclerListView mentionListView; private LinearLayoutManager mentionLayoutManager; + private ExtendedGridLayoutManager mentionGridLayoutManager; private AnimatorSetProxy mentionListAnimation; - private ChatAttachView chatAttachView; - private BottomSheet chatAttachViewSheet; + private ChatAttachAlert chatAttachAlert; private LinearLayout reportSpamView; private AnimatorSetProxy reportSpamViewAnimator; private TextView addToContactsButton; @@ -238,6 +247,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ArrayList forwardingMessages; private MessageObject forwaringMessage; private MessageObject replyingMessageObject; + private int editingMessageObjectReqId; private boolean paused = true; private boolean wasPaused = false; private boolean readWhenResume = false; @@ -254,6 +264,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private int readWithDate; private int readWithMid; private boolean scrollToTopOnResume; + private boolean forceScrollToTop; private boolean scrollToTopUnReadOnResume; private long dialog_id; private int lastLoadIndex; @@ -575,17 +586,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - URLSpanBotCommand.enabled = false; if (userId != 0 && currentUser.bot) { BotQuery.loadBotInfo(userId, true, classGuid); - URLSpanBotCommand.enabled = true; } else if (info instanceof TLRPC.TL_chatFull) { for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = info.participants.participants.get(a); TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); if (user != null && user.bot) { BotQuery.loadBotInfo(user.id, true, classGuid); - URLSpanBotCommand.enabled = true; } } } @@ -659,7 +667,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentEncryptedChat != null) { MediaController.getInstance().stopMediaObserver(); try { - if (Build.VERSION.SDK_INT >= 14) { + if (Build.VERSION.SDK_INT >= 23) { getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } } catch (Throwable e) { @@ -673,8 +681,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (stickersAdapter != null) { stickersAdapter.onDestroy(); } - if (chatAttachView != null) { - chatAttachView.onDestroy(); + if (chatAttachAlert != null) { + chatAttachAlert.onDestroy(); } AndroidUtilities.unlockOrientation(getParentActivity()); /*MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); @@ -701,13 +709,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not cantDeleteMessagesCount = 0; hasOwnBackground = true; - if (chatAttachView != null){ - chatAttachView.onDestroy(); - chatAttachView = null; + if (chatAttachAlert != null){ + chatAttachAlert.onDestroy(); + chatAttachAlert = null; } - chatAttachViewSheet = null; Theme.loadRecources(context); + Theme.loadChatResources(context); actionBar.setBackButtonDrawable(new BackDrawable(false)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @@ -720,15 +728,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedMessagesCanCopyIds[a].clear(); } cantDeleteMessagesCount = 0; - chatActivityEnterView.setEditinigMessageObject(null, false); - actionBar.hideActionMode(); - updatePinnedMessageView(true); + if (chatActivityEnterView.isEditingMessage()) { + chatActivityEnterView.setEditingMessageObject(null, false); + } else { + actionBar.hideActionMode(); + updatePinnedMessageView(true); + } updateVisibleRows(); } else { finishFragment(); } } else if (id == copy) { String str = ""; + int previousUid = 0; for (int a = 1; a >= 0; a--) { ArrayList ids = new ArrayList<>(selectedMessagesCanCopyIds[a].keySet()); if (currentEncryptedChat == null) { @@ -740,30 +752,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not Integer messageId = ids.get(b); MessageObject messageObject = selectedMessagesCanCopyIds[a].get(messageId); if (str.length() != 0) { - str += "\n"; - } - if (messageObject.type == 0 && messageObject.messageOwner.message != null) { - str += messageObject.messageOwner.message; - } else if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.caption != null) { - str += messageObject.messageOwner.media.caption; - } else { - str += messageObject.messageText; + str += "\n\n"; } + str += getMessageContent(messageObject, previousUid, true); + previousUid = messageObject.messageOwner.from_id; } } if (str.length() != 0) { - try { - if (Build.VERSION.SDK_INT < 11) { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(str); - } else { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", str); - clipboard.setPrimaryClip(clip); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } + AndroidUtilities.addToClipboard(str); } for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); @@ -776,8 +772,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == edit_done) { if (chatActivityEnterView != null && (chatActivityEnterView.isEditingCaption() || chatActivityEnterView.hasText())) { chatActivityEnterView.doneEditingMessage(); - actionBar.hideActionMode(); - updatePinnedMessageView(true); } } else if (id == delete) { if (getParentActivity() == null) { @@ -872,13 +866,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } createChatAttachView(); - chatAttachView.loadGalleryPhotos(); + chatAttachAlert.loadGalleryPhotos(); if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) { chatActivityEnterView.closeKeyboard(); } - - chatAttachView.init(ChatActivity.this); - showDialog(chatAttachViewSheet); + chatAttachAlert.init(ChatActivity.this); + showDialog(chatAttachAlert); } else if (id == bot_help) { SendMessagesHelper.getInstance().sendMessage("/help", dialog_id, null, null, false, chatActivityEnterView == null || chatActivityEnterView.asAdmin(), null, null, null); } else if (id == bot_settings) { @@ -1042,20 +1035,62 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); - actionModeTextView = new TextView(actionMode.getContext()); - actionModeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - actionModeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - actionModeTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); - actionModeTextView.setVisibility(View.GONE); - actionModeTextView.setGravity(Gravity.CENTER_VERTICAL); - actionModeTextView.setText(LocaleController.getString("Edit", R.string.Edit)); - actionMode.addView(actionModeTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 65, 0, 0, 0)); - actionModeTextView.setOnTouchListener(new View.OnTouchListener() { + actionModeTitleContainer = new FrameLayout(context) { + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + + actionModeTextView.setTextSize(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 18 : 20); + actionModeTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.AT_MOST)); + + if (actionModeSubTextView.getVisibility() != GONE) { + actionModeSubTextView.setTextSize(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 14 : 16); + actionModeSubTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(20), MeasureSpec.AT_MOST)); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int height = bottom - top; + + int textTop; + if (actionModeSubTextView.getVisibility() != GONE) { + textTop = (height / 2 - actionModeTextView.getTextHeight()) / 2 + AndroidUtilities.dp(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 2 : 3); + } else { + textTop = (height - actionModeTextView.getTextHeight()) / 2; + } + actionModeTextView.layout(0, textTop, actionModeTextView.getMeasuredWidth(), textTop + actionModeTextView.getTextHeight()); + + if (actionModeSubTextView.getVisibility() != GONE) { + textTop = height / 2 + (height / 2 - actionModeSubTextView.getTextHeight()) / 2 - AndroidUtilities.dp(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 1 : 1); + actionModeSubTextView.layout(0, textTop, actionModeSubTextView.getMeasuredWidth(), textTop + actionModeSubTextView.getTextHeight()); + } + } + }; + actionMode.addView(actionModeTitleContainer, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 65, 0, 0, 0)); + actionModeTitleContainer.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); + actionModeTitleContainer.setVisibility(View.GONE); + + actionModeTextView = new SimpleTextView(context); + actionModeTextView.setTextSize(18); + actionModeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + actionModeTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); + actionModeTextView.setText(LocaleController.getString("Edit", R.string.Edit)); + actionModeTitleContainer.addView(actionModeTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + actionModeSubTextView = new SimpleTextView(context); + actionModeSubTextView.setGravity(Gravity.LEFT); + actionModeSubTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); + actionModeTitleContainer.addView(actionModeSubTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); if (currentEncryptedChat == null) { if (!isBroadcast) { @@ -1064,8 +1099,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not actionModeViews.add(actionMode.addItem(copy, R.drawable.ic_ab_fwd_copy, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItem(forward, R.drawable.ic_ab_fwd_forward, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItem(delete, R.drawable.ic_ab_fwd_delete, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(actionMode.addItem(edit_done, R.drawable.check_blue, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionMode.getItem(edit_done).setVisibility(View.GONE); + actionModeViews.add(editDoneItem = actionMode.addItem(edit_done, R.drawable.check_blue, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); + editDoneItem.setVisibility(View.GONE); + editDoneItemProgress = new ContextProgressView(context); + editDoneItem.addView(editDoneItemProgress, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + editDoneItemProgress.setVisibility(View.INVISIBLE); } else { actionModeViews.add(actionMode.addItem(reply, R.drawable.ic_ab_reply, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); actionModeViews.add(actionMode.addItem(copy, R.drawable.ic_ab_fwd_copy, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); @@ -1115,48 +1153,40 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (chatActivityEnterView.isPopupView(child)) { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, MeasureSpec.EXACTLY)); } else if (child == mentionContainer) { - int orientation = mentionsAdapter.getOrientation(); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mentionContainer.getLayoutParams(); int height; mentionListViewIgnoreLayout = true; - if (mentionsAdapter.isBotContext()) { - if (orientation == LinearLayoutManager.HORIZONTAL) { - height = AndroidUtilities.dp(90); - if (botSwitchCell.getVisibility() == VISIBLE) { - height += AndroidUtilities.dp(36); - mentionListView.setPadding(0, AndroidUtilities.dp(2 + 36), AndroidUtilities.dp(5), 0); - } else { - mentionListView.setPadding(0, AndroidUtilities.dp(2), AndroidUtilities.dp(5), 0); + + + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { + int size = mentionGridLayoutManager.getRowsCount(widthSize); + int maxHeight = size * 102; + if (mentionsAdapter.isBotContext()) { + if (mentionsAdapter.getBotContextSwitch() != null) { + maxHeight += 34; } - } else { - int size = mentionsAdapter.getItemCount(); - int maxHeight = 0; + } + height = heightSize - chatActivityEnterView.getMeasuredHeight() + (maxHeight != 0 ? AndroidUtilities.dp(2) : 0); + mentionListView.setPadding(0, Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))), 0, 0); + } else { + int size = mentionsAdapter.getItemCount(); + int maxHeight = 0; + if (mentionsAdapter.isBotContext()) { if (mentionsAdapter.getBotContextSwitch() != null) { maxHeight += 36; size -= 1; } maxHeight += size * 68; - - height = heightSize - chatActivityEnterView.getMeasuredHeight() + (maxHeight != 0 ? AndroidUtilities.dp(2) : 0); - mentionListView.setPadding(0, Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))), 0, 0); - } - layoutParams.height = height; - layoutParams.topMargin = 0; - } else { - if (orientation == LinearLayoutManager.HORIZONTAL) { - mentionListView.setPadding(0, AndroidUtilities.dp(2), AndroidUtilities.dp(5), 0); - height = 90; } else { - mentionListView.setPadding(0, AndroidUtilities.dp(2), 0, 0); - if (mentionsAdapter.isBotContext()) { - height = 36 * 3 + 18; - } else { - height = 36 * Math.min(3, mentionsAdapter.getItemCount()) + (mentionsAdapter.getItemCount() > 3 ? 18 : 0); - } + maxHeight += size * 36; } - layoutParams.height = AndroidUtilities.dp(height + (height != 0 ? 2 : 0)); - layoutParams.topMargin = -AndroidUtilities.dp(height); + height = heightSize - chatActivityEnterView.getMeasuredHeight() + (maxHeight != 0 ? AndroidUtilities.dp(2) : 0); + mentionListView.setPadding(0, Math.max(0, height - AndroidUtilities.dp(Math.min(maxHeight, 68 * 1.8f))), 0, 0); } + + layoutParams.height = height; + layoutParams.topMargin = 0; + mentionListViewIgnoreLayout = false; child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY)); } else { @@ -1348,11 +1378,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (chatAdapter.isBot/* || ChatObject.isChannel(currentChat) && currentChat.megagroup*/) { + forceScrollToTop = false; + if (chatAdapter.isBot) { int childCount = getChildCount(); for (int a = 0; a < childCount; a++) { View child = getChildAt(a); - if (child instanceof BotHelpCell/* || child instanceof ChatMigrateCell*/) { + if (child instanceof BotHelpCell) { int height = b - t; int top = height / 2 - child.getMeasuredHeight() / 2; if (child.getTop() > top) { @@ -1727,10 +1758,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onDraw(Canvas canvas) { - if (mentionsAdapter.isBotContext() && mentionsAdapter.getOrientation() == LinearLayoutManager.VERTICAL) { - background.setBounds(0, mentionListViewScrollOffsetY - AndroidUtilities.dp(2), getMeasuredWidth(), getMeasuredHeight()); + if (mentionListView.getChildCount() <= 0) { + return; + } + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout() && mentionsAdapter.getBotContextSwitch() == null) { + background.setBounds(0, mentionListViewScrollOffsetY - AndroidUtilities.dp(4), getMeasuredWidth(), getMeasuredHeight()); } else { - background.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + background.setBounds(0, mentionListViewScrollOffsetY - AndroidUtilities.dp(2), getMeasuredWidth(), getMeasuredHeight()); } background.draw(canvas); } @@ -1760,7 +1794,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY && mentionsAdapter.isBotContext() && mentionsAdapter.getOrientation() == LinearLayoutManager.VERTICAL) { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { return false; } boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, mentionListView, 0); @@ -1769,7 +1803,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean onTouchEvent(MotionEvent event) { - if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY && mentionsAdapter.isBotContext() && mentionsAdapter.getOrientation() == LinearLayoutManager.VERTICAL) { + if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { return false; } //supress warning @@ -1800,7 +1834,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (newPosition != -1) { mentionListViewIgnoreLayout = true; - mentionLayoutManager.scrollToPositionWithOffset(newPosition, newTop); + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { + mentionGridLayoutManager.scrollToPositionWithOffset(newPosition, newTop); + } else { + mentionLayoutManager.scrollToPositionWithOffset(newPosition, newTop); + } super.onLayout(false, l, t, r, b); mentionListViewIgnoreLayout = false; } @@ -1822,9 +1860,87 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return false; } }; + mentionLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mentionGridLayoutManager = new ExtendedGridLayoutManager(context, 100) { + + private Size size = new Size(); + + @Override + protected Size getSizeForItem(int i) { + if (mentionsAdapter.getBotContextSwitch() != null) { + i++; + } + Object object = mentionsAdapter.getItem(i); + if (object instanceof TLRPC.BotInlineResult) { + TLRPC.BotInlineResult inlineResult = (TLRPC.BotInlineResult) object; + if (inlineResult.document != null) { + size.width = inlineResult.document.thumb != null ? inlineResult.document.thumb.w : 100; + size.height = inlineResult.document.thumb != null ? inlineResult.document.thumb.h : 100; + for (int b = 0; b < inlineResult.document.attributes.size(); b++) { + TLRPC.DocumentAttribute attribute = inlineResult.document.attributes.get(b); + if (attribute instanceof TLRPC.TL_documentAttributeImageSize || attribute instanceof TLRPC.TL_documentAttributeVideo) { + size.width = attribute.w; + size.height = attribute.h; + break; + } + } + } else { + size.width = inlineResult.w; + size.height = inlineResult.h; + } + } + return size; + } + + @Override + protected int getFlowItemCount() { + if (mentionsAdapter.getBotContextSwitch() != null) { + return getItemCount() - 1; + } + return super.getFlowItemCount(); + } + }; + mentionGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + Object object = mentionsAdapter.getItem(position); + if (object instanceof TLRPC.TL_inlineBotSwitchPM) { + return 100; + } else { + if (mentionsAdapter.getBotContextSwitch() != null) { + position--; + } + return mentionGridLayoutManager.getSpanSizeForItem(position); + } + } + }); + mentionListView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = 0; + outRect.right = 0; + outRect.top = 0; + outRect.bottom = 0; + if (parent.getLayoutManager() == mentionGridLayoutManager) { + int position = parent.getChildAdapterPosition(view); + if (mentionsAdapter.getBotContextSwitch() != null) { + if (position == 0) { + return; + } + position--; + if (!mentionGridLayoutManager.isFirstRow(position)) { + outRect.top = AndroidUtilities.dp(2); + } + } else { + outRect.top = AndroidUtilities.dp(2); + } + outRect.right = mentionGridLayoutManager.isLastInRow(position) ? 0 : AndroidUtilities.dp(2); + } + } + }); mentionListView.setItemAnimator(null); mentionListView.setLayoutAnimation(null); - mentionLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mentionListView.setClipToPadding(false); mentionListView.setLayoutManager(mentionLayoutManager); mentionListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER); mentionContainer.addView(mentionListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -1832,29 +1948,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not mentionListView.setAdapter(mentionsAdapter = new MentionsAdapter(context, false, dialog_id, new MentionsAdapter.MentionsAdapterDelegate() { @Override public void needChangePanelVisibility(boolean show) { + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { + mentionListView.setLayoutManager(mentionGridLayoutManager); + } else { + mentionListView.setLayoutManager(mentionLayoutManager); + } if (show) { - int orientation = mentionsAdapter.getOrientation(); - if (orientation == LinearLayoutManager.HORIZONTAL) { - mentionListView.setClipToPadding(false); - mentionListView.setDisallowInterceptTouchEvents(true); - } else { - mentionListView.setClipToPadding(!mentionsAdapter.isBotContext()); - mentionListView.setDisallowInterceptTouchEvents(false); - } - if (mentionsAdapter.isBotContext() && orientation == LinearLayoutManager.HORIZONTAL && mentionsAdapter.getBotContextSwitch() != null) { - botSwitchShadow.setVisibility(View.VISIBLE); - botSwitchCell.setVisibility(View.VISIBLE); - botSwitchCell.setText(mentionsAdapter.getBotContextSwitch().text); - } else { - botSwitchShadow.setVisibility(View.GONE); - botSwitchCell.setVisibility(View.GONE); - } - if (!mentionsAdapter.isBotContext() || orientation == LinearLayoutManager.HORIZONTAL) { - mentionListViewScrollOffsetY = 0; - mentionListViewLastViewPosition = -1; - } - mentionLayoutManager.setOrientation(orientation); - if (mentionListAnimation != null) { mentionListAnimation.cancel(); mentionListAnimation = null; @@ -1863,6 +1962,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (mentionContainer.getVisibility() == View.VISIBLE) { ViewProxy.setAlpha(mentionContainer, 1.0f); return; + } + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { + mentionGridLayoutManager.scrollToPositionWithOffset(0, 10000); } else { mentionLayoutManager.scrollToPositionWithOffset(0, 10000); } @@ -1989,7 +2091,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (object instanceof TLRPC.User) { TLRPC.User user = (TLRPC.User) object; if (user != null) { - chatActivityEnterView.replaceWithText(start, len, "@" + user.username + " "); + if (user.username != null) { + chatActivityEnterView.replaceWithText(start, len, "@" + user.username + " "); + } else { + String name = user.first_name; + if (name == null || name.length() == 0) { + name = user.last_name; + } + Spannable spannable = new SpannableString(name + " "); + spannable.setSpan(new URLSpanUserMention("" + user.id), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + chatActivityEnterView.replaceWithText(start, len, spannable); + } } } else if (object instanceof String) { if (mentionsAdapter.isBotCommands()) { @@ -2003,16 +2115,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (text == null) { return; } + int uid = mentionsAdapter.getContextBotId(); TLRPC.BotInlineResult result = (TLRPC.BotInlineResult) object; HashMap params = new HashMap<>(); params.put("id", result.id); params.put("query_id", "" + result.query_id); - params.put("bot", "" + mentionsAdapter.getContextBotId()); + params.put("bot", "" + uid); params.put("bot_name", mentionsAdapter.getContextBotName()); - mentionsAdapter.addRecentBot(); SendMessagesHelper.prepareSendingBotContextResult(result, params, dialog_id, replyingMessageObject, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); chatActivityEnterView.setFieldText(""); showReplyPanel(false, null, null, null, false, true); + SearchQuery.increaseInlineRaiting(uid); } else if (object instanceof TLRPC.TL_inlineBotSwitchPM) { processInlineBotContextPM((TLRPC.TL_inlineBotSwitchPM) object); } @@ -2062,31 +2175,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int lastVisibleItem = mentionLayoutManager.findLastVisibleItemPosition(); + int lastVisibleItem; + if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { + lastVisibleItem = mentionGridLayoutManager.findLastVisibleItemPosition(); + } else { + lastVisibleItem = mentionLayoutManager.findLastVisibleItemPosition(); + } int visibleItemCount = lastVisibleItem == RecyclerView.NO_POSITION ? 0 : lastVisibleItem; if (visibleItemCount > 0 && lastVisibleItem > mentionsAdapter.getItemCount() - 5) { mentionsAdapter.searchForContextBotForNextOffset(); } - mentionListViewUpdateLayout(); } }); - - botSwitchCell = new BotSwitchCell(context); - botSwitchCell.setVisibility(View.GONE); - botSwitchCell.setBackgroundResource(R.drawable.list_selector); - mentionContainer.addView(botSwitchCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 0, 2, 0, 0)); - botSwitchCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - processInlineBotContextPM(mentionsAdapter.getBotContextSwitch()); - } - }); - - botSwitchShadow = new View(context); - botSwitchShadow.setBackgroundResource(R.drawable.header_shadow); - botSwitchShadow.setVisibility(View.GONE); - mentionContainer.addView(botSwitchShadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.TOP, 0, 38, 0, 0)); } pagedownButton = new ImageView(context); @@ -2113,7 +2214,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not contentView.addView(chatActivityEnterView, contentView.getChildCount() - 1, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM)); chatActivityEnterView.setDelegate(new ChatActivityEnterView.ChatActivityEnterViewDelegate() { @Override - public void onMessageSend(String message) { + public void onMessageSend(CharSequence message) { moveScrollToLastMessage(); showReplyPanel(false, null, null, null, false, true); if (mentionsAdapter != null) { @@ -2184,15 +2285,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void onMessageEditEnd() { - mentionsAdapter.setNeedBotContext(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); - chatListView.setOnItemLongClickListener(onItemLongClickListener); - chatListView.setOnItemClickListener(onItemClickListener); - chatListView.setClickable(true); - chatListView.setLongClickable(true); - actionModeTextView.setVisibility(View.GONE); - selectedMessagesCountTextView.setVisibility(View.VISIBLE); - chatActivityEnterView.setAllowStickersAndGifs(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 23, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); + public void onMessageEditEnd(boolean loading) { + if (loading) { + showEditDoneProgress(true, true); + } else { + mentionsAdapter.setNeedBotContext(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); + chatListView.setOnItemLongClickListener(onItemLongClickListener); + chatListView.setOnItemClickListener(onItemClickListener); + chatListView.setClickable(true); + chatListView.setLongClickable(true); + mentionsAdapter.setAllowNewMentions(true); + actionModeTitleContainer.setVisibility(View.GONE); + selectedMessagesCountTextView.setVisibility(View.VISIBLE); + chatActivityEnterView.setAllowStickersAndGifs(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 23, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); + if (editingMessageObjectReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(editingMessageObjectReqId, true); + editingMessageObjectReqId = 0; + } + actionBar.hideActionMode(); + updatePinnedMessageView(true); + updateVisibleRows(); + } } @Override @@ -2260,22 +2373,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); - replyNameTextView = new TextView(context); - replyNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + replyNameTextView = new SimpleTextView(context); + replyNameTextView.setTextSize(14); replyNameTextView.setTextColor(Theme.REPLY_PANEL_NAME_TEXT_COLOR); replyNameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - replyNameTextView.setSingleLine(true); - replyNameTextView.setEllipsize(TextUtils.TruncateAt.END); - replyNameTextView.setMaxLines(1); - replyLayout.addView(replyNameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 52, 4, 52, 0)); + replyLayout.addView(replyNameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.TOP | Gravity.LEFT, 52, 6, 52, 0)); - replyObjectTextView = new TextView(context); - replyObjectTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + replyObjectTextView = new SimpleTextView(context); + replyObjectTextView.setTextSize(14); replyObjectTextView.setTextColor(Theme.REPLY_PANEL_MESSAGE_TEXT_COLOR); - replyObjectTextView.setSingleLine(true); - replyObjectTextView.setEllipsize(TextUtils.TruncateAt.END); - replyObjectTextView.setMaxLines(1); - replyLayout.addView(replyObjectTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 52, 22, 52, 0)); + replyLayout.addView(replyObjectTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.TOP | Gravity.LEFT, 52, 24, 52, 0)); replyImageView = new BackupImageView(context); replyLayout.addView(replyImageView, LayoutHelper.createFrame(34, 34, Gravity.TOP | Gravity.LEFT, 52, 6, 0, 0)); @@ -2433,7 +2540,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void mentionListViewUpdateLayout() { - if (mentionListView.getChildCount() <= 0 || !mentionsAdapter.isBotContext() || mentionsAdapter.getOrientation() != LinearLayoutManager.VERTICAL) { + if (mentionListView.getChildCount() <= 0) { mentionListViewScrollOffsetY = 0; mentionListViewLastViewPosition = -1; return; @@ -2457,6 +2564,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private void checkBotCommands() { + URLSpanBotCommand.enabled = false; + if (currentUser != null && currentUser.bot) { + URLSpanBotCommand.enabled = true; + } else if (info instanceof TLRPC.TL_chatFull) { + for (int a = 0; a < info.participants.participants.size(); a++) { + TLRPC.ChatParticipant participant = info.participants.participants.get(a); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user != null && user.bot) { + URLSpanBotCommand.enabled = true; + break; + } + } + } else if (info instanceof TLRPC.TL_channelFull) { + URLSpanBotCommand.enabled = !info.bot_info.isEmpty(); + } + } + public void processInlineBotContextPM(TLRPC.TL_inlineBotSwitchPM object) { if (object == null) { return; @@ -2482,15 +2607,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void createChatAttachView() { - if (chatAttachView == null) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - chatAttachView = new ChatAttachView(getParentActivity()); - chatAttachView.setDelegate(new ChatAttachView.ChatAttachViewDelegate() { + if (getParentActivity() == null) { + return; + } + if (chatAttachAlert == null) { + chatAttachAlert = new ChatAttachAlert(getParentActivity()); + chatAttachAlert.setDelegate(new ChatAttachAlert.ChatAttachViewDelegate() { @Override public void didPressedButton(int button) { if (button == 7) { - chatAttachViewSheet.dismiss(); - HashMap selectedPhotos = chatAttachView.getSelectedPhotos(); + chatAttachAlert.dismiss(); + HashMap selectedPhotos = chatAttachAlert.getSelectedPhotos(); if (!selectedPhotos.isEmpty()) { ArrayList photos = new ArrayList<>(); ArrayList captions = new ArrayList<>(); @@ -2511,54 +2638,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not showReplyPanel(false, null, null, null, false, true); } return; - } else { - if (chatAttachViewSheet != null) { - chatAttachViewSheet.dismissWithButtonClick(button); - } + } else if (chatAttachAlert != null) { + chatAttachAlert.dismissWithButtonClick(button); } processSelectedAttach(button); } - }); - builder.setDelegate(new BottomSheet.BottomSheetDelegate() { - - @Override - public void onRevealAnimationStart(boolean open) { - if (chatAttachView != null) { - chatAttachView.onRevealAnimationStart(open); - } - } - - @Override - public void onRevealAnimationProgress(boolean open, float radius, int x, int y) { - if (chatAttachView != null) { - chatAttachView.onRevealAnimationProgress(open, radius, x, y); - } - } - - @Override - public void onRevealAnimationEnd(boolean open) { - if (chatAttachView != null) { - chatAttachView.onRevealAnimationEnd(open); - } - } - - @Override - public void onOpenAnimationEnd() { - if (chatAttachView != null) { - chatAttachView.onRevealAnimationEnd(true); - } - } @Override public View getRevealView() { return menuItem; } + + @Override + public void didSelectBot(TLRPC.User user) { + if (chatActivityEnterView == null || user.username == null || user.username.length() == 0) { + return; + } + chatActivityEnterView.setFieldText("@" + user.username + " "); + chatActivityEnterView.openKeyboard(); + } }); - builder.setApplyTopPadding(false); - builder.setApplyBottomPadding(false); - builder.setUseRevealAnimation(); - builder.setCustomView(chatAttachView); - chatAttachViewSheet = builder.create(); } } @@ -2658,6 +2757,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (document instanceof TLRPC.TL_document) { SendMessagesHelper.getInstance().sendSticker(document, dialog_id, replyingMessageObject, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); showReplyPanel(false, null, null, null, false, true); + chatActivityEnterView.addStickerToRecent(document); } chatActivityEnterView.setFieldText(""); } @@ -3047,7 +3147,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not @Override public boolean dismissDialogOnPause(Dialog dialog) { - return !(dialog == chatAttachViewSheet && PhotoViewer.getInstance().isVisible()) && super.dismissDialogOnPause(dialog); + return !(dialog == chatAttachAlert && PhotoViewer.getInstance().isVisible()) && super.dismissDialogOnPause(dialog); } private void searchLinks(final CharSequence charSequence, final boolean force) { @@ -3226,76 +3326,75 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } - public void showReplyPanel(boolean show, MessageObject messageObject, ArrayList messageObjects, TLRPC.WebPage webPage, boolean cancel, boolean animated) { + public void showReplyPanel(boolean show, MessageObject messageObjectToReply, ArrayList messageObjectsToForward, TLRPC.WebPage webPage, boolean cancel, boolean animated) { if (chatActivityEnterView == null) { return; } if (show) { - if (messageObject == null && messageObjects == null && webPage == null) { + if (messageObjectToReply == null && messageObjectsToForward == null && webPage == null) { return; } boolean openKeyboard = false; - if (messageObject != null && messageObject.getDialogId() != dialog_id) { - messageObjects = new ArrayList<>(); - messageObjects.add(messageObject); - messageObject = null; + if (messageObjectToReply != null && messageObjectToReply.getDialogId() != dialog_id) { + messageObjectsToForward = new ArrayList<>(); + messageObjectsToForward.add(messageObjectToReply); + messageObjectToReply = null; openKeyboard = true; } - if (messageObject != null) { + if (messageObjectToReply != null) { + forwardingMessages = null; + replyingMessageObject = messageObjectToReply; + chatActivityEnterView.setReplyingMessageObject(messageObjectToReply); + if (foundWebPage != null) { + return; + } String name; - if (messageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); + if (messageObjectToReply.isFromUser()) { + TLRPC.User user = MessagesController.getInstance().getUser(messageObjectToReply.messageOwner.from_id); if (user == null) { return; } name = UserObject.getUserName(user); } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.messageOwner.to_id.channel_id); + TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObjectToReply.messageOwner.to_id.channel_id); if (chat == null) { return; } name = chat.title; } - - forwardingMessages = null; - replyingMessageObject = messageObject; - chatActivityEnterView.setReplyingMessageObject(messageObject); - if (foundWebPage != null) { - return; - } replyIconImageView.setImageResource(R.drawable.reply); replyNameTextView.setText(name); - if (messageObject.messageText != null) { - String mess = messageObject.messageText.toString(); + + if (messageObjectToReply.messageText != null) { + String mess = messageObjectToReply.messageText.toString(); if (mess.length() > 150) { mess = mess.substring(0, 150); } mess = mess.replace('\n', ' '); replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); } - } else if (messageObjects != null) { - if (messageObjects.isEmpty()) { + } else if (messageObjectsToForward != null) { + if (messageObjectsToForward.isEmpty()) { return; } replyingMessageObject = null; chatActivityEnterView.setReplyingMessageObject(null); - forwardingMessages = messageObjects; - + forwardingMessages = messageObjectsToForward; if (foundWebPage != null) { return; } chatActivityEnterView.setForceShowSendButton(true, animated); ArrayList uids = new ArrayList<>(); replyIconImageView.setImageResource(R.drawable.forward_blue); - MessageObject object = messageObjects.get(0); + MessageObject object = messageObjectsToForward.get(0); if (object.isFromUser()) { uids.add(object.messageOwner.from_id); } else { uids.add(-object.messageOwner.to_id.channel_id); } - int type = messageObjects.get(0).type; - for (int a = 1; a < messageObjects.size(); a++) { - object = messageObjects.get(a); + int type = messageObjectsToForward.get(0).type; + for (int a = 1; a < messageObjectsToForward.size(); a++) { + object = messageObjectsToForward.get(a); Integer uid; if (object.isFromUser()) { uid = object.messageOwner.from_id; @@ -3305,7 +3404,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!uids.contains(uid)) { uids.add(uid); } - if (messageObjects.get(a).type != type) { + if (messageObjectsToForward.get(a).type != type) { type = -1; } } @@ -3351,50 +3450,50 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } replyNameTextView.setText(userNames); if (type == -1 || type == 0 || type == 10 || type == 11) { - if (messageObjects.size() == 1 && messageObjects.get(0).messageText != null) { - String mess = messageObjects.get(0).messageText.toString(); + if (messageObjectsToForward.size() == 1 && messageObjectsToForward.get(0).messageText != null) { + String mess = messageObjectsToForward.get(0).messageText.toString(); if (mess.length() > 150) { mess = mess.substring(0, 150); } mess = mess.replace('\n', ' '); replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); } else { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjectsToForward.size())); } } else { if (type == 1) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedPhoto", messageObjects.size())); - if (messageObjects.size() == 1) { - messageObject = messageObjects.get(0); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedPhoto", messageObjectsToForward.size())); + if (messageObjectsToForward.size() == 1) { + messageObjectToReply = messageObjectsToForward.get(0); } } else if (type == 4) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedLocation", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedLocation", messageObjectsToForward.size())); } else if (type == 3) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedVideo", messageObjects.size())); - if (messageObjects.size() == 1) { - messageObject = messageObjects.get(0); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedVideo", messageObjectsToForward.size())); + if (messageObjectsToForward.size() == 1) { + messageObjectToReply = messageObjectsToForward.get(0); } } else if (type == 12) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedContact", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedContact", messageObjectsToForward.size())); } else if (type == 2) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedAudio", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedAudio", messageObjectsToForward.size())); } else if (type == 14) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMusic", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMusic", messageObjectsToForward.size())); } else if (type == 13) { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjectsToForward.size())); } else if (type == 8 || type == 9) { - if (messageObjects.size() == 1) { + if (messageObjectsToForward.size() == 1) { if (type == 8) { replyObjectTextView.setText(LocaleController.getString("AttachGif", R.string.AttachGif)); } else { String name; - if ((name = FileLoader.getDocumentFileName(messageObjects.get(0).getDocument())).length() != 0) { + if ((name = FileLoader.getDocumentFileName(messageObjectsToForward.get(0).getDocument())).length() != 0) { replyObjectTextView.setText(name); } - messageObject = messageObjects.get(0); + messageObjectToReply = messageObjectsToForward.get(0); } } else { - replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedFile", messageObjects.size())); + replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedFile", messageObjectsToForward.size())); } } } @@ -3425,8 +3524,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) replyNameTextView.getLayoutParams(); FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) replyObjectTextView.getLayoutParams(); - TLRPC.PhotoSize photoSize = messageObject != null ? FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80) : null; - if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || messageObject.type == 13 || messageObject != null && messageObject.isSecretMedia()) { + TLRPC.PhotoSize photoSize = messageObjectToReply != null ? FileLoader.getClosestPhotoSizeWithSize(messageObjectToReply.photoThumbs, 80) : null; + if (photoSize == null || photoSize instanceof TLRPC.TL_photoSizeEmpty || photoSize.location instanceof TLRPC.TL_fileLocationUnavailable || messageObjectToReply.type == 13 || messageObjectToReply != null && messageObjectToReply.isSecretMedia()) { replyImageView.setImageBitmap(null); replyImageLocation = null; replyImageView.setVisibility(View.INVISIBLE); @@ -3471,7 +3570,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } private void moveScrollToLastMessage() { - if (chatListView != null) { + if (chatListView != null && !messages.isEmpty()) { chatLayoutManager.scrollToPositionWithOffset(messages.size() - 1, -100000 - chatListView.getPaddingTop()); } } @@ -3606,15 +3705,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not int count = chatListView.getChildCount(); for (int a = 0; a < count; a++) { View view = chatListView.getChildAt(a); - if (view instanceof ChatBaseCell) { - ChatBaseCell cell = (ChatBaseCell) view; - if (cell.getMessageObject() != null && cell.getMessageObject().getId() == object.getId()) { + if (view instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) view; + MessageObject messageObject = cell.getMessageObject(); + if (messageObject != null && messageObject.getId() == object.getId()) { found = true; break; } } else if (view instanceof ChatActionCell) { ChatActionCell cell = (ChatActionCell) view; - if (cell.getMessageObject() != null && cell.getMessageObject().getId() == object.getId()) { + MessageObject messageObject = cell.getMessageObject(); + if (messageObject != null && messageObject.getId() == object.getId()) { found = true; break; } @@ -4006,8 +4107,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private void processRowSelect(View view) { MessageObject message = null; - if (view instanceof ChatBaseCell) { - message = ((ChatBaseCell) view).getMessageObject(); + if (view instanceof ChatMessageCell) { + message = ((ChatMessageCell) view).getMessageObject(); } else if (view instanceof ChatActionCell) { message = ((ChatActionCell) view).getMessageObject(); } @@ -4184,14 +4285,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not PhotoViewer.getInstance().openPhotoForSelect(arrayList, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { @Override public void sendButtonPressed(int index) { - MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) arrayList.get(0); - if (photoEntry.imagePath != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); - showReplyPanel(false, null, null, null, false, true); - } else if (photoEntry.path != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); - showReplyPanel(false, null, null, null, false, true); - } + sendPhoto((MediaController.PhotoEntry) arrayList.get(0)); } }, this); AndroidUtilities.addMediaToGallery(currentPicturePath); @@ -4355,11 +4449,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not first_unread_id = 0; last_message_id = 0; unread_to_load = 0; - if (chatAdapter != null) { - chatAdapter.removeMessageObject(unreadMessageObject); - } else { - messages.remove(unreadMessageObject); - } + removeMessageObject(unreadMessageObject); unreadMessageObject = null; } } @@ -4462,6 +4552,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } firstLoading = false; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (parentLayout != null) { + parentLayout.resumeDelayedFragmentAnimation(); + } + } + }); } if (load_type == 1) { @@ -4591,6 +4689,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + if (load_type == 0 && newRowsCount == 0) { + loadsCount--; + } if (forwardEndReached[loadIndex] && loadIndex != 1) { first_unread_id = 0; @@ -4635,7 +4736,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentEncryptedChat != null || isBroadcast) { endReached[loadIndex] = true; } - cacheEndReached[loadIndex] = true; + if (load_type != 2) { + cacheEndReached[loadIndex] = true; + } } else if (load_type != 2) { endReached[loadIndex] = true;// TODO if < 7 from unread } @@ -4643,7 +4746,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not loading = false; if (chatListView != null) { - if (first || scrollToTopOnResume) { + if (first || scrollToTopOnResume || forceScrollToTop) { + forceScrollToTop = false; chatAdapter.notifyDataSetChanged(); if (scrollToMessage != null) { int yOffset; @@ -4768,6 +4872,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (id == NotificationCenter.updateInterfaces) { int updateMask = (Integer) args[0]; if ((updateMask & MessagesController.UPDATE_MASK_NAME) != 0 || (updateMask & MessagesController.UPDATE_MASK_CHAT_NAME) != 0) { + if (currentChat != null) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(currentChat.id); + if (chat != null) { + currentChat = chat; + } + } else if (currentUser != null) { + TLRPC.User user = MessagesController.getInstance().getUser(currentUser.id); + if (user != null) { + currentUser = user; + } + } updateTitle(); } boolean updateSubtitle = false; @@ -5031,11 +5146,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (!obj.isOut()) { if (paused) { if (!scrollToTopUnReadOnResume && unreadMessageObject != null) { - if (chatAdapter != null) { - chatAdapter.removeMessageObject(unreadMessageObject); - } else { - messages.remove(unreadMessageObject); - } + removeMessageObject(unreadMessageObject); unreadMessageObject = null; } if (unreadMessageObject == null) { @@ -5108,6 +5219,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (paused) { scrollToTopOnResume = true; } else { + forceScrollToTop = true; moveScrollToLastMessage(); } } @@ -5231,8 +5343,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messages.remove(index); } } - updated = true; } + updated = true; } } } @@ -5291,7 +5403,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messages.remove(index); } } - chatAdapter.notifyDataSetChanged(); + if (chatAdapter != null) { + chatAdapter.notifyDataSetChanged(); + } } return; } @@ -5673,6 +5787,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (old.replyMessageObject != null) { messageObject.replyMessageObject = old.replyMessageObject; } + messageObject.messageOwner.attachPath = old.messageOwner.attachPath; + messageObject.attachPathExists = old.attachPathExists; + messageObject.mediaExists = old.mediaExists; messagesDict[loadIndex].put(old.getId(), messageObject); int index = messages.indexOf(old); if (index >= 0) { @@ -5894,6 +6011,11 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + @Override + public boolean needDelayOpenAnimation() { + return firstLoading; + } + @Override public void onTransitionAnimationStart(boolean isOpen, boolean backward) { NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.chatInfoDidLoaded, NotificationCenter.dialogsNeedReload, @@ -5909,14 +6031,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().setAnimationInProgress(false); if (isOpen) { openAnimationEnded = true; - int count = chatListView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = chatListView.getChildAt(a); - if (view instanceof ChatMessageCell) { - ((ChatMessageCell) view).setAllowedToSetPhoto(true); - } - } - if (currentUser != null) { MessagesController.getInstance().loadFullUser(currentUser, classGuid, false); } @@ -6486,6 +6600,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatListView.setOnItemClickListener(onItemClickListener); chatListView.setLongClickable(true); } + checkBotCommands(); } @Override @@ -6546,8 +6661,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < count; a++) { View view = chatListView.getChildAt(a); MessageObject object = null; - if (view instanceof ChatBaseCell) { - ChatBaseCell cell = (ChatBaseCell) view; + if (view instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) view; object = cell.getMessageObject(); } if (object != null && object.getId() < 0 && object.messageOwner.random_id != 0) { @@ -6561,10 +6676,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean fixLayoutInternal() { if (!AndroidUtilities.isTablet() && ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { selectedMessagesCountTextView.setTextSize(18); - actionModeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); } else { selectedMessagesCountTextView.setTextSize(20); - actionModeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); } if (AndroidUtilities.isTablet()) { @@ -6611,8 +6724,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a <= count; a++) { View child = chatListView.getChildAt(a); MessageObject message = null; - if (child instanceof ChatBaseCell) { - message = ((ChatBaseCell) child).getMessageObject(); + if (child instanceof ChatMessageCell) { + message = ((ChatMessageCell) child).getMessageObject(); } else if (child instanceof ChatActionCell) { message = ((ChatActionCell) child).getMessageObject(); } @@ -6645,8 +6758,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not for (int a = 0; a < count; a++) { View child = chatListView.getChildAt(a); MessageObject message = null; - if (child instanceof ChatBaseCell) { - message = ((ChatBaseCell) child).getMessageObject(); + if (child instanceof ChatMessageCell) { + message = ((ChatMessageCell) child).getMessageObject(); } else if (child instanceof ChatActionCell) { message = ((ChatActionCell) child).getMessageObject(); } @@ -6709,7 +6822,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } if (user != null && user.id != UserConfig.getClientUserId()) { - FrameLayout frameLayout = new FrameLayout(getParentActivity()); + FrameLayoutFixed frameLayout = new FrameLayoutFixed(getParentActivity()); if (Build.VERSION.SDK_INT >= 21) { frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); } @@ -6815,8 +6928,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } MessageObject message = null; - if (v instanceof ChatBaseCell) { - message = ((ChatBaseCell) v).getMessageObject(); + if (v instanceof ChatMessageCell) { + message = ((ChatMessageCell) v).getMessageObject(); } else if (v instanceof ChatActionCell) { message = ((ChatActionCell) v).getMessageObject(); } @@ -6846,7 +6959,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean allowChatActions = true; boolean allowPin = message.getDialogId() != mergeDialogId && message.getId() > 0 && ChatObject.isChannel(currentChat) && currentChat.megagroup && (currentChat.creator || currentChat.editor) && (message.messageOwner.action == null || message.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); boolean allowUnpin = message.getDialogId() != mergeDialogId && info != null && info.pinned_msg_id == message.getId() && (currentChat.creator || currentChat.editor); - boolean allowEdit = message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend(); + boolean allowEdit = message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend() && message.getDialogId() != mergeDialogId; if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46 || type == 1 && message.getDialogId() == mergeDialogId || currentEncryptedChat == null && message.getId() < 0 || isBroadcast || currentChat != null && (ChatObject.isNotInChat(currentChat) || ChatObject.isChannel(currentChat) && !currentChat.creator && !currentChat.editor && !currentChat.megagroup)) { allowChatActions = false; } @@ -7055,9 +7168,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (item != null) { item.setVisibility(View.VISIBLE); } - item = actionMode.getItem(edit_done); - if (item != null) { - item.setVisibility(View.GONE); + if (editDoneItem != null) { + editDoneItem.setVisibility(View.GONE); } actionBar.showActionMode(); @@ -7081,6 +7193,109 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not updateVisibleRows(); } + private void showEditDoneProgress(final boolean show, boolean animated) { + if (editDoneItemAnimation != null) { + editDoneItemAnimation.cancel(); + } + if (!animated) { + if (show) { + ViewProxy.setScaleX(editDoneItem.getImageView(), 0.1f); + ViewProxy.setScaleY(editDoneItem.getImageView(), 0.1f); + ViewProxy.setAlpha(editDoneItem.getImageView(), 0.0f); + ViewProxy.setScaleX(editDoneItemProgress, 1.0f); + ViewProxy.setScaleY(editDoneItemProgress, 1.0f); + ViewProxy.setAlpha(editDoneItemProgress, 1.0f); + editDoneItem.getImageView().setVisibility(View.INVISIBLE); + editDoneItemProgress.setVisibility(View.VISIBLE); + editDoneItem.setEnabled(false); + } else { + ViewProxy.setScaleX(editDoneItemProgress, 0.1f); + ViewProxy.setScaleY(editDoneItemProgress, 0.1f); + ViewProxy.setAlpha(editDoneItemProgress, 0.0f); + ViewProxy.setScaleX(editDoneItem.getImageView(), 1.0f); + ViewProxy.setScaleY(editDoneItem.getImageView(), 1.0f); + ViewProxy.setAlpha(editDoneItem.getImageView(), 1.0f); + editDoneItem.getImageView().setVisibility(View.VISIBLE); + editDoneItemProgress.setVisibility(View.INVISIBLE); + editDoneItem.setEnabled(true); + } + } else { + editDoneItemAnimation = new AnimatorSetProxy(); + if (show) { + editDoneItemProgress.setVisibility(View.VISIBLE); + editDoneItem.setEnabled(false); + editDoneItemAnimation.playTogether( + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "scaleX", 0.1f), + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "scaleY", 0.1f), + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "alpha", 0.0f), + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "scaleX", 1.0f), + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "scaleY", 1.0f), + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "alpha", 1.0f)); + } else { + editDoneItem.getImageView().setVisibility(View.VISIBLE); + editDoneItem.setEnabled(true); + editDoneItemAnimation.playTogether( + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "scaleX", 0.1f), + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "scaleY", 0.1f), + ObjectAnimatorProxy.ofFloat(editDoneItemProgress, "alpha", 0.0f), + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "scaleX", 1.0f), + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "scaleY", 1.0f), + ObjectAnimatorProxy.ofFloat(editDoneItem.getImageView(), "alpha", 1.0f)); + + } + editDoneItemAnimation.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (editDoneItemAnimation != null && editDoneItemAnimation.equals(animation)) { + if (!show) { + editDoneItemProgress.clearAnimation(); + editDoneItemProgress.setVisibility(View.INVISIBLE); + } else { + editDoneItem.getImageView().clearAnimation(); + editDoneItem.getImageView().setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (editDoneItemAnimation != null && editDoneItemAnimation.equals(animation)) { + editDoneItemAnimation = null; + } + } + }); + editDoneItemAnimation.setDuration(150); + editDoneItemAnimation.start(); + } + } + + private String getMessageContent(MessageObject messageObject, int previousUid, boolean name) { + String str = ""; + if (name) { + if (previousUid != messageObject.messageOwner.from_id) { + if (messageObject.messageOwner.from_id > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); + if (user != null) { + str = ContactsController.formatName(user.first_name, user.last_name) + ":\n"; + } + } else if (messageObject.messageOwner.from_id < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-messageObject.messageOwner.from_id); + if (chat != null) { + str = chat.title + ":\n"; + } + } + } + } + if (messageObject.type == 0 && messageObject.messageOwner.message != null) { + str += messageObject.messageOwner.message; + } else if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.caption != null) { + str += messageObject.messageOwner.media.caption; + } else { + str += messageObject.messageText; + } + return str; + } + private void processSelectedOption(int option) { if (selectedObject == null) { return; @@ -7111,26 +7326,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 3: { - try { - CharSequence str; - if (selectedObject.type == 0 && selectedObject.messageOwner.message != null) { - str = selectedObject.messageOwner.message; - } else if (selectedObject.messageOwner.media != null && selectedObject.messageOwner.media.caption != null) { - str = selectedObject.messageOwner.media.caption; - } else { - str = selectedObject.messageText; - } - if (Build.VERSION.SDK_INT < 11) { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(str); - } else { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", str); - clipboard.setPrimaryClip(clip); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } + AndroidUtilities.addToClipboard(getMessageContent(selectedObject, 0, false)); break; } case 4: { @@ -7263,77 +7459,63 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedObject = null; return; } - final MessageObject editingMessageObject = selectedObject; - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); - progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); - progressDialog.setCanceledOnTouchOutside(false); - progressDialog.setCancelable(false); + + mentionsAdapter.setNeedBotContext(false); + chatListView.setOnItemLongClickListener(null); + chatListView.setOnItemClickListener(null); + chatListView.setClickable(false); + chatListView.setLongClickable(false); + chatActivityEnterView.setEditingMessageObject(selectedObject, !selectedObject.isMediaEmpty()); + if (chatActivityEnterView.isEditingCaption()) { + mentionsAdapter.setAllowNewMentions(false); + } + actionModeTitleContainer.setVisibility(View.VISIBLE); + selectedMessagesCountTextView.setVisibility(View.GONE); + checkEditTimer(); + + chatActivityEnterView.setAllowStickersAndGifs(false, false); + final ActionBarMenu actionMode = actionBar.createActionMode(); + actionMode.getItem(reply).setVisibility(View.GONE); + actionMode.getItem(copy).setVisibility(View.GONE); + actionMode.getItem(forward).setVisibility(View.GONE); + actionMode.getItem(delete).setVisibility(View.GONE); + if (editDoneItemAnimation != null) { + editDoneItemAnimation.cancel(); + editDoneItemAnimation = null; + } + editDoneItem.setVisibility(View.VISIBLE); + showEditDoneProgress(true, false); + actionBar.showActionMode(); + updatePinnedMessageView(true); + updateVisibleRows(); TLRPC.TL_messages_getMessageEditData req = new TLRPC.TL_messages_getMessageEditData(); req.peer = MessagesController.getInputPeer((int) dialog_id); req.id = selectedObject.getId(); - final int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + editingMessageObjectReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - try { - if (!getParentActivity().isFinishing()) { - progressDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - if (response != null) { - TLRPC.TL_messages_messageEditData res = (TLRPC.TL_messages_messageEditData) response; - if (mentionsAdapter != null) { - mentionsAdapter.setNeedBotContext(false); - chatListView.setOnItemLongClickListener(null); - chatListView.setOnItemClickListener(null); - chatListView.setClickable(false); - chatListView.setLongClickable(false); - chatActivityEnterView.setEditinigMessageObject(editingMessageObject, res.caption); - actionModeTextView.setVisibility(View.VISIBLE); - selectedMessagesCountTextView.setVisibility(View.GONE); - - chatActivityEnterView.setAllowStickersAndGifs(false, false); - final ActionBarMenu actionMode = actionBar.createActionMode(); - actionMode.getItem(reply).setVisibility(View.GONE); - actionMode.getItem(copy).setVisibility(View.GONE); - actionMode.getItem(forward).setVisibility(View.GONE); - actionMode.getItem(delete).setVisibility(View.GONE); - actionMode.getItem(edit_done).setVisibility(View.VISIBLE); - actionBar.showActionMode(); - updatePinnedMessageView(true); - } - } else { + editingMessageObjectReqId = 0; + if (response == null) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("EditMessageError", R.string.EditMessageError)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); showDialog(builder.create()); + + if (chatActivityEnterView != null) { + chatActivityEnterView.setEditingMessageObject(null, false); + } + } else { + showEditDoneProgress(false, true); } } }); } }); - progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - try { - dialog.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - try { - progressDialog.show(); - } catch (Exception e) { - //don't promt - } break; } case 13: { @@ -7397,18 +7579,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not break; } case 16: { - try { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(selectedObject.messageOwner.media.phone_number); - } else { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", selectedObject.messageOwner.media.phone_number); - clipboard.setPrimaryClip(clip); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } + AndroidUtilities.addToClipboard(selectedObject.messageOwner.media.phone_number); break; } case 17: { @@ -7497,7 +7668,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); } - chatActivityEnterView.setEditinigMessageObject(null, false); + chatActivityEnterView.setEditingMessageObject(null, false); actionBar.hideActionMode(); updatePinnedMessageView(true); cantDeleteMessagesCount = 0; @@ -7515,17 +7686,18 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return; } int count = chatListView.getChildCount(); + MessageObject editingMessageObject = chatActivityEnterView != null ? chatActivityEnterView.getEditingMessageObject() : null; for (int a = 0; a < count; a++) { View view = chatListView.getChildAt(a); - if (view instanceof ChatBaseCell) { - ChatBaseCell cell = (ChatBaseCell) view; + if (view instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) view; boolean disableSelection = false; boolean selected = false; if (actionBar.isActionModeShowed()) { MessageObject messageObject = cell.getMessageObject(); - if (selectedMessagesIds[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId())) { - view.setBackgroundColor(0x6633b5e5); + if (messageObject == editingMessageObject || selectedMessagesIds[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId())) { + view.setBackgroundColor(Theme.MSG_SELECTED_BACKGROUND_COLOR); selected = true; } else { view.setBackgroundColor(0); @@ -7545,6 +7717,38 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private void checkEditTimer() { + if (chatActivityEnterView == null) { + return; + } + MessageObject messageObject = chatActivityEnterView.getEditingMessageObject(); + if (messageObject == null) { + return; + } + int dt = MessagesController.getInstance().maxEditTime + 5 * 60 - Math.abs(ConnectionsManager.getInstance().getCurrentTime() - messageObject.messageOwner.date); + if (dt > 0) { + if (dt > 5 * 60) { + if (actionModeSubTextView.getVisibility() != View.GONE) { + actionModeSubTextView.setVisibility(View.GONE); + } + } else { + if (actionModeSubTextView.getVisibility() != View.VISIBLE) { + actionModeSubTextView.setVisibility(View.VISIBLE); + } + actionModeSubTextView.setText(LocaleController.formatString("TimeToEdit", R.string.TimeToEdit, String.format("%d:%02d", dt / 60, dt % 60))); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + checkEditTimer(); + } + }, 1000); + } else { + editDoneItem.setVisibility(View.GONE); + actionModeSubTextView.setText(LocaleController.formatString("TimeToEditExpired", R.string.TimeToEditExpired)); + } + } + private ArrayList createVoiceMessagesPlaylist(MessageObject startMessageObject, boolean playingUnreadMedia) { ArrayList messageObjects = new ArrayList<>(); messageObjects.add(startMessageObject); @@ -7631,8 +7835,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not MessageObject messageToOpen = null; ImageReceiver imageReceiver = null; View view = chatListView.getChildAt(a); - if (view instanceof ChatBaseCell) { - ChatBaseCell cell = (ChatBaseCell) view; + if (view instanceof ChatMessageCell) { + ChatMessageCell cell = (ChatMessageCell) view; MessageObject message = cell.getMessageObject(); if (message != null && message.getId() == messageObject.getId()) { messageToOpen = message; @@ -7702,6 +7906,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 0; } + public void sendPhoto(MediaController.PhotoEntry photoEntry) { + if (photoEntry.imagePath != null) { + SendMessagesHelper.prepareSendingPhoto(photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); + showReplyPanel(false, null, null, null, false, true); + } else if (photoEntry.path != null) { + SendMessagesHelper.prepareSendingPhoto(photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, chatActivityEnterView == null || chatActivityEnterView.asAdmin()); + showReplyPanel(false, null, null, null, false, true); + } + } + public void showOpenUrlAlert(final String url) { if (Browser.isInternalUrl(url)) { Browser.openUrl(getParentActivity(), url, inlineReturn == 0); @@ -7720,6 +7934,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + private void removeMessageObject(MessageObject messageObject) { + int index = messages.indexOf(messageObject); + if (index == -1) { + return; + } + messages.remove(index); + if (chatAdapter != null) { + chatAdapter.notifyItemRemoved(chatAdapter.messagesStartRow + messages.size() - index - 1); + } + } + public class ChatActivityAdapter extends RecyclerView.Adapter { private Context mContext; @@ -7793,9 +8018,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not view = new ChatMessageCell(mContext); } ChatMessageCell chatMessageCell = (ChatMessageCell) view; - chatMessageCell.setDelegate(new ChatBaseCell.ChatBaseCellDelegate() { + chatMessageCell.setDelegate(new ChatMessageCell.ChatMessageCellDelegate() { @Override - public void didPressedShare(ChatBaseCell cell) { + public void didPressedShare(ChatMessageCell cell) { if (getParentActivity() == null) { return; } @@ -7818,7 +8043,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedChannelAvatar(ChatBaseCell cell, TLRPC.Chat chat, int postId) { + public void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int postId) { if (actionBar.isActionModeShowed()) { processRowSelect(cell); return; @@ -7836,12 +8061,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedOther(ChatBaseCell cell) { + public void didPressedOther(ChatMessageCell cell) { createMenu(cell, true); } @Override - public void didPressedUserAvatar(ChatBaseCell cell, TLRPC.User user) { + public void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user) { if (actionBar.isActionModeShowed()) { processRowSelect(cell); return; @@ -7856,7 +8081,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedBotButton(ChatBaseCell cell, TLRPC.KeyboardButton button) { + public void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button) { if (getParentActivity() == null || bottomOverlayChat.getVisibility() == View.VISIBLE && !(button instanceof TLRPC.TL_keyboardButtonCallback) && !(button instanceof TLRPC.TL_keyboardButtonUrl)) { return; } @@ -7864,7 +8089,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedCancelSendButton(ChatBaseCell cell) { + public void didPressedCancelSendButton(ChatMessageCell cell) { MessageObject message = cell.getMessageObject(); if (message.messageOwner.send_state != 0) { SendMessagesHelper.getInstance().cancelSendingMessage(message); @@ -7872,7 +8097,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didLongPressed(ChatBaseCell cell) { + public void didLongPressed(ChatMessageCell cell) { createMenu(cell, false); } @@ -7886,7 +8111,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (url == null) { return; } - if (url instanceof URLSpanNoUnderline) { + if (url instanceof URLSpanUserMention) { + TLRPC.User user = MessagesController.getInstance().getUser(Utilities.parseInt(((URLSpanUserMention) url).getURL())); + if (user != null) { + MessagesController.openChatOrProfileWith(user, null, ChatActivity.this, 0); + } + } else if (url instanceof URLSpanNoUnderline) { String str = ((URLSpanNoUnderline) url).getURL(); if (str.startsWith("@")) { MessagesController.openByUserName(str.substring(1), ChatActivity.this, 0); @@ -7914,19 +8144,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (which == 0) { Browser.openUrl(getParentActivity(), urlFinal, inlineReturn == 0); } else if (which == 1) { - try { - if (Build.VERSION.SDK_INT < 11) { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(urlFinal); - } else { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", urlFinal); - clipboard.setPrimaryClip(clip); - } - Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } + AndroidUtilities.addToClipboard(urlFinal); } } }); @@ -7952,7 +8170,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedReplyMessage(ChatBaseCell cell, int id) { + public void didPressedReplyMessage(ChatMessageCell cell, int id) { MessageObject messageObject = cell.getMessageObject(); if (messageObject.replyMessageObject != null && !messageObject.replyMessageObject.isImportant() && channelMessagesImportant == 2) { channelMessagesImportant = 1; @@ -7962,7 +8180,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedViaBot(ChatBaseCell cell, String username) { + public void didPressedViaBot(ChatMessageCell cell, String username) { if (bottomOverlayChat != null && bottomOverlayChat.getVisibility() == View.VISIBLE || bottomOverlay != null && bottomOverlay.getVisibility() == View.VISIBLE) { return; } @@ -7973,7 +8191,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } @Override - public void didPressedImage(ChatBaseCell cell) { + public void didPressedImage(ChatMessageCell cell) { MessageObject message = cell.getMessageObject(); if (message.isSendError()) { createMenu(cell, false); @@ -8060,7 +8278,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } }); - chatMessageCell.setAllowedToSetPhoto(openAnimationEnded); if (currentEncryptedChat == null) { chatMessageCell.setAllowAssistant(true); } @@ -8139,8 +8356,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not boolean selected = false; boolean disableSelection = false; if (actionBar.isActionModeShowed()) { - if (selectedMessagesIds[message.getDialogId() == dialog_id ? 0 : 1].containsKey(message.getId())) { - view.setBackgroundColor(0x6633b5e5); + MessageObject messageObject = chatActivityEnterView != null ? chatActivityEnterView.getEditingMessageObject() : null; + if (messageObject == message || selectedMessagesIds[message.getDialogId() == dialog_id ? 0 : 1].containsKey(message.getId())) { + view.setBackgroundColor(Theme.MSG_SELECTED_BACKGROUND_COLOR); selected = true; } else { view.setBackgroundColor(0); @@ -8204,15 +8422,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not notifyItemChanged(messagesStartRow + messages.size() - index - 1); } - public void removeMessageObject(MessageObject messageObject) { - int index = messages.indexOf(messageObject); - if (index == -1) { - return; - } - messages.remove(index); - notifyItemRemoved(messagesStartRow + messages.size() - index - 1); - } - @Override public void notifyDataSetChanged() { updateRows(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index 92884aa38..902b93cad 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -27,7 +27,9 @@ import android.os.SystemClock; import android.text.Editable; import android.text.InputFilter; import android.text.Layout; +import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; @@ -62,6 +64,7 @@ import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.FileLog; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; @@ -84,14 +87,14 @@ import java.util.Locale; public class ChatActivityEnterView extends FrameLayoutFixed implements NotificationCenter.NotificationCenterDelegate, SizeNotifierFrameLayout.SizeNotifierFrameLayoutDelegate, StickersAlert.StickersAlertDelegate { public interface ChatActivityEnterViewDelegate { - void onMessageSend(String message); + void onMessageSend(CharSequence message); void needSendTyping(); void onTextChanged(CharSequence text, boolean bigChange); void onAttachButtonHidden(); void onAttachButtonShow(); void onWindowSizeChanged(int size); void onStickersTab(boolean opened); - void onMessageEditEnd(); + void onMessageEditEnd(boolean loading); } private class SeekBarWaveformView extends View { @@ -105,8 +108,10 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat seekBarWaveform.setDelegate(new SeekBar.SeekBarDelegate() { @Override public void onSeekBarDrag(float progress) { - audioToSendMessageObject.audioProgress = progress; - MediaController.getInstance().seekToProgress(audioToSendMessageObject, progress); + if (audioToSendMessageObject != null) { + audioToSendMessageObject.audioProgress = progress; + MediaController.getInstance().seekToProgress(audioToSendMessageObject, progress); + } } }); } @@ -177,7 +182,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat mCursorDrawableField.setAccessible(true); mCursorDrawable = (Drawable[]) mCursorDrawableField.get(editor); } catch (Throwable e) { - FileLog.e("tmessages", e); + // } } @@ -293,10 +298,10 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat private ImageView asAdminButton; private ImageView notifyButton; private RecordCircle recordCircle; - private ContextProgressView contextProgressView; private CloseProgressDrawable2 progressDrawable; private MessageObject editingMessageObject; + private int editingMessageReqId; private boolean editingCaption; private int currentPopupContentType = -1; @@ -619,7 +624,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat return; } checkSendButton(true); - String message = getTrimmedString(charSequence.toString()); + CharSequence message = getTrimmedString(charSequence.toString()); if (delegate != null) { if (count > 2 || charSequence == null || charSequence.length() == 0) { messageWebPageSearch = true; @@ -674,10 +679,6 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } if (isChat) { - contextProgressView = new ContextProgressView(context); - contextProgressView.setVisibility(INVISIBLE); - frameLayout.addView(contextProgressView, LayoutHelper.createFrame(38, 48, Gravity.BOTTOM | Gravity.RIGHT)); - attachButton = new LinearLayout(context); attachButton.setOrientation(LinearLayout.HORIZONTAL); attachButton.setEnabled(false); @@ -997,15 +998,6 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } public void showContextProgress(boolean show) { - /*if (contextProgressView == null) { - return; - } - contextProgressView.setVisibility(show ? VISIBLE : INVISIBLE); - try { - messageEditText.setPadding(0, AndroidUtilities.dp(11), show ? AndroidUtilities.dp(38) : 0, AndroidUtilities.dp(12)); - } catch (Exception e) { - FileLog.e("tmessages", e); - }*/ if (progressDrawable == null) { return; } @@ -1268,6 +1260,9 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat notifyButton.setImageResource(silent ? R.drawable.notify_members_off : R.drawable.notify_members_on); ViewProxy.setPivotX(attachButton, AndroidUtilities.dp((botButton == null || botButton.getVisibility() == GONE) && (notifyButton == null || notifyButton.getVisibility() == GONE) ? 48 : 96)); } + if (attachButton != null) { + updateFieldRight(attachButton.getVisibility() == VISIBLE ? 1 : 0); + } } } @@ -1372,7 +1367,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat checkSendButton(true); return; } - String message = messageEditText.getText().toString(); + CharSequence message = messageEditText.getText(); if (processSendingText(message)) { messageEditText.setText(""); lastTypingTimeSend = 0; @@ -1386,36 +1381,65 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } } + private ArrayList getEntities(CharSequence message) { + ArrayList entities = null; + if (message instanceof Spannable) { + Spannable spannable = (Spannable) message; + URLSpanUserMention spans[] = spannable.getSpans(0, message.length(), URLSpanUserMention.class); + if (spans != null && spans.length > 0) { + entities = new ArrayList<>(); + for (int b = 0; b < spans.length; b++) { + TLRPC.TL_inputMessageEntityMentionName entity = new TLRPC.TL_inputMessageEntityMentionName(); + entity.user_id = MessagesController.getInputUser(Utilities.parseInt(spans[b].getURL())); + if (entity.user_id != null) { + entity.offset = spannable.getSpanStart(spans[b]); + entity.length = Math.min(spannable.getSpanEnd(spans[b]), message.length()) - entity.offset; + if (message.charAt(entity.offset + entity.length - 1) == ' ') { + entity.length--; + } + entities.add(entity); + } + } + } + } + return entities; + } + public void doneEditingMessage() { if (editingMessageObject != null) { - SendMessagesHelper.getInstance().editMessage(editingMessageObject, messageEditText.getText().toString(), messageWebPageSearch, parentFragment); - setEditinigMessageObject(null, false); + delegate.onMessageEditEnd(true); + editingMessageReqId = SendMessagesHelper.getInstance().editMessage(editingMessageObject, messageEditText.getText().toString(), messageWebPageSearch, parentFragment, getEntities(messageEditText.getText()), new Runnable() { + @Override + public void run() { + editingMessageReqId = 0; + setEditingMessageObject(null, false); + } + }); } } - public boolean processSendingText(String text) { + public boolean processSendingText(CharSequence text) { text = getTrimmedString(text); if (text.length() != 0) { int count = (int) Math.ceil(text.length() / 4096.0f); for (int a = 0; a < count; a++) { - String mess = text.substring(a * 4096, Math.min((a + 1) * 4096, text.length())); - SendMessagesHelper.getInstance().sendMessage(mess, dialog_id, replyingMessageObject, messageWebPage, messageWebPageSearch, asAdmin(), null, null, null); + CharSequence mess = text.subSequence(a * 4096, Math.min((a + 1) * 4096, text.length())); + SendMessagesHelper.getInstance().sendMessage(mess.toString(), dialog_id, replyingMessageObject, messageWebPage, messageWebPageSearch, asAdmin(), getEntities(mess), null, null); } return true; } return false; } - private String getTrimmedString(String src) { - src = src.trim(); + private CharSequence getTrimmedString(CharSequence src) { if (src.length() == 0) { return src; } - while (src.startsWith("\n")) { - src = src.substring(1); + while (src.length() > 0 && (src.charAt(0) == '\n' || src.charAt(0) == ' ')) { + src = src.subSequence(1, src.length()); } - while (src.endsWith("\n")) { - src = src.substring(0, src.length() - 1); + while (src.length() > 0 && (src.charAt(src.length() - 1) == '\n' || src.charAt(src.length() - 1) == ' ')) { + src = src.subSequence(0, src.length() - 1); } return src; } @@ -1424,7 +1448,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat if (editingMessageObject != null) { return; } - String message = getTrimmedString(messageEditText.getText().toString()); + CharSequence message = getTrimmedString(messageEditText.getText()); if (message.length() > 0 || forceShowSendButton || audioToSend != null) { boolean showBotButton = messageEditText.caption != null && sendButton.getVisibility() == VISIBLE; boolean showSendButton = messageEditText.caption == null && cancelBotButton.getVisibility() == VISIBLE; @@ -1816,10 +1840,14 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } } - public void setEditinigMessageObject(MessageObject messageObject, boolean caption) { + public void setEditingMessageObject(MessageObject messageObject, boolean caption) { if (audioToSend != null || editingMessageObject == messageObject) { return; } + if (editingMessageReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(editingMessageReqId, true); + editingMessageReqId = 0; + } editingMessageObject = messageObject; editingCaption = caption; if (editingMessageObject != null) { @@ -1834,7 +1862,18 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } else { inputFilters[0] = new InputFilter.LengthFilter(4096); if (editingMessageObject.messageText != null) { - setFieldText(Emoji.replaceEmoji(new SpannableStringBuilder(editingMessageObject.messageText.toString()), messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false)); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(editingMessageObject.messageText.toString()); + ArrayList entities = getEntities(editingMessageObject.messageText); + if (entities != null) { + for (int a = 0; a < entities.size(); a++) { + TLRPC.TL_inputMessageEntityMentionName entity = (TLRPC.TL_inputMessageEntityMentionName) entities.get(a); + if (entity.offset + entity.length < editingMessageObject.messageText.length() && editingMessageObject.messageText.charAt(entity.offset + entity.length) == ' ') { + entity.length++; + } + stringBuilder.setSpan(new URLSpanUserMention("" + entity.user_id.user_id), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + setFieldText(Emoji.replaceEmoji(stringBuilder, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false)); } else { setFieldText(""); } @@ -1856,7 +1895,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat sendButtonContainer.setVisibility(GONE); } else { messageEditText.setFilters(new InputFilter[0]); - delegate.onMessageEditEnd(); + delegate.onMessageEditEnd(false); audioSendButton.setVisibility(VISIBLE); attachButton.setVisibility(VISIBLE); sendButtonContainer.setVisibility(VISIBLE); @@ -1909,9 +1948,9 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat return messageEditText.getSelectionStart(); } - public void replaceWithText(int start, int len, String text) { + public void replaceWithText(int start, int len, CharSequence text) { try { - StringBuilder builder = new StringBuilder(messageEditText.getText()); + SpannableStringBuilder builder = new SpannableStringBuilder(messageEditText.getText()); builder.replace(start, start + len, text); messageEditText.setText(builder); messageEditText.setSelection(start + text.length()); @@ -2265,6 +2304,11 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } } + public void addStickerToRecent(TLRPC.Document sticker) { + createEmojiView(); + emojiView.addRecentSticker(sticker); + } + private void showPopup(int show, int contentType) { if (show == 1) { if (contentType == 0 && emojiView == null) { @@ -2378,6 +2422,10 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat return editingMessageObject != null; } + public MessageObject getEditingMessageObject() { + return editingMessageObject; + } + public boolean isEditingCaption() { return editingCaption; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java new file mode 100644 index 000000000..9b22e8bd7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -0,0 +1,1369 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui.Components; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.*; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.AnimationCompat.AnimatorListenerAdapterProxy; +import org.telegram.messenger.AnimationCompat.AnimatorSetProxy; +import org.telegram.messenger.AnimationCompat.ObjectAnimatorProxy; +import org.telegram.messenger.AnimationCompat.ViewProxy; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.query.SearchQuery; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.PhotoAttachCameraCell; +import org.telegram.ui.Cells.PhotoAttachPhotoCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.ChatActivity; +import org.telegram.ui.PhotoViewer; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +public class ChatAttachAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider, BottomSheet.BottomSheetDelegateInterface { + + public interface ChatAttachViewDelegate { + void didPressedButton(int button); + View getRevealView(); + void didSelectBot(TLRPC.User user); + } + + private class InnerAnimator { + private AnimatorSet animatorSet; + private float startRadius; + } + + private LinearLayoutManager attachPhotoLayoutManager; + private PhotoAttachAdapter photoAttachAdapter; + private ChatActivity baseFragment; + private AttachButton sendPhotosButton; + private View views[] = new View[20]; + private RecyclerListView attachPhotoRecyclerView; + private View lineView; + private EmptyTextProgressView progressView; + private ArrayList viewsCache = new ArrayList<>(8); + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + private Drawable shadowDrawable; + private ViewGroup attachView; + private ListAdapter adapter; + private TextView hintTextView; + private ArrayList innerAnimators = new ArrayList<>(); + + private AnimatorSetProxy currentHintAnimation; + private boolean hintShowed; + private Runnable hideHintRunnable; + + private boolean deviceHasGoodCamera = false;//Build.VERSION.SDK_INT >= 16; + + private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); + + private boolean loading = true; + + private ChatAttachViewDelegate delegate; + + private int scrollOffsetY; + private boolean ignoreLayout; + + private boolean useRevealAnimation; + private float revealRadius; + private int revealX; + private int revealY; + private boolean revealAnimationInProgress; + + private class AttachButton extends FrameLayout { + + private TextView textView; + private ImageView imageView; + + public AttachButton(Context context) { + super(context); + + imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER); + addView(imageView, LayoutHelper.createFrame(64, 64, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); + + textView = new TextView(context); + textView.setLines(1); + textView.setSingleLine(true); + textView.setGravity(Gravity.CENTER_HORIZONTAL); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setTextColor(Theme.ATTACH_SHEET_TEXT_COLOR); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 64, 0, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(85), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(90), MeasureSpec.EXACTLY)); + } + + public void setTextAndIcon(CharSequence text, Drawable drawable) { + textView.setText(text); + imageView.setBackgroundDrawable(drawable); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + } + + private class AttachBotButton extends FrameLayout { + + private BackupImageView imageView; + private TextView nameTextView; + private AvatarDrawable avatarDrawable = new AvatarDrawable(); + private boolean pressed; + + private boolean checkingForLongPress = false; + private CheckForLongPress pendingCheckForLongPress = null; + private int pressCount = 0; + private CheckForTap pendingCheckForTap = null; + + private TLRPC.User currentUser; + + private final class CheckForTap implements Runnable { + public void run() { + if (pendingCheckForLongPress == null) { + pendingCheckForLongPress = new CheckForLongPress(); + } + pendingCheckForLongPress.currentPressCount = ++pressCount; + postDelayed(pendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout()); + } + } + + class CheckForLongPress implements Runnable { + public int currentPressCount; + + public void run() { + if (checkingForLongPress && getParent() != null && currentPressCount == pressCount) { + checkingForLongPress = false; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + onLongPress(); + MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); + onTouchEvent(event); + event.recycle(); + } + } + } + + public AttachBotButton(Context context) { + super(context); + + imageView = new BackupImageView(context); + imageView.setRoundRadius(AndroidUtilities.dp(27)); + addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); + + nameTextView = new TextView(context); + nameTextView.setTextColor(Theme.ATTACH_SHEET_TEXT_COLOR); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + nameTextView.setMaxLines(2); + nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + nameTextView.setLines(2); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, 65, 6, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(85), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); + } + + private void onLongPress() { + if (baseFragment == null || currentUser == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.formatString("ChatHintsDelete", R.string.ChatHintsDelete, ContactsController.formatName(currentUser.first_name, currentUser.last_name))); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SearchQuery.removeInline(currentUser.id); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.show(); + } + + public void setUser(TLRPC.User user) { + if (user == null) { + return; + } + currentUser = user; + TLRPC.FileLocation photo = null; + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + avatarDrawable.setInfo(user); + if (user != null && user.photo != null) { + photo = user.photo.photo_small; + } + imageView.setImage(photo, "50_50", avatarDrawable); + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean result = false; + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + pressed = true; + invalidate(); + result = true; + } else if (pressed) { + if (event.getAction() == MotionEvent.ACTION_UP) { + getParent().requestDisallowInterceptTouchEvent(true); + pressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + delegate.didSelectBot(MessagesController.getInstance().getUser(SearchQuery.inlineBots.get((Integer) getTag()).peer.user_id)); + setUseRevealAnimation(false); + dismiss(); + setUseRevealAnimation(true); + invalidate(); + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + pressed = false; + invalidate(); + } + } + if (!result) { + result = super.onTouchEvent(event); + } else { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + startCheckLongPress(); + } + } + if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) { + cancelCheckLongPress(); + } + + return result; + } + + protected void startCheckLongPress() { + if (checkingForLongPress) { + return; + } + checkingForLongPress = true; + if (pendingCheckForTap == null) { + pendingCheckForTap = new CheckForTap(); + } + postDelayed(pendingCheckForTap, ViewConfiguration.getTapTimeout()); + } + + protected void cancelCheckLongPress() { + checkingForLongPress = false; + if (pendingCheckForLongPress != null) { + removeCallbacks(pendingCheckForLongPress); + } + if (pendingCheckForTap != null) { + removeCallbacks(pendingCheckForTap); + } + } + } + + public ChatAttachAlert(Context context) { + super(context, false); + setDelegate(this); + setUseRevealAnimation(true); + if (deviceHasGoodCamera) { + //CameraController.getInstance().initCamera(); + } + NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.reloadInlineHints); + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); + + containerView = listView = new RecyclerListView(context) { + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = MeasureSpec.getSize(heightMeasureSpec); + if (Build.VERSION.SDK_INT >= 21) { + height -= AndroidUtilities.statusBarHeight; + } + int contentSize = backgroundPaddingTop + AndroidUtilities.dp(294) + (SearchQuery.inlineBots.isEmpty() ? 0 : ((int) Math.ceil(SearchQuery.inlineBots.size() / 4.0f) * AndroidUtilities.dp(100) + AndroidUtilities.dp(12))); + if (Build.VERSION.SDK_INT < 11) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(294) + backgroundPaddingTop, MeasureSpec.EXACTLY)); + } else { + int padding = contentSize == AndroidUtilities.dp(294) ? 0 : (height - AndroidUtilities.dp(294)); + if (padding != 0 && contentSize < height) { + padding -= (height - contentSize); + } + if (padding == 0) { + padding = backgroundPaddingTop; + } + if (getPaddingTop() != padding) { + ignoreLayout = true; + setPadding(backgroundPaddingLeft, padding, backgroundPaddingLeft, 0); + ignoreLayout = false; + } + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Math.min(contentSize, height), MeasureSpec.EXACTLY)); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (Build.VERSION.SDK_INT >= 11) { + updateLayout(); + } + } + + @Override + public void onDraw(Canvas canvas) { + if (Build.VERSION.SDK_INT >= 11) { + if (useRevealAnimation && Build.VERSION.SDK_INT <= 19) { + canvas.save(); + canvas.clipRect(backgroundPaddingLeft, scrollOffsetY, getMeasuredWidth() - backgroundPaddingLeft, getMeasuredHeight()); + if (revealAnimationInProgress) { + canvas.drawCircle(revealX, revealY, revealRadius, ciclePaint); + } else { + canvas.drawRect(backgroundPaddingLeft, scrollOffsetY, getMeasuredWidth() - backgroundPaddingLeft, getMeasuredHeight(), ciclePaint); + } + canvas.restore(); + } else { + shadowDrawable.setBounds(0, scrollOffsetY - backgroundPaddingTop, getMeasuredWidth(), getMeasuredHeight()); + shadowDrawable.draw(canvas); + } + } + } + }; + if (Build.VERSION.SDK_INT < 11) { + containerView.setBackgroundDrawable(shadowDrawable); + } else { + containerView.setWillNotDraw(false); + listView.setClipToPadding(false); + } + listView.setLayoutManager(layoutManager = new LinearLayoutManager(getContext())); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + listView.setAdapter(adapter = new ListAdapter(context)); + listView.setVerticalScrollBarEnabled(false); + listView.setEnabled(true); + listView.setGlowColor(0xfff5f6f7); + listView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = 0; + outRect.right = 0; + outRect.top = 0; + outRect.bottom = 0; + } + }); + + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (listView.getChildCount() <= 0) { + return; + } + if (hintShowed) { + if (layoutManager.findLastVisibleItemPosition() > 1) { + hideHint(); + hintShowed = false; + ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit().putBoolean("bothint", true).commit(); + } + } + if (Build.VERSION.SDK_INT >= 11) { + updateLayout(); + } + } + }); + containerView.setPadding(backgroundPaddingLeft, Build.VERSION.SDK_INT < 11 ? backgroundPaddingTop : 0, backgroundPaddingLeft, 0); + + attachView = new FrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(294), MeasureSpec.EXACTLY)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int width = right - left; + int height = bottom - top; + int t = AndroidUtilities.dp(8); + attachPhotoRecyclerView.layout(0, t, width, t + attachPhotoRecyclerView.getMeasuredHeight()); + progressView.layout(0, t, width, t + progressView.getMeasuredHeight()); + lineView.layout(0, AndroidUtilities.dp(96), width, AndroidUtilities.dp(96) + lineView.getMeasuredHeight()); + hintTextView.layout(width - hintTextView.getMeasuredWidth() - AndroidUtilities.dp(5), height - hintTextView.getMeasuredHeight() - AndroidUtilities.dp(5), width - AndroidUtilities.dp(5), height - AndroidUtilities.dp(5)); + + int diff = (width - AndroidUtilities.dp(85 * 4 + 20)) / 3; + for (int a = 0; a < 8; a++) { + int y = AndroidUtilities.dp(105 + 95 * (a / 4)); + int x = AndroidUtilities.dp(10) + (a % 4) * (AndroidUtilities.dp(85) + diff); + views[a].layout(x, y, x + views[a].getMeasuredWidth(), y + views[a].getMeasuredHeight()); + } + } + }; + + views[8] = attachPhotoRecyclerView = new RecyclerListView(context); + attachPhotoRecyclerView.setVerticalScrollBarEnabled(true); + attachPhotoRecyclerView.setAdapter(photoAttachAdapter = new PhotoAttachAdapter(context)); + attachPhotoRecyclerView.setClipToPadding(false); + attachPhotoRecyclerView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); + attachPhotoRecyclerView.setItemAnimator(null); + attachPhotoRecyclerView.setLayoutAnimation(null); + if (Build.VERSION.SDK_INT >= 9) { + attachPhotoRecyclerView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); + } + attachView.addView(attachPhotoRecyclerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80)); + attachPhotoLayoutManager = new LinearLayoutManager(context) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }; + attachPhotoLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + attachPhotoRecyclerView.setLayoutManager(attachPhotoLayoutManager); + attachPhotoRecyclerView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @SuppressWarnings("unchecked") + @Override + public void onItemClick(View view, int position) { + if (baseFragment == null || baseFragment.getParentActivity() == null) { + return; + } + if (!deviceHasGoodCamera || position != 0) { + if (deviceHasGoodCamera) { + position--; + } + ArrayList arrayList = (ArrayList) MediaController.allPhotosAlbumEntry.photos; + if (position < 0 || position >= arrayList.size()) { + return; + } + PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); + PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, 0, ChatAttachAlert.this, baseFragment); + AndroidUtilities.hideKeyboard(baseFragment.getFragmentView().findFocus()); + } else { + final File path = AndroidUtilities.generatePicturePath(); + /*CameraController.getInstance().takePicture(path, ((PhotoAttachCameraCell) view).cameraSession, new Runnable() { + @Override + public void run() { + PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); + final ArrayList arrayList = new ArrayList<>(); + int orientation = 0; + try { + ExifInterface ei = new ExifInterface(path.getAbsolutePath()); + int exif = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + switch (exif) { + case ExifInterface.ORIENTATION_ROTATE_90: + orientation = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + orientation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + orientation = 270; + break; + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + arrayList.add(new MediaController.PhotoEntry(0, 0, 0, path.getAbsolutePath(), orientation, false)); + + PhotoViewer.getInstance().openPhotoForSelect(arrayList, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { + @Override + public void sendButtonPressed(int index) { + AndroidUtilities.addMediaToGallery(path.getAbsolutePath()); + baseFragment.sendPhoto((MediaController.PhotoEntry) arrayList.get(0)); + } + + @Override + public boolean cancelButtonPressed() { + path.delete(); + return true; + } + }, baseFragment); + } + });*/ + } + } + }); + + views[9] = progressView = new EmptyTextProgressView(context); + if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + progressView.setText(LocaleController.getString("PermissionStorage", R.string.PermissionStorage)); + progressView.setTextSize(16); + } else { + progressView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); + progressView.setTextSize(20); + } + attachView.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80)); + attachPhotoRecyclerView.setEmptyView(progressView); + + views[10] = lineView = new View(getContext()) { + @Override + public boolean hasOverlappingRendering() { + return false; + } + }; + lineView.setBackgroundColor(0xffd2d2d2); + attachView.addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT)); + CharSequence[] items = new CharSequence[]{ + LocaleController.getString("ChatCamera", R.string.ChatCamera), + LocaleController.getString("ChatGallery", R.string.ChatGallery), + LocaleController.getString("ChatVideo", R.string.ChatVideo), + LocaleController.getString("AttachMusic", R.string.AttachMusic), + LocaleController.getString("ChatDocument", R.string.ChatDocument), + LocaleController.getString("AttachContact", R.string.AttachContact), + LocaleController.getString("ChatLocation", R.string.ChatLocation), + "" + }; + for (int a = 0; a < 8; a++) { + AttachButton attachButton = new AttachButton(context); + attachButton.setTextAndIcon(items[a], Theme.attachButtonDrawables[a]); + attachView.addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP)); + attachButton.setTag(a); + views[a] = attachButton; + if (a == 7) { + sendPhotosButton = attachButton; + sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); + } + attachButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + delegate.didPressedButton((Integer) v.getTag()); + } + }); + } + + hintTextView = new TextView(context); + hintTextView.setBackgroundResource(R.drawable.tooltip); + hintTextView.setTextColor(Theme.CHAT_GIF_HINT_TEXT_COLOR); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + hintTextView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + hintTextView.setText(LocaleController.getString("AttachBotsHelp", R.string.AttachBotsHelp)); + hintTextView.setGravity(Gravity.CENTER_VERTICAL); + hintTextView.setVisibility(View.INVISIBLE); + hintTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.scroll_tip, 0, 0, 0); + hintTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); + attachView.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 32, Gravity.RIGHT | Gravity.BOTTOM, 5, 0, 5, 5)); + + for (int a = 0; a < 8; a++) { + viewsCache.add(photoAttachAdapter.createHolder()); + } + + if (loading) { + progressView.showProgress(); + } else { + progressView.showTextView(); + } + } + + private void hideHint() { + if (hideHintRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(hideHintRunnable); + hideHintRunnable = null; + } + if (hintTextView == null) { + return; + } + currentHintAnimation = new AnimatorSetProxy(); + currentHintAnimation.playTogether( + ObjectAnimatorProxy.ofFloat(hintTextView, "alpha", 0.0f) + ); + currentHintAnimation.setInterpolator(decelerateInterpolator); + currentHintAnimation.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (currentHintAnimation == null || !currentHintAnimation.equals(animation)) { + return; + } + currentHintAnimation = null; + if (hintTextView != null) { + hintTextView.clearAnimation(); + hintTextView.setVisibility(View.INVISIBLE); + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (currentHintAnimation != null && currentHintAnimation.equals(animation)) { + currentHintAnimation = null; + } + } + }); + currentHintAnimation.setDuration(300); + currentHintAnimation.start(); + } + + private void showHint() { + if (SearchQuery.inlineBots.isEmpty()) { + return; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences.getBoolean("bothint", false)) { + return; + } + hintShowed = true; + + hintTextView.setVisibility(View.VISIBLE); + currentHintAnimation = new AnimatorSetProxy(); + currentHintAnimation.playTogether( + ObjectAnimatorProxy.ofFloat(hintTextView, "alpha", 0.0f, 1.0f) + ); + currentHintAnimation.setInterpolator(decelerateInterpolator); + currentHintAnimation.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (currentHintAnimation == null || !currentHintAnimation.equals(animation)) { + return; + } + currentHintAnimation = null; + AndroidUtilities.runOnUIThread(hideHintRunnable = new Runnable() { + @Override + public void run() { + if (hideHintRunnable != this) { + return; + } + hideHintRunnable = null; + hideHint(); + } + }, 2000); + } + + @Override + public void onAnimationCancel(Object animation) { + if (currentHintAnimation != null && currentHintAnimation.equals(animation)) { + currentHintAnimation = null; + } + } + }); + currentHintAnimation.setDuration(300); + currentHintAnimation.start(); + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.albumsDidLoaded) { + if (photoAttachAdapter != null) { + loading = false; + progressView.showTextView(); + photoAttachAdapter.notifyDataSetChanged(); + } + } else if (id == NotificationCenter.reloadInlineHints) { + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + } + + @SuppressLint("NewApi") + private void updateLayout() { + if (listView.getChildCount() <= 0) { + listView.setTopGlowOffset(scrollOffsetY = listView.getPaddingTop()); + containerView.invalidate(); + return; + } + View child = listView.getChildAt(0); + Holder holder = (Holder) listView.findContainingViewHolder(child); + int top = child.getTop(); + int newOffset = 0; + if (top >= 0 && holder != null && holder.getAdapterPosition() == 0) { + newOffset = top; + } + if (scrollOffsetY != newOffset) { + listView.setTopGlowOffset(scrollOffsetY = newOffset); + containerView.invalidate(); + } + } + + @Override + protected boolean canDismissWithSwipe() { + return false; + } + + public void updatePhotosButton() { + int count = photoAttachAdapter.getSelectedPhotos().size(); + if (count == 0) { + sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); + sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_hide_states); + sendPhotosButton.imageView.setImageResource(R.drawable.attach_hide2); + sendPhotosButton.textView.setText(""); + } else { + sendPhotosButton.imageView.setPadding(AndroidUtilities.dp(2), 0, 0, 0); + sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_send_states); + sendPhotosButton.imageView.setImageResource(R.drawable.attach_send2); + sendPhotosButton.textView.setText(LocaleController.formatString("SendItems", R.string.SendItems, String.format("(%d)", count))); + } + + if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + progressView.setText(LocaleController.getString("PermissionStorage", R.string.PermissionStorage)); + progressView.setTextSize(16); + } else { + progressView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); + progressView.setTextSize(20); + } + } + + public void setDelegate(ChatAttachViewDelegate chatAttachViewDelegate) { + delegate = chatAttachViewDelegate; + } + + public void loadGalleryPhotos() { + if (MediaController.allPhotosAlbumEntry == null && Build.VERSION.SDK_INT >= 21) { + MediaController.loadGalleryPhotosAlbums(0); + } + } + + public void init(ChatActivity parentFragment) { + if (MediaController.allPhotosAlbumEntry != null) { + for (int a = 0; a < Math.min(100, MediaController.allPhotosAlbumEntry.photos.size()); a++) { + MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(a); + photoEntry.caption = null; + photoEntry.imagePath = null; + photoEntry.thumbPath = null; + } + } + if (currentHintAnimation != null) { + currentHintAnimation.cancel(); + currentHintAnimation = null; + } + ViewProxy.setAlpha(hintTextView, 0.0f); + hintTextView.setVisibility(View.INVISIBLE); + hintTextView.clearAnimation(); + attachPhotoLayoutManager.scrollToPositionWithOffset(0, 1000000); + photoAttachAdapter.clearSelectedPhotos(); + baseFragment = parentFragment; + layoutManager.scrollToPositionWithOffset(0, 1000000); + updatePhotosButton(); + } + + public HashMap getSelectedPhotos() { + return photoAttachAdapter.getSelectedPhotos(); + } + + public void onDestroy() { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.albumsDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.reloadInlineHints); + baseFragment = null; + } + + private PhotoAttachPhotoCell getCellForIndex(int index) { + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + if (view instanceof PhotoAttachPhotoCell) { + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; + int num = (Integer) cell.getImageView().getTag(); + if (num < 0 || num >= MediaController.allPhotosAlbumEntry.photos.size()) { + continue; + } + if (num == index) { + return cell; + } + } + } + return null; + } + + @Override + public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + int coords[] = new int[2]; + cell.getImageView().getLocationInWindow(coords); + PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + object.parentView = attachPhotoRecyclerView; + object.imageReceiver = cell.getImageView().getImageReceiver(); + object.thumb = object.imageReceiver.getBitmap(); + object.scale = ViewProxy.getScaleX(cell.getImageView()); + object.clipBottomAddition = (Build.VERSION.SDK_INT >= 21 ? 0 : -AndroidUtilities.statusBarHeight); + cell.getCheckBox().setVisibility(View.GONE); + return object; + } + return null; + } + + @Override + public void updatePhotoAtIndex(int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + cell.getImageView().setOrientation(0, true); + MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); + if (photoEntry.thumbPath != null) { + cell.getImageView().setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.path != null) { + cell.getImageView().setOrientation(photoEntry.orientation, true); + cell.getImageView().setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); + } else { + cell.getImageView().setImageResource(R.drawable.nophotos); + } + } + } + + @Override + public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + return cell.getImageView().getImageReceiver().getBitmap(); + } + return null; + } + + @Override + public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + PhotoAttachPhotoCell cell = getCellForIndex(index); + if (cell != null) { + cell.getCheckBox().setVisibility(View.VISIBLE); + } + } + + @Override + public void willHidePhotoViewer() { + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + if (view instanceof PhotoAttachPhotoCell) { + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; + if (cell.getCheckBox().getVisibility() != View.VISIBLE) { + cell.getCheckBox().setVisibility(View.VISIBLE); + } + } + } + } + + @Override + public boolean isPhotoChecked(int index) { + return !(index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) && photoAttachAdapter.getSelectedPhotos().containsKey(MediaController.allPhotosAlbumEntry.photos.get(index).imageId); + } + + @Override + public void setPhotoChecked(int index) { + boolean add = true; + if (index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) { + return; + } + MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); + if (photoAttachAdapter.getSelectedPhotos().containsKey(photoEntry.imageId)) { + photoAttachAdapter.getSelectedPhotos().remove(photoEntry.imageId); + add = false; + } else { + photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); + } + int count = attachPhotoRecyclerView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = attachPhotoRecyclerView.getChildAt(a); + int num = (Integer) view.getTag(); + if (num == index) { + ((PhotoAttachPhotoCell) view).setChecked(add, false); + break; + } + } + updatePhotosButton(); + } + + @Override + public boolean cancelButtonPressed() { + return false; + } + + @Override + public void sendButtonPressed(int index) { + if (photoAttachAdapter.getSelectedPhotos().isEmpty()) { + if (index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) { + return; + } + MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); + photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); + } + delegate.didPressedButton(7); + } + + @Override + public int getSelectedCount() { + return photoAttachAdapter.getSelectedPhotos().size(); + } + + private void onRevealAnimationEnd(boolean open) { + NotificationCenter.getInstance().setAnimationInProgress(false); + revealAnimationInProgress = false; + if (open && Build.VERSION.SDK_INT <= 19 && MediaController.allPhotosAlbumEntry == null) { + MediaController.loadGalleryPhotosAlbums(0); + } + if (open) { + showHint(); + } + } + + @Override + public void onOpenAnimationEnd() { + onRevealAnimationEnd(true); + } + + @Override + public void onOpenAnimationStart() { + + } + + private class Holder extends RecyclerView.ViewHolder { + + public Holder(View itemView) { + super(itemView); + } + } + + private class ListAdapter extends RecyclerView.Adapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = attachView; + break; + case 1: + view = new ShadowSectionCell(mContext); + break; + default: + FrameLayout frameLayout = new FrameLayout(mContext) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int diff = (right - left - AndroidUtilities.dp(85 * 4 + 20)) / 3; + for (int a = 0; a < 4; a++) { + int x = AndroidUtilities.dp(10) + (a % 4) * (AndroidUtilities.dp(85) + diff); + View child = getChildAt(a); + child.layout(x, 0, x + child.getMeasuredWidth(), child.getMeasuredHeight()); + } + } + }; + for (int a = 0; a < 4; a++) { + frameLayout.addView(new AttachBotButton(mContext)); + } + view = frameLayout; + frameLayout.setLayoutParams(new RecyclerView.LayoutParams(LayoutHelper.MATCH_PARENT, AndroidUtilities.dp(100))); + break; + } + return new Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (position > 1) { + position -= 2; + position *= 4; + FrameLayout frameLayout = (FrameLayout) holder.itemView; + for (int a = 0; a < 4; a++) { + AttachBotButton child = (AttachBotButton) frameLayout.getChildAt(a); + if (position + a >= SearchQuery.inlineBots.size()) { + child.setVisibility(View.INVISIBLE); + } else { + child.setVisibility(View.VISIBLE); + child.setTag(position + a); + child.setUser(MessagesController.getInstance().getUser(SearchQuery.inlineBots.get(position + a).peer.user_id)); + } + } + } + } + + @Override + public int getItemCount() { + return 1 + (!SearchQuery.inlineBots.isEmpty() ? 1 + (int) Math.ceil(SearchQuery.inlineBots.size() / 4.0f) : 0); + } + + @Override + public int getItemViewType(int position) { + switch (position) { + case 0: + return 0; + case 1: + return 1; + default: + return 2; + } + } + } + + private class PhotoAttachAdapter extends RecyclerView.Adapter { + + private Context mContext; + private HashMap selectedPhotos = new HashMap<>(); + + public PhotoAttachAdapter(Context context) { + mContext = context; + } + + public void clearSelectedPhotos() { + if (!selectedPhotos.isEmpty()) { + for (HashMap.Entry entry : selectedPhotos.entrySet()) { + MediaController.PhotoEntry photoEntry = entry.getValue(); + photoEntry.imagePath = null; + photoEntry.thumbPath = null; + photoEntry.caption = null; + } + selectedPhotos.clear(); + updatePhotosButton(); + notifyDataSetChanged(); + } + } + + public Holder createHolder() { + PhotoAttachPhotoCell cell = new PhotoAttachPhotoCell(mContext); + cell.setDelegate(new PhotoAttachPhotoCell.PhotoAttachPhotoCellDelegate() { + @Override + public void onCheckClick(PhotoAttachPhotoCell v) { + MediaController.PhotoEntry photoEntry = v.getPhotoEntry(); + if (selectedPhotos.containsKey(photoEntry.imageId)) { + selectedPhotos.remove(photoEntry.imageId); + v.setChecked(false, true); + photoEntry.imagePath = null; + photoEntry.thumbPath = null; + v.setPhotoEntry(photoEntry, (Integer) v.getTag() == MediaController.allPhotosAlbumEntry.photos.size() - 1); + } else { + selectedPhotos.put(photoEntry.imageId, photoEntry); + v.setChecked(true, true); + } + updatePhotosButton(); + } + }); + return new Holder(cell); + } + + public HashMap getSelectedPhotos() { + return selectedPhotos; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (!deviceHasGoodCamera || position != 0) { + if (deviceHasGoodCamera) { + position--; + } + PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) holder.itemView; + MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(position); + cell.setPhotoEntry(photoEntry, position == MediaController.allPhotosAlbumEntry.photos.size() - 1); + cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); + cell.getImageView().setTag(position); + cell.setTag(position); + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + Holder holder; + switch (viewType) { + case 1: + holder = new Holder(new PhotoAttachCameraCell(mContext)); + break; + default: + if (!viewsCache.isEmpty()) { + holder = viewsCache.get(0); + viewsCache.remove(0); + } else { + holder = createHolder(); + } + break; + } + + return holder; + } + + @Override + public int getItemCount() { + int count = 0; + if (deviceHasGoodCamera) { + count++; + } + if (MediaController.allPhotosAlbumEntry != null) { + count += MediaController.allPhotosAlbumEntry.photos.size(); + } + return count; + } + + @Override + public int getItemViewType(int position) { + if (deviceHasGoodCamera && position == 0) { + return 1; + } + return 0; + } + } + + private void setUseRevealAnimation(boolean value) { + if (!value || value && Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet()) { + useRevealAnimation = value; + } + } + + @SuppressLint("NewApi") + protected void setRevealRadius(float radius) { + revealRadius = radius; + if (Build.VERSION.SDK_INT <= 19) { + containerView.invalidate(); + } + if (!isDismissed()) { + for (int a = 0; a < innerAnimators.size(); a++) { + InnerAnimator innerAnimator = innerAnimators.get(a); + if (innerAnimator.startRadius > radius) { + continue; + } + innerAnimator.animatorSet.start(); + innerAnimators.remove(a); + a--; + } + } + } + + protected float getRevealRadius() { + return revealRadius; + } + + @SuppressLint("NewApi") + private void startRevealAnimation(final boolean open) { + ViewProxy.setTranslationY(containerView, 0); + + final AnimatorSet animatorSet = new AnimatorSet(); + + View view = delegate.getRevealView(); + if (view.getVisibility() == View.VISIBLE && ((ViewGroup) view.getParent()).getVisibility() == View.VISIBLE) { + final int coords[] = new int[2]; + view.getLocationInWindow(coords); + float top; + if (Build.VERSION.SDK_INT <= 19) { + top = AndroidUtilities.displaySize.y - containerView.getMeasuredHeight() - AndroidUtilities.statusBarHeight; + } else { + top = containerView.getY(); + } + revealX = coords[0] + view.getMeasuredWidth() / 2; + revealY = (int) (coords[1] + view.getMeasuredHeight() / 2 - top); + if (Build.VERSION.SDK_INT <= 19) { + revealY -= AndroidUtilities.statusBarHeight; + } + } else { + revealX = AndroidUtilities.displaySize.x / 2 + backgroundPaddingLeft; + revealY = (int) (AndroidUtilities.displaySize.y - containerView.getY()); + } + + int corners[][] = new int[][]{ + {0, 0}, + {0, AndroidUtilities.dp(304)}, + {containerView.getMeasuredWidth(), 0}, + {containerView.getMeasuredWidth(), AndroidUtilities.dp(304)} + }; + int finalRevealRadius = 0; + int y = revealY - scrollOffsetY + backgroundPaddingTop; + for (int a = 0; a < 4; a++) { + finalRevealRadius = Math.max(finalRevealRadius, (int) Math.ceil(Math.sqrt((revealX - corners[a][0]) * (revealX - corners[a][0]) + (y - corners[a][1]) * (y - corners[a][1])))); + } + int finalRevealX = revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth(); + + ArrayList animators = new ArrayList<>(3); + animators.add(ObjectAnimator.ofFloat(this, "revealRadius", open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0)); + animators.add(ObjectAnimator.ofInt(backDrawable, "alpha", open ? 51 : 0)); + if (Build.VERSION.SDK_INT >= 21) { + containerView.setElevation(AndroidUtilities.dp(10)); + try { + animators.add(ViewAnimationUtils.createCircularReveal(containerView, finalRevealX, revealY, open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0)); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + animatorSet.setDuration(320); + } else { + if (!open) { + animatorSet.setDuration(200); + containerView.setPivotX(revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth()); + containerView.setPivotY(revealY); + animators.add(ObjectAnimator.ofFloat(containerView, "scaleX", 0.0f)); + animators.add(ObjectAnimator.ofFloat(containerView, "scaleY", 0.0f)); + animators.add(ObjectAnimator.ofFloat(containerView, "alpha", 0.0f)); + } else { + animatorSet.setDuration(250); + containerView.setScaleX(1); + containerView.setScaleY(1); + containerView.setAlpha(1); + if (Build.VERSION.SDK_INT <= 19) { + animatorSet.setStartDelay(20); + } + } + } + animatorSet.playTogether(animators); + animatorSet.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { + currentSheetAnimation = null; + onRevealAnimationEnd(open); + containerView.invalidate(); + if (Build.VERSION.SDK_INT >= 11) { + containerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + if (!open) { + containerView.setVisibility(View.INVISIBLE); + try { + dismissInternal(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (currentSheetAnimation != null && animatorSet.equals(animation)) { + currentSheetAnimation = null; + } + } + }); + + if (open) { + innerAnimators.clear(); + NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload}); + NotificationCenter.getInstance().setAnimationInProgress(true); + revealAnimationInProgress = true; + + int count = Build.VERSION.SDK_INT <= 19 ? 11 : 8; + for (int a = 0; a < count; a++) { + if (Build.VERSION.SDK_INT <= 19) { + if (a < 8) { + views[a].setScaleX(0.1f); + views[a].setScaleY(0.1f); + } + views[a].setAlpha(0.0f); + } else { + views[a].setScaleX(0.7f); + views[a].setScaleY(0.7f); + } + + InnerAnimator innerAnimator = new InnerAnimator(); + + int buttonX = views[a].getLeft() + views[a].getMeasuredWidth() / 2; + int buttonY = views[a].getTop() + attachView.getTop() + views[a].getMeasuredHeight() / 2; + float dist = (float) Math.sqrt((revealX - buttonX) * (revealX - buttonX) + (revealY - buttonY) * (revealY - buttonY)); + float vecX = (revealX - buttonX) / dist; + float vecY = (revealY - buttonY) / dist; + views[a].setPivotX(views[a].getMeasuredWidth() / 2 + vecX * AndroidUtilities.dp(20)); + views[a].setPivotY(views[a].getMeasuredHeight() / 2 + vecY * AndroidUtilities.dp(20)); + innerAnimator.startRadius = dist - AndroidUtilities.dp(27 * 3); + + views[a].setTag(R.string.AppName, 1); + animators = new ArrayList<>(); + final AnimatorSet animatorSetInner; + if (a < 8) { + animators.add(ObjectAnimator.ofFloat(views[a], "scaleX", 0.7f, 1.05f)); + animators.add(ObjectAnimator.ofFloat(views[a], "scaleY", 0.7f, 1.05f)); + + animatorSetInner = new AnimatorSet(); + animatorSetInner.playTogether( + ObjectAnimator.ofFloat(views[a], "scaleX", 1.0f), + ObjectAnimator.ofFloat(views[a], "scaleY", 1.0f)); + animatorSetInner.setDuration(100); + animatorSetInner.setInterpolator(decelerateInterpolator); + } else { + animatorSetInner = null; + } + if (Build.VERSION.SDK_INT <= 19) { + animators.add(ObjectAnimator.ofFloat(views[a], "alpha", 1.0f)); + } + innerAnimator.animatorSet = new AnimatorSet(); + innerAnimator.animatorSet.playTogether(animators); + innerAnimator.animatorSet.setDuration(150); + innerAnimator.animatorSet.setInterpolator(decelerateInterpolator); + innerAnimator.animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSetInner != null) { + animatorSetInner.start(); + } + } + }); + innerAnimators.add(innerAnimator); + } + } + currentSheetAnimation = animatorSet; + animatorSet.start(); + } + + @Override + protected boolean onCustomOpenAnimation() { + if (useRevealAnimation) { + startRevealAnimation(true); + return true; + } + return false; + } + + @Override + protected boolean onCustomCloseAnimation() { + if (useRevealAnimation) { + backDrawable.setAlpha(51); + startRevealAnimation(false); + return true; + } + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachView.java deleted file mode 100644 index 1f31dbf6a..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachView.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.Manifest; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.text.TextUtils; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimationCompat.ViewProxy; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.MediaController; -import org.telegram.messenger.MessageObject; -import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.support.widget.LinearLayoutManager; -import org.telegram.messenger.R; -import org.telegram.messenger.support.widget.RecyclerView; -import org.telegram.tgnet.TLRPC; -import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Cells.PhotoAttachPhotoCell; -import org.telegram.ui.ChatActivity; -import org.telegram.ui.PhotoViewer; - -import java.util.ArrayList; -import java.util.HashMap; - -public class ChatAttachView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { - - public interface ChatAttachViewDelegate { - void didPressedButton(int button); - } - - private LinearLayoutManager attachPhotoLayoutManager; - private PhotoAttachAdapter photoAttachAdapter; - private ChatActivity baseFragment; - private AttachButton sendPhotosButton; - private View views[] = new View[20]; - private RecyclerListView attachPhotoRecyclerView; - private View lineView; - private EmptyTextProgressView progressView; - private ArrayList viewsCache = new ArrayList<>(8); - - private float[] distCache = new float[20]; - - private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); - - private boolean loading = true; - - private ChatAttachViewDelegate delegate; - - private static class AttachButton extends FrameLayout { - - private TextView textView; - private ImageView imageView; - - public AttachButton(Context context) { - super(context); - - imageView = new ImageView(context); - imageView.setScaleType(ImageView.ScaleType.CENTER); - addView(imageView, LayoutHelper.createFrame(64, 64, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); - - textView = new TextView(context); - textView.setLines(1); - textView.setSingleLine(true); - textView.setGravity(Gravity.CENTER_HORIZONTAL); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setTextColor(0xff757575); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 64, 0, 0)); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(85), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(90), MeasureSpec.EXACTLY)); - } - - public void setTextAndIcon(CharSequence text, Drawable drawable) { - textView.setText(text); - imageView.setBackgroundDrawable(drawable); - } - } - - public ChatAttachView(Context context) { - super(context); - - NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded); - - views[8] = attachPhotoRecyclerView = new RecyclerListView(context); - attachPhotoRecyclerView.setVerticalScrollBarEnabled(true); - attachPhotoRecyclerView.setAdapter(photoAttachAdapter = new PhotoAttachAdapter(context)); - attachPhotoRecyclerView.setClipToPadding(false); - attachPhotoRecyclerView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); - attachPhotoRecyclerView.setItemAnimator(null); - attachPhotoRecyclerView.setLayoutAnimation(null); - if (Build.VERSION.SDK_INT >= 9) { - attachPhotoRecyclerView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); - } - addView(attachPhotoRecyclerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80)); - attachPhotoLayoutManager = new LinearLayoutManager(context) { - @Override - public boolean supportsPredictiveItemAnimations() { - return false; - } - }; - attachPhotoLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - attachPhotoRecyclerView.setLayoutManager(attachPhotoLayoutManager); - attachPhotoRecyclerView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { - @SuppressWarnings("unchecked") - @Override - public void onItemClick(View view, int position) { - if (baseFragment == null || baseFragment.getParentActivity() == null) { - return; - } - ArrayList arrayList = (ArrayList) MediaController.allPhotosAlbumEntry.photos; - if (position < 0 || position >= arrayList.size()) { - return; - } - PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); - PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, 0, ChatAttachView.this, baseFragment); - AndroidUtilities.hideKeyboard(baseFragment.getFragmentView().findFocus()); - } - }); - - views[9] = progressView = new EmptyTextProgressView(context); - if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - progressView.setText(LocaleController.getString("PermissionStorage", R.string.PermissionStorage)); - progressView.setTextSize(16); - } else { - progressView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); - progressView.setTextSize(20); - } - addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80)); - attachPhotoRecyclerView.setEmptyView(progressView); - - views[10] = lineView = new View(getContext()); - lineView.setBackgroundColor(0xffd2d2d2); - addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT)); - CharSequence[] items = new CharSequence[]{ - LocaleController.getString("ChatCamera", R.string.ChatCamera), - LocaleController.getString("ChatGallery", R.string.ChatGallery), - LocaleController.getString("ChatVideo", R.string.ChatVideo), - LocaleController.getString("AttachMusic", R.string.AttachMusic), - LocaleController.getString("ChatDocument", R.string.ChatDocument), - LocaleController.getString("AttachContact", R.string.AttachContact), - LocaleController.getString("ChatLocation", R.string.ChatLocation), - "" - }; - for (int a = 0; a < 8; a++) { - AttachButton attachButton = new AttachButton(context); - attachButton.setTextAndIcon(items[a], Theme.attachButtonDrawables[a]); - addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP)); - attachButton.setTag(a); - views[a] = attachButton; - if (a == 7) { - sendPhotosButton = attachButton; - sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); - } - attachButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (delegate != null) { - delegate.didPressedButton((Integer) v.getTag()); - } - } - }); - } - setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - for (int a = 0; a < 8; a++) { - viewsCache.add(photoAttachAdapter.createHolder()); - } - - if (loading) { - progressView.showProgress(); - } else { - progressView.showTextView(); - } - } - - @Override - public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.albumsDidLoaded) { - if (photoAttachAdapter != null) { - loading = false; - progressView.showTextView(); - photoAttachAdapter.notifyDataSetChanged(); - } - } - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(294), MeasureSpec.EXACTLY)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int width = right - left; - - int t = AndroidUtilities.dp(8); - attachPhotoRecyclerView.layout(0, t, width, t + attachPhotoRecyclerView.getMeasuredHeight()); - progressView.layout(0, t, width, t + progressView.getMeasuredHeight()); - lineView.layout(0, AndroidUtilities.dp(96), width, AndroidUtilities.dp(96) + lineView.getMeasuredHeight()); - - int diff = (width - AndroidUtilities.dp(85 * 4 + 20)) / 3; - for (int a = 0; a < 8; a++) { - int y = AndroidUtilities.dp(105 + 95 * (a / 4)); - int x = AndroidUtilities.dp(10) + (a % 4) * (AndroidUtilities.dp(85) + diff); - views[a].layout(x, y, x + views[a].getMeasuredWidth(), y + views[a].getMeasuredHeight()); - } - } - - public void updatePhotosButton() { - int count = photoAttachAdapter.getSelectedPhotos().size(); - if (count == 0) { - sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); - sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_hide_states); - sendPhotosButton.imageView.setImageResource(R.drawable.attach_hide2); - sendPhotosButton.textView.setText(""); - } else { - sendPhotosButton.imageView.setPadding(AndroidUtilities.dp(2), 0, 0, 0); - sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_send_states); - sendPhotosButton.imageView.setImageResource(R.drawable.attach_send2); - sendPhotosButton.textView.setText(LocaleController.formatString("SendItems", R.string.SendItems, String.format("(%d)", count))); - } - - if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - progressView.setText(LocaleController.getString("PermissionStorage", R.string.PermissionStorage)); - progressView.setTextSize(16); - } else { - progressView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); - progressView.setTextSize(20); - } - } - - public void setDelegate(ChatAttachViewDelegate chatAttachViewDelegate) { - delegate = chatAttachViewDelegate; - } - - public void onRevealAnimationEnd(boolean open) { - if (open && Build.VERSION.SDK_INT <= 19 && MediaController.allPhotosAlbumEntry == null) { - MediaController.loadGalleryPhotosAlbums(0); - } - } - - public void loadGalleryPhotos() { - if (MediaController.allPhotosAlbumEntry == null && Build.VERSION.SDK_INT >= 21) { - MediaController.loadGalleryPhotosAlbums(0); - } - } - - @SuppressLint("NewApi") - public void onRevealAnimationStart(boolean open) { - if (!open) { - return; - } - int count = Build.VERSION.SDK_INT <= 19 ? 11 : 8; - for (int a = 0; a < count; a++) { - if (Build.VERSION.SDK_INT <= 19) { - if (a < 8) { - views[a].setScaleX(0.1f); - views[a].setScaleY(0.1f); - } - views[a].setAlpha(0.0f); - } else { - views[a].setScaleX(0.7f); - views[a].setScaleY(0.7f); - } - views[a].setTag(R.string.AppName, null); - distCache[a] = 0; - } - } - - @SuppressLint("NewApi") - public void onRevealAnimationProgress(boolean open, float radius, int x, int y) { - if (!open) { - return; - } - int count = Build.VERSION.SDK_INT <= 19 ? 11 : 8; - for (int a = 0; a < count; a++) { - if (views[a].getTag(R.string.AppName) == null) { - if (distCache[a] == 0) { - int buttonX = views[a].getLeft() + views[a].getMeasuredWidth() / 2; - int buttonY = views[a].getTop() + views[a].getMeasuredHeight() / 2; - distCache[a] = (float) Math.sqrt((x - buttonX) * (x - buttonX) + (y - buttonY) * (y - buttonY)); - float vecX = (x - buttonX) / distCache[a]; - float vecY = (y - buttonY) / distCache[a]; - views[a].setPivotX(views[a].getMeasuredWidth() / 2 + vecX * AndroidUtilities.dp(20)); - views[a].setPivotY(views[a].getMeasuredHeight() / 2 + vecY * AndroidUtilities.dp(20)); - } - if (distCache[a] > radius + AndroidUtilities.dp(27)) { - continue; - } - - views[a].setTag(R.string.AppName, 1); - final ArrayList animators = new ArrayList<>(); - final ArrayList animators2 = new ArrayList<>(); - if (a < 8) { - animators.add(ObjectAnimator.ofFloat(views[a], "scaleX", 0.7f, 1.05f)); - animators.add(ObjectAnimator.ofFloat(views[a], "scaleY", 0.7f, 1.05f)); - animators2.add(ObjectAnimator.ofFloat(views[a], "scaleX", 1.0f)); - animators2.add(ObjectAnimator.ofFloat(views[a], "scaleY", 1.0f)); - } - if (Build.VERSION.SDK_INT <= 19) { - animators.add(ObjectAnimator.ofFloat(views[a], "alpha", 1.0f)); - } - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(animators); - animatorSet.setDuration(150); - animatorSet.setInterpolator(decelerateInterpolator); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(animators2); - animatorSet.setDuration(100); - animatorSet.setInterpolator(decelerateInterpolator); - animatorSet.start(); - } - }); - animatorSet.start(); - } - } - } - - public void init(ChatActivity parentFragment) { - if (MediaController.allPhotosAlbumEntry != null) { - for (int a = 0; a < Math.min(100, MediaController.allPhotosAlbumEntry.photos.size()); a++) { - MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(a); - photoEntry.caption = null; - photoEntry.imagePath = null; - photoEntry.thumbPath = null; - } - } - attachPhotoLayoutManager.scrollToPositionWithOffset(0, 1000000); - photoAttachAdapter.clearSelectedPhotos(); - baseFragment = parentFragment; - updatePhotosButton(); - } - - public HashMap getSelectedPhotos() { - return photoAttachAdapter.getSelectedPhotos(); - } - - public void onDestroy() { - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.albumsDidLoaded); - baseFragment = null; - } - - private PhotoAttachPhotoCell getCellForIndex(int index) { - int count = attachPhotoRecyclerView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = attachPhotoRecyclerView.getChildAt(a); - if (view instanceof PhotoAttachPhotoCell) { - PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; - int num = (Integer) cell.getImageView().getTag(); - if (num < 0 || num >= MediaController.allPhotosAlbumEntry.photos.size()) { - continue; - } - if (num == index) { - return cell; - } - } - } - return null; - } - - @Override - public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - int coords[] = new int[2]; - cell.getImageView().getLocationInWindow(coords); - PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); - object.viewX = coords[0]; - object.viewY = coords[1] - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - object.parentView = attachPhotoRecyclerView; - object.imageReceiver = cell.getImageView().getImageReceiver(); - object.thumb = object.imageReceiver.getBitmap(); - object.scale = ViewProxy.getScaleX(cell.getImageView()); - object.clipBottomAddition = (Build.VERSION.SDK_INT >= 21 ? 0 : -AndroidUtilities.statusBarHeight); - cell.getCheckBox().setVisibility(View.GONE); - return object; - } - return null; - } - - @Override - public void updatePhotoAtIndex(int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - cell.getImageView().setOrientation(0, true); - MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); - if (photoEntry.thumbPath != null) { - cell.getImageView().setImage(photoEntry.thumbPath, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.path != null) { - cell.getImageView().setOrientation(photoEntry.orientation, true); - cell.getImageView().setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, cell.getContext().getResources().getDrawable(R.drawable.nophotos)); - } else { - cell.getImageView().setImageResource(R.drawable.nophotos); - } - } - } - - @Override - public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - return cell.getImageView().getImageReceiver().getBitmap(); - } - return null; - } - - @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { - PhotoAttachPhotoCell cell = getCellForIndex(index); - if (cell != null) { - cell.getCheckBox().setVisibility(View.VISIBLE); - } - } - - @Override - public void willHidePhotoViewer() { - int count = attachPhotoRecyclerView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = attachPhotoRecyclerView.getChildAt(a); - if (view instanceof PhotoAttachPhotoCell) { - PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) view; - if (cell.getCheckBox().getVisibility() != VISIBLE) { - cell.getCheckBox().setVisibility(VISIBLE); - } - } - } - } - - @Override - public boolean isPhotoChecked(int index) { - return !(index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) && photoAttachAdapter.getSelectedPhotos().containsKey(MediaController.allPhotosAlbumEntry.photos.get(index).imageId); - } - - @Override - public void setPhotoChecked(int index) { - boolean add = true; - if (index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); - if (photoAttachAdapter.getSelectedPhotos().containsKey(photoEntry.imageId)) { - photoAttachAdapter.getSelectedPhotos().remove(photoEntry.imageId); - add = false; - } else { - photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); - } - int count = attachPhotoRecyclerView.getChildCount(); - for (int a = 0; a < count; a++) { - View view = attachPhotoRecyclerView.getChildAt(a); - int num = (Integer) view.getTag(); - if (num == index) { - ((PhotoAttachPhotoCell) view).setChecked(add, false); - break; - } - } - updatePhotosButton(); - } - - @Override - public boolean cancelButtonPressed() { - return false; - } - - @Override - public void sendButtonPressed(int index) { - if (photoAttachAdapter.getSelectedPhotos().isEmpty()) { - if (index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) { - return; - } - MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(index); - photoAttachAdapter.getSelectedPhotos().put(photoEntry.imageId, photoEntry); - } - delegate.didPressedButton(7); - } - - @Override - public int getSelectedCount() { - return photoAttachAdapter.getSelectedPhotos().size(); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - public class PhotoAttachAdapter extends RecyclerView.Adapter { - - private Context mContext; - private HashMap selectedPhotos = new HashMap<>(); - - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - - public PhotoAttachAdapter(Context context) { - mContext = context; - } - - public void clearSelectedPhotos() { - if (!selectedPhotos.isEmpty()) { - for (HashMap.Entry entry : selectedPhotos.entrySet()) { - MediaController.PhotoEntry photoEntry = entry.getValue(); - photoEntry.imagePath = null; - photoEntry.thumbPath = null; - photoEntry.caption = null; - } - selectedPhotos.clear(); - updatePhotosButton(); - notifyDataSetChanged(); - } - } - - public Holder createHolder() { - PhotoAttachPhotoCell cell = new PhotoAttachPhotoCell(mContext); - cell.setDelegate(new PhotoAttachPhotoCell.PhotoAttachPhotoCellDelegate() { - @Override - public void onCheckClick(PhotoAttachPhotoCell v) { - MediaController.PhotoEntry photoEntry = v.getPhotoEntry(); - if (selectedPhotos.containsKey(photoEntry.imageId)) { - selectedPhotos.remove(photoEntry.imageId); - v.setChecked(false, true); - photoEntry.imagePath = null; - photoEntry.thumbPath = null; - v.setPhotoEntry(photoEntry, (Integer) v.getTag() == MediaController.allPhotosAlbumEntry.photos.size() - 1); - } else { - selectedPhotos.put(photoEntry.imageId, photoEntry); - v.setChecked(true, true); - } - updatePhotosButton(); - } - }); - return new Holder(cell); - } - - public HashMap getSelectedPhotos() { - return selectedPhotos; - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - PhotoAttachPhotoCell cell = (PhotoAttachPhotoCell) holder.itemView; - MediaController.PhotoEntry photoEntry = MediaController.allPhotosAlbumEntry.photos.get(position); - cell.setPhotoEntry(photoEntry, position == MediaController.allPhotosAlbumEntry.photos.size() - 1); - cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); - cell.getImageView().setTag(position); - cell.setTag(position); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - Holder holder; - if (!viewsCache.isEmpty()) { - holder = viewsCache.get(0); - viewsCache.remove(0); - } else { - holder = createHolder(); - } - return holder; - } - - @Override - public int getItemCount() { - return (MediaController.allPhotosAlbumEntry != null ? MediaController.allPhotosAlbumEntry.photos.size() : 0); - } - - @Override - public int getItemViewType(int position) { - return 0; - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index 63c0773bf..3dbd1a357 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -52,6 +52,7 @@ import org.telegram.messenger.Utilities; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.GridLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; @@ -519,7 +520,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private GridView stickersGridView; private TextView stickersEmptyView; private RecyclerListView gifsGridView; - private FlowLayoutManager flowLayoutManager; + private ExtendedGridLayoutManager flowLayoutManager; private GifsAdapter gifsAdapter; private AdapterView.OnItemClickListener stickersOnItemClickListener; @@ -612,18 +613,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } cell.disable(); TLRPC.Document document = cell.getSticker(); - int index = newRecentStickers.indexOf(document.id); - if (index == -1) { - newRecentStickers.add(0, document.id); - if (newRecentStickers.size() > 20) { - newRecentStickers.remove(newRecentStickers.size() - 1); - } - } else if (index != 0) { - newRecentStickers.remove(index); - newRecentStickers.add(0, document.id); - } - - saveRecentStickers(); + addRecentSticker(document); if (listener != null) { listener.onStickerSelected(document); } @@ -637,7 +627,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (needGif) { gifsGridView = new RecyclerListView(context); - gifsGridView.setLayoutManager(flowLayoutManager = new FlowLayoutManager() { + gifsGridView.setLayoutManager(flowLayoutManager = new ExtendedGridLayoutManager(context, 100) { private Size size = new Size(); @@ -657,6 +647,25 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return size; } }); + flowLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + return flowLayoutManager.getSpanSizeForItem(position); + } + }); + gifsGridView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = 0; + outRect.top = 0; + outRect.bottom = 0; + int position = parent.getChildAdapterPosition(view); + if (!flowLayoutManager.isFirstRow(position)) { + outRect.top = AndroidUtilities.dp(2); + } + outRect.right = flowLayoutManager.isLastInRow(position) ? 0 : AndroidUtilities.dp(2); + } + }); if (Build.VERSION.SDK_INT >= 9) { gifsGridView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); } @@ -1223,8 +1232,8 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific if (switchToGifTab) { if (gifTabBum >= 0 && gifsGridView.getVisibility() != VISIBLE) { showGifTab(); + switchToGifTab = false; } - switchToGifTab = false; } if (gifTabBum == -2 && gifsGridView != null && gifsGridView.getVisibility() == VISIBLE) { listener.onGifTab(false); @@ -1240,6 +1249,23 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } } + public void addRecentSticker(TLRPC.Document document) { + if (document == null) { + return; + } + int index = newRecentStickers.indexOf(document.id); + if (index == -1) { + newRecentStickers.add(0, document.id); + if (newRecentStickers.size() > 20) { + newRecentStickers.remove(newRecentStickers.size() - 1); + } + } else if (index != 0) { + newRecentStickers.remove(index); + newRecentStickers.add(0, document.id); + } + saveRecentStickers(); + } + public void addRecentGif(MediaController.SearchImage searchImage) { if (searchImage == null || searchImage.document == null || recentImages == null) { return; @@ -1877,7 +1903,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { MediaController.SearchImage photoEntry = recentImages.get(i); if (photoEntry.document != null) { - ((ContextLinkCell) viewHolder.itemView).setGif(photoEntry.document, flowLayoutManager.getRowOfIndex(i) != flowLayoutManager.getRowCount() - 1); + ((ContextLinkCell) viewHolder.itemView).setGif(photoEntry.document, false); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java index 3eb99cf9f..f08d7585e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java @@ -105,4 +105,9 @@ public class EmptyTextProgressView extends FrameLayout { super.requestLayout(); } } + + @Override + public boolean hasOverlappingRendering() { + return false; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java new file mode 100644 index 000000000..624a714a3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java @@ -0,0 +1,224 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.util.SparseArray; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.support.widget.GridLayoutManager; + +import java.util.ArrayList; + +public class ExtendedGridLayoutManager extends GridLayoutManager { + + private SparseArray itemSpans = new SparseArray<>(); + private ArrayList> rows; + private SparseArray itemsToRow = new SparseArray<>(); + private int calculatedWidth; + + public ExtendedGridLayoutManager(Context context, int spanCount) { + super(context, spanCount); + } + + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + + private void prepareLayout(float viewPortAvailableSize) { + itemSpans.clear(); + itemsToRow.clear(); + int preferredRowSize = AndroidUtilities.dp(100); + + float totalItemSize = 0; + int[] weights = new int[getFlowItemCount()]; + for (int a = 0; a < getFlowItemCount(); a++) { + Size size = sizeForItem(a); + totalItemSize += (size.width / size.height) * preferredRowSize; + weights[a] = Math.round(size.width / size.height * 100); + } + + int numberOfRows = Math.max(Math.round(totalItemSize / viewPortAvailableSize), 1); + + rows = getLinearPartitionForSequence(weights, numberOfRows); + + int i = 0, a; + float previousItemSize = 0; + for (a = 0; a < rows.size(); a++) { + ArrayList row = rows.get(a); + + float summedRatios = 0; + for (int j = i, n = i + row.size(); j < n; j++) { + Size preferredSize = sizeForItem(j); + summedRatios += preferredSize.width / preferredSize.height; + } + + float rowSize = viewPortAvailableSize; + + if (rows.size() == 1 && a == rows.size() - 1) { + if (row.size() < 2) { + rowSize = (float) Math.floor(viewPortAvailableSize / 3.0f); + } else if (row.size() < 3) { + rowSize = (float) Math.floor(viewPortAvailableSize * 2.0f / 3.0f); + } + } + + int spanLeft = getSpanCount(); + for (int j = i, n = i + row.size(); j < n; j++) { + Size preferredSize = sizeForItem(j); + int width = Math.round(rowSize / summedRatios * (preferredSize.width / preferredSize.height)); + int itemSpan; + if (j != n - 1) { + itemSpan = (int) (width / viewPortAvailableSize * getSpanCount()); + spanLeft -= itemSpan; + } else { + itemsToRow.put(j, a); + itemSpan = spanLeft; + } + itemSpans.put(j, itemSpan); + } + i += row.size(); + } + } + + private int[] getLinearPartitionTable(int[] sequence, int numPartitions) { + int n = sequence.length; + int i, j, x; + + int tmpTable[] = new int[n * numPartitions]; + int solution[] = new int[(n - 1) * (numPartitions - 1)]; + + for (i = 0; i < n; i++) { + tmpTable[i * numPartitions] = sequence[i] + (i != 0 ? tmpTable[(i - 1) * numPartitions] : 0); + } + + for (j = 0; j < numPartitions; j++) { + tmpTable[j] = sequence[0]; + } + + for (i = 1; i < n; i++) { + for (j = 1; j < numPartitions; j++) { + int currentMin = 0; + int minX = Integer.MAX_VALUE; + + for (x = 0; x < i; x++) { + int cost = Math.max(tmpTable[x * numPartitions + (j - 1)], tmpTable[i * numPartitions] - tmpTable[x * numPartitions]); + if (x == 0 || cost < currentMin) { + currentMin = cost; + minX = x; + } + } + tmpTable[i * numPartitions + j] = currentMin; + solution[(i - 1) * (numPartitions - 1) + (j - 1)] = minX; + } + } + + return solution; + } + + private ArrayList> getLinearPartitionForSequence(int[] sequence, int numberOfPartitions) { + int n = sequence.length; + int k = numberOfPartitions; + + if (k <= 0) { + return new ArrayList<>(); + } + + if (k >= n || n == 1) { + ArrayList> partition = new ArrayList<>(sequence.length); + for (int i = 0; i < sequence.length; i++) { + ArrayList arrayList = new ArrayList<>(1); + arrayList.add(sequence[i]); + partition.add(arrayList); + } + return partition; + } + + int[] solution = getLinearPartitionTable(sequence, numberOfPartitions); + int solutionRowSize = numberOfPartitions - 1; + + k = k - 2; + n = n - 1; + ArrayList> answer = new ArrayList<>(); + + while (k >= 0) { + if (n < 1) { + answer.add(0, new ArrayList()); + } else { + ArrayList currentAnswer = new ArrayList<>(); + for (int i = solution[(n - 1) * solutionRowSize + k] + 1, range = n + 1; i < range; i++) { + currentAnswer.add(sequence[i]); + } + answer.add(0, currentAnswer); + n = solution[(n - 1) * solutionRowSize + k]; + } + k = k - 1; + } + + ArrayList currentAnswer = new ArrayList<>(); + for (int i = 0, range = n + 1; i < range; i++) { + currentAnswer.add(sequence[i]); + } + answer.add(0, currentAnswer); + return answer; + } + + private Size sizeForItem(int i) { + Size size = getSizeForItem(i); + if (size.width == 0) { + size.width = 100; + } + if (size.height == 0) { + size.height = 100; + } + float aspect = size.width / size.height; + if (aspect > 4.0f || aspect < 0.2f) { + size.height = size.width = Math.max(size.width, size.height); + } + return size; + } + + protected Size getSizeForItem(int i) { + return new Size(100, 100); + } + + private void checkLayout() { + if (itemSpans.size() != getFlowItemCount() || calculatedWidth != getWidth()) { + calculatedWidth = getWidth(); + prepareLayout(getWidth()); + } + } + + public int getSpanSizeForItem(int i) { + checkLayout(); + return itemSpans.get(i); + } + + public int getRowsCount(int width) { + if (rows == null) { + prepareLayout(width); + } + return rows != null ? rows.size() : 0; + } + + public boolean isLastInRow(int i) { + checkLayout(); + return itemsToRow.get(i) != null; + } + + public boolean isFirstRow(int i) { + checkLayout(); + return !(rows == null || rows.isEmpty()) && i < rows.get(0).size(); + } + + protected int getFlowItemCount() { + return getItemCount(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlowLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FlowLayoutManager.java deleted file mode 100644 index 4d5d6c418..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FlowLayoutManager.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.BuildVars; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.support.widget.RecyclerView; - -import java.util.ArrayList; - -public class FlowLayoutManager extends RecyclerView.LayoutManager { - - private SparseArray framesPos; - private SparseArray itemsToRow; - private SparseArray rowToItems; - private ArrayList> rows; - private Size actualSize = new Size(); - private int lastCalculatedWidth; - private int lastCalculatedHeight; - private float minimumInteritemSpacing = AndroidUtilities.dp(2); - private float preferredRowSize; - - private static final int DIRECTION_NONE = -1; - private static final int DIRECTION_UP = 0; - private static final int DIRECTION_DOWN = 1; - - private int firstVisiblePosition; - private boolean forceClearOffsets; - - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getItemCount() == 0) { - detachAndScrapAttachedViews(recycler); - return; - } - - int childTop; - if (framesPos == null || framesPos.size() != getItemCount() || lastCalculatedHeight != getHeight() || lastCalculatedWidth != getWidth()) { - prepareLayout(); - firstVisiblePosition = 0; - childTop = 0; - } else if (getChildCount() == 0) { - firstVisiblePosition = 0; - childTop = 0; - } else if (getVisibleChildCount(0) >= state.getItemCount()) { - firstVisiblePosition = 0; - childTop = 0; - } else { - final View topChild = getChildAt(0); - if (forceClearOffsets) { - childTop = 0; - forceClearOffsets = false; - } else { - childTop = getDecoratedTop(topChild); - } - - Rect lastFrame = framesPos.get(getIndexOfRow(rows.size() - 1)); - if (getHeight() > lastFrame.y + lastFrame.height) { - firstVisiblePosition = 0; - childTop = 0; - } - - int maxFirstRow = rows.size() - (getVisibleRowCount(0) - 1); - boolean isOutOfRowBounds = getFirstVisibleRow() > maxFirstRow; - if (isOutOfRowBounds) { - int firstRow; - if (isOutOfRowBounds) { - firstRow = maxFirstRow; - } else { - firstRow = getFirstVisibleRow(); - } - firstVisiblePosition = getIndexOfRow(firstRow); - - childTop = (int) framesPos.get(getIndexOfRow(rows.size() - 1)).y; - - if (getFirstVisibleRow() == 0) { - childTop = Math.min(childTop, 0); - } - } - } - detachAndScrapAttachedViews(recycler); - fillGrid(DIRECTION_NONE, childTop, 0, recycler, state); - } - - @Override - public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) { - removeAllViews(); - } - - private void fillGrid(int direction, int emptyTop, int directionRowCount, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (firstVisiblePosition < 0) { - firstVisiblePosition = 0; - } - if (firstVisiblePosition >= getItemCount()) { - firstVisiblePosition = (getItemCount() - 1); - } - - SparseArray viewCache = null; - int topOffset = emptyTop; - int childCount = getChildCount(); - if (childCount != 0) { - final View topView = getChildAt(0); - topOffset = getDecoratedTop(topView); - switch (direction) { - case DIRECTION_UP: { - //FileLog.d("tmessages", "up topOffset from " + topOffset); - for (int a = 0; a < directionRowCount; a++) { - topOffset -= preferredRowSize; - } - //FileLog.d("tmessages", "up topOffset to " + topOffset); - /*framesPos.get(firstVisiblePosition - 1).height*/ //TODO support various row height - break; - } - case DIRECTION_DOWN: { - //FileLog.d("tmessages", "down topOffset from " + topOffset); - for (int a = 0; a < directionRowCount; a++) { - topOffset += preferredRowSize; - } - //FileLog.d("tmessages", "down topOffset to " + topOffset); - /*framesPos.get(firstVisiblePosition).height*/ //TODO support various row height - break; - } - } - - viewCache = new SparseArray<>(getChildCount()); - for (int i = 0; i < childCount; i++) { - viewCache.put(firstVisiblePosition + i, getChildAt(i)); - } - for (int i = 0; i < childCount; i++) { - detachView(viewCache.valueAt(i)); - } - } - - int firstRow = getRowOfIndex(firstVisiblePosition); - switch (direction) { - case DIRECTION_UP: - //FileLog.d("tmessages", "up first position from " + firstVisiblePosition + " row from " + getRowOfIndex(firstVisiblePosition)); - for (int a = 0; a < directionRowCount; a++) { - firstVisiblePosition -= getCountForRow(firstRow - (a + 1)); - } - //FileLog.d("tmessages", "up first position to " + firstVisiblePosition + " row to " + getRowOfIndex(firstVisiblePosition)); - break; - case DIRECTION_DOWN: - //FileLog.d("tmessages", "down first position from " + firstVisiblePosition + " row from " + getRowOfIndex(firstVisiblePosition)); - for (int a = 0; a < directionRowCount; a++) { - firstVisiblePosition += getCountForRow(firstRow + a); - } - //FileLog.d("tmessages", "down first position to " + firstVisiblePosition + " row to " + getRowOfIndex(firstVisiblePosition)); - break; - } - - int lastHeight = 0; - int lastRow = -1; - //FileLog.d("tmessages", "fill from " + firstVisiblePosition + " to " + getVisibleChildCount(topOffset)); - for (int i = firstVisiblePosition, v = firstVisiblePosition + getVisibleChildCount(topOffset); i < v; i++) { - if (i < 0 || i >= state.getItemCount()) { - continue; - } - int newRow = getRowOfIndex(i); - if (lastRow != -1 && newRow != lastRow) { - topOffset = lastHeight; - } - - Rect rect = framesPos.get(i); - View view = viewCache != null ? viewCache.get(i) : null; - if (view == null) { - view = recycler.getViewForPosition(i); - addView(view); - RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams(); - layoutParams.width = (int) rect.width; - layoutParams.height = (int) rect.height; - view.setLayoutParams(layoutParams); - measureChildWithMargins(view, 0, 0); - layoutDecorated(view, (int) rect.x, topOffset, (int) (rect.x + rect.width), (int) (topOffset + rect.height)); - } else { - attachView(view); - viewCache.remove(i); - } - lastHeight = (int) (topOffset + rect.height); - lastRow = newRow; - } - if (viewCache != null) { - for (int i = 0; i < viewCache.size(); i++) { - final View removingView = viewCache.valueAt(i); - recycler.recycleView(removingView); - } - } - } - - @Override - public void scrollToPosition(int position) { - if (position >= getItemCount()) { - return; - } - forceClearOffsets = true; - firstVisiblePosition = position; - requestLayout(); - } - - @Override - public boolean canScrollVertically() { - return true; - } - - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - - final View topView = getChildAt(0); - final View bottomView = getChildAt(getChildCount() - 1); - - int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView); - if (viewSpan < getHeight()) { - return 0; - } - - int delta; - int rowsCount; - int firstVisibleRow = getFirstVisibleRow(); - int lastVisibleRow = firstVisibleRow + getVisibleRowCount(0) - 1; - boolean topBoundReached = firstVisibleRow == 0; - boolean bottomBoundReached = lastVisibleRow >= rows.size() - 1; - //FileLog.d("tmessages", "first = " + firstVisibleRow + " last = " + lastVisibleRow + " rows = " + rows.size() + " height = " + getHeight() + " preferredRowSize = " + preferredRowSize); - if (dy > 0) { - int bottom = getDecoratedBottom(bottomView); - int amount = getHeight() - bottom + dy; - rowsCount = (int) Math.ceil(Math.abs(amount) / preferredRowSize); - //FileLog.d("tmessages", "scroll bottom = " + dy + " decorated bottom = " + bottom + " amount = " + amount + " rowsCount = " + rowsCount); - if (amount < 0 || lastVisibleRow + rowsCount <= rows.size() - 1) { - delta = -dy; - //FileLog.d("tmessages", "delta1 = " + delta); - } else { - rowsCount = rows.size() - 1 - lastVisibleRow; - delta = getHeight() - bottom - (int) (rowsCount * preferredRowSize); - //FileLog.d("tmessages", "delta2 = " + delta + " rowsCount = " + rowsCount); - } - } else { - int top = getDecoratedTop(topView); - int amount = Math.abs(dy) + top; - rowsCount = (int) Math.ceil(Math.abs(amount) / preferredRowSize); - //FileLog.d("tmessages", "scroll top = " + dy + " decorated top = " + top + " amount = " + amount + " rowsCount = " + rowsCount); - if (amount < 0 || firstVisibleRow - rowsCount >= 0) { - delta = -dy; - //FileLog.d("tmessages", "delta1 = " + delta); - } else { - rowsCount = firstVisibleRow; - delta = -top + (int) (firstVisibleRow * preferredRowSize); - //FileLog.d("tmessages", "delta2 = " + delta + " rowsCount = " + rowsCount); - } - } - - offsetChildrenVertical(delta); - - if (dy > 0) { - if (getDecoratedBottom(topView) < 0 && !bottomBoundReached) { - fillGrid(DIRECTION_DOWN, 0, rowsCount, recycler, state); - } else if (!bottomBoundReached) { - fillGrid(DIRECTION_NONE, 0, 0, recycler, state); - } - } else { - if (getDecoratedTop(topView) > 0 && !topBoundReached) { - fillGrid(DIRECTION_UP, 0, rowsCount, recycler, state); - } else if (!topBoundReached) { - fillGrid(DIRECTION_NONE, 0, 0, recycler, state); - } - } - - return -delta; - } - - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - private int getCountForRow(int row) { - if (row < 0 || row >= rows.size()) { - return 0; - } - return rows.get(row).size(); - } - - private int getIndexOfRow(int row) { - if (rows == null) { - return 0; - } - Integer value = rowToItems.get(row); - return value == null ? 0 : value; - } - - public int getRowCount() { - return rows != null ? rows.size() : 0; - } - - public int getRowOfIndex(int index) { - if (rows == null) { - return 0; - } - Integer value = itemsToRow.get(index); - return value == null ? 0 : value; - } - - private int getFirstVisibleRow() { - return getRowOfIndex(firstVisiblePosition); - } - - private int getVisibleChildCount(int topOffset) { - int startRow = getFirstVisibleRow(); - int count = 0; - for (int a = startRow, e = startRow + getVisibleRowCount(topOffset); a < e; a++) { - if (a < rows.size()) { - count += rows.get(a).size(); - } else { - break; - } - } - return count; - } - - private int getVisibleRowCount(int topOffset) { - int height = getHeight(); - View topChild = getChildAt(0); - int top = topChild != null ? getDecoratedTop(topChild) : topOffset; - int count = 0; - int startRow = getFirstVisibleRow(); - for (int a = startRow; a < rows.size(); a++) { - if (top > height) { - break; - } - top += framesPos.get(getIndexOfRow(a)).height; - count++; - } - return count; - } - - private void prepareLayout() { - if (framesPos == null) { - framesPos = new SparseArray<>(); - itemsToRow = new SparseArray<>(); - rowToItems = new SparseArray<>(); - } else { - framesPos.clear(); - itemsToRow.clear(); - rowToItems.clear(); - } - preferredRowSize = getHeight() / 2.0f; - float viewPortAvailableSize = getWidth(); - if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", "preferredRowSize = " + preferredRowSize + " width = " + viewPortAvailableSize); - } - - float totalItemSize = 0; - int[] weights = new int[getItemCount()]; - for (int a = 0; a < getItemCount(); a++) { - Size size = sizeForItem(a); - totalItemSize += (size.width / size.height) * preferredRowSize; - weights[a] = Math.round(size.width / size.height * 100); - } - - int numberOfRows = Math.max(Math.round(totalItemSize / viewPortAvailableSize), 1); - - rows = getLinearPartitionForSequence(weights, numberOfRows); - - int i = 0, a; - Point offset = new Point(0, 0); - float previousItemSize = 0; - for (a = 0; a < rows.size(); a++) { - ArrayList row = rows.get(a); - - float summedRatios = 0; - for (int j = i, n = i + row.size(); j < n; j++) { - Size preferredSize = sizeForItem(j); - summedRatios += preferredSize.width / preferredSize.height; - } - - float rowSize = viewPortAvailableSize - ((row.size() - 1) * minimumInteritemSpacing); - - if (rows.size() == 1 && a == rows.size() - 1) { - if (row.size() < 2) { - rowSize = (float) Math.floor(viewPortAvailableSize / 3.0f) - ((row.size() - 1) * minimumInteritemSpacing); - } else if (row.size() < 3) { - rowSize = (float) Math.floor(viewPortAvailableSize * 2.0f / 3.0f) - ((row.size() - 1) * minimumInteritemSpacing); - } - } - - for (int j = i, n = i + row.size(); j < n; j++) { - Size preferredSize = sizeForItem(j); - - actualSize.width = Math.round(rowSize / summedRatios * (preferredSize.width / preferredSize.height)); - actualSize.height = preferredRowSize;//Math.round(rowSize / summedRatios); - if (a == row.size() - 1) { - actualSize.height = preferredRowSize; - } - Rect rect = new Rect(offset.x, offset.y, actualSize.width, actualSize.height); - if (rect.x + rect.width >= viewPortAvailableSize - 2.0f) { - rect.width = Math.max(1.0f, viewPortAvailableSize - rect.x); - } - framesPos.put(j, rect); - itemsToRow.put(j, a); - if (j == i) { - rowToItems.put(a, j); - } - offset.x += actualSize.width + minimumInteritemSpacing; - previousItemSize = actualSize.height; - } - - if (row.size() > 0) { - offset.x = 0; - offset.y += previousItemSize; - } - - i += row.size(); - } - } - - private int[] getLinearPartitionTable(int[] sequence, int numPartitions) { - int n = sequence.length; - int i, j, x; - - int tmpTable[] = new int[n * numPartitions]; - int solution[] = new int[(n - 1) * (numPartitions - 1)]; - - for (i = 0; i < n; i++) { - tmpTable[i * numPartitions] = sequence[i] + (i != 0 ? tmpTable[(i - 1) * numPartitions] : 0); - } - - for (j = 0; j < numPartitions; j++) { - tmpTable[j] = sequence[0]; - } - - for (i = 1; i < n; i++) { - for (j = 1; j < numPartitions; j++) { - int currentMin = 0; - int minX = Integer.MAX_VALUE; - - for (x = 0; x < i; x++) { - int cost = Math.max(tmpTable[x * numPartitions + (j - 1)], tmpTable[i * numPartitions] - tmpTable[x * numPartitions]); - if (x == 0 || cost < currentMin) { - currentMin = cost; - minX = x; - } - } - tmpTable[i * numPartitions + j] = currentMin; - solution[(i - 1) * (numPartitions - 1) + (j - 1)] = minX; - } - } - - return solution; - } - - private ArrayList> getLinearPartitionForSequence(int[] sequence, int numberOfPartitions) { - int n = sequence.length; - int k = numberOfPartitions; - - if (k <= 0) { - return new ArrayList<>(); - } - - if (k >= n || n == 1) { - ArrayList> partition = new ArrayList<>(sequence.length); - for (int i = 0; i < sequence.length; i++) { - ArrayList arrayList = new ArrayList<>(1); - arrayList.add(sequence[i]); - partition.add(arrayList); - } - return partition; - } - - int[] solution = getLinearPartitionTable(sequence, numberOfPartitions); - int solutionRowSize = numberOfPartitions - 1; - - k = k - 2; - n = n - 1; - ArrayList> answer = new ArrayList<>(); - - while (k >= 0) { - if (n < 1) { - answer.add(0, new ArrayList()); - } else { - ArrayList currentAnswer = new ArrayList<>(); - for (int i = solution[(n - 1) * solutionRowSize + k] + 1, range = n + 1; i < range; i++) { - currentAnswer.add(sequence[i]); - } - answer.add(0, currentAnswer); - n = solution[(n - 1) * solutionRowSize + k]; - } - k = k - 1; - } - - ArrayList currentAnswer = new ArrayList<>(); - for (int i = 0, range = n + 1; i < range; i++) { - currentAnswer.add(sequence[i]); - } - answer.add(0, currentAnswer); - return answer; - } - - private Size sizeForItem(int i) { - Size size = getSizeForItem(i); - if (size.width == 0) { - size.width = 100; - } - if (size.height == 0) { - size.height = 100; - } - float aspect = size.width / size.height; - if (aspect > 4.0f || aspect < 0.2f) { - size.height = size.width = Math.max(size.width, size.height); - } - return size; - } - - protected Size getSizeForItem(int i) { - return new Size(100, 100); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java index 3da5c1ac1..c8c4bfe29 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -512,11 +512,6 @@ public class PhotoViewerCaptionEnterView extends FrameLayoutFixed implements Not private void openKeyboardInternal() { showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2); - /*int selection = messageEditText.getSelectionStart(); - MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - messageEditText.onTouchEvent(event); - event.recycle(); - messageEditText.setSelection(selection);*/ AndroidUtilities.showKeyboard(messageEditText); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java new file mode 100644 index 000000000..6080201c3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java @@ -0,0 +1,28 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.View; + +public class ProgressCircleView extends View { + + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public ProgressCircleView(Context context) { + super(context); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java index 21fdedfed..0e7ee7e26 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java @@ -382,4 +382,9 @@ public class RecyclerListView extends RecyclerView { */ } } + + @Override + public boolean hasOverlappingRendering() { + return false; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java index 440205d3a..43e0ac00a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java @@ -63,7 +63,6 @@ import java.util.TimerTask; public class ShareAlert extends BottomSheet { private FrameLayout frameLayout; - private FrameLayout container; private TextView doneButtonBadgeTextView; private TextView doneButtonTextView; private LinearLayout doneButton; @@ -89,11 +88,6 @@ public class ShareAlert extends BottomSheet { public ShareAlert(final Context context, final MessageObject messageObject, boolean publicChannel) { super(context, true); - setApplyTopPadding(false); - setApplyBottomPadding(false); - if (Build.VERSION.SDK_INT >= 11) { - setDisableBackground(true); - } shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); @@ -121,18 +115,26 @@ public class ShareAlert extends BottomSheet { loadingLink = false; } }); - } }); } - container = new FrameLayout(context) { + containerView = new FrameLayout(context) { private boolean ignoreLayout = false; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return scrollOffsetY != 0 && ev.getY() < scrollOffsetY || super.onInterceptTouchEvent(ev); + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); } @Override @@ -180,11 +182,12 @@ public class ShareAlert extends BottomSheet { } } }; - if (Build.VERSION.SDK_INT >= 11) { - container.setWillNotDraw(false); + if (Build.VERSION.SDK_INT < 11) { + containerView.setBackgroundDrawable(shadowDrawable); + } else { + containerView.setWillNotDraw(false); } - container.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); - setCustomView(container); + containerView.setPadding(backgroundPaddingLeft, Build.VERSION.SDK_INT < 11 ? backgroundPaddingTop : 0, backgroundPaddingLeft, 0); frameLayout = new FrameLayout(context); frameLayout.setBackgroundColor(0xffffffff); @@ -327,7 +330,7 @@ public class ShareAlert extends BottomSheet { } } }); - container.addView(gridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); + containerView.addView(gridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); gridView.setAdapter(listAdapter = new ShareDialogsAdapter(context)); gridView.setGlowColor(0xfff5f6f7); gridView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @@ -339,6 +342,9 @@ public class ShareAlert extends BottomSheet { } else { dialog = searchAdapter.getItem(position); } + if (dialog == null) { + return; + } ShareDialogCell cell = (ShareDialogCell) view; if (selectedDialogs.containsKey(dialog.id)) { selectedDialogs.remove(dialog.id); @@ -365,13 +371,13 @@ public class ShareAlert extends BottomSheet { searchEmptyView.showTextView(); searchEmptyView.setText(LocaleController.getString("NoChats", R.string.NoChats)); gridView.setEmptyView(searchEmptyView); - container.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); + containerView.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); - container.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP)); + containerView.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP)); shadow = new View(context); shadow.setBackgroundResource(R.drawable.header_shadow); - container.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); + containerView.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); updateSelectedCount(); } @@ -387,6 +393,11 @@ public class ShareAlert extends BottomSheet { return -1000; } + @Override + protected boolean canDismissWithSwipe() { + return false; + } + @SuppressLint("NewApi") private void updateLayout() { if (gridView.getChildCount() <= 0) { @@ -401,7 +412,7 @@ public class ShareAlert extends BottomSheet { frameLayout.setTranslationY(scrollOffsetY); shadow.setTranslationY(scrollOffsetY); searchEmptyView.setTranslationY(scrollOffsetY); - container.invalidate(); + containerView.invalidate(); } } @@ -506,7 +517,7 @@ public class ShareAlert extends BottomSheet { public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ShareDialogCell cell = (ShareDialogCell) holder.itemView; TLRPC.Dialog dialog = getItem(position); - cell.setDialog(dialog, selectedDialogs.containsKey(dialog.id), null); + cell.setDialog((int) dialog.id, selectedDialogs.containsKey(dialog.id), null); } @Override @@ -606,9 +617,10 @@ public class ShareAlert extends BottomSheet { found = 2; } if (found != 0) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); DialogSearchResult dialogSearchResult = dialogsResult.get((long) user.id); if (user.status != null) { user.status.expires = cursor.intValue(1); @@ -622,7 +634,6 @@ public class ShareAlert extends BottomSheet { dialogSearchResult.dialog.id = user.id; resultCount++; } - data.reuse(); break; } } @@ -641,9 +652,10 @@ public class ShareAlert extends BottomSheet { for (int a = 0; a < search.length; a++) { String q = search[a]; if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); if (!(chat == null || ChatObject.isNotInChat(chat) || ChatObject.isChannel(chat) && !chat.creator && !chat.editor && !chat.megagroup)) { DialogSearchResult dialogSearchResult = dialogsResult.get(-(long) chat.id); dialogSearchResult.name = AndroidUtilities.generateSearchName(chat.title, null, q); @@ -652,7 +664,6 @@ public class ShareAlert extends BottomSheet { resultCount++; } } - data.reuse(); break; } } @@ -691,9 +702,10 @@ public class ShareAlert extends BottomSheet { found = 2; } if (found != 0) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); DialogSearchResult dialogSearchResult = new DialogSearchResult(); if (user.status != null) { user.status.expires = cursor.intValue(1); @@ -707,7 +719,6 @@ public class ShareAlert extends BottomSheet { } searchResults.add(dialogSearchResult); } - data.reuse(); break; } } @@ -827,7 +838,7 @@ public class ShareAlert extends BottomSheet { public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ShareDialogCell cell = (ShareDialogCell) holder.itemView; DialogSearchResult result = searchResult.get(position); - cell.setDialog(result.dialog, selectedDialogs.containsKey(result.dialog.id), result.name); + cell.setDialog((int) result.dialog.id, selectedDialogs.containsKey(result.dialog.id), result.name); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java index 79a0c0514..9eda81801 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java @@ -46,9 +46,7 @@ public class SizeNotifierFrameLayoutPhoto extends FrameLayout { int usableViewHeight = rootView.getHeight() - AndroidUtilities.getViewInset(rootView); getWindowVisibleDisplayFrame(rect); int top = rect.top; - int size = (rect.bottom - rect.top); - - size = AndroidUtilities.displaySize.y - top - usableViewHeight; + int size = AndroidUtilities.displaySize.y - top - usableViewHeight; if (size <= AndroidUtilities.dp(10)) { size = 0; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index eae765ee7..0e5993d8f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -54,7 +54,6 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not void onStickerSelected(TLRPC.Document sticker); } - private FrameLayout container; private RecyclerListView gridView; private GridLayoutManager layoutManager; private GridAdapter adapter; @@ -79,25 +78,29 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not private int scrollOffsetY; private int reqId; - private boolean ignoreLayout = false; + private boolean ignoreLayout; public StickersAlert(Context context, TLRPC.InputStickerSet set, TLRPC.TL_messages_stickerSet loadedSet, StickersAlertDelegate stickersAlertDelegate) { super(context, false); - setApplyTopPadding(false); - setApplyBottomPadding(false); - if (Build.VERSION.SDK_INT >= 11) { - setDisableBackground(true); - } delegate = stickersAlertDelegate; inputStickerSet = set; stickerSet = loadedSet; shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); - container = new FrameLayout(context) { + containerView = new FrameLayout(context) { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return scrollOffsetY != 0 && ev.getY() < scrollOffsetY || super.onInterceptTouchEvent(ev); + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); } @Override @@ -151,15 +154,14 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } } }; - if (Build.VERSION.SDK_INT >= 11) { - container.setWillNotDraw(false); + if (Build.VERSION.SDK_INT < 11) { + containerView.setBackgroundDrawable(shadowDrawable); + } else { + containerView.setWillNotDraw(false); } - container.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); - setCustomView(container); + containerView.setPadding(backgroundPaddingLeft, Build.VERSION.SDK_INT < 11 ? backgroundPaddingTop : 0, backgroundPaddingLeft, 0); titleTextView = new TextView(context); - - titleTextView = new TextView(getContext()); titleTextView.setLines(1); titleTextView.setSingleLine(true); titleTextView.setTextColor(Theme.STICKERS_SHEET_TITLE_TEXT_COLOR); @@ -168,7 +170,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not titleTextView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); titleTextView.setGravity(Gravity.CENTER_VERTICAL); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - container.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + containerView.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); titleTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -182,7 +184,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not shadow[0].clearAnimation(); shadow[0].setVisibility(View.INVISIBLE); shadow[0].setTag(1); - container.addView(shadow[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); + containerView.addView(shadow[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); gridView = new RecyclerListView(context) { @Override @@ -265,7 +267,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } }; gridView.setOnItemClickListener(stickersOnItemClickListener); - container.addView(gridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 48)); + containerView.addView(gridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 48)); emptyView = new FrameLayout(context) { @Override @@ -276,7 +278,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not super.requestLayout(); } }; - container.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48)); + containerView.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48)); gridView.setEmptyView(emptyView); emptyView.setOnTouchListener(new View.OnTouchListener() { @Override @@ -290,10 +292,10 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not shadow[1] = new View(context); shadow[1].setBackgroundResource(R.drawable.header_shadow_reverse); - container.addView(shadow[1], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); + containerView.addView(shadow[1], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); pickerBottomLayout = new PickerBottomLayout(context, false); - container.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM)); + containerView.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); pickerBottomLayout.cancelButton.setTextColor(Theme.STICKERS_SHEET_CLOSE_TEXT_COLOR); pickerBottomLayout.cancelButton.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); @@ -310,7 +312,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not stickerPreviewLayout.setBackgroundColor(0xdfffffff); stickerPreviewLayout.setVisibility(View.GONE); stickerPreviewLayout.setSoundEffectsEnabled(false); - container.addView(stickerPreviewLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + containerView.addView(stickerPreviewLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); stickerPreviewLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -465,13 +467,18 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not } } + @Override + protected boolean canDismissWithSwipe() { + return false; + } + @SuppressLint("NewApi") private void updateLayout() { if (gridView.getChildCount() <= 0) { gridView.setTopGlowOffset(scrollOffsetY = gridView.getPaddingTop()); titleTextView.setTranslationY(scrollOffsetY); shadow[0].setTranslationY(scrollOffsetY); - container.invalidate(); + containerView.invalidate(); return; } View child = gridView.getChildAt(0); @@ -488,7 +495,7 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not gridView.setTopGlowOffset(scrollOffsetY = newOffset); titleTextView.setTranslationY(scrollOffsetY); shadow[0].setTranslationY(scrollOffsetY); - container.invalidate(); + containerView.invalidate(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java index 34a95d0c7..ecdb77c92 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java @@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.NotificationCenter; import org.telegram.ui.ActionBar.Theme; public class TypingDotsDrawable extends Drawable { @@ -91,15 +92,28 @@ public class TypingDotsDrawable extends Drawable { public void draw(Canvas canvas) { int y; if (isChat) { - y = AndroidUtilities.dp(8.3f) + getBounds().top; + y = AndroidUtilities.dp(8.5f) + getBounds().top; } else { - y = AndroidUtilities.dp(9) + getBounds().top; + y = AndroidUtilities.dp(9.3f) + getBounds().top; } canvas.drawCircle(AndroidUtilities.dp(3), y, scales[0] * AndroidUtilities.density, paint); canvas.drawCircle(AndroidUtilities.dp(9), y, scales[1] * AndroidUtilities.density, paint); canvas.drawCircle(AndroidUtilities.dp(15), y, scales[2] * AndroidUtilities.density, paint); + checkUpdate(); + } + + private void checkUpdate() { if (started) { - update(); + if (!NotificationCenter.getInstance().isAnimationInProgress()) { + update(); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + checkUpdate(); + } + }, 100); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java new file mode 100644 index 000000000..60d8584d8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java @@ -0,0 +1,27 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui.Components; + +import android.text.TextPaint; + +import org.telegram.ui.ActionBar.Theme; + +public class URLSpanUserMention extends URLSpanNoUnderline { + + public URLSpanUserMention(String url) { + super(url); + } + + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setColor(Theme.MSG_LINK_TEXT_COLOR); + ds.setUnderlineText(false); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java index 41c99a782..f43bc62fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java @@ -113,9 +113,6 @@ public class WebFrameLayout extends FrameLayout { public WebFrameLayout(Context context, final BottomSheet parentDialog, String title, String descripton, String originalUrl, final String url, int w, int h) { super(context); embedUrl = url; - if (embedUrl.toLowerCase().contains("youtube")) { - //embedUrl += "&enablejsapi=1"; - } hasDescription = descripton != null && descripton.length() > 0; openUrl = originalUrl; width = w; @@ -136,14 +133,6 @@ public class WebFrameLayout extends FrameLayout { dialog.getContainer().addView(fullscreenVideoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); fullscreenVideoContainer.setVisibility(INVISIBLE); - /*LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.LEFT | Gravity.TOP)); - - - - */ - webView = new WebView(context); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index c5771d909..70e3223d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -45,6 +45,7 @@ import org.telegram.messenger.ImageLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.UserObject; +import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.FileLog; @@ -60,6 +61,7 @@ import org.telegram.ui.Adapters.DialogsAdapter; import org.telegram.ui.Adapters.DialogsSearchAdapter; import org.telegram.messenger.AnimationCompat.ObjectAnimatorProxy; import org.telegram.messenger.AnimationCompat.ViewProxy; +import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.ProfileSearchCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Cells.DialogCell; @@ -148,6 +150,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().addObserver(this, NotificationCenter.needReloadRecentDialogsSearch); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didLoadedReplyMessages); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.reloadHints); } @@ -177,6 +180,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.needReloadRecentDialogsSearch); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didLoadedReplyMessages); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.reloadHints); } delegate = null; } @@ -186,7 +190,12 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. searching = false; searchWas = false; - Theme.loadRecources(context); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + Theme.loadRecources(context); + } + }); ActionBarMenu menu = actionBar.createMenu(); if (!onlySelect && searchString == null) { @@ -732,7 +741,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. type = 1; } dialogsSearchAdapter = new DialogsSearchAdapter(context, type, dialogsType); - dialogsSearchAdapter.setDelegate(new DialogsSearchAdapter.MessagesActivitySearchAdapterDelegate() { + dialogsSearchAdapter.setDelegate(new DialogsSearchAdapter.DialogsSearchAdapterDelegate() { @Override public void searchStateChanged(boolean search) { if (searching && searchWas && searchEmptyView != null) { @@ -743,6 +752,61 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } } + + @Override + public void didPressedOnSubDialog(int did) { + if (onlySelect) { + didSelectResult(did, true, false); + } else { + Bundle args = new Bundle(); + if (did > 0) { + args.putInt("user_id", did); + } else { + args.putInt("chat_id", -did); + } + if (actionBar != null) { + actionBar.closeSearchField(); + } + if (AndroidUtilities.isTablet()) { + if (dialogsAdapter != null) { + dialogsAdapter.setOpenedDialogId(openedDialogId = did); + updateVisibleRows(MessagesController.UPDATE_MASK_SELECT_DIALOG); + } + } + if (searchString != null) { + if (MessagesController.checkCanOpenChat(args, DialogsActivity.this)) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + presentFragment(new ChatActivity(args)); + } + } else { + if (MessagesController.checkCanOpenChat(args, DialogsActivity.this)) { + presentFragment(new ChatActivity(args)); + } + } + } + } + + @Override + public void needRemoveHint(final int did) { + if (getParentActivity() == null) { + return; + } + TLRPC.User user = MessagesController.getInstance().getUser(did); + if (user == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.formatString("ChatHintsDelete", R.string.ChatHintsDelete, ContactsController.formatName(user.first_name, user.last_name))); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SearchQuery.removePeer(did); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } }); if (MessagesController.getInstance().loadingDialogs && MessagesController.getInstance().dialogs.isEmpty()) { @@ -939,6 +1003,10 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. } } else if (id == NotificationCenter.didLoadedReplyMessages) { updateVisibleRows(0); + } else if (id == NotificationCenter.reloadHints) { + if (dialogsSearchAdapter != null) { + dialogsSearchAdapter.notifyDataSetChanged(); + } } } @@ -1007,12 +1075,21 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. ((UserCell) child).update(mask); } else if (child instanceof ProfileSearchCell) { ((ProfileSearchCell) child).update(mask); + } else if (child instanceof RecyclerListView) { + RecyclerListView innerListView = (RecyclerListView) child; + int count2 = innerListView.getChildCount(); + for (int b = 0; b < count2; b++) { + View child2 = innerListView.getChildAt(b); + if (child2 instanceof HintDialogCell) { + ((HintDialogCell) child2).checkUnreadCounter(mask); + } + } } } } - public void setDelegate(DialogsActivityDelegate delegate) { - this.delegate = delegate; + public void setDelegate(DialogsActivityDelegate dialogsActivityDelegate) { + delegate = dialogsActivityDelegate; } public void setSearchString(String string) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index a7b7ab950..021b157ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -639,7 +639,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } Uri uri = (Uri) parcelable; if (uri != null) { - if (isInternalUri(uri)) { + if (AndroidUtilities.isInternalUri(uri)) { error = true; } } @@ -673,11 +673,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa documentsMimeType = type; } } - if (sendingText != null) { - if (sendingText.contains("WhatsApp")) { //remove unnecessary caption 'sent from WhatsApp' from photos forwarded from WhatsApp - sendingText = null; - } - } } } else if (sendingText == null) { error = true; @@ -699,7 +694,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } Uri uri = (Uri) parcelable; if (uri != null) { - if (isInternalUri(uri)) { + if (AndroidUtilities.isInternalUri(uri)) { uris.remove(a); a--; } @@ -1032,21 +1027,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa return false; } - private boolean isInternalUri(Uri uri) { - String pathString = uri.getPath(); - if (pathString == null) { - return false; - } - while (true) { - String newPath = Utilities.readlink(pathString); - if (newPath == null || newPath.equals(pathString)) { - break; - } - pathString = newPath; - } - return pathString != null && pathString.toLowerCase().contains("/data/data/" + getPackageName() + "/files"); - } - private void runLinkRequest(final String username, final String group, final String sticker, final String botUser, final String botChat, final String message, final boolean hasUrl, final Integer messageId, final int state) { final ProgressDialog progressDialog = new ProgressDialog(this); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); @@ -1406,13 +1386,20 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } else { actionBarLayout.presentFragment(fragment, dialogsFragment != null, dialogsFragment == null, true); + if (photoPathsArray != null) { + ArrayList captions = null; + if (sendingText != null && photoPathsArray.size() == 1) { + captions = new ArrayList<>(); + captions.add(sendingText); + sendingText = null; + } + SendMessagesHelper.prepareSendingPhotos(null, photoPathsArray, dialog_id, null, captions, true); + } + if (sendingText != null) { SendMessagesHelper.prepareSendingText(sendingText, dialog_id, true); } - if (photoPathsArray != null) { - SendMessagesHelper.prepareSendingPhotos(null, photoPathsArray, dialog_id, null, null, true); - } if (documentsPathsArray != null || documentsUrisArray != null) { SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, documentsMimeType, dialog_id, null, true); } @@ -1659,6 +1646,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } ConnectionsManager.getInstance().setAppPaused(true, false); AndroidUtilities.unregisterUpdates(); + if (PhotoViewer.getInstance().isVisible()) { + PhotoViewer.getInstance().onPause(); + } } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index 78bc7cb0d..6150ba85d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -143,8 +143,12 @@ public class LocationActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.locationPermissionGranted); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); - if (mapView != null) { - mapView.onDestroy(); + try { + if (mapView != null) { + mapView.onDestroy(); + } + } catch (Exception e) { + FileLog.e("tmessages", e); } if (adapter != null) { adapter.destroy(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index 9071f57d8..b4015385d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -698,14 +698,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat imagesArrLocations.clear(); imagesArrLocationsSizes.clear(); avatarsArr.clear(); - for (TLRPC.Photo photo : photos) { + for (int a = 0; a < photos.size(); a++) { + TLRPC.Photo photo = photos.get(a); if (photo == null || photo instanceof TLRPC.TL_photoEmpty || photo.sizes == null) { continue; } TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 640); if (sizeFull != null) { - if (currentFileLocation != null) { - for (TLRPC.PhotoSize size : photo.sizes) { + if (setToImage == -1 && currentFileLocation != null) { + for (int b = 0; b < photo.sizes.size(); b++) { + TLRPC.PhotoSize size = photo.sizes.get(b); if (size.location.local_id == currentFileLocation.local_id && size.location.volume_id == currentFileLocation.volume_id) { setToImage = imagesArrLocations.size(); break; @@ -1513,6 +1515,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } })); + mentionsAdapter.setAllowNewMentions(false); mentionListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override @@ -3330,6 +3333,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat redraw(0); //workaround for camera bug } + public void onPause() { + if (captionDoneItem.getVisibility() != View.GONE) { + closeCaptionEnter(true); + } + } + public boolean isVisible() { return isVisible && placeProvider != null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java index 41e6345d3..599240296 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java @@ -293,7 +293,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC popupContainer.addView(chatActivityEnterView, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, RelativeLayout.ALIGN_PARENT_BOTTOM)); chatActivityEnterView.setDelegate(new ChatActivityEnterView.ChatActivityEnterViewDelegate() { @Override - public void onMessageSend(String message) { + public void onMessageSend(CharSequence message) { if (currentMessageObject == null) { return; } @@ -311,7 +311,7 @@ public class PopupNotificationActivity extends Activity implements NotificationC } @Override - public void onMessageEditEnd() { + public void onMessageEditEnd(boolean loading) { } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 4ffd7be05..605d9e9c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -123,6 +123,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private boolean openAnimationInProgress; private boolean playProfileAnimation; + private boolean allowProfileAnimation = true; private int extraHeight; private int initialAnimationExtraHeight; private float animationProgress; @@ -152,6 +153,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private final static int set_admins = 11; private final static int edit_channel = 12; private final static int convert_to_supergroup = 13; + private final static int add_shortcut = 14; private int emptyRow; private int emptyRowChat; @@ -191,7 +193,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), ActionBar.getCurrentActionBarHeight() + (!AndroidUtilities.isTablet() ? AndroidUtilities.statusBarHeight : 0) + AndroidUtilities.dp(91)); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), ActionBar.getCurrentActionBarHeight() + (actionBar.getOccupyStatusBar() ? AndroidUtilities.statusBarHeight : 0) + AndroidUtilities.dp(91)); } @Override @@ -343,21 +345,21 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. ActionBar actionBar = new ActionBar(context) { @Override public boolean onTouchEvent(MotionEvent event) { - return super.onTouchEvent(event); //TODO + return super.onTouchEvent(event); } }; actionBar.setItemsBackgroundColor(AvatarDrawable.getButtonColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id)); actionBar.setBackButtonDrawable(new BackDrawable(false)); actionBar.setCastShadows(false); actionBar.setAddToContainer(false); - actionBar.setOccupyStatusBar(!AndroidUtilities.isTablet()); + actionBar.setOccupyStatusBar(Build.VERSION.SDK_INT >= 21 && !AndroidUtilities.isTablet()); return actionBar; } @Override public View createView(Context context) { hasOwnBackground = true; - extraHeight = 88; + extraHeight = AndroidUtilities.dp(88); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(final int id) { @@ -505,6 +507,27 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. Bundle args = new Bundle(); args.putInt("chat_id", chat_id); presentFragment(new ConvertGroupActivity(args)); + } else if (id == add_shortcut) { + try { + long did; + if (currentEncryptedChat != null) { + did = ((long) currentEncryptedChat.id) << 32; + } else if (user_id != 0) { + did = user_id; + } else if (chat_id != 0) { + did = -chat_id; + } else { + return; + } + AndroidUtilities.installShortcut(did); + /*try { + Toast.makeText(getParentActivity(), LocaleController.getString("ShortcutAdded", R.string.ShortcutAdded), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + FileLog.e("tmessages", e); + }*/ + } catch (Exception e) { + FileLog.e("tmessages", e); + } } } }); @@ -1172,6 +1195,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (extraHeight != newOffset) { extraHeight = newOffset; topView.invalidate(); + if (playProfileAnimation) { + allowProfileAnimation = extraHeight != 0; + } needLayout(); } } @@ -1425,7 +1451,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. updateRowsIds(); if (listAdapter != null) { listAdapter.notifyDataSetChanged(); - checkListViewScroll(); } } } else if (id == NotificationCenter.blockedUsersDidLoaded) { @@ -1454,7 +1479,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. updateRowsIds(); if (listAdapter != null) { listAdapter.notifyDataSetChanged(); - checkListViewScroll(); } TLRPC.Chat newChat = MessagesController.getInstance().getChat(chat_id); if (newChat != null) { @@ -1474,7 +1498,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. updateRowsIds(); if (listAdapter != null) { listAdapter.notifyDataSetChanged(); - checkListViewScroll(); } } } else if (id == NotificationCenter.userInfoDidLoaded) { @@ -1495,7 +1518,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) obj.messageOwner.action.encryptedAction; if (listAdapter != null) { listAdapter.notifyDataSetChanged(); - checkListViewScroll(); } } } @@ -1522,7 +1544,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { - if (!backward && playProfileAnimation) { + if (!backward && playProfileAnimation && allowProfileAnimation) { openAnimationInProgress = true; } NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded}); @@ -1531,7 +1553,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { - if (!backward && playProfileAnimation) { + if (!backward && playProfileAnimation && allowProfileAnimation) { openAnimationInProgress = false; } NotificationCenter.getInstance().setAnimationInProgress(false); @@ -1545,7 +1567,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. animationProgress = progress; ViewProxy.setAlpha(listView, progress); - //ViewProxy.setTranslationY(listView, -AndroidUtilities.dp(48) + AndroidUtilities.dp(48) * progress); + ViewProxy.setTranslationX(listView, AndroidUtilities.dp(48) - AndroidUtilities.dp(48) * progress); int color = AvatarDrawable.getProfileBackColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id); int r = Color.red(Theme.ACTION_BAR_COLOR); @@ -1587,7 +1609,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. @Override protected AnimatorSetProxy onCustomTransitionAnimation(final boolean isOpen, final Runnable callback) { - if (playProfileAnimation) { + if (playProfileAnimation && allowProfileAnimation) { final AnimatorSetProxy animatorSet = new AnimatorSetProxy(); animatorSet.setDuration(180); if (Build.VERSION.SDK_INT > 15) { @@ -2169,13 +2191,14 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. menu.clearItems(); animatingItem = null; + ActionBarMenuItem item = null; if (user_id != 0) { if (ContactsController.getInstance().contactsDict.get(user_id) == null) { TLRPC.User user = MessagesController.getInstance().getUser(user_id); if (user == null) { return; } - ActionBarMenuItem item = menu.addItem(10, R.drawable.ic_ab_other); + item = menu.addItem(10, R.drawable.ic_ab_other); if (user.bot) { if (!user.bot_nochats) { item.addSubItem(invite_to_group, LocaleController.getString("BotInvite", R.string.BotInvite), 0); @@ -2194,7 +2217,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } } else { - ActionBarMenuItem item = menu.addItem(10, R.drawable.ic_ab_other); + item = menu.addItem(10, R.drawable.ic_ab_other); item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact), 0); item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock), 0); item.addSubItem(edit_contact, LocaleController.getString("EditContact", R.string.EditContact), 0); @@ -2214,7 +2237,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } if (ChatObject.isChannel(chat)) { - ActionBarMenuItem item = null; if (chat.creator || chat.megagroup && chat.editor) { item = menu.addItem(10, R.drawable.ic_ab_other); item.addSubItem(edit_channel, LocaleController.getString("ChannelEdit", R.string.ChannelEdit), 0); @@ -2226,7 +2248,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. item.addSubItem(leave_group, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu), 0); } } else { - ActionBarMenuItem item = menu.addItem(10, R.drawable.ic_ab_other); + item = menu.addItem(10, R.drawable.ic_ab_other); if (chat.creator && chat_id > 0) { item.addSubItem(set_admins, LocaleController.getString("SetAdmins", R.string.SetAdmins), 0); } @@ -2239,10 +2261,14 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. item.addSubItem(leave_group, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), 0); } } else { - ActionBarMenuItem item = menu.addItem(10, R.drawable.ic_ab_other); + item = menu.addItem(10, R.drawable.ic_ab_other); item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName), 0); } } + if (item == null) { + item = menu.addItem(10, R.drawable.ic_ab_other); + } + item.addSubItem(add_shortcut, LocaleController.getString("AddShortcut", R.string.AddShortcut), 0); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index 308309019..ea53eb90d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -583,7 +583,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter } }); } - BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 2); + BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); cell.setBackgroundResource(R.drawable.list_selector); cell.setTextAndIcon(LocaleController.getString("Save", R.string.Save).toUpperCase(), 0); cell.setTextColor(Theme.AUTODOWNLOAD_SHEET_SAVE_TEXT_COLOR); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java index e2ee12828..56a53fc45 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java @@ -325,6 +325,9 @@ public class WallpapersActivity extends BaseFragment implements NotificationCent height = temp; } TLRPC.PhotoSize size = FileLoader.getClosestPhotoSizeWithSize(wallPaper.sizes, Math.min(width, height)); + if (size == null) { + return; + } String fileName = size.location.volume_id + "_" + size.location.local_id + ".jpg"; File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); if (!f.exists()) { diff --git a/TMessagesProj/src/main/res/drawable-hdpi/book_bot.png b/TMessagesProj/src/main/res/drawable-hdpi/book_bot.png new file mode 100755 index 0000000000000000000000000000000000000000..a46f3ff102f68a846468f9390946f79fb9bade0b GIT binary patch literal 3825 zcmaJ^c|4T++a61HGDMbWjIuSB8H`M}VaAfi*2GB4m>G=BjLa}7p<{^vJkRr8p6j~q`+MKlKTm>_gN?X|f(QTr5Vx~M zxo}6XokvK3`<}SLJj@;B8E72iIL(K_#0QZ8NFvRf1hS*xeMv4PJTdItGm;4azy~H{ zaSR;h7=l2f=-_uSI-!(6E*k(aF%J#I6V8$tAa9Z{nQ97NYH9<6$V5}Hn;}LI6KF;9 zBin`tk&cHuUM_-$u2Ze&*a2=?DAsh~cfM9x1n64i8hHFC&5QcCB%n0<)1?ENz zBKja)P}cv%;;u}=ehfw+LRXi`Wa=>Wb!b7px=>?d;~fncOq+|)riW1(_)u*sUG`2#U=iii~cQl#|o4{Ze$cGh#W#9S_jc6pudld zApbrW%irq#!zKPc7pvcLb-Bst?lks)HTu^U*F8Jae{IWM{A+v?mFxB(uB{(k-Gv7L zM7h;NVng39c+Sy1++@sOeH=}hjJR@LEm&&bon2kWa3uo;7V)wo{Il!~%Xk(|?7NcF zjYrq_XdKZeE0i2_?}D|9IhCaD7IxovBqX5IW8d&%;Jr;+}n4)mOboTf5_f71b@F@lU^z;?3g9vBgv%KU}39)j$} zO-o4eNU%C_tzD}PsA6C6hX)XI;-;xRqfP1^J}ho38?rGx_(`m;xqVfdm0EG9h7(w0 zqAoV!u6QW#Ny{ynjjf!9jY2iRhpk9-5u!AdqxosG(9h}xkOO);7+ITBIMI>s_xLI* zJMmJKdIxT+B7u6lI2YXQhRV*pr_V#hw_nu0u=PVA&u>_?u=4)urEpwdO>?${fKK8N zX+*;GT?FK(u|=2QSjQ?$p3-+c$}xf68ZDu$fIj)b$`!bbhAw0b{E0T~o`>XJ!?X`T z%^{`Cf#U5KoJDb2()0 zr1X@f%|;dmkZ}=-+m>2uDH65bk1gVFw(Ynzkd}N6GH!GQAjk5xNAxP3fqQ4Hy8tl| zt9$13Be1Bj83Ke!U*yqcW#sM6Z(5pq%O~mm^Mym@n`D$9{gAE4Fj8dclZ%1E@m6M2 z&gA;T(%KRGOqtV1q*m}ObCMOHWJ1Te+2Dyw0CkabD^}*@N%x}lUZuniZ~31~Ax*PR z6d6OK!Q(~MtbG#kNnFjapO2>|^>S7}#*t_Efu^^5kD>T7Gu>IpEN|mSQiJ-B96gG^ zmo}oq=ld_0b~z5EWJ}jER=VUUg4_}VZe6si7K}=LA7Dw=k2)9O$GGXUB-?sY5sY%#vt)G!bYGf6*f52h<)jJPsbGllT;cOCrc5}Bf(4?q$ zF~5L*_>JCfr;pdIJA0DMqOi;<&h_cc$jVkIVp_u{wF}CQ^zS-{3R^IpPL?v};4p=PKfcgH*0&jBCbN?B;ufj`y6sKl6U)HC| zw@+o09r|8uJ-t62snun=#(C#_FeO#KkIhKa5OSctyhGB8{Nu;B7s88$M1Jh<)}pK7 zh?{k;W=P#A|I-2E@K}b<^l7FZ+x3 zP+rHEX(O#PKhI&9&+K*6VAtwo$LN>xgM3#`XCYsG+$x#AX3iO}O#0?ikWe(CI$HU* z>e-~y8~z+wM4JsDNTy%F64h{$u$@rknHX)LScx(7$6HmAbPsIs8Z4_m<2QlKcLnaP zH_52FuXeD&Rbwrw>=gzda7^rt$_H|UMIah zt`l(=&D4xl6Y+jI@$J}4g)i>*>qo5jU(%m#mSu)y9C-QlNyduns4i{!<)=-tU8mcO z(FL5ULde=a?cp?w8+f_l)D9hf4Fq zzVxgpeOsQ4Yc|ojhuz7`hcGj(J}@NZced};{&&^Y&OWW_ z=Pf<1IgZMeg34ef69veUkofbu%7A0^5~-Vl2#cukBz^Jmo6c(3^cQ0bcec$Uo*%s1 zzy6+;J9$}d6a0N_1m}3GGV8jE1(dG~T6Z*85V-vQi*;s!R={0BkVTV;LCwr|v$B!6 z^1O5ZLACZJuymIL^|?-orn&M6M6)zDMk+mT8oRVhfUQ+r$YVMiE#2ZiDmpFVp!EDg z&(`$)Pm;pJ=g5AiRvx+RXLW!7F(>$~cUIUeM`dZ3?*LO){D*9h*W~m8bm4M^EZ;}P zJ17E8n$5n)D)~Mc0?H%tnt<}v#_RH%6gM%wyD)%(G@(M`WWgf^Q|OyLltp5QowE*s zw+vIG)_7ZFufo{@gN49fbuE(ITOme-Zg1$P1N0Cjn*45+ttHUcUV7gDL zp1mHS5xMu)7(@kC2sGQ8g1k*AcnLcbg8 zy6o*`i?@I}5aeDcbW$!!3JIkqiv|`P3)McEYoDeHP-LSH;$ja<>N)rSLAoKpV`LxB z7W)8t7gtekyy&b|l>pnc&!ZjmM@~t;ySKUOy%{^;X3;K0o)+wlef^=fep@NX{xw0E zH+4`heKjic&3Pu@vvP7(U4>fJ*Xkb?xDQsh9|vY1o7JXi%>egrvz}+&EBU&1vF@|v zZMQt^iL4DB(?~}(K)H%)V{U0tvC3oGcHx}nGY89}HkOG3T=W`DJl_9v;~vHjYZy=T zg*cUp7MJWwf7%z#;wj;b^CS1w0GlQJsee2=szB>9Rrh(anv=BofU}t!wC3h+mO3x* zY!Ity+as)c{0i@#L1TPRN&)__r6FMHK$6C}&K@_O>6qu^iZbc4e)$)^!ev5}qtaxc zt~^~VHphb=EztVas-U{csD>)z6stnc{Gpg}&-0@HnebAMn~l4dJ)vc>IZrj-gXBql z&ifV1%Z}XkD_F{Jdr*{5ntd~2>zG|}ClWi0y9VgF8ClL@Mt2!xWDluWx8LMRcs%hy zb7~imMLiL<^?bf)AnvtLwr~cdkfB6cNsoFNHB{zha}{9m(MJgxVqLmUtd z7MFSD{{rs4doT&CQ(C3<01UR7prqCgRSB?1<1lEkUh!<$I&$CgQ9EYXhZEXU^Xq{8 zd*m~@)5#M}DP$egt2#?u#`*=jt6}nEN#*TI0!^H&_78g$IB&vQ%;h6ugr_B#o$XY|g7g%9v%8K?vYYn;tdPwO z)1Nas*QU$_lzXm`05tbg zA}&u_+&3|MwRH}@|e`8IO6h%zoiQ8M}v zsCn_|$me`Nt3SP7iQY|$NFf|du;!%m%Gd^5(Yr><#P$)ZnXg

    CF|eHq2^83GftG k<#Zv=zau62dmDfcutgqtD>5aKxAPNfhju_!S$bXiAO1wB_W%F@ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/book_channel.png b/TMessagesProj/src/main/res/drawable-hdpi/book_channel.png new file mode 100755 index 0000000000000000000000000000000000000000..b4589d99ce71b912b32f3275cd27906342cb0d31 GIT binary patch literal 3110 zcmaJ@c|4SB8y+TGmPnRS7^9?PnZ+1pELnyqgY1%J%nUOzi!mD0p>RY<=oFa>MW$>a zWveJlc4Hf5$tg-?IVqK2rEheq^ZoJly}$Q;pXGO5*L~m5ef{yIxH|8X-K@4500783 zI$+&|W6;JYDK7j!zrZ#X4j`7D56hh%!eZl@B!D%M9!vr|Qt_cAHxi!6iRdJm0{}n> z#lwf?gLA?V=u|`e2F5U&$`G;v0Q0@k3_Kx>!~zGCLMb#0$Z}&71WX}XK)j7{FdV~{ zM5Z_#W0KsDIeQR}g%Qw1$X-jZc{D~SKqay8;Am<%EfN!L0r{qj5zaTZp%Cyl2rJA2 z@=sDeI9ISOok;>48yXo9U~o7Xg))RAjZrALJ{SRmBcL$hi!y*CF~%qi!UX*Nfe529 zi6Iy_to`>`!j%Pt%wjPxP$-+tHe?$a(wU)9I2w)K&_Eyzgb0I34vmG6HlRgn|4_h^ zA_+_igGHg!z#EGAV0skG0wT=xpAx8ypR%;b@5dxOFlaQM0figFHcI*d#NqxwluG@H zj%2x!{>}G)3P*Zy7$m40DUu$=BnTT9qP-D{fw5(h@GLshgH8|saf+^FI*T4jrZd2{ z?kKRXH-$!|vm^I>!{cxmM_MEcPa}{Vu@(@af+2-M#Gp}FBWn|T7#xj7!{K%)Q#(_b zJq&Gc1cRYbFqHKVE|yM+qLOH=A6(+UT=*}!8&;q)gpsi%CgmuJXwRfm!QV>8P=1+< zo!u|>zH^Dc%*D?Bmt3eY8R$l1|EtkIwuJ84nEte_aPialB%09eOrfpOU7&aXK$7c- zwf2Y}Tk!wne$G?%xbMtw;Oe+$QBmeWg@Niz*F(5Ei!sioS++41G-*<4P0MBLlU zZ_TIJOS2Xcu}39dZ3A_vwKIuQgGn_A9q`bT$B$bko2Qw_e->L)WcqA5d1mONbcd}#yB0n$UHI;>iqZ6e z89dQN9~5N$G-Dz?MmP1U1Y+86=2ORV)~yg@utL4!8lrkXZ_!!0T7&~YtC!cp)fRg+ z1mLC7s*lYpck*9Exgf_JZp#)N`vUd44uQ#~`sBIXLa6q|g?sVV;}1);+ZpJ!NR&Ax z!Gfeq53o=2(}#_&%k_NR^3v;;ugGP15(eBktwp4{}X4KQj?_w$_qATA7os zwSP6ZS-r!(!w-D4QecA_1bk}Be{7(-rZM_p>qMc(ipk4x`xX}!3tQq;+F()N_mC9L|F@DC|> zIu$aXif6t`_U(qs-(|CW=F&=r<6?sr(3i;sgBA4vao3jW17tNsp8KG#gkNT-U7kc_ z-y#-Rvvha!6U`-+N|#fb3J#66E#(R1Q(qGKu;6fMq`y`uz@W`oaF-rmn{{@8j>quPBr4AAR4L3?TPHUCylvn8S;M}Gv~OIKw;FR*eWCg5T`HNVM1 zfA#p9`~Bl_jXgS_^}Wz4X1_lhZYqbt=Y|Q&%c3{!N0B2L-Nw?Bv9_j}YRwM>Q5K=M z`v`!`_J^sgXE##kw%--Ec>E|oHv**9UBnRwxk~-E{N~Dh5J19m7??tgyQOQXv43;q z7gROx;@Ii;eVH%AIsu*;G6Q~31W%?l0CuS}Mc5{BRaN5>kX6*1CxUMNUZkjKweDLF z>xnkrZ}=Ygo}F^1-R6_B|8`!LxxZ!;W2d_lSa$dIXOV-8x+h0OFV$Y=kCns)6SG7n z3egHv`+$GS)1+=B_+ zb$?$`W6y4_UFd@j_vDK>isce~9Y@S5UROh9Bd((>Gkd4H`^Vvv0Yk?s`tGD-fwP&~ zdHxEItlLg<(%PP*f^(f*w~wX7tSl=Vo3zd=>^65-NDRE)<9LL*b!Jvy_R}(@>Nt*6 zWu^{%W8*k4CQL=EP z*0#3#^fJFdu{-9adDC;3h}`~h^)KfI_0=!l_+Bc6292cJ)PWiw-q_PnmMu3)){K?8 zFo5vpJUK8q=^fC~<0G?u)YIkx!e9M;ewI<3P2JZ5QlCT2d^HoN_(mo|B(s2ta)|2R9D4o)=ZzS{3Ho6%mT;9TuNgS5jhZ z5qIJmnri8)z|)e>rWZVWAj?&07^5c-R7ow2KC+FlJ~O_lqUGSIKJvD{jNo8me~G@q zUu(MD=+f2E-Ywgz!;LOpg;^{V%GD0klN%HZHT~ki<%EEqeHZKtvPJUjbAyBWq0WH>u{LYmAxR zctGA_o?7YDKC6a1(*-H5dpo*ViR_0*76)G0(j@lmUqAz;Pnk7-iBDV(=2VbKuCfD{4H*$z*}`oy>?i|&d7mvXv(zpap8Huq3wUk9%4Uj zwa;NgyhWQB+~E_v>AI#rSkkCTQOmMqo7bIyKt5CbA$Ka0X1XA|VZ)l-M&_OolFMwvFq>@{iAA||aNIAQ$URcXWh5es z2wkKwQAbH4BxMzzInU`l&mT|E@AdorzL(eg{r-GD-_QG>Z<2%EF$pmxF#rG{fwROq z3P$g(PjtKB|7TvfzF<&fnY*&^G+$OYfk6hCkZ3++5ROXlBRi4_q==9{G6nz;22-3| zS+2G=Xd;aYC2V1!Y${#A1^_TdY&wA$NM?b2$bOVyL-69Q4lsy9G6cIIZQ-_bGqOL$ zGLk{YN7^|NBLj&BB(Tv@5QdEw2vEr^0*Flw3TC3&hTy++(SrHbHVh2<3&ILC1phlJ zS6c^=8I3^(A)z`DA{>DLp-@nSE)s=8Xo0lh2yGZ#@S-3HT{IGf*46|4{eT707$je` zBi7>YSb~)y*q_CsqhYY{@Nj6j4wT05gCPtI47N11wIKoogc%XcBCsLBOtl{hSTd8y zpwL+qS}EoMWf_76nf|*3D*YeXVCLV)Bsee_n?Q#lpzy7degJK4|38#U{Rhot zIgzNpsTENW_re;65SQ;^uN)BfI;FA8sMf{YzWd$l-5E)BmP{PP03kHn}`m1C#<)^ur zng3MpZ!YPlxtLk}lnWCi1KVose>M8YmcTt*(|>F$So~vra5++O~q*dT6w94U~JIZX#;#G0(hf=NPM{C;pe$#?w1SM_WLTg`$YML-V=JIhJ zJ!ON=Ngd*MoF3vojM-q;5W^@bT4F{kc`rc?m&YCi$+2|K{Mk@Fps%xEe^A+Pd*1VH z-6w)bDd*>&^hf3KgJ*Cv$16(jFMieT-|fghox$I};ymM(7vlpxS^PLv`wIs;qcJ1b zH4DXc+=AR)L#_aIT5`A1wf7OC3;C7U(zkNc%>#=%F%NB=ssT*{SMt3GlYx&T-fmyn zvC=z`ckRmY9t`uOO`;zpC`vZ;$l5n&zia+-rerABe2=CbCk#=s z5f>4wxvSTFPc&%UNd}04_B`>^ZAm||eSFE_aYgTSEsnd5H%v69ZTykuc$h`wh$Cg%e9&-Yl|Rr2Z71h!>HBt;l4?49V18cas*QFvME<2Ge?T52!&+VUjh*Con~ zK}Eu6Xxl(;-sLn%Cw}0vD=67p#QXdvlOU%7^d99CvhBH%2mMB0J5`CWIeh%LJ{OUo zk2-|X3{kp6a<~NdI-7kh;{8+m^!Dd)YOS3H4AMcFG;%9^BdDl>=aF7O~-{WjFG)rtP=y(pB7N9L*nj zKaEK}#Ed?eZu!wKm{Rlbl>hk(Khyecl``DPHzEZY;LXn{k%OPeakVsycMD?^l=a~T z)$!z>)xp$LhnjAdVD+;h0H2*w{cCUd4x*<>1zbI2B@U3RxiDixG4?YH8QdQEq%6d@XBzW*lK8# zwg_i%UHYX;M&5LsG)Kl;q?)VwxlaAI1@YHC*9SfGft@qmb{SEm%JzHqAC%S~q!bQ4 z!^8w4p6c)}uJ2duEfeRg4Lve$u37X^m?-GIZ|Fg76%I06H;hk!@72#!i7f-#hul77 zkW25Cl8~PtJ|roX_Mi&#sy5u~aA>HdRKJjLB_+lnnt$J6-)iWt+>uGw#o})X36+z0 zb_Q0eg~qO%Eu) zG@fZSYf63b=*es6uE$i!QB5_VGzh!X($I}JT9!Q$dZD*@bkr_-?tSIhA`$~tGj3K+ zrSp$(gvUK%z`W$_rr2RZd4n@Tv`(ET-Zj%3K=QyTjEyF1oU2ZJp}kv-8*bp?7HT`aug`bu>Bx*8KIkDLgb}R@64cB45HfV#R}Xf6i-`AU~Yyl_jA?k5AF>cgl?R68ZOk`dEPbU%a^b$)5O}+ zO;5dsyH)wpB0X{aY^dDs@tp4FET^9C@I{vwYj-X&>|>--ZX?SHAMGAF(a-v_RTSqJ zE7m=zz9+tQygTK9Uz!=jwGO3ZQokSZvHpz^nUZkv)Sgf6XtQVja^vx+i;02K-ZpGb zPx8Jzr+8)SwXkJKYZNk#nduRzj*;`9_V1LJ$)E^Hiz^`=pJ0qa`LY%LLN=|>gKa7| zB+?z!@@BXtgBF+F^b_7W+%;D_U~BWxjgg-;t2r^{p(6Qh-GXa=me<4k9)Ok?`aWWb z3w-|v$=P@t@=H@ikLzwNpSHo62l6j}&uTR}F{N#k-*^oW5L}nhNv`YP&fDSMaY>`I zB%N@d@am61tL!>*-QcD?szdF_*NA#m@weeSBi(>O3ku>{A&(~d4*)VgZx#g_AD-^2 zE-KEv*+VD#3GIm5B=@&Kjd%)cHHEV8-&{sL1sOY%J?UvYveu`0*6in!A>R#qu1@l| zm5?k)Jp1c?@5_uR(ckt}+uX-(NEHWQtw%VSxus_{-Y`$jB|jt@z4D$qYB(Y$x@U8f z;9#J!DKZm2A5p)5&5=u8s1|!Ir0ROgncgdAM+J4K-fQ5t+w?Y!^*kIAe!}cl$Y9e; z`_FdrLXdh>`MoZCZ!GvKSun2O_{evxb(lliqmo}m5Uf|z;?GdLaA(<4(7=m*Hw)Dr zIxo?!sk7a?RunsS3qA3eJ;sF~g;7VmX3g&1zAxcsC0Z=W3xkN&;^$-+S7TLEz~Wew z7H;$;muk{!u@Bm_c5<6#FM`g)4{$>!e_2Hk4r{vu0I(YVe#Oz-*L+^y`JPrMlWoQM zRF-@Mp2-~yPm@RS8=k@DBm$-(O7=WM!rQ)0`ITLfw}bZU34=k^=+=akeKN!i*y>L2 zPszI{<(E~g3J{}#u0>DZKleg7CMqU;TH+pWJO1ILiuTd*uaZ{nS9ALz08X{ovQ;ea$}=itwPV_pfh1sV z?<;WNM0u2B$!<9Mv)+*S_6KARLp<%*`6EVJCvq2kRKP1W!{;_rr(0sKJaw;I-buGs z4rs^#j2Sz=Mclci5!&nlllvt!WK3Cm?a7y4uZd4ZnVd7P$e$i)&U>!jWY!dP&tk)# aBLrCc0*)`pUMbqTe&NjRur;RMvHt=EtUIXy literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/book_logo.png b/TMessagesProj/src/main/res/drawable-hdpi/book_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..634d8a342ee48b4a980e3ecd4d2db7ec18bc9fdb GIT binary patch literal 1474 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`W@2t;Waw&OXzAi;Xy|I@XyR(*=e*Lod8?_ZUZaL~|h`%lTAlP!?NznPP^UDs->fIpC(_r^Nb*pScdIATt%uMT&?qt&$ z;)nbesnpyk-}lh~^f*<)xd-dk##p^Coix+%5HzhO2GPTsRU>#45oJHrfm@6x{o^sB8kattH z=;f{V-=?}8^V=a}lk$+y+OThL_``(`Tc<~zx|_mbxLNnD%;{&B&K>H#cFk}9bUE|5 zWvh~$cj^|;cHgwl>Ts2iO`DtZS-V{eR(}8W!^HjJ*)3~+&-&VQ;o*9julw#UJHJwR zsX_Oq^+#fK{+zw_OK;w?1FtQVQj^2NfeJGx?hH@>D6%hWjBU1)JOYTBGr zDKC9}H219N)>1W&a77TgZoZtHzj{{JTvR5n%&!#cYZecZ1}(@ zQMjw{ykYjdzdky?*}G32;al9I_L=>w<_`ULyGpM0r)`^frl%<1wboAGY0o)^j+Kin zI`3azzUdwBg{k^Ko)zz5i~6xlEa0k`h1eqLP2w&0S6ctMVxbx$kzOcZ;%xHj%d?rQ uLjpurN(Ct1+CNL~vL=Vxo2K(D5)73g? literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/book_user.png b/TMessagesProj/src/main/res/drawable-hdpi/book_user.png new file mode 100755 index 0000000000000000000000000000000000000000..c39c071232a7708a3e1d55b34280c375c7b00093 GIT binary patch literal 3161 zcmaJ@c{r4N8-B-@C0SB-#*kw-yUf^zktHKp;zTuL%rG>InPG6g%DxvOS<0F%6|$64 z6r~)JN*~!$qOumJ5cx)@I^Q2(-+R69`&+L2zMtp$J@L`C@TS2?`h{6Udx>&10rQn@@5n*!kcn7$OS1D)hgaix&_!h;`D%m6?TLUZ@x zdJ&v3WG3B+w23haqqF#I05G!*W0A-~6fW48;!k6kL*{R`LclaXbBHI3041<&C;>G4 z2sXtn!r7f15kxligIHRC&B8Ez0Xl_C0*BF$GB}tpbI3Pc41d14Z43c_gK&e)A%7?3 zMQ{PzFxeC^$_QadhQeTAG};J;M4{0z127y4gBwHnH`)+}#Gud^xC!|C1>r|y`%y8j z_&wia@mJ=M04|q>F*fG$ct$*g5tHq23^O$~-PC}?4fzN|PB?>03NvJIG=C`IDI7AJ z#^TbL4DhBR$(I?zHHYvs{ksG@>!&P(^Zl6k2WA{bVj07XpqnNA01^oQA4;eHM02>V zlz;O5pTZpXa2CbbmBL|$u*v+!Q8hP1u`o7l3W>{PyEB=q!F@JcUgQrTFb(GwI-OC1Ys6&IR{N zz3*JVU*}@|ORh0L8RN~y{#T=aZ1LT*IsIu{{^Fa!n-QxJY*KPS%P3K{ZCP^ry2M&#z+>Z=@j-W~ z_QQRZNztUF=oxyk@WGq&v)mWX-o5j;uwL=b{^g6K`HL?;BU*)?hJPOUMkCj z`sDOdQ|-xjbWW&K3n*%&uXopy2K9;`2g9y_guCIB7|K#u$|$z7W8hD-woCa=0@rh- z;3f$5y2o$HNgpH^1t)pk#YuWr5RT@e2Q}&O@hJ!g%Q2}RyI9kge4Lu{UYaM4wy0{E zbq^o$wNo2pFl8FHXGsv3FJ zIdjW3SenxQ$pGOnSHg6~lL?ywcF?fAKB&Ndj->r`!WOd_G>JGMQ5&_A1ein%5#u+S zuO{shC>mS|h~_oDes_+P39lEvp1D%B_$Ez!U;;tWbQ8*{cyZIc^Eoc#(oy%L4FGf% z)W3)oHOFA2=Mt2z0N4kk7OK^CsVfEh1&YXn69Y)!76s=Dl|YCXm-EfuUR2QEgGTnF>|qJtsrR&HB+OoL4f|AtE4AVQJ~pe)3*y2sga4qI2)M zMWMxxkej;ZhPiNrEA1cyT~@B;uzmcJQpfV$9l&%kwrpawJ-(IOSfG?Q)V{jjP&J4= z5gLWmXoBb1D! z4>ixwPX?2#Wt7$15MeuMqM)Oz)|Ji*8yh!bho74??zatD0tMNZ-mW^7ELtP{!ptQy z0rG*I6LK^si=FR4zix)r9=A|QDDT~tr!}|oX6%#}f0@|h!hU;HJg`c*8A}^~e z=(Yv2iKqiTM&mF8E6^2j{~TH4ned7`+gBf!ia9|H}$#98RmsO-FRTDsXH=-yaB z!oekVUnn&%oaSG)H!zyGNRhUBmR%g(qN`ng-te-zm!5NE)&#z%kcmvEI=Yr1L6a-_ z;QczfddLPsYD!A!>F_vNJ8N5M;*|!UUD`Kj4i8n@pLkzoHK7G$6LY1(p1zMB`x)e5 zHQ#g7;}r>cxCbGbFKaVRVnTM*<1nAsu`kN3cXN$Xi&j?s~r%mspxx*hLzEoYSDQt1tYI*E7jv%xq zKhMq>Qmv7+%gmHTR(QjELGY(;jT*XNjpRI}Uq)Y02?_n9vg}q0=BC-f*p5ghw86zk z0-WrhCA==QQ(GTF$k&|kqXcNoMBjHh3zKTQv%Ql{=L$Ka8o7ZP5>$bDveWaAoO;xL z-&7B2VDITfud8eAkwL{x#b;jl%GJd5U&|G4uKOrY+G%>P)e^w&Ht?2@#}?lx#{~`T z0&5z^B~uAc1LNZ&d(JMOpGdeY4Ztj?^n;v|)>B0encrIKjSKAh^I^%odkEb-d5tle z$32A>s^4)KX=hKJzbNg$mJtv*TU2_kb55;hN6i-kr>at0G)Q2;(btiyQgS-2z~HyB zm*7Frg7HXWNrw{;y)|RLP%M*er-H7oSotzc8B!RtytF<&v#aNIpzK$7bgKp?qE(T3 z@iJ2}IuB~qL-qghg z9xsyVe6t1P5URM2R>pR9zZYZ-0O~rxquv_iQb?%9?~j&SJoWhBA5b`*Khb8;VhfVT zr%qOk5LTNa9`+BEXg_#75tv2S%>v${#-LZEYHI+I&qr$C5!`0#PF0)QiYzBw=|{P} zIfL;QhiY1ORu+wNE{xeqe+otc+Z0#(XibtJG)_%vR*HPNe?02eso9eIUy1jnJOqKM z$G6``Rw12LtEE20xK#Hqq%%Thb~9ScvOl!Tv>l1F2tBF@Bi0|&n2I1Bo`~-cy6$q; zG*nW;-`=ugX#4wwaglDYhKl#t!~j~0SlZbRJFX?LysnUT+SYSzP)?0fTDwlmh)*4V zmFMqp#FfL+Tp(GLAH`F}Ue0F)*A3Q^7pD~tpjS+vZ0S@+CV3;#7}WUc*$tJ&ln-Vq zglBsAw!tUIG6$ANF7|A&Tg33n=+u13T-n3s&Z69<>3>VvW+uu64_&9rnRK1c!QZ$0 zeI6_rop)0FTn%L1vHjX_VNa(#Myf#$9R9rJGu1) z;Z1n6UJR|)_p{P-oJ?x$pAv^P)&p5pI-cP^MIUDnU*g7Zl%CwF+OJfRXz5gE3Ef%0 z!pxn|R6|l|I!6qI6vWO!PQ_db&SqD|`L@0F8XKrksINWgB>|Q7;vFufo~_QgmL7a3 zN}-jEotwWf7JbsY%~quk>=0>1>>PHzw{oG7o&Z)=sf*Wt7OazgqOi*DWKaGXZy^&a ncZ4x)U*>J=exthd4KXM%sGD<6NNFNw^Jm||)){}rni%~b5e8A> literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/instant_camera.png b/TMessagesProj/src/main/res/drawable-hdpi/instant_camera.png new file mode 100755 index 0000000000000000000000000000000000000000..44f8c061f513d7f73fed77c9afc14c0e1d9d5c40 GIT binary patch literal 2450 zcmaJ@cT^Mk8jVN^QVgPiJ_sRz0t+dW2qB>+6s3fsbRlI30a8c?5_DORhlL^{hzD6+ zz_u1_ARw-Yz@ArLY&Sca4JT=yB zSqFo`G`zhifzW6JJy^Ih^!BTfwL!yXkQxdG@nbMi4!Q|ecw3?!foQ!Q_4PtS z(S)p6Vj#uiYbpy`CQbOl!=`0d!c++ z?<<$}T`%s`?{cw_8SF}7|EtieTTu6`Ouw})wD{Ke01xVRA=K8Ck@TG~n6k1r#Wh$m z+#ep{W4uf6wYpV4zV(;;IEuPH ze&%^ z#3v*y9iFh);7khaF+T=awhOcNYhJm0`5x}1&%=%I3FYp_QRRZ5%?IZry~hy03)|Mk znBLf8Z|UUZ)Ei&(Q%knVA4_Hy78aR#)Q7==o9~h;*A8@ERf<7`CMiwP+9-!}*7YmA z$}nD7Sa|AS$PtSdc3SHQ>{nTR#ADL@9(Mfs;;)yX~N`8 zXJ@Csv;<5vC_Yb>9e{08aKWJ5{@v4CM^Qdf?vq6c+K;WQth5zw=#$B0WNSjPmMCW z-gPM$+m@MkvCHpMQr_0-P-J3^3p*t4gj(T+e%7JZ>iFs*c?$8@NTk7*E%D{9o*T{p zMMYte_VN(&)O7ps)2B8^j~*R0x3s)_E!ct|S2G-uC7l}|9|t8Vy2GURwHEKsaipJwdl>ao@V;&+_^K0T|Lsajo3%TJPW~T&KP4* z=H})}W6z!=k1t1?nwaR}%ST2=wkwT&Qc*rGKSr!h@NkhL{g>uuXGt47X!?~$khw}N zi;S}B>gtA8PZpEO{HUd+#ossVY;9c?%V40_UwvmF5{ViZ@8*^A8ZKSps5?4-=u#Qc zEG#WuG#Z(V(X(M^MY zzUQ85f=nMli;C~wxiftD2RM8p{o~V)uEZDf5zc{mQ{pmJRn?w}&tx+B3Yzbk zwj)?Nh5t#({qks4RTXaJ7A#Mxa3rg4LSJ8>WPn6EjkgyL4-X3txFtOvjn(NL94yK8 zRoqduG@_|c+?Pg z*5>ABnToPN27MphU zDqh}i@#auK=$oN#UQQ?DW*%&p%oOpczP=u7XlST%t9vq`(z9S$UBTrh3xA@ltPI{W zW7ixL_~Z`-E1f#iw2BMbHS-t;y>%ByJJsjQanV{`heJ`iI;E3$DV0~H7t>FRJMF> zX@BJys^3V>Nob`RHhXUOm1MiY0vAlgzuvLudy@Csn2??*(OHv{| zx7-ha3Oc9KPVZY@UfxGx%e^K#=-OnPe)oHlwLY&^QO>usRD4tr17#WcrnX@-A6m95 zpB@-D7KvZJ_Q|-Kvz{i&eUe|7IaO6%wr>pHBxcacV-(1bHl087+CXK<}`Vr zb4WuQg_5Mav8v4 zq}24xJX@vryZ0+8WTx0Eg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i={n4aiL` zNmQuF&B-gas<2f8n`;GRgM{^!6u?SKvTcwn`GuBNuFf>#!Gt)CP zF*P$Y)KM@pFf`IP03tJ8LlY}gGbUo-h6WQb!1OB;3-k^33_xCjDfIQluQWFouDZA+C>7yetOgf{R2HP_ z2c;J0mlh=hBQ8xDWL1Hcb5UwyNq$jCetr%t6azByOY(~|@(UE4gUu8)!ZY(y^2>`g z!Rmc|tvvIJOA_;vQ$1a5m4GJbWoD*WnVUNrJ6oDt8X5z!p{tpNiG`D+g}J4fnUSfR zv6Bl-uSMv>2~2MaLa!rEy`ZF!TL84#CABECEH%ZgC_h&L>}9J=+-@<)X&zK> z3U0Sp;MA)Rbc{YIVv!;mCIn19ASOK70y*%6pPC0u?M1+39b0(M8JH!Wd%8G=RNQ(q zc_Y^$2cEX~M-F~3xX|}jX~m87d`%~LM}ctJH9Kw>{hK&v+Kk;|>CEy|y!Ux$M7^G4 z+!)c!cO|MwYC>58=ZkBS8Yu@=lma6*F#HTSK8HK?$7ZM38cUfkPcOb1IN59I#yW$+FqVV(6lMm4S1_j9p5EQjwBW zBuks!DZV<6_@af1U#O5RaYm;)-ydJ!`+MK_S$@}b-S_?6*B{R@ii`awIW;)|05*{v z2=3z1YWBILSJL9NqCM<9r0~0VgVm1JnTM9UV)Nnc<5=0MSu<+3Ljd!3B1`QAOHYFp-99w!Q z!y%4K_lR@xq{f9)u{5Zq1;ku{6ALiu{6L6+8NuS=1bFCIU7UEnz72;$zC!rncRws0btyf=0uTCZ=dK(hy>dKpMjl;umd%G{KpoamITg-yWzq8kZJ~ zb0^q+izQy+p`m;}2M348#Kgd2P%t((1dhaFvFjSf#zta<5igd-4-^=&c-r3;2y`Bm z%i!=CY!+l)F))Z7#m7U%nf_e@lk-EC#rt+l;sb*V0y%Ib46$C)cOaSk|DjCg4>XVO zPX8z0|0&G#jOEbb?sOhIic1wYE?9d#6bEO^r3doaTu(MT;`=F5LfL#aFORxAIPH8Nd!DptN>#$XgF)EH3DUa!62~&ED}jXW9(1}0(LLT zm>_Nf#?IzDm%ye*G3hM+cP{N8F7~I~bt^DA;>ZL#mk~{;*>TxS$k&o_jGyO%{;A$K zF74;J?ENVhE=~r%-q`tStlm(?0`pwM)sJS_*b{Iu?I@K!M8Y?R~@ymxC&O=q`Z zH~7ix<3*NPDB!pHtI11NxPugi?4pmwv@|d1SikmtM^r#xs~E(jFr)-oC{t%fy?mad zX6L}R2;$lzA#dhbf0V#P{G#lepuAR`% zi9nyuA1>T)Gw5GzgTAQ$M!q&y69KVV@(Cil#Y*VU4~&5)^m`r_el%00=SzD{m9BMM zD+bhQIF+Ioq%AScYn(H^D=63cM#~5KJ%iFAd>@4HC2jYqXGbU}uevm(PobYo+<*T^ zu-brmiQ!GBGWO;?Lz2`}aJ$n$OwOzR(itz2ykV_-+q$dW?%6Mfd;SU)Mh@@lYZDW&QO*nEz-} z$5iXQf}0e^S!!G*s=nnQs3khSv7x%FN6HGa+y#6l4qs{Pk&ch4^6pw#ST1wYRroNv z>XMYBHa?R4N_d~6xB2XYj-A;t2Wx8#iM+PYcw%IEf@^(T4Yo10_PVvhW#-g^56x}$igpt^myaR`Z5ur+DXRV|ieTSewTEY z?5glkQ)TShN&lQWoLZ3^g=W7&u>td>eIsyMn|3>E9~u2k#U<_q*+v)Z?x;u~`r_Hcfi465jO>F7SS-<76WJ3-c$t^42!RHYIrE_h?swnsv7!vuCzk@bIdJ>ZCBn7~51|m^_gcY|6^a zdvw}rN$aM=K=(_NmhgbrjLDKJ(+V2ki_-k4>lAhLUBu)*Z7E3KzPTM)pTnx$IQgQ6 zS6Cd|G@CUCo3R1w9ZC}AzuP5Y^xCt~F+^)2jC4amIkWa!aet20yUN|U_V{jzv^Y6# z#&~7;xKV}cwTk0K&YT^}inT~}@1eId1?tG<15hi3C7g~zqoEiK0=>lwgF%}^El_9+Bue&T;OH$lD-6zJEA*cSCacD0 zgyN`#9sksljcj1yLLm=_M8?F#AYv>LTz(i5jm2VDG%PIOG6Y-@#}U$E;T(bCR|Ntf zp!1nLA(P92t|-z%xY0rzn9S+lOJMW9$#Mk$Y?Exmkg+r#5{*EuEa@xI!{h%$+3as< zfshLP>-T>O3q0d^0Fnv_xY2yN?BGHTS4#14j(mV7>p{O~g15QI7Uj zXlpFN!U2ogO8CkpaOu%(fFu0MW&F#v_%3%P3T&RNG6CQ-_X3O^d@dXMWyv_^cfH8W zeAmn0T*mjkpuWpR%FG~F4)(te`sXm@hEa9%rn3rhjGa?U-IR;pS`hy5sAgTGxLlB}e`8M*7$C+nf6n zxbAP00h9)55E!~vM@%RD8E)O^QmLw$pQ&c;qnLz6HIXE`TvnfNlgSRiLQ z5jMGQQa|k%P<)=gp)Emg%WSd@AqL0DgIL$b)=3_I(Q{q>K zNT@WJJL;5_-jwamuoPkMYU|sLgiK%cX;WzY%L*fDPBa~bPiFA>T7$0q-Oo$yIyh(SDze{;`h8` z-A{5e<(nOv71~wumIfbA%W+}*MfX}B?XC$oEs~_282(`=NOkwwPLuePqCz#x$;g3$t-;Z31q|Ik7<0NMqRuWF!1|g4J4Q}27DYgIo?B7&gNg2@ zS9(fB+Fk?DXFClHhF&o^RQYpo5FLJ{VT&E{TDOvsc})_5a#z0Rwc2)_faDHx;>N?J z$m=C$EP0a~Dx(CWh3LAJ2O(qFGoi*4aaJl-hBkP&;b(rfS>#1%PG<$>0AH$Fp#3ss zF)w1|oUmbtEXK8xW5ML{>yHBy=PfJRq}|3*%N0e!Jk>u3ipBw~HyTeta?Rfg?v`V9(l zQe0=Zg}NLmY{*23CpzFx=py@JP7FoUn#r&SdnOb=Z$E1@M={kktH}&^+zp{ z?3DSN{jU??*EB>c_&_I#+Tw=a;oolJh~9Jf)7&=G_S#^=!L_*?nef8$)5O-~!z8he zkr9#lzQ2Qa8%=GYA;4q?vETvi)Q^QD!;8jvjlwpbTI$k--p~*wG)0gpM?12sewI&u z#Xzi^|HGdVXZ51VK@lmIPrv5dl9$N#b{p8oh%>@6EhZ?Qr&ZH`QZDTse^*N2XHnh! zre@ZpGba&xhVAwB5SkkSa4p&8RV+MwLKX8cj9(N%Kjb=@iqN_SKORIW*goYCxp(7J zk8$fxu=2q!)v7vG2|RZIH`M4UYBmML<>q^!AAgLGihT*+V|B zRUpjtMO+c0_R6(GkkP&btBbwh-pKi7aBu!=p}+KnsvGna_2a_cdZqjFM%EeW37)Pq z9z*Wsdi0me8M9^=u0vEzU0bsCHSd&)YNVDsTd12$@;;QqsJvX3S$%g>uUYQd!7_^XHR%rzN|Fco{a5hbYPc4VQnl`< zw3wa^R)79nJZ2ww<~N%=NM-fNMcG7C6Jmebv zq+8MklAk)udB)oe2?6Oy$5|!- zHPa2bQ(SOv^>bd;plKs_!}(!NVPVoH^K>g>x&8afkx64{sT5r7W|pVZ{{HE5lr{qY zP9w+$cPtBg-)!yJ4HsS=jHTY^{g7Q9aaG~b#c>7ZAYS<~r7V&>7^1qv?WelU<45-X zV(eiGG>oKIuHqOAYD`^S-ksP}D@xWYD{ebp^+b0mc5QT8m-!mWCk|F-|h4yoMoXSiVJ_bvwE_bL*va}=SP&4<)49fGR9H+8?pZW*IbXG-7Cybn3G#522?|Y95 zN+u-)!TZN&44nmb4hvxoMOE2t^PstX1)^NX2NAjk*G1V~sjklNHy(HCceodE$OB52 zh~-I^r|mAwD+w?C;Q&^$UEP=;<30%aB>y2`lL}d|oH%&LBH(#1ex@4+`D`prfT=4g zy<0uMZYYUW>B~FNjb+nHVuD1g7OM>1zB_7VR9o?mwW=?7dTN*ee1-vF19}_lTE$kjk_0HewDmQ+ts41^Utw5rt z5eItm!fo#>mUwF0f!%d>^&5lEoHMbA6ptdjFR0$Htc`%`ztNkI?&}Zzz4~#ILz05x z=lLsYw+CmOf|tVc-Y?>#3oi7P8PawRl!L31R@FVzx6=zOTqcX;K-ve3e{_n@n_an` Nx)4c(OAf&){{c6KrK2xB)`25H)~G^vO~m4G2PQK|$+6alr-kC01#UW5Drz#J`%iIc~%R#AAc2+K8Luqu(n$OZuaK$V2cOM~R76i6TxQ_)XaFQHLF zJ{28HWcjcpK~SnNG*b#iXGX;EGShe<9~~Hg@>fxe0wPGxMX5yTVi`q6MSswx80V&K z92)fjB2S~D|4b^5#YP3eQV2!F61;gncsz5yOmVG=X@w z@Uwa!x%|)PLjNomXG{iXYV6+{J-cOek7@eJw#LOL<3nPj+oeWZXY|AE0ART>j7g7C z4c_Lk*qk-?0Z7-pnJG?A$tCu8XvZ`{wPBE2Z9_H$H$^Ls8%h%!4T|B3Rrik!<0;UW zO*XC4ss?4_@Wk@+LHqD}M7Z_5JU8{PU*(UxjYDQ0#maqUS}lC%bQM>!J!Ivu*@~vLkgZWwNXJ{Bx;VC>6;fnCrN9)N0_ZhU5 znO)lanV>GS-XZ@TGr%h?1NSgMY71UJ9pYft=h}k_q?2gEx?S2r?e+w(GJrC+WXyqY zS27ndc0vyqBlnm+%W-gFyu032pU`dIPra&nRuZn>=l_qTbDyizU+oh`uL3qqVe~G= zo7gT7(}%2v;$slI^iPTx)83xfX`Bk&0f%Rpp5IGzqSMdSvk^ad4b$ca-R1sE)bD`S zE!Hcmq;yTT*VnoseNW}oM&a38u4MprtiN(;)|G&}?&?C!vK`wkBz2K3Sr(O(FBHAb z`V-UB9GkvBGPO$@^96^BLUEP0trsOpr@L2@No^~LbK54J;<`L0lucVZ*6-)6BD_(7 z9Gg1y7Wagk*W1f4c_b^m557wf`RLLPBWBjQH_k~8I zDM)&3$3;P}?G|Tal5yUQ=T;zxm$IUe39n zmK$+!4(D#bb68Gzb&sg_Sfulq_Hrq_`G<;OV-~l~BF;L5hN(k}cK>uUW?65pj&zRx zZc)tX@2!w*L33)bdd(soZn}mfs9tMfRSX2&lC8Y3H2&a&MwGh9VsfWHI52d^a-r5u zmss^v^w}L9Z_6c@hvzw-tBJBa|83ReqZ~%foXvY%@zkgG#|4Y~u)3ui5ca*1_N`-u z;|Hs1zrN&C-Q4Oh-*0~wX6K8*^=>wu1&Nd^*4_7BYcHnN5Ziibi&aSzVegXAGiXaj z9w)r7*MNpwPATJlEiHRpd7=YJDUT2$#E|vm3Tf>Cao}`Qa+7{@b*5a~dcm@Aev2;E z&-3n#{hLFM>iwIl!~5iwtJ7pJ8Z|ZK1SfTm2w_YiVuh_LR0R9?iV>`h?riXbBg=!71OiDI&@MGCT;C5I`=4_ zVG$ZJs&7Uq(oRN6_K8Nw*%ePN8b46ysSTFPvr9!?X-^s5SFMQ03W_Q5i`G_+ay5_8 z`tvmR1_BdPyTjauXbG+io^m?UeD(P9+9Pxic1WjrdPjc3Q*D%4XkyN98z@OiP_}io z89T8;Hq1k*1U~)oH2a%Bugk=Uw|zN1#bB+86`b38_51(m$*u~|`UPy|8{{kRA7-ZH z*wFs*xvmO7R!cyn7cbnlx4v#ocb^65LKnWiRAtdW{@yMba6Fq{xha0#xao%^EI5LB IhLN1}U;FmBFaQ7m literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/book_logo.png b/TMessagesProj/src/main/res/drawable-mdpi/book_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..f452a54eb038814223dca316bb44033cd29f6e7b GIT binary patch literal 1279 zcmeAS@N?(olHy`uVBq!ia0vp^{2c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`X6a&J;tUL7OBY8&Lsv5=M<;U^3u8A&MT zz-YdM;fSLH^Si^Q%%)n*H;*?&^fVe&upSXpV3sJIW|zQJ6E&=zTRR1smnlfMY4_RvZ`4a+`Ly1kXZe})#}=GA-?dQi$&&)4Tkh(V4w${4#C6 z-}fbPpWm!-mV4t#oqZoWAKsq-KVi>X#jvxd9T>N9b$DM~{QB_udY)kY+Jpo~ssBH( z+n4k{Z>Uh!Y1L%1RBw!!azZ4H*C(-j#sNkq&(&rD!XFtY?BVrL_Bz&(#y24`@j^&? p@08VjO@BC!WH(4XtYhFXV7OG&5S*J;#0V;qJYD@<);T3K0RT)6wr&6b literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/book_user.png b/TMessagesProj/src/main/res/drawable-mdpi/book_user.png new file mode 100755 index 0000000000000000000000000000000000000000..66bf3b1d502369546857367e871a252da3ff54de GIT binary patch literal 2303 zcmaJ@eK=Hk8$Q-3)%qBfUTf$WA7$5kGZ*L&vnlEeO&i_Klk%{o;k>0olL~pmAXlo->ER%S93`8vxurIZiCIo>yQ( zGOko0K?JZEoKxg%fs+wBR_pX*3L?p8S+VTXF=-EsAm>O3Bs_60rFkHm{r{mN(Py*_ z34#9h`#*(cVG0RE2!Uj9vXrYW9N&5_l!WFjg*XT-4TE9f{3!+}zz8f$fF+=JC>2~C zDG>AE6q)@8Jey5piDd{!%!OD?I##QI7YKMXFCQZxwwNh9Fen^=n__7zRfiHRa z$mM<63-ya!g4PUSuCV_o^!%2#d*-H}+g7{y-1v}K+wD?qThDal1_FSdjK%Z{llMM~ zW=lfDmoWtXMUJeAPSeUdQ~O?1Omc5-&gppMkF_z%y&jZIN_)#!&1b~O?$gRx-#w!h z4!q$tV*_51rI#}+{k#r_By^r&snUZk}uoVw~1^{wfItUsn|Eu;1X*H zUQJQU)Y;t*z%vb6W3IkmB<_LL>ou9`qAFuh<=&R|lNmOVc+LH?`;8;#{DFlNP8t^9 z?cNXi6LigpI%nNNgCBLrx6Fc_x9}sZtCq{7Q>#sel&E9zm8n29K1u`=s;2V7_2gMIjBZAt@GMPeqYBiNhj~mh)5x|;Z}N8;7K?^flwkNM&(yb=?v+EG zPp@pz?gA zMk_W7>i@abt@iy)hE@H+;lWUJZ&P*dg4Byh;Y};X_TYpC@pMv%4}jp?8IDugwAb zSS*GVf$|1~QH&E@8~3}TqIhIcjepvvr9;Y7`;EuO5)!{6j4UZF>)E(*W7+T|eR#@V zoXv1F_1~ydqnYe{Zp#yqh5p9eyy9NHPkm!{-_J(5J=p^#cU;%(qqv8oq8cy6 z|DJCg=Q*N(zj!nYdVRUq`N%@hGp`-pU>Qkm#4V5QzuNcUHqO%s^|%X98JV)WH1hJJxVto!e|+D4W8d`*6-6wkhhv^_C1{fbp|_^x^Ae%q^PxF>XZ}wG(=(H^~>e26BFEH(b3p-R{!t#?6M_%9M&-&dt$%?;5_g z6QWhu>}6Sp&^oilK6>qoAJ)CTN2{J4INpCN>f1^Ee@*8ojv5wU0-M&8bc`*M0_-Au z+pjg3=a}gz7is>WnGDrTGx>T%a#?=z(7WgSLh)}aV3*Z1;RbF-eL=#g4a?bQrTb;S zEGS;Nw2v~Z55!H(`n08=3IEPz6}3G3-*@cXn7+RfgSwRy<;{2M3bXwcGkYtMBT`@g z2jP$9F+#UM+ceKI=^Fr@T+w#GwufAC`q!hW@4k99$yh{2n_J{%8oZ@OjU+FMb2n)@ znbctD8#Lm8V7Te4xp^j)@Yj#05z`dr-OBu$Wn05vJrP=gYa80qc1ZV_T-UKY2N!M* zF350iMCWvjybcyWy+57CXlZ_GiLnW22{gzY8yzf+Y}wf{@*W)rG;gb_eZ$UvJ9i~v M`2;dgt%=+DUw;w93IG5A literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/instant_camera.png b/TMessagesProj/src/main/res/drawable-mdpi/instant_camera.png new file mode 100755 index 0000000000000000000000000000000000000000..3dc347e88da8c14a246f26f16defe26013064f12 GIT binary patch literal 1639 zcmeAS@N?(olHy`uVBq!ia0vp^N8U}fi7AzZCsS=07?_nZLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ2+zz*$uBR~ z1grP;werj>E=kNwPW5!LRRWrzmzkMjryA&s6|>*(wvaTU>CO z2i2Q`(=Dd1IQ8lS9itD5Sfq%C2?0|NhzU=&Kn^_Nr{)1udl4{Mw^+^u<}s$*o-U3d z6}QfW`*sH+u+Lm-{DGp!DD`d4yHhtQ&YKF z>m6peZuQt^?mS`_pTg3e|-=S5H2< zB+WzWXkG8|$KeyQMLWFSDf3UtpJgl5`G%+caDSWY;yK?R7F47c+OB)}c-HZT6&oYo zByGQ4Ydin^?_;xivt$;8nMmEWV>;|m^W$*AjeyDJ$CesoH?@44zG_vp9=q@LueCtc zGxgdRxv#pcu;azFjNbIA%O!c(-gm6B&ot*Z_1!JT!zO=8IB$No@_(8`kjct+`)Ic4IO+n61#f2`cw>-L!Q?1>b^ws*a+ zzy3;Qd%W1pcC#plOJDk-=D8M}Eu3doMCjae)G5jcue;5*Gd2sil zz`Q`V4z0sF0U|xFHWy3sBWn}{cAR%-o!hoeb*ZM27R)fIVAP3Q8#Z~u6l0O{kd+~mcDV38wc8LHxp0O}LtKCetAE1v z4PmQog}$&py{?!ctFcVLBtYbig5ht5{Oz|-IxXCw&7wQ~^xZhOyP+!k7>}Bq-s$;h zuhTcirlkGr-^MC0k?X3w<<0+g!p%-sg+~c23ctfFJU@SEKet5m8Yk*-ZVSmn+G@Vo7mHoAs%*!1g6ddiUU81}>ZvFRbnF=AFd9&ny&pB}M-of*oc2}!1 z|IG>TxxY^}o literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/scroll_tip.png b/TMessagesProj/src/main/res/drawable-mdpi/scroll_tip.png new file mode 100755 index 0000000000000000000000000000000000000000..7023f70173ea6de5bc56b3b99505ca8a7e6e7298 GIT binary patch literal 1112 zcmaJ=TSyd97@o|mM7w|_rQ|e*6u5I)-O-(#Y;|^Pv0ZF;%LNl0XO24A%o%5n?J5=C z2%;CCqN1YL%0jA#A|ktCl8_JfP!WPfMMRgEsE4dG>RJzN!_1ru-}n9h`Ty@6uMh9c z&EAkr5JYZpmlVO{O8l)|k%j-O^Nzj6!+PY8qbO`e3E2XKPlYXj4C-eI z1hJITVsR7??G_ZM)3Sr1le&r71W{F;G-ag&AhHFtX@*FBzI~S>HC3bi(r)1i1(s(L$Fl@zdm4vdYSHN>;o1w@#2h5~|;NMQw9(^R3t&zAEw z9LJSacsb6``+OdErLU4@{hZtDkt$PM2`XJWFi?uC{^7dQavdw^CXOrtOFIPAfCY7O z?#P0cHWxOKR&Rl;rq6{<%VlsfjMLb^8lBq0?s2Azw#AD@;{yY`-NLr!k9L*gD=`t2 ze6i$<;h{U(_jjxrd^+&`f$v8B$B!A?kMa8gMSBf^_x$nP=e*82-{tjwzd!3A-*XZzEsVIq!e9UZz-@d6W4%Ap zem#c{?7w@^xPkpah;rJ2Vngzxgx~@R06jd(g8(u1#hoWu6L9#@fPR8D0Kg6f4s^Ax0}3JHb)fbt=8ES21_W>7 znXo{DO_&ANGwh(Iltucmw+uC30K)opxe@z92kdP4h5G8q1;CTh4hK9y34HOEok3f(^{U|sp!jCNV zTLD8Ldj=ByDMXSV}0|DC4&geg#Q{t8HW#a|))2AZ4y z|4?7wf6!!#HQ~Sh{-45RY^Xm$!J0rO1qFKU7tTxSmnnZugFpg~LJGu^NIt)_Xz5L& zkjUO7e~5vNDgqXXSnkS7xHnyTt33`$8Kh19@k zAd#n4^^}y<)sgB3swkA2ih-W$Z!U)98RSdwqx|OL|I1bSSMIM?@b%xfj3ERPg9&)U zK$0)yuaGr~|L%+8zv}(X#s9l6$baQ3>^r0ItFZs8(7(6#yXV*RpSIm!{L}aZzx{3x z+;3~aAeU4C;14cij2@Oc`RN?d$7M_a{idZ}$F(=O{YiL1YB`)}EeRWHh^JE|n`~{5 zgBMSp+NkbGtumq0EhnR9%5UzL(6IGQx}|YY9yYcnUBQz93+k>5GjAhX!XK%Hq^;w< z-W2A3XU?cSD*o~laQ02xY7y(X&iiTdi-A9j3d)o*LXDSOFHiLQ;Ovy-IJj~q7#{0C zgqX7AXbpLFhW5ZGk0N}LhsCWa452)l&BxQVuRSaD9uGCs{JZc`@VX2uk{Qn6aspak z(`IrrrgB3LMQd@w9$#d<&Rtc^Zenvt5oNy4{jRi12on&iDBgmyG?*Q^a!$ZB138uv z<6zD``9|zz#`&BmkD_5PkA$CxxR?0jKhJ$G^HFpop!*}7<#c_fpF||Jri}1OC`CPs zIGqV3$fB-0=So~nG`62lr#5FlX?#M!=cJI|ZRWTwKMm zDP%Lo=`Oo^fEp_5h0n=k%K|TS+65zIrXYZ`pdyDvoHxR>Jr&bF!IZrN@3dgL<%ZuY zps#7L+8BJx?!Y6+QzP9eT;Wd&!~z@H@W?2cHxVgW#Obw!)^L3Wy*i5qWxD0ez6@;Q z7pS`NS2|zPN_@92fSbpHnLL5uu4j`_3e$?yK*Cl?k z+foJ_L^U!iRCk~oDawO-8mJFUeb;I<&>1i-g}*VPxP}JvqrH~4?Ac&i23?=HZd=j{ zp)#eTZ5Mzsn2nYDg{FXJnIJzmbAHY!$O{XQ?&8%qV>ZW92j2!GfNItyIRtX03jHYVHX^KwnLY^;|^JMc;*;9q1qeKCDe9 zhQ`_1z4rF>?AZ(t4i;Qo#M|gy5*HABccVhFxS;_xQ_G>AeLtVAZ?X*Pyr6&ffz{@K zT(vTaSrM>zc&p<5;b>DWLm?sTf=v0w2(+r|i*32ck=L2LuR6QEdh_$En zGzbC^W2wg0&j%dc+$#3OR!b9aPsHx(ys>Vt2W_=x^wdY$*$LiRR|z<`$@`R#QLoFF zD4uFM-cm7m|55ic&+&xkd5b`CYpYG3XU9&4zZJZcSV60j8>qZlld};>E{7l9I3UGm zqulG_mTeP1Hm0)uWl3nVrDf2cSAYY*t;jlA4+rqyOmQrmTxttf7;3$=rF3-B^^Yq) zwpi-|YQJcj@y}zu-tN@yI^G6!S9ZtC+A2@=$37*xHjD%??NRvMD%A_I)Z)UW8{58q zeyS_pwVohY^QYZwriWAx60JwJeX3>4o4IejSSNQcV~n3QL@ z2N2`7$D=HHTqQpa@Xx7^8BUI>`3R?FHl!b%ui&+e$%?-)I{WoVQq={J-?ehUL0e%- z#_mwgso+n80XwzLy&3IW^Of%x0i^_ZO-=2{+|$^rV1Gpypnpx94gF~M4OIA#e35GI zyR$M)YPqZIPvN*DfQDKwTiSkt^c&ROI;I9i1p~N>3VwT2n)r35nkVW^zmkSnq;XYtK=uPs_>d)H^{4{s$0a? zyU(InZR4P>pGEUgal!C+0}o1+`z#78`j<`Lde^Y5$UhgaOI*dswm(r8Gp1CZ zPtRlf#N`C2T=mN|R8`TE(7G;zxa!c9_@Kjbp8d>wah(~F>=U|^8raRw&~Hmd<5ifL zyU<`=5+y~!7jC_V< z2{j%rpHR|>J*UT2!p|_d&te7Md_=UG)$gBa4@#$dM0)C6!18iAT;<242ZCw7_KlmT zw4P066-z{Rbm%tV`>gEV&3qRSDZuIy zmg_RVzj97oj9sBsofDAffZb%|G3@G*!zrUpYo7x~4hY;+;gk+^{~+}+agi=whSiFY z6a3y~EFq+ux?&zg`GQ@QF2xn5@E>p$w(-d9Kf_--1hVS;@J!gb-lgjH7h&A_V7siD zo409fo1Y$NO0SJCqkFXeNEy>RbRooOr*&uR3B7XhoYs_Z<y#3yGnt7Z@*ruH#w6BmHbxszO^ zcPt^(-8jap-c8w|xfLTWkmQ$J+p?GYOOozz!bCLl#^ABehYH+3fcY-nPm6jJbp6|^ zZwtAxnhi}uoRl~!|Mgo+-md%&#OQpz3|(*pnTTB-*LeWu=CTF!PDOR?Bi zWy7rG*EecyaoOAhqKtc?ltytu!DkxD>!KIW0*cJEW)HWqK*eIKH&7kHr6m8C<5K=g za~#jpOs5{rBf4wF7Fh9OFC)lmK-mS#!B1s zoRf?b`B0)aw`Q=m=Z@o0JSfMA&hL&3b z(#?8XDR|>yT_Z^^M)${Fa*5NW^3!;Q)MV^o36U)0oxX!k7}d6Bp=TN`60x$KZ5Fkm zV2kVeS3Rfbv)r~cpjURLeV<gqTnc|pheh_y&6k&CnTzSWHf|hQ%C^U*DxBC^ z;9q%L-NZ3xz>lh|ZKC^*o_4$<+xgLMmR?4G^|C!g`L(afPxm%evFzBj9YdRkFPg~2 zbLODXt3E`XA7RHq)GNy^8}jOEDcjp;j9TkXr6>V!gnc&B%6;nu%>z?TMlY_Pgh5w2wpQoNRv^d;gX zhuA_=!hmELSmJ&|k2>V?tv_LaIFjWc+0%1$IXkvS!3HoFS}o8y6f8*5t{@LZUZgUL zRlziN>0vbT^5PK%&K#(Ahp|S_?&blIFj(vA2QW!9qIm^+q57Gv5E%c%3l7pWT>*1lZM${s}Z1c5$P7Y%A}cscf~#tUQ4F`IfM294oPK zjrV#>lF_@m>N{Oy-9}T?xJU5#@x7!ZHb93p?y>iWq2|bZxwsVZ-oiUyWx1CV6D%r4 z&>~k@8|PpA(Q3HqdL;br^n~cW%w+b2?TPyiwclP~g>>?iFs88E2V&do-G-|s9m<}e zvOrIH?B--`i`a~sp4Xj7B_3ai?Kfh1xBt94-?6YVn_v&5Ip^+SR}zPsPpmm+$>ila zQ_O@?2l~3O>fvB8I$_GQf5;3->#&(KFfd`8IlcDql<1zxlau%x!v^9K>kk>{mqdF{ z!TMkHv1{eHSf*EF_fm$q!hI|LfiVm|TuBR5Jrg<>9t z6=iZ{Ewd+N(981FNi>tRRjZQvjCiLUzJ|o=Xe+4gF?F-;l5YnydM;;l>dj&X{9Ji( z@$tgCIlB?iJw;au2>%vZb`!=lO||91z1Pp%t^{>h#)uy%JOb<5tb zhd|aJWiInwSzD*UV4(%AHH&_XE``Sh?U^6)wQxZFVT)+qTT?BSYr{3mR!o_S19KFS z>!S>>^`RB}M2hYQa| pb(6am;8H1Pg zm*6ZKd25fPgy`LDMs5=gTe;SrTo+~_H;l}t0G0tvKMIscCsQfT6mmdBNGk;g0ALu+ z)syQ3ZZfj9xgMC1L{r&ODq&_7A3Tp?(x9jbRF#xN{!{r3IM6JPb$(5(`fW zqV0`jQ(Ph)T>T@1{V@SBb2BI|94iu_Q@CVkIQ=MtgAF%@{jG}?&DXZ!FzDY9Zm=or zKS_C#9HDq7n*ue^)7SM!Adyf*Lp>zgz|at>14SW_C^$lN8|os_SOY^W$_V<;1rtSM z2LxiB2{!-460J;OL0m2i3x|h=h3SRq>oM6>I1+=wtZAT7x*~)wCxXExhwCyp>c12S z6plZe#^TbL4CtC7*^e2@HHC>X{m&8Ttbb)0oPWwBDi}PR%z`8J5Nk*J1tgLFe<+>) zFPg)3ru;YG|5KRb8o{E#ohcku)@Xge5XKTr$I-LL`{NL<)K|S^(C_5M^X(jW}-=8q6jD*kDd1 zSh|M48TY0gJ@jg`*&L7tmrGz-QqFeW z)?%F+r7OLpJz`gM6yhz2>;B|I;xl*Z8q>D7Z0PuCT<>r4{v&1lBJ=%)o{;gWsEavE z-!EYDXNfvJcTFaihV@1+MlqbDegKcuruo`@?VJj}Tf1LiqaRj3E9cs3V>;&XcE4j< z_4;`^yZJKh8SUT0qnwB#zC7n)YEB;iYv!OeGO|aUlBnMPgC9$+PXf)tQX?QU4#VF2 zSmq%7I90`W^-qkBM55U+l1X|0{9RMKLG1<8c8?r5YJi-U41(beX5O|l5p6nOuRKHM zQ&!XZc1lbp&{J3&*<4jw7Y|9DIv2zgV zRt0aO?MG zzQ0InUHW%5s@)q;uy6D5{v%zx6U`7>cT)wa8lR-EpI+3fapop#dGT)|4w6t7x21f$ z6V1GM--bHc^)-|V&Is(4zRb^wyH>K%{Bv`BljP?)59(# z$FxU{DDU2zHrTX3Wx>YxPM1}%97`3Gl&n-MLG;k_N*5)Pfr9A5F-uxZL zrI9BNaHRWXL3ga9D;$f_Df{Gj{ylS_1sglalJC)3fM3T2G3TyTtYpi9LOp_z!4l zDo~>O4Wf0gxVkRY3}X^A>>l`7%B9_K zF_79d7hI=T?${N0?>uoZaN~f9d>jqaZ}xS7>^VB#SW-j#npsvUbKUOqY#Qg=7Fzg2 zZ#RC5X-q@j&9-YGM`wE8lP@yr#s1v#x{l_7^<1Nnysl&JVBB7Xi67FZmx}%UHHN zo7xNw%{^!_S)lLoG-}J>3&f7qTUy5R2Tz-n=?mS@8sV_vWwE0^sb~bZZ@%hA==2Ra zVfA-6*i?Il>Cs8SL}K1ov4T0jtG;-NTTTIu%5g&D3yiNN+4|p_V$<|RcC~r}Y_tBZ z9Xs{IF3v^<64XTgE`MRafBiUDc0Yq74=0w=OrMe584E=!f}yzWg82 zh5#(it}g8du32%a17#8Stkiv_^=IP3FjQ4SvwIF5(C&3BM)|pBcMo{5H~mSj@UDtQ z+s6efDswV1Q>Ymr1hlXhyGzaG4kJwBFKw!GUAlpbgB?Cw`i)<1zuLY|`bE_z%TJ)K zKV=B!(gnK|^Kx@xRc~?Yd;#4PoWJ51ryDw&q0%p|d?Fe{;0Rf7gvF_}-J5lup6#Wp zU~A`q6m|_o2~oR#fzXn1Cd}PUZNxzauPyze{41^0I^rEpg=R0Y#;pn z(@vzAyJs0Je6#JSla~(#cxSknt2+Qo+MVKQHRf4%T%y^kvnU)X3`Tzm?7TuOg!j#o zo+eM%;;1TB`^K_8UKrG6p`Wk5ACyYHq&%oQ9C5Jmk4m4`)-I3PLY9*({oy-k%bRhH zprvER-~w3J1pP0YKNllY^8DUcr;SLY*iF;XKMM16F!)t}ZrX8yGOJX?#903NkhJO&;D<*5Vb-Yss^tT0^?RgSa zCw8g3Dpu1=0$>C-;@Q`(ei5FWa38ibiXAVtkvE&%GJQO?1>T=LQMVpIoWPVcY>A5y zB4WUb_-YiuTM{>el7@!XRf5z)=F%_KT;ym$POZmT5S*v_bZ{0N<581dY#dxaR&U~+ z(s(JRY^H-EDG6!`J4|Mr|1qIGkDMsQWG|&W1ONx>-3kpAu@&K=amC@mL*`ItFkoCb z<#Dcp8d@ADcx$d+KCLJN28KTG-0qs9=f$fvJp@vnvOd*`0vw4BCD)y2leD~^B+b?l zJ~bE7fCBp*i-Z%e0h6ekXR&?3MIauG&6cd(a<3>k_Ds#qShZYL)TrX- zLEL~xYtp>+=IRE7n9Pd8f$YN!%}Lq&N*kv9`@r^(S3V2&p1lWV3awS-P(G)J@LVOp zvtt=N<5E5gmBa#_ zCtrVtb6nRc-d1ufI$w~i)A(Q$_#{h?W5kIONX2@F&N2C@t%ea8f8!JBQXC{uKxF7(98F-qf*ShvAW&+X?j$Yo(1h zZI7#zhir`{-_VudOAAQ1h}X7%hyA>HvD*5MPsgZJ9Ore}d0H{3%n!-Q_!vn^e4u1n zf&U||^<7jlm3};8%9rH`A{a)9(PNJ2CAt#3*2LJ$^#zy+qdn0B4 zVLQ0@Zq;(@*&9xf4IIffcu#V$8xafWdf+iwu(7|J7uFi<<`Ejuh1CFn17be5j$}u3 zGc|X-zr5Q%Mn1%!u*U`f4ebzuoBJg!8H~Yt`QS9g7N0eXfqgtQ#T?+~3g!eP*4xJ@ zjEJ=fv#@m!yX3CwA*QVb)(BDC6Y$59-M}IKemIg^h^E-zx@vp#{cVUC_-_dLlBU>y zoN_d`0weK6EEp~ilXF*qLcs`xJX8seKtN@|iV9Fgh{E2Dkb^3z!4YbT%HV%4u{~=< z4^K5~ebhg;_Ewr=-efXC4FU-c4wesw$>WJ$5U8rE>b{1eqTC)rjueU`yM@T%NaDX0 z^syv&q7Q-WgU5mQ72PoSK(eOTo~Qo_!JqK2EROU~n)VU~32`Gppz;d)A^irLoB#h% zfB%2cB(gR3zyAK8!X(>J0v2M8CE){!?t6vv6yG;RP(u>2Ze%>s7LWJ)okc5eJQ+{& z#uLCu8w6O&!3XDo4<t{;2m4*W=H=DE*NO*>eW6U)cXu=-*p=-LpUaw{7-<*B?oE-SHX0VNmm;5?|{Jbt%ON^!2^VNPyN4!EwNf=OcWbXb1 z%1S@68ucd_)ZR0fPSCbWE7ggvE4?SFR3K?yP->!Q!k_!rtJ}pZjt`sDxRchr~&eBMH|;oqr+AL&w~QuD121 zDtb}1JeysQ)Wn3|3cWtsB4k#}|H&8tRk&Ws4L!Mf+HHv#KlYU`dQ*8-n4wL5e*t)8 zaB!82fne;mOfEAs2PA}MZv`_lj#I*mV2hqwFUMPDZENo!>Do{iR}r^fZ;*aGijhTi z!PqzW6@A5M_3glGoDb1N7Cf{%#;lO zE*u1bj~%jn>*=ptMp^adr;7SuStyiO6K(e_ky=$!jq!Ayn?$BkujgG(#q%GfdSQzg zw3RUcZ;2Wxnm>9J+}IcwdY%Q8af8ZU7{5EhL^Z01ZBI*6{Gi_^?#=V?3-J%V(AQw5 zMEvZbzaQfYXn#Ow6Umvm(5*Vds zAdkc;gUdh0qYzL**&KI)g=1ANCnxN@c@Z}inI^LXVbs}GrSSu~rsGD8N9(01%7L-X zN@l-xvs!Tg(}({Y{KeN#5@ska1`-k%{v4og;W*mw%P~@%a)*0F*3aiMb4V6F{jA^u ztI627pCNG3=ta#p!E+!u{|62S;N{o%Wz%@5D%`wjw^d0+(PC!irV=c98ZBaCX<6Ku zD=S0qD@aP$TL~K~s#Aw+y4R^|pxlp+FC1=Jyn<6sA=bbe-aE-ax=QGWv3*ZPoP1>* zO#rE~H>jkBa4*l<(p`*l;0D*=_)g=ezWc@Ztpl;_@Hh6>CMM+Oj`4Ag$HD00%6!o6 zyqewyBXjeD@)eA4#RH`ucLgTJuEvzQtAchC=&gy}t{#5Tw=b&sw0JExN}ys>F8NBw zH}@MUbcmagdJJZg>tk{&%*JT*j9JgJ)6r-6$ASs~1sob2@;qJV%$TkGg25sP%kvXy=om zZ#dApURGyuJD>U(KA~J6g!0>=4F@FMnaa6sZwyq;H`!mIM98ewWzL45fN;CO7|Vo# zj`{2Fgw~PlPv)bEarzrqd4cMP_0qu}5ZZrZx%}eTNM~{vYDeD4Xs)LF+1a`G1_HL? zo_3tM|Kz=KCUaFp0YIp@oF~)N+Wmf!Mq1{(o!@FDP7AkH=X&tPD-2b}1JqS=9jDF9 ztXZrJg}<+n`$b>uo{-iH7jp#a%ZF>c-`5vRE{90Ho*tDJnQfdXla?-LpM2vAlCtZS z9GWZsuC(?1dxaSU8-nzzpX#glsbM`yrBb_D5)mxSz$o6f%u7|3nvo+uk$QcEVpl7# zezuk8m*%}V)$=FH*#Vy!5w^SeKO;la1#N&M?d@?ZsUpYu8OB>Do=Hh}i?c*oyhZ}b zr6cOGc1>SaFh4e*o;A(dAlcpGJ9n!sTkC41hRwNgZUIon0rYrzCf{KGTUwu9EKb#R zUi6tWRqxidFf0pD;dIV~uXx~c$)!!zpyw^+Ntl=cj6vv)8`<|14ugiv##>V^&6PG5 zfmh06IU6bq#(s{B)TVO^G_c9^aQL)-y@^o{ z*_8-oqdlBJuIL@oIizC=>e6EQY~Y#>VREkLJnSmcOQpng%mgSEhzk%n3j8gvy$i&g zOh}o7XR=Usbnf79pDyt_Y{+fF8uRiIjL2%hb*egH@>-NOcqj*_5>*X@Nr{WU-walX zx=T<{WWah4O@BO69j@W649{%*QiiGou^C!JeIeEmom^EWQ*|IIOLtH#Agpy)OP?O_4&XN%AVE?g*UYdP)RgJ$inqyY#%3b+#q;o^y%D zEyR)`+oZH2B>0*t!If!t;atdz-YIjc&IersACO&U)`*(!wF5<+6Zpg@6NHoE-HG1V zE4XnBZf#Cbc*?JtS*UZ^3U}nP$wRjg?0Rcs8Z6rAH5O$iTEU)SSh3NUo2vHdr0S8t zahy=>QXrcvEAhiWIm1QV8hQj&;xGZYs`pV*Q^wfvHI#_#aUC=>)S)*l| zWzNsV$(N*N7Vb1_SDnzB8kYIxw~ZyL_trXBR|rHatuV13CwRSkD6rTsn%kz$#H7M# zGdcC;qD5|9O8lLBH9cc4_Gww-P0qP8D2sbYF_q)8lzOaoQ+;H@!K)oo=lVO79lz}& zMU`8u$vrrDJSs&I#x*(b3UU%%mA{bK+KeA zy_ce+!j?*Hru|$QSzCAhieVvSE2XoaxwDp=TXxguwLs!gLi_{U+g}$v%7~&TyxAr zpaP`Nd_6R^lDNVvEt&gh1Mjk_jf}W&2@BRA5?ol&?LAo?I;9s_zV33kBSM@-XjIRB z0nJwQ{OLG>@*@7!>%}=`o4%$GpVVBD}#or#`WC74#c=N{!2DG|(QxEC1^3X1v<}fav z+xQ|wn~Dje-3k?%3W4-O?+h#s1#Ys(-S~L4mkn?gPm&!xy^D5^LM*Rsd$0pK&@SQKTne4ZNZL;f|-vzkNL}|ZXbO_y%b((o63<5G}8lqSEIoBvYKaMksY?uH-Gc8~+ zuGsd#;lu}#7Vox9Z?q{4(2yBvJdqAFqR%=~Z2_7oCa0j_V3m2QR30Q<8Vn38hf*6Z zHvf7uZO1r3Z*V*W&=OxNx3?h0*J&5N$$PfNvlj9J6y&#G;JC_sb6y%U`JHxQ`O0Y| zpu@Q;=UG^1YDTHmbON1P8@+s<39#~IBy84SC8=26FIjB77Tg?KvnfnUt;;z!sWCCz^?ltq&Zgu<|3F_F z6cK)_3AccE@M00`W; zW5?j9o^|pJIpz_TPfu5$FAuT~1Tj-ci5IEc*>^mS%ZynbbBmo2*$OS~fEWHq4on83 zqiWcL6Y^SjkNxV0>D9)!C5iB)+YF`@9Z(|(2eVpe9Fy@ zdtdU_QkY5}3~hP+pyI<$IUf2kapTF`sqD+2ruEI6!3;tXI`1f|6CI&2fF6!y5diK^ WJi`|T)A9R%E{qK<^egpTqyGo2Ago{j literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/book_logo.png b/TMessagesProj/src/main/res/drawable-xhdpi/book_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..3524d3067e3e9872e4548ada038a14550d5f197e GIT binary patch literal 1635 zcmaJ>Yfuws6phkSz!yjrL2S2+tuSq#hz(TUSBnt^tZDF7c z6%|It2cwZt6%h>-Wqc2gh>xN$T9k*5h-K=4h+wr+9ZNSTv_DFBcK3VCIp^N{-7{Zt zoHBBbho1+7!I%>r1;^9lN6yd9mHvlv998t-OUX2pib$a>hzVtc>xpC(h&CdrXgrGO zb26`?%NYz85L0U?jUpD(5k?l`#IUSJl4dg)%U4)QM3;e5Kr)(&;S%ua*(MOc^b&Bb zK*3RvQZx;V$~B>?T%}r0^U6!9nGXq zf#PBLyIAx}0;W+E39;E0i-lzgW)Y@THdibbJ2iMbCXHa4b8rf=GI4XjqymhZbta6Y zFaighibyh%MM*$9)3+rU$thXf{O*|O17lkelFemtoFz>H6^j3d8jVwEGZl~ilka~D zo7Fia%8p0PM3zZMH!dZ>8H$9YCKRCvlbRq5lcyM$Mo@$~jUWN3N(cn5#c(}gF$YcH z6$&UCH&Y0%L!)5{NGq@~Ob-cQUPuT(Le3S#;asjvD2BPBaJft<5by-T5V>@c3lq95 zBZ^a#T>TVR%9)nyw1SbOBg3c(%SQEb6JZ1w%(@f-N9hY?2LxP)z+R9yJDi$$cIh+*;XEdhpI=S!JB2GBm4a5u8+!*5x8-ZK$Z; z3eBs|gi^;`i$?FC?iJk=kF2{cNo^Ys?R{*k4egM8;GdYkH^o0Pz4X|1pOqb9FUH`#ptYiYaLeh8^?4h;x=t7N5dDC| z5~3QN!-Q*vzIMl|e2?SdL6^Q?->#0`)jYHI%d1O&oa=w~0DqYwrCwVy@MzQVR`RuR z!n@Ei zQ8u5Qx-(~7x%N;|0XDX;Dq;2eowc@+D>QaO@Cw8 z&Vjn`meedfP$X*RB))NLX^ojZl5YD()p+Zy#?k#aFxltUwyGPeB%OkOnHOA>XSmTh zbhFW%C$F{#3UVCx{P?^;{sM!w9~(b{s?*)aXK@(z2ZQr(9`^m(dF@Bbl&~WrDgSR` CMSO_> literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/book_user.png b/TMessagesProj/src/main/res/drawable-xhdpi/book_user.png new file mode 100755 index 0000000000000000000000000000000000000000..64df4bf317337ac62977c0dcbc937bfcbbd0445a GIT binary patch literal 3972 zcmaJ^c|26@+dnf#mcl4Pc``{QjAeu&LyVD~>`P%VW1pF3Ms_A5yQH#LmYy&vWy_L% zH;58hDtQcwD9X+|p5N2+d;fTQ&*z+TFX#LHUe|qH*L}|C#G090;Nd>P4FCX-zMhUb ztEKH-ocmb!77c6C-P-xuqSGsj`w0-Z(1E1?TQbK#4EbHi*MK-B98-O2+cWBs9*$ zQ!j{&vj{S=bPe)$Rdo~B)PSi6sIdfmaa1fUz}JUBQ42tc|E;UW>hDd_f(#5HuYiz~XPwG$g|li(%4!G|*guCj z%Np4YuV${J`;RTw2qo@8rIOU-Q&;1PT>PaK-8Cpu|}UvYwu9YRWpgXeFeos)DMH zs)7PW8L6nCtBS#(6}1qGx(HpRUtArctDi59K>fva`!84Px7@u_@FlS<>)^-#X_88fp_u0Oec%J(anA{^2mrEiA?n~7*Z&+{ywVkR=YqnhZVHP%m|typFlQ!``2 z9w4Xm)I|jfEfcUL6_D0Ew!U|ZCNcyr6??}ROXovWDW(OiXmF%IRdm9C#hG}4WO(Tr zA8=vuc63|Qp|+5ebq=N-oVK#W&NN|KkLQ1)+(s_LnPrUC3c!5Iktxg6{eGHPA)K*A zKioJy(!9k*ImFJ!k)i2Ue~1b?i@5Lp9^C|qj_$~Ck2nP&UGCvEni#pxt`Y0m!bjR8 zV667?HUf5;VTL69VX; zT^u_@3mJInVS4`v@#STr5SJ(F!(q*CFB01T;7{$BSv`p%{&P0 z<#w=D_>qBF{m){exlNOtJjqESyZ15?Iqr6L8Mm`ijZb3M8DJ@CIAUjkZOd0;_}>)Ce+C0>WeH5o#h>fKollnN^9yz*J|^S>D|#q5ZY4yL~;F((ewn^9R z$SdXoltZcY$APv(4f*yx_UD2gUhR2d?R_CRuBEXmDse^t$lf<3Rl(Vpv($EcZkT7) z&R-^wvvlFEC3qy)K32GD&dh7!Kw^;H~nQm3QyQ(qN^eebKjN%4#o< zY$$@yuMeFpu4tDf-uD`IgxuwH2-gMti{IXwvrWjmxLW+Ic}zI|%-o?D6lIW)6#?5* zc15Lq*KnCu};1_mn*3R*Vn?wnqVP4#IOTng@RCZG2 zEih-)rNr^33iNT@6ojC6nD%`7()f734N?ObNrwUwux_UE3 z7`RSi%^6Oq!CHVr{^N@_hp}M~79^K8ZHFJNg^v zJJsw!r1%Bm@=fX6Z;DeokvYf-6ZDCnKEvWp(AeV`_Q1assb~PX@ca!C-eNNO?x$%Y z&=*B`*W6nRF2braHw(v1+hGt|YFVhml4om-Ft3HvZt1koaq6ldu1%Nhk-PhTqvseO z1o{YYc5#W@xS0s zlYA{i6XLHL_R!s++p=WF1o*in=tpqu~|cXY5K%%7A52ha=; zVe)<|YR(Lu!wi|$P5&_=l%VpSZdLPY|9PN?o@Q=^DH<+|-f62lqfn+givcyhx{MMk z%Hh%coKh89zlj;(guc|i?8gpw4Wb4h-D0d)06vA?PY-Kb(CU8mJ+t4f_T|NznLl9! z>q|J{>Tg2<^Qd!mWOw+C+FU9jeErd9ue*bpcPbn|^N`+C+{w9mqQ+mn5&?*>sOE%8 zC|gVYN{w>`tZ^vYWbS)lcp$|C2NHMU*Uw8IpeP~dcga#Et{i%{t_pBx!l z)#a?|j!)FT+~^B}^)ETgzv&rDSG^k=M6jN!Ltr1bEy%g%BOUH)g=LSDBd%u_lWS^< z`tGbNWq1Mu87QFsTl9Qw(|1{qc8;20xf(0{R~`d3J1Sn{FX=%sofr2b;=VfCjD}?- zx^sX0L*am4WZty(g6HPVmCBaN#Xk=M#bUb`f6i+b3a2$3q4}P+-~!fA{Q8v2TD#c+ zU%mH(jYE#<>%H$Fw35jxw+X9gjd*+J=>hBPY6 z7QSl$G@AaPEb}nSaZ;eVzOBx=b6H7kU~fVFJMEeWD~%k0bR~_sl?lNO`dyGp>MqGrl#q zVzKAug1J%a*&m@Wy)3v4&?}Thw3x@UD!^3m#ZHCU4}_FY0dq0DVHp^EIWnhXM4b+53m) zwg$Dm@+ZN3sjYBV9>W0SbrHLtd!%PW4AfcN;g*7|6B@3tf5a(}<3Lttn(R-OCFC^v;fVTq;=* zXXhMi;a`e3nXNDc0E6CnK_{i`mOun5bY@{q{v12-hMRwOvQfmDGIJIG=(3nc6#$49 z-Vl!XQvTAXExdDlY_Vb)odX4EXun;?_>%?v{+s=)eu$T$mCZ#K2TA!($Nz97a4a2Y zn7{rlXC8Fx8!SWQOwb|>1-r7u&+IXA)QJXY^e!1~zr;%VNf>0&o8fBWr1w@!wWDji zDCpkGDRZgTHpNkimNi5$&8^XI-C=MNr+7{NNk=EO#4z$G7=XI2on7X_n>6wyi(Z+3 zyc2T?S>FK%Nxag*bH2U|QqqW1LiMRnzvX!Wr$H)jW;7fYnBmV>(}b0Y$J3YMfP*iR ztK+Mi9!2k(QGXDFaEN&&kOU37lEWw|2oqL2kQi~a-~7lxjh4`P@Q7pB?R8^(qD%Az ztv`jjWU47|=_+%6EPP$-x%6}CP7*?KcJ`1s`DSABn!4rG_mFnF6j*N``4E9$`bKTn zc8R;sz*m074L->t&U|jOi>%kJ`Kk&x0UA+AowdheNk+_{`>z&nEp5SQ?`Q>^O4XoPSJf6$P4LQ+M|Vt3|_DGda4hXaEtI}R$W-(o-? MW1{m!+d1NY0NkqFNB{r; literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/instant_camera.png b/TMessagesProj/src/main/res/drawable-xhdpi/instant_camera.png new file mode 100755 index 0000000000000000000000000000000000000000..767694370cc1aaada80c3190dbef9f23b34c959e GIT binary patch literal 2883 zcmaJ@c{r478=tI&N_L6fhH56v7|h5R%NQeM3Nu-oa4n5CJqtL$s`6i1=7Q0F*k zL1UYW#v#jxlrZR=IOjXEe^la(PIbONzP|f<-}hOr-|u(d_jBLZdp)TxM-FY3+bIVE zfwnq2*tv;EJMn9^Sw{TLz0;^F9yED)PactVk{9OBp@Oi1tN<$D$nZZ!b))(RMx1G< zVn85CFrDPd^CUQKU~4M?6OI-OFsM9#Ae<4*y69{NWCYR^Wq);90EWu(07@Z!7HZwnn!{ZP*go!y4fxx4% z=18P1(jIvbfybMhm}0+j?O2o$29?SC$_@O7YxiC5Mk_GbV$XI|4n33_XwP9WfX^wT z>EDk9`(3>++`#Y0V*6b#TpSF1qp<%~=&xJi?%9}rYg_T+TjNui;%?`N+xp*^9_Sm(&cl3xJyPY|B5<@nd8u<>x*DZMz?J?rnRTpomY+ zqGmQatL>?&PHvOZ7FD@pKf_QIxw)b?~n8zl~=?IT&G*FK#yHc_+%z#6vy_Is%@$u?CaAqB5Uch z7a|)Uifz!fq;95Q{{=*v#Jh?H9fpc(or&tLR_x}j4w5$*G+OvpnbZE071$H;r@D)g zrA>1h{e2hCAavHj2E-WGb4nWL21HF~`CX?UG|hzaEymN2){KjG0UfH@zt2|s@+Ye7 z@dR19ElJlaD(HI~hM&WFWNLhaf`fe$AMAxd6eeM{%#oe7*#f}kJ-@shQf%~#!cW>X zzX}7j4%y%Y)9fW&u=jOy!yLC#VE~09O1;?Wes_Rb){trE#2+dzFVD}BNb!N4An?gh z?P!ETSz5u6>=VdTDPgCcZ`RWv6mds29aEOlq=|ZY-C;ArHyK%3ML^K>)b#W;lTO#9 zF&I~Ey5;2M2eeSZp2dZQ?>S;Cwl8a!eNeoN;K0-qYY!)I&gf)4dm$37BkfWl{eCj{ zRUlg+J0P2g;HKnaLwzU|(PAO8O|gRY*JXElhITEJ7im zRuZhrJ`f2%(d6?(e*TiAj|HMWW_U`oGQEpry)+?uRJSa67(Mvvl~+U&Z6}aGKiaA!Bq{BsaB6C5l1x&DxR~yO%&rbrfut~xU&czx`fc)ybTT1n zYirxjqSm{>y3;`{mRUB&=;+yqi1$0K8nVV>h98C8-Km8&ggSV8Yu&l^$xJpTxu~d! z?xHyJ)FSL%wC-sen*raC|Cx^35ha7%Z{y|VH4$v8n{sP0##ZaL;Y; z`!O$rAHD^Kt|cA3VcZWl$6%rpqze0TQ&UsbdLQ{}@GL@8{r{rw4nslMW1&Se->*c4%8IjVWq6 zaNMRvcaf;S=+@}R`ZfK$n_F&<#=YjloI7_8 z{YiRH_=i0BS}jYo7XPB9p`k&cS0cB#xG8B~BA8D9eFnnXVjhx|5;EC!^)jUF`Hh=5 zf7)RPDC(=Y*msAGKbC$rHb(X>Z(TbUz9W$597d58epkW#d#*bY3G8lnz@dC~F9w zK%r11`}c7r%l5KH+I(lHb8CCv{(Axw&5KTZk4I)>qF30nZ5dh~Y3b?kO|)&Rl|{~K zz5JG_qa4n>Yw;x$S1&a;bkzjn^JDC z%x9Ydw(cWTefJ;B8>5z)sd{XjoCjg@z}&X9x{o??3g&e)meFx@o$l|*P3t6Y?~Zy( z%@36dHT!VZ#1eDvN!1f5oQi(fLHla2pI+Q~^DOHWX4+QuWr4c=p+n8TJ}JU*%g{J( zyv3)j7}vY40ri}@3H(AmcXAcFdW%pLAdsi(>FG7xalb3CDu<8lk)L0nlb%rEPcb_O zs)D;WMMwa`t~xM2{GztYQhrOEA^37fh@~4yP$RR;H&1DOMb}OUXn~~_XF1kR5?MbI zwAZlyrbE}up%92e2>NQH{8@FAy0swjRSJ4A-JPsZQ1Od$C?5j(*D((dt+B+>L;W U`nTN-*tpg@;*Z!>+LGh`0wTuK;s5{u literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/scroll_tip.png b/TMessagesProj/src/main/res/drawable-xhdpi/scroll_tip.png new file mode 100755 index 0000000000000000000000000000000000000000..9d088d80ebefbaebb53b3f1badd16e1d67105ead GIT binary patch literal 1316 zcmeAS@N?(olHy`uVBq!ia0vp^LO`s8U}fi7AzZCsS=07?_nZLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ2+zz*$uBR~ z1grP;werj>E=kNwPW5!LRRWrzmzkMjW#(q!?BeQZW@v0~X=vzbW@&2X>|*HTXl80; zH{644~kf%h=vIPQxAvNIhdYKVDCF>!VSd)@*Wpd2TuE71@d!Kgw`tyC^ zQz!nLucg0kIq!Szs#D&<1BKVwzO*FX^=z|IQegB_IQrRQiG}j!$=OSuDaN1m>|WqH z;alHK)e6ox|K=9%p5JJ=gR!pX_~GT{XKs21w6+FZKTzD_@!sq6=^R(7tq=C=DIc== z=5uw)PA)m!v)syOlFwe5oYJz)^um@ItD*v~Udk^DdwDvJUAOz^oEce#ZZjPdBdlkv z+a{O9Eo63}TlvXMIa6Ja8!WQk9__ZG<{l>+!r6K%=5&7YzkTrb174E@(@sl2f2x0f hui%f8nRToZ43k~?Uw&HsrUq1QdAjUpK~XmuNpE#`Jey*FdG}` zo1L_jKMy_a$=ht)o9?9H4M5rinB(07f}Q0o(jyuZ5)LPbU8kA|F_)Coe0 zaLYHqIYi2rApEz2K91n(@97udiT8#7QFL~}2L@;eokaTIQ}FTokE}1@-)TBYm`sSX zpA14;_Rp052AY`s|4<*F|DXv0X1M=}_x}_oSlsf%$(Z2?_&|TxlZA5={^QC|RnH&i z9Dw(?z~jCD&LY|aAAl!#;Qiow=1OoeYfoP+KA0f>FT9C~s4{ZUK+5YX8pz2ZRP*M@AgK$^_f4mR;UsG1~{O4F?|55MXT$rWm0HhS$gz|%;8zeb;?tWTvAO=;k&~ro~pnA z6BQ|=9dnI-#2fnFwO=!D{UUxlZ!&x`dVbw*b$g;|r(ymAf75!%MAKw&Gog7l%w7C+ zUan}AWHEO1Jvp}INl9!;Ldk;?9SpBIvw2KOYRTpB9l0U7A&DWbA@!l!LIepz5+o5& zzfncmZZWfc7_p0$$z~)C)c3?!mdCz<)ZUlRpPoTQ%nO;b4zZJ57dembb+cs~MwJ1! zQQ3#TQJ3Vuw~?QgbQSqHxZI9j;6cgA{WdE<3&sFLVpew|;tN-aJM&^ggv5NVB)NBm zz!K);CWaK6KhMX~ghI=UeAr0zlQltC3C6uH4zSU_f$ah$N_JSbk zK-~8}PZr&(K%Ue71rj@k=;X{f^e9V{x2-8$x(|BXvl)z<4_va>e|T;>TO5et38Kk-Uk}jb>yzpK!8esTfhMHhsEzl@8eqj*edR_A3%2hEobR z`niIK(`>b}N?QdL<7NwiFO|^?I8y)JvygMA+pV*M62V#R4vEg8$RIN4BC?{m3I;KX z78`n$-E6uJz@n}fTe%+K6u&eR9dmz3WNc0OK1fFc?lf6fGP1W)Mb@r=edl|T0XOeB;z`3J?Y-{) zTw(utCgt+Bx+QQ*>B@VhC~IXS7QzG7Iyxe49^BYOn#%3E=%oH|4K`IObO?Ux9ow9< zCN<#0RjDunE(h5jIyKj5`(4(>aT_~ifl5nn1l$VBbIWp@=kd_2F=9-*LarsFBqb%u zjn$WL+_+(ZMr+4@qDMZ%NJuA|8Q(-?W^$V&Z5b@BGB7Z65h0$X^M1+G$bm=YVcl25 ziw=;x6|`q9NizMPMt&-oT>Ut(*_&H%%C@ERawWOZ1?>%LsjF5}jA^N_&Rz79D5-jt z^Rl$3qhCZ3o*vn?`NsYgP|Or^dHZP1*-hGjC+>nr$Z{H@eV$LW14%BZdN`^yY>unr zYPXNSN%BEG@_zw1I`219vy-!rmiO4}6;>Q>e;8eaE{+U)moeQ5Uf_Cu8wU^@hJ1P{lR%-Pbq$e1w)k-?qG8sNa+!Tc-qDi@ot)|LW=Bl> zeDo8CGb!?VUF%{UNw*tFz~kWwt$@dUIL+KV_0TBkUz(9pz)A;*cRqTB-^aq{G;=BV zV)1Lro_D0j_PMG4cCo{e$>YV3>F))DrQkjWYIJo?9y`Oc-zp`5w2JWVau~qBmYkKI<1s7nkxh8aj>$@R@MRWu+46`@wc%Nl7u;u&F3nhU12U1yP-t?7j$j#!wLa4J;Da#D=jsS1au%S z+qqOmQmSAuO-N$e_H@gA1E>csFvZH++B3oW=|~&L$4>5KwYY`sTQfVp&l<~OUQIT9 zuEnr0Tz?kg@Ad&&d4HjDb=>y4ZNb-jxj|bljBTHX(RwL?mW^9tZ)bbCgGtf&PdGmQ zr~ZL+adPKsgU-MV^jyz>e|q(yyf|eB75QA=+RrF#Lth}}b!uJIe$M65;ZYCeS9Q$F zBTrhC4LA=MX!*QKJtkNCDsAGY;5+#+2*q+5GN|6!zd+z&CKS20R+K+~^-;>=0o^>= z_Ik-}x;T&NGy>9Nas)M|cC9@hFh`vcM> z(yg_Ju18!@zNGA;ycG+i&SrR842n}a1LtU2ay5$f`88;yyXorRt}i>z8VgXbHwvuy z-I*z(W!k{g`&E3CVzYN2-oN{fhe9?zca8_!0m4`9V(5WF7xA4G(JtFPV2|TGf4X#l!9mT8!lz<@|1W zNMYIwTFWnA)2*$qSjewmyzIEmyi&iNb9L-8_3EV4l!F#(D$g4V$kdMouKY$$Nc4_Y z?#v8&t=$Wn(f!b`eK2s9{8fU&Lp3CX-Tjie;LtCgQCcxvH6Is0Pt+)7Ma9xAE&A3rW@|KIfQz5E+<$B*HIU`FK~2|f3;Q+f6Sr9f#q?lPt7H^NzK6{f5r1h zB!G7V59C{%_NZfMYn;jwpV^r?8mmc~Nx{v|MduJDx+?Yv3!;A5S*G!?3nN1bSH3tr zv3uNWi=NUO8q?B$#yMm_y_~W0{bR}-19<&oapTk+2xwneUA}@ETG@hsSwWK4{40my zrha5ueONT!XrNRazLfRD<-zl4ovM2pZ*uC{z>uA}vezR0sq~0;>sy=XKf!u-erB?THi`?-j zpDEgBt{Ak?me`zOX)@uY5U7nAV}cP{M_P8=UYkW#6Qj-}{k&9r-M8CNqF?7;Eb_xc zPWkwE-l5oco2pxHI(OD{z1`2s$6J+Zq7ZXq{&{_h5olugwR6PzGJ&7Hwdzi4E~Tpg>y%Y#+%h@{tAEU&(8_fRttXSCN86brtsIp(F{zS9;s zTCvV8)LhSTvepGTfd=JdhZl8*lRwkA8NFt4-L;dqbQydi-74u+tkmjBhiEy0 zUT5w@KEPkUJ>XUBSe0O}Vnq-eF{yXj3Uxbb9&wS*7yYd#sz$Z~O5lK<6Sr^H$M{!jX*3inA{()`;rz;ttoNT-N-=Pf`1y%>0-M)mu>CK)u z;;rM^jq;4H+g-CqoyqayzfgLaHpXqOFUS=}>9Fy>x&HlUL%B)dLzZtry1GNq<3)+4 zPeYHlUV+n3pD$CfiVJUO5Sy;Bf6j~sCY76?E;cxIhjR8t9`wRjzBPZ#)R3vswsGz; z8T}v_HWK;xd$bxTqbI?I*H@5Rrbcdk?KuW9bchuA#o`=>=@>Nhct5<5RC!@}ub(x= z?&#b&2f9rGwmed0cwv(X`ZgV;#(zw~mr<;u*0*eHaS$#F5AJ;{xA&L zM>+HMGOeF|C5;4*glErLsDyPHpXPBCZgg;7cV7 z%VincFLS27l(}b13$fwQ#L}^JxbMm)(8x{4{a8htJ6wg~Cid&5a{U*6m0_;J9+ET9 z?j3j}Lc{>y2(K8PIbu1RZz<=cFI1%Nb4wLTaH3PY3Ft?>SX@dbIAXu{_uJQAQ`_8X z6|=Lr_VcXQY;5GpuGdz@Riw;0C#gLBnh(gg(YcSYV12tDHaTnLD_ifbmu2r5z-rx> zh~Q+XKqKaC)colvn1}r?oL>BW0l?Wtr(=X*e;}0#e|I_-%mfuUCD;My%D4XWy-yd^ z_s4*IDn^8FLWyV2YBZ6c*cgZ>DEp~tNOaynRr~|+%^=wGSiubjhFe!U&lw0;pHj}z z7P@8%$BRl**ri~UBHs_K7@+(@s&F^d&!Y2Oy=@61c|bjT^%EAN+(#Cp5u@)uRb7ld za)1y;|1#nAZ_VVqTLbm8={|%ZT4j1t8FlYCHiq^YZ(WJf=7RV8r750%VCk~n%}SXE z)49U7I_s8SSU_cSl~0F6Rhu2~^0CKDavvY&Yp;(<_kPj%APAq)E8zk{zLZ)i4u9+a zWM^$In3ip*{Nqx!XGpRnHmnJ(K5to?O6Xi2tgm~6SovZ!>Dl zwXx!fHV)6>uF;%k11n?WDx)eI?6iPH6Ye%-X}1n zvnLp5hS+#^ZUUC63pGC!?0I$=vml(;r`b#9w~=;SP#Tu!Aal4-qfH48;O!#aHV%~tlc#>q7+Y0{45OEP%+jRfwUDT8H*rM7^MR@l zX(XY<=+?-^0YdH>w8b?i=WX{0mAdSmg=MW9ND#3oU?F^dj#=pQkGZeu3r8n=i$+w= zJNl{AFLS?{vG9R5I{|s_8UQ4BD5-Xmr&o+THNj-QSjkfhprhr@qv{?XY{n07C{>EY z&R>hRurUe^CMQLr>!W2hAcx|EH_}d9%6Z#?oU|h?czjQPU2o)|H>*gUy6+aE-|ct1 z!}KO+<2k?IOYVbit|4>d$VD|RA+x8-o4%vkC;OU!CGJy^j6+%JFyb)Hk;#QVZy$VS zCfsxGD|$BykL8SbEeQaRJ`AC*-FlRA&-By!5KUY^dIY!>|9wnp>9!%b<>?BF=G)Og zS^MEF(uIH-4%%Z|Cx`&{&m;O)Z(aq;iYnpObtwKr1ekS8Qr>$T+J)ssvs{bkEb6J@BK(~eiZ zMLa`0ZHgH`l3@GEnOdjibZz4r$Zu)en3q*2Z(ZEKfYM-R9b=@j9+R8^Hzk(w`_JfH zEz@3#;`&&DSV>!F|H)RVKB4V7m>cp23=mK)dgoqP^TdGVFTb~c{6<)1pwCoIBa8l& zcqIwD6!Vz%W9;LpNQB148Bsr8R>^PvFyA~-S`@j>RQ|j8%y~bMY;{<2_twZ~dzHjz z1ucf_%l_af&c-sV_=Tvky5tN>!R|BEJeCQfgviHwNL$Nv1r`q!FH&NXUr>bL@&Xt+ zeH}|+N^D4nB+hFdWsDNfR>@i7nU%EJ%(R!mfqha(R9n*SKbn=l#;169jTDHJfZP$e zDEGT24K`>3A|4midRkb4b?UQ^FYJYt@a)MAv`#+ff*qVHfM9>z3uA1th60UUn}Qrm zsc>nUxATOt89E3srLqqqy%wS==&%)~skr?as4$P)l5Lv2LQAjSUh1_R+UOl4;6Epr zzo$fU*FUctt>@Jn-{^bSG0mV&c&3*;C#LuSC1Gc|_EU-uLc2Y|77I(E&f7blDH@sfw(n zup4|n%`b9tT$TW~XU=Gve5MJJQz|eg{xD<*>F*y*Tl{>hD#1rl*sEp zFdeIZzD888+wh*`ta*u7|3f?g}8c&@MVv^@~Rjpo?Kd&bOo%*cea%PAd z>EpHK;qub`B~AkK^m&A|wbl%fQ`_{OS#q5DJ*<^h>!sVT2xJlNc$MYIkq81l40&5# Uzq5Yf&kr4ADwx=wfg3%`jo2><{9 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/book_channel.png b/TMessagesProj/src/main/res/drawable-xxhdpi/book_channel.png new file mode 100755 index 0000000000000000000000000000000000000000..dd04a6844d2d5ebc749f9a9c286a01892a330935 GIT binary patch literal 5326 zcmaJ_c|26@+dpG!Bx^in$ugEm*@wvtvM+;)EFt?WG@3EP$i78L*2orFik2ZlWQj(} zlBGqXu_Q|f*%DEfcY1!$^ZedF-se4^bI!edzTfY4-S>Un=W|~t(fW)j_d$_^003~K z%#3VUCH?nv0K$6Pj1t*dg>ZneV}LE$DZ2$mE z5$qfT9MP6YESaR_`5U7YOro&Z0HCcKO!35C#0NkzcyEHQj?{AVGbtzmrz7Q{fmT6N z4DlBTW}*Ig+t4$1*wBku1Wro#G*mkn$r2#p13aO@B%&`B8LT7qr!JCJ|J|)D1^p8e za8XC_w}38siuQB{S);YzCN8gRI(0#r>!RZUrib;A`^)sY%-q}nOy zUzZfinm^79X=7yamn~LHN9saA00pV69269!6a-Tu`+F;^A`pn*8ft2aEQBI8#5ce* zSkad%{kMVH!|BK?D=2H4>L>+k<5OtlN4;FWFgRPrT%ENkJsq<@>DAPxQTo&jWkJ2IL0 z_bggpAP11C7swQ-p)DMG+=1YWBL`9C|HPxwNR%%%z|$9tM;Ylzu@saD1RPRbU*8x8 zJEf+IFhZ!R8p9C=YKA7p>INp7Fcmcu1C77AMr7=cmpp5kGf``A}3L&~Vxbn^AlbGyiPI-QkceK>q8zr}tPz9y( zD3N<-TC@TuBc>x-m&3~G;XJ=nBUoV&sqRH7m zJEg;30~ReRSLLS2<7rRIm#jbfbgX&qTZ?z6?OLo;8T&<` zO%VE(?_xA9`(J&`)8-zPEBEK+Xt7?CXs}VVEA7XFG3ms^!WoVsKQGVgGuG7gG(T+| z*bK7^6I!rw3f5bfpoxpn72)?!mo#a`V8e80 z4t}!vcyGQK^!4uGzFiQcb=ESsNxs`M-aBC_zs=mLaC_a?!g00jhU=>cH<@KLo~gFJ>?o`p1LVA2TEe z0@d~6JrHY=DB^pXmnFiqj?nz;?Cp41|0~|qJl|p*Y75!NHS%y)) zIzQqs9H445E=fZ=ToNa{bD{)iq%zM)e>NTA9 z;ii8*NX)Vp9!F03wr-(lI&EW*gn&kkYMpGc2k<4mvMTuqMIb18ke&gzVevtNe&wj2Z?J&8o zrJQkm^$Od3h&Ru6=b_BIM(2w_!ROHP4U>l3Ai;2tknasChEPj8ajz8tiMBo+-VlWR z#yen7*3!;+`<~PKay|ED6%?YYUJ|j7+Gk2s0hT(Ad4%pNNjC254)HUkp6h<{RERAi zv(1XF_*J`GF%=LcdUe40|M+#R$E9?L>+(2#>iN@cnavXG+O50+@$n?CNKCi&m!!re zkZ*?32>}D*M7AYHO#tj+`;PPZ`?z+cVHrP;(DNTO@M`;z^l`Z(F}dN-KIs6y*lp>~ z-FBOw_UMd`#jLpvpa`=fkn})Zb>4zET08aL74{KB)Tb@ZcOSKm&nk2Ew10lw{_R^S z&c4L1QZe)A@Im<#E_1K$jEiFn48DFF2Hb5Tu9omCb;!z=Ew(!eH#G!{wqruxS-VT8 zRd~{q<~U?!1t~Oo~UXb7>AahT z-J7p}aJx{Z&+0xN?FmDGJ<8{h@s`24xdj;m-nB_Tc2lkIcV zRSwEI!}~*s9Pur^yO0Z6KvkvW*Z@OI$h|vJ=<0-Pp3pd8DC=TaBydmb_Rq~_s}BHk zX~j1lt(lvjpD8UjTzhj9$%AQ>5d)9+yU8{jLZk0hLxHnp zvX#Ohc)aPS&0uD}N7-y+y)D=7OMT*}hWC6|2|Qxcj6zVz(w7IvtJd$qgx%f)PpTtF zF(hqR;*Igx;k!AcCcAlgT7vI^b}ox!*=o3 zY9C2Pns0%307$*eO^>LL>?%9;Y-cPSy?RMuQ#)T>&qC-^ zF$}~jnC_;ctGoWa944}o;HLdzrI%Z=1O{Y-uh)PL6W&&j^ZGhu<)9WeOyY00KsMJz ziy6~$d2gTS@WWdwZcfb~K&1?BK`sPd>fNTbu3FUv`R#F{a%M_FkJ#*H&(B>ST<%hH zO5t!~YY$iDdNY2$n;oM3YC>R9tuMu0MoXjs2FNy$M7Fp2&9yx^2fx&7uFXh@us#b` zVSrHaSX>4M%)Blb(vcNxuES+b3X?zdt)U|!LFa0x-3#DweGY%)*T!MP@Z#s8U=NEI zbG5F-6O-&&Dtajl^4=0C^0QRrSups?qcddUS3<%Ih(Ik zxKw;ds|||@o7<^zpt}{fNc}c$eRS$8nL(jg% zf%=DBp06*Qz)c^lgdB3$WCs`2H(uGQ&Ls{tq@s>1y zcPN5L^i(qKPLi1sS?a|5*X>`%5ivD?yw1;!>O-$u(Ked4?Oqz6EF(}|T^cl%Z#grr z$A0HcaLedMiMOW#&n$Kgi6whNkT)mCUpUcDxHKAw%wH4|VeO5!@EC!4vz7M-kS}M> zaNRx_nu1=4ZC2ig0&eER&CgC8jK3cR?Pxxt1_Z0m*VR^6o_Lf-T(y6A>F{dz_Fz0R z^6Of{hxW}fNU*|uU1pI)61!)5`(wG`inZ$tKhzgq`}ppxWOwGM(f6*sbdvHP;{5Ey z7m!;(COIlsjkKU?Cte9P4aG8d7i}Q9);lNW>#kJwGLGyYrQJ3lkbG~rjyy5rc;<(D zQH4%c_5oI4gPFIyub=F3=xKEw!S&odFDC2fcmu@i3LkYDk3t?la?hgo@ZpONozJ+L zkQuP1R8!TZ{0hY>FS>fJeW{=*GI~7uFZ7EHa5BHWKcw$9iHI zMVt?WfbI14{a`ysvW+G4hg)sdR%Z6B9OuUUr0v&25ZC0;lnF)Wb3S{X;WB(9VSQOO zmk~FPfUCF7kP!N`i>_BoVnzA+DOPSF-#UMdy9T=;dnR2l@>g0Z7FaSAyhLQ4 zQwj@&J0ULfrn3u1_$jO=>_T~p4y_y{R=c^{e6`EFZpg6~!{5)@-oCPj#{erDgGC!` zsa&x$vEt*y*|M^KILpT8!012hbwnW#FV&1P&8x#Y58E92sJ_-B8xMm6sVNbNc?jJn zy}T!xIU0nfVzo0(_HAV(P9$>!OYTcKMwf@@{k?lqdGQZNj^2TbiomK*4v7?7t&>EG zwHL*1!xu5}9B%|Xu&(u*HN8K@1e%?W0;6&{UvDRgXqIIy@thqweS4z8dm*)>A}u1+ zQ>rLWMfIecaIxXK-up0+=o!?O;)ANa^@Xn^=9kAwL-mOAH6ESmYKP=h=^0Bl9dYb1v7e;+LU@zq3tm(V0X-7WY2iLw=mgnU;A%@1}XPek3IqGK8 zs;`&yXC>_@aiw*ynOgO{5Af-xdweA(Se=eN8TG^*WARKG0_@3b8x*L-K=!)6G+0`_ zHq+O?T%jaigU~!8uAFRTK4RgG|1vr07i}*g!+p_?>bx89FqWGb?E511)b3D|y;P!+RT3XF6iXN^oF#fup z-Rv7DBeEXM5#5RY+P9wogs-+mSZH5I`*RF#JInNFRkoNNk7XO}+khfwS|;1}FT3IG z3|=L&Gi7B=p@Tf3t4XY5;IX!`PkRm;wGmg-Jf>HXPBMKcYaKwyekVnx1-Y8$DS$0( zsl%RbSc@3yeZ}E)1tH+v*|J?0iL>9aVhA$b+Oe%3m>%1w1>j!x9A}-1rdx|9+CY-R zvQ1wDCi-)!P+0s%^^VyQsNqDPSE{^{Ffcy)5k~7Hnr47BKQx>2b5w@6E$1J$UU^au zVP?yq`C#NV*e2Bzh$itft5n4TcPV-*<=TT*Gxo_FQ5AOZV)a zo-zvis@Abdj{2?)OWN*m`?V{5z<88Qwfd{VXoZcL*K_+*^j(y%vWF)rP_Pn-3RmMs zWTHn^=hUAo6fAq6ZC}KTZ)X_`u@wNn z(CgfKT@jolu>m8>ev_QBzp`4cWV)EJR$0u+Ut4PMNMecyg)5#pH@~9hJ|5y{fCdikEtFF@6sJyE@vueGOt@nPIamiG?seVn&`GECL< z#2*~#W3-=It07UE-Pha(On$wLEwxvtPmd4(5}T3p95Lz#);_77;&8Y|1W1(H@)m0E z#I?PYc@fko=S!5oO6F)7`l$BAydiT_QbB+cHZ5l_VRsk?kJ$fXCDNGRJ%HUcD)*5P zl*6Vj%4Hp7FNtN?QZjf^gk>r|=BP1PX6Qol3U5Z2m600z#m!G=Bi#52<_5-&-0Ay% zmU84{c(Bv(USv z=!kI_xBrb1_jUI?X9IxJOs*MI)5?%w_aodY&3cXSCKs zEYkR)zKP>QS4RZ|-%S->C11sJ0e2+Mp4Zpi&BII4SDEiGUB&bH?`>MHH<>!I3_Z9Q- z;{RI#j`VWGqCIhFj0f*;MSBN~H%^)F+|z%B;O_Y!Sr4y&r|CRlP+xmbsDwD|cSwH& z_4NLKsJr`r&|WwrL;5&z{%{3G{wE4X`}TZSXC z=m$u|EiA^J_pgu@(f{ZRO!FW0{>?@Fqc5;q|Hy@&I|Kb)*#A}Nzqii2=lAqKZF|1> zPvav!&bu9Z-qtnqIb;C9s;doGGx43<%BG1!nELg$t*@uA`?aA^2NK8k*sW7ERSA-+ zI!PR|hJ>&@CP$G-KBI&v?SfkASNTTCY57_L1suf@adC%QwJbK}WOq!MLOE2s1L?Q? zSL?n7wLP@W-VNSGJBRPAw|@Nb$!oW1Vf1I#EV5{I-m7&tYd7mSdsmeEd6r;1>xRWa zHLhk0v5r8a(2i(Rqv44@CdbH{snVQmq$Sc4mF{GT;53))ksOfh!p0Y*5@`psaDMAu z1vHlv?ZgulA6mgUHboMh zQZ_9_$mV8PQ*x?L;)n_GD|&%mAuDHl-39 z`c&c>w-Sq>RZ2aiY7TsQjWWwwP)b8YD`w!Xg-S1}Td0h>kXw~<@V4a+|FM#-(B-$m zzDYzW2)_fcCv>`P=u7NUTYieu(cT$E{=AclID&;}>=y0%oJ!@@ff)Yy3i(c8?!bV^MZKJ{slUIM?|9yxL|{z|bxO_02K zXr~LUYZs7pUty32!o#Lat<2GxYEf~X@8}z*;Wh%zj+Lpqg7JVq`Q=m>^H$z6Kz{k8RVX+gFS-w*;C-#%03yf* zJSW~7kLlbb<7J=qFNP5*6HY#=5xa3`K9rI{OJa>}! z;1HChnQ7iR3dGxRT{2FaN#un=uISzPOMaFuviJwYgFbZZVv<>%o!g~SP=0E)vSf;S z{1$1>`(OZI*xKKPV3H+RbXLlBQU^K_K(N}iM!G-3z-tqBeqY;d;h}tERJSdWk{%&X z^DTlxw{Sa;Y(Sogn|7K<6wvFgDZQ2ll+(a#L%)QTM&mttlbxgH%uEM4y2V{>CR3_U zp{$EMAx^;i{CTJ&|6x~ne}M*@oNT)mKI}3qp&7o%%(AB%^F_DW9N&8`NLMTTadLxf z$)N_}#*Dikp);iUXB5o^nyqWvdg5z7j}J;KTPuJZHX=LOGIih_hJDS9+LFty-8uK^ z?%p84khCAS-^2hjAV7D*LAhTlR<>C}z&MhIEhfZ}bFcRBOkI@|H9IItoTlC@$~#fV zqfHez`l<0s&x$)m**`qyt$=upj=#xBWLH$beRSCRun0hJY{LAFVM z(PW+Rxu1%|!_e`=70B?A*jR^ALW;SWaNYAf3&^eHpdM4PdId zKA#MFP}+0dDFFR#WeW10{#nKb_@i+#NILO}OQ3CNVxsQL#MaZ+?&p&8s?;gr(9}aCN6-c6G$q7b=_7jPf<>gkHy{7EeOoEGPOuOuR7421^ z@}l?miXaWccg@~YgfOy9e!(a9eF1l44}aZ~=$2aVn#xL9P#)MB%nD^UMZd;IHwWmL z3Ndd{>y3)XJ(|xR1w1c!W}@%X5$!CeYnnQryx~BGa4-MZ%JH*+NhqW8KyMRelh!{v z148C@s0n6%@j{lH^K_mesDQP3@^rB0*py)TD}9U94sRoKt8J}x&)D2nGYlWS_2E1Bu&Z7$CNRF> zZL5BcWc!iJ;zH~eBgL-O`%`rBWaeWgaPbGaZ<9cU#F6%WOPhDtXR~duT4%nF%E(Qj zt(x}y;}5z$EBdAJr`K1TBF3wpf3(^1*|c9;|DXD4H`lJF}Je zX2zK)8^z#%5Ae?;)%M}*gLmmg-6KX2|OAL8mj)=!GCf%zlSc%FWxZrMk#|Yak9%s+`-Qbb8vzM-M%lcPk{q;v6 z>p$M4B=EvxY~4b*^LfG|67ZnD3?X>32nXIIm_1LY*7lgH1H91%glX}EdEw~_9E6bl zPzJ-9dw}S~C3U!V2T)jW&a1vao|YKIz&D_jr*`}0JbA^w9A&*l^Pe6Xy7YjW`_ znEui@ZTkm}#zA;QFF1w(^t{~5H&~`H!Z>RLfrQd(mGfiZenmAB@uS?2Xl4Y;B zS1&zdIi6-AGeNy}-6H;h`+~YM>KI!nbN3Y(RiB>erujB67;KVRH6YVE^F`4$S(m%X z{tm}yGW)T2Gw;wr_K~-GiqLM@TUO}&JDQUOyDOHCY1Yv$%BoRQH4omKNf>R3)q`F-+};hRssQ!SRh zyb2YsrL78kRl%C{s-`7f(W2?KWS32|PUJ+Hp#JwiMW27TH;RX_UU83^vF&)enWI8F zh5=J|9*W>r&CB-ph?Gxy%4s0>lybH89_XqH)8_l*V2Wn{k%Jo|!z(O^4;mE$TRym} zKoa4NJ)p(tBYCGt*1$LG!Q#(6^!s-Zt8!_9G<)5pfCX6gF3-W+DaL3vclxE68gdwr ze~UIi{z3p+C}?3Z?V!0W2p=PrL~RbOiWi?zR`M$xZHW5T*Aa8)fnp-#jeIZ|j{yr4 zj@0f)h4QN$)*F(~5_3(uuYc?K_=R7YJwVJaicV@m-GP+`mTFO7Sd{XdmkW@+-$MR! z$_G8vRYIhAH2LyXy5C$=`Nsj(rTu-1GX>eX7Ntjrrs8bOtaEQeV}PdAtfX)fahc36tmM<&=aCoV!&ZY?!NVBFq9 z{jr>+ih^r-w0^AI5_o;!G_opeiLK_LUtiT7a)Lzhfj70vM(HXg{D@|+YSjAeiYL^d z{Cflz`)~yN`D8A9B@>lA(&hxno1dhc5gSql(VH<9aXdv(VCn3hMw!%e${mLJIUtQN zsIwZpqrc~+XjMC+`_uCIv{BxN$KTmU^HJjRRup>)VigjM7oCF3tT(T=W{b!16t+bo zk9Eab#YUZ;_9MsLD0%lTRcOB8JrH7>%%lWiNP>l^ZlGE$Hsl-Uw07GyTfvKlSs;$h z)f!F$3>{6CVQ0-0Bkr&uz01484b2Z>n2i0Cmg~{8OGqhTueQRBVlPXcMLw|Kgbhzt zoM9Jzn)M7LjYr|?T=ahR2ql#%ZZ}jj)X&vHZpOiCU7h=D-k1Av!2kcVL zm}ozj&Ynr8f(UdO$tujWpym@XK``ETvq=`m8^?NH*7$viE}iyIyY%R2#K&j&N%Ih`H4)8IHJ=5O^kXF^DYtSUGRPrKHgw@ZCHBab9Xisc6xEI(&!*0AayDEcEwi*5>Tx zREm>~b7Q7npQIt%qGK1Y$@{&1gw0nNuOq`#q}T~+SBm@gW=%-EbJ|X$obkiF;$~Ls zQ`cEHi_PTO&x}@tWl#)qc>B_Uhz1ka z&rAd=l{w7FS8aVFgPmsw83l8TTIj?Y_815ys1#_>*q)MAdacBiP^QIIv0Nt0J~A!P zU@;>HdM)Uy?hK|;Q%IKMzNEct>#Kykfv32gnN4H1w29!fsqm6Qicdlo zJykvZ{FkB+%ooUkh|8y%DKvTH@x+jcN2xd&RUf@PW(a0-78D|DYvdTLj^Yn7NTOlS zz!X4`TNI88Wjz5}J{loL$@GkVJ~byY+Cp{x`BNNkxF+qWZ8bwvI(IWOLcN#y3zp;*u8Wh=1~m%&B3_w6LAlm&qs=t=`6`Oe@FBBo#8*ny>xOO-nnCS2v4PABsF!>2Yr#cZ;`X?uTakhJ$0)0JI5Df@;F-*T2idpC&(YRbP zWE`un5aK!WtDcNLi6HabHKQd!UYj>YZFw#XRaymj^uh-3r@Z8D74K(=1g*{8-a@XM zRhyPy-G+!k^dP1iqOW!*q3f?fdTlK|+-70i(W32~C&d~4{>fR4zdl|$v%`acfvy3a UPHOwj-@h8PHTB^Y>i0ta2VaAvumAu6 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/book_logo.png b/TMessagesProj/src/main/res/drawable-xxhdpi/book_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..ebe816fdf5031626d12f59824b30cd7efa2265e1 GIT binary patch literal 2056 zcmaJ?X;f2Z8jc{KZ0dj`i-8y{o08-P63B&!fv^q4A_Q3!Y?ccNc$1i1Bw>jxgBF=u zTu#d_SX)2^%Oa}>7^O;JfJjv+)uGivQ&a?sC}3r7tl0h-=AL`+x18sB-uHXo=bSGu zbWebZp_L&9gE0vTWQL)mi}qWmkN!cchsEe%iTFh!;o^8Cg(HP948E8P;eudJ0u%;u z_=>|S$P0tf#S7U{NE9msJ|vQXaytH z9Rx|D<9|;oiWQ3U6-yx;h2ZMKBLM)8N+kep6e<+nm`0-G@4d}Om;yLE;5A(;mBP?GP@N8 zCM4rYg%U(47U8st9IiMSq2tj^e?I~&`5-Hjy)P45Fru6zAp!)F_DCy07VG~*VfX`D zhJ-TCL|Ref%yJXF^qe6WKg(zE^e#pz31{* z&xNuomxv}q)He1{jb7P8-J_j;ur0dyV0=h~x?PIen!2Sk9D`Y#9>ip@n{LG`H>9QeXR$;gm7W*f<%G zzddFLW7FQfT(g2|ydOlpzpj3vel^`XBGRzSEyhgRdX+{D zZ-&7dTXNcHK_O{NyU}y3{U^=l0$cW}$OEtH33K3@(g0O`?zQMOoiUpijWnlw*Q%Nj zjbXQYbZ(q(naxc}!TK-pnO9Eb4sN!war&unX7MA;5Rh{u-~2z7w>g&T=**5|+5bM* z!*WQ9-fs7Haqp^R4@|TsrM!4UeRf(JnX(wkl=@4#k6epk5auNkb`?1Z#PA6nSnJ0FR zw!5pi%*I3ydr2@EYT4Z5G~2j9$aLr$WL4E>4uk-Q@|J&OJ$Js*P-Z{!b<2_MNAb0V z51U;Yk$I+O-&d!rsw^A6cBtCvS*z1s8%uZgy=C3--R&zIerxs_ms_#1lRyt;{LaaU zMM1(s@a$rHQ`U`jpA9GUsToz8;6TNa{a>BaY%W~&__=wgzaqLQ@uOD{9-Gdl%vbv? z8Q;z+pnBxhJ-RFE91iUGNujc9Jr`RCf3MhUV*EV9QuTbB^1Fj^@CEGA&Wc3UqgIoW z7wxHDjdZTY6Lck|D||gO_K$^NfBy8BUq>c$?ht0~hvprx?0?p)Pt1OR3|f;irnt$M zrotwSV}+DDvma^v+C} zd3gNky~C5Re&Ua+r(9T>Yw=GZ~c&gUN$+qXY%nVoHZgt0*t|D$ZZv zWi79t$mjK%PMa++E-6Yr?jE1(!wjDC?&x82END)N%~@|IAi`K7UK@;2)H zP`7ir^<(Smmb4luU|H$wGFq74Hqk|SwhL)}v0ji|r z8}^8>L62S<>FYRT$M4t-=c?D~h;<~w`@t_a>IO5@mrfhszy4I`b=dVinbGS90KEsn ze<2-BI8fSqqd{HIZZ!tiUEN@|bEqGFJb|Is=3G_=ly0+sGJhwkgxm6IYM0%;ep9KV zGX8BrYDrdZL&g~o%MBrf=Ga@8F4NS8R&U@pSVtV&P83+W+^^Tv@Efrk YV-P9i1Z5qV*WS%RetVd8KA&X&7o{Id0{{R3 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/book_user.png b/TMessagesProj/src/main/res/drawable-xxhdpi/book_user.png new file mode 100755 index 0000000000000000000000000000000000000000..2f54d6b5e7de5b8f0adaad72ba241fa18b95943f GIT binary patch literal 5470 zcmaJ_cT^MGx1JCUA|MKgbVF!@v;+uUsv4vk2n3{uP%b5u00AjM6ahsLq>B_aRB0;G z2@ntnMMZj5#1g6sQUrW>fA`+s`{Ug=Yi3TF@B8-NXYV!ZtP^i;dXfDo|4{${*bPwz z=!2H}`#AzRc%!GhnGYI1;(095g5X9BaSp%(x~>ElJk-#~*&UC@JG+MZ_uw@F01WfA z#1gS4#_BkNkDT*wj2y|w?|=;en%X2kXPg(F2z9}`d-`g@RvTMkP)}Dan6h@!AJ|{K_vRA%gcv^gvf;`$`JzG<>6{-YQHrQ2-yRKY+$G_(U~Oc z8z}m>f&o4d7vSkf^d$H~e=9n>5Q2zWumey36M~Q5KeE1oe=XC&g2|Jd{p8_t3co}8 z8)#zk|3iIz{y_&4(fI%R`+o`tT88@J<j&VSiG%=40>S(5 zRW$b?5D9@E1V5<01rmD3+SAvS5E3Z$C*H(F-Ox9X=|hVR&-64S~_RA z&oeArdIdwU#N1rB{bbpS-ZS2~#yCY;fGX+Cl*6MVGhUn0SKV6~YNDAg=@?_M4CV5M z>2PzYhDp@91gEJr@NiT~OPRVEm^KRVCeJ)%EExc%%RRl7KhYd+$9WXVn;!^_{}WT7t^IjmpGOv_D@+>Y7g za70H~DWkw+=ZhFzrJJ2qgw>bWce9pB#bFPF1LNbS&2EOe2w@^J+g4eWDKbe}HOu*+ z_KT^E%ftS|7x}hYsz&ZDvsjFnDCw+OW)*Qg*g9=73^%1C3f8L2t!fh>M`C9$@7ZpI z9`geibl%%@usEfp!^gT`R{E*a{`!S$3$_-gic_ulsg4R6QK%s8c{yUf>9K6}LXajz zkz~ zDsTyxmx+{>%EU6zO!-bcBcMyqyMBsB&6#%t9tmw1e8@YLV33h0!Q-2F3p7L7Y4M}n zw0rcLMw>iH!7L`?Vx4cE%whP~>42R4`;a=54Ilpyo)amot_wfYn4=KSwBugKQ@7EX;eF@9IuAZ-po$A05ZfK?#0`rKF9c+-g9puRMU#e zBN<9utGdQoB@@C{V5j_VxBVJYx0p>=LMQ0D)ftd2N#{c8_u3wyeKl_g6}q51!Y;*i znlnklrbz+dgle^^i{L*ReuZvZwLfHao)Jd-+8Qq%{sZ_B?b?Op^Cn@sfXar@-K7X>aMrlm!4U}i~Oa`dx%oljN8VjE)wQ_Dg zJ3(!h#eD@qa0m7E4fNDk`|~v_NTpgRBGj}*ODt{CUqWASN^_wx7^xs-ug4X^Ps$yw z<@*-}T>G=7c^vkk657Q(r?i>dRQcA<{M;{7RLMwb`B1zrdGW(8$)CAG1^KpzQp^74 z^@P|kMoC|ot*pW_aR*N}E89<5X!J>^YAn9dvF!~2|~9@byJ4FoIem04Q)wJjs^ z>pSIWk(JDCd9G`pAbUGnR~^&lwIW&$Yoiu3-(^O=e9(L`cU4TSM7f&b$@0~}#-+AAPxiHG z_nn(ICyaEzJvSo_GdXpAy60(&lGsvSb5NTRNbnkOd1Arb)2Eq=lX)dxCUdvD=rAd{ zt@YVtpqN-2hZF6Q1D6eg2OE@h36{jM&6U3J80@gVeM*mLQkM=6GZEyKe`jWWl~m~l zlvjJWt2H>42F~7f+Sv_FWVy56-QLc0mI3oA{b>XT2EX((eczqu8Y*aRVdb(2V0fpT z43gsD_98EeQy+S@$9|99T-W(r`Te?sXhm8R#APeig>4O>ufVV~bDxal#B}ib}qJI`7`NT5wEFol(Gj z<7_(MAzk-*ikxfL^aEv{S|)H0OZjESHrCbcmBhALaS(E0W9kC&L{7#jV`G!2O!5m) zYCdPqbV8{98|*SgGGOCLFLFtVjn&*^Xf5GN4`3Wvcf^%Yl~zy8KACw{p{X3S;<7D+ z&j%APX+p4glDV9bI{8_zm2{c3O>}+z4M_JWz{7kw zNSs{Q?_g=$%Rlnr%G}(gRvTzk*2~OY`Dm(_Kx^|Q#+_w= za`p-TpgJb@(VF>exsoUE=(|jwH$bY+b{-|lD!q#@f?_|0`RQDs$6l~9ey3#p8aQ8@ zyX;&2&MzS!oig9ky4Pzu-k0IfS#tv7fm7oqF!j|5hHu<^)Z6rnCt_d7I>gXOF>hD= zRjk}`YvZ&rp@!|9GP=6P^>N{F$(UAGhCfO;1@)~tG(KN|$XH6guR6Ut$%4p?m>3y; zXB-qz`;rUd3>>eN>;5Bop)0sw(@z7Ay z9m`J28Je45xj|<;vSRt`4S)W8!6)r@y@r;oqR?SjO@A9St4XVSh77#afOxmjZ1+EI zu%r8nnpa-|Vk2VMLt+E=P+qdyGebMW4nWf)m6pcG>vVfT$E8<^jT%Tq*=3PIq*6ru z*%$??0zH~`&zaZh`tQ$28g;}5#GJ{BGR&M715`i~drXW%#68ET(A%z1DDD_VgtFq< zp`D-WOZT4|hauBa>g%698uHA_b4=d8aoGK|ZegJwXBx{VRSKh2+Wo|ot%uL^x8?Tp z-oY>R>iPG2?12XXoVVUprNz@l5}YC<9R`xjN+cHAY4?ncP0QTGv{naWGYtyc)z%9O zD!Rs_Yb#nt#)YhGdc^I|qnW{T8Bg1H^UDw!?KK}+_}bol{LY1_0G)mtyT#^lg0blJ z$kV<#P#B<@D2Bc+nz%1j(xF5}AD=j~{-HaRkFR-7bD@Kjc<}SNQ^m@zR4jr-Dqo_< z2EBQU9b8VW*X=Xv0XPj7s>`u% zq{QZN$k?gPH`JkK!rJ|Hj@&><`DI3Ju8^Sr^|qj!{LX<_pDISisr{^BuhOnC`x#vT zSDVg|37f6JxV;)#BF-j1G=@$87?E)2k&ab`=kXny_g9dHt4VQ=^CO}a6a`k7kE4Db z6V-udn`ECJ8=PJSd~2dz*min|-%GX3_+JHeZtWvVjPKGWPAid~WE+v0Z@Zv|YJSA<)57RT+O_kRtYeqOX+fGZMDyQEli3t%DLjZnY#q>W-+?*0M* zm+D`x@0i!x8?eZ0bJXl6C5{00FOP^Pso&enchp>p8rg|nX}`9lH6<%~m6^9q*q`Bz z{vmYki!scAY~&JN4SMi&dqANwN+%E7b3&qFjbDa&{AX_ub@W^)b<-L_HS2sYbK+*w zrutswdybAza;iA#Te{xAT*bkC#gQKKxf}0-egQ93emV|(`e5ApJd``q^zXS|GxPciUSOqqLXc}XCuN_k0K!RunB^j&kk*F-8sxSA>T} zzvewYrs~RaBxRM`1t?G2G80%~vpI!QxNT-S9#CSIl{qj8%oz%-cpq$j*>0uZ)2^P1 z!u9}I#l6K>9M^{K@39;;>T-yC_e=I5x&Bs))%Q>0NXp=F6XkA%SbB;Q-0m4jp?6!5 zeiz$|lZd^A1h(4$IIV{|l7*-~`ofaqFU$lkjAR z;S6jAjq`X}QL_@T#eC;*oOr~qi{JM0Sm6MYGwPgo$gMs%F?N!MmMzP??V2Kzpdj!3#O+85RJO%n9|6j~4c2`w4$nmsH% zNQa5Z^H{50FEi<@#QYv-O$bkji2r36QXvdGbw@ zHQP~x->+cxgi52J$m6xbcyUKGuv7n-Ip%#xX&CnABa~?lZ}$2k>+Z}@x{?aDqY+gA zb&DLNC6E09R5?C3>YNuaY`LA4Ilj7iD$UFKyKrj`f|Czn|4D0Mt0usA@g*N^l~ZC2 zMvv(bj+~_>81w^kQM6osypc}&)o&RalwEA$hU&bLnbx-rGL!QQn{YC8Ga+a6u2IQD`n3|A+35PkHUPMldro{sQ^7s1 zkeRVQO}V2VSnI?4{mR2oTKWt9+gaV@h9;@o(^Cd0)+G$?nmi)7{F9@Jy)essr?{S- zA4zd5zm6^$%X@JI?%ME_0~I&BD=BjZMU0<;_UX8D4Q8pc?)5per!bvodK6N;j|z7< zZF&jj$?(V^B|l4mV2|EuZXMVaE6*9@gZ`8XZ1kg?Vaq?ZQuRE@6l=GpZM7H65ET?- zMt1MTmBf7VX^y|XkVY2u7q+&4yz^{La6Ui*-@7Ai@k1(yAaUaErM53=r>%V~v?Qj2 zJ~E#f8q7lM*GUB~8XAM zoUXr_eUxS|uDngiRjrbFU*c+V~!52+{o82o{7d`V`I|ClYz8!CtKLyP@s2L~b# z`zae980E5TORs)2cYVH!Sl2PGBwhM;8(d;qf6T zr;rlaYOIwdl;x1#>HJRT_x|yo_x?Q3b1$Fk`d-(4-QVYWt|!&e-a?39mLC8B2w7R8 zoY=h|`}RD@!+yGA#Rb`&49y%(b0+!G!Z8#)zywFa;(=BKj6dE9kHJNT_TUWw08UV# z3z~+uvqk!n2vE#V8E6E7%+>|~42&Yk7~fz#4T#122NDfIt4+^9z(AZK$W_}8W=A%| z2LxJ1QSi=D_Ab6r!M=JpkkLt?K?ITwK)};5zz9MJk&28k1pS4JWRHI?tAl`liO_-# zLH}_IZRZFyBT?``ZKx*17p9>BL?ECVTG|MNh8hqK(}1hP*cSq#p@q~&AmKW|zZ-~c zje_$-I-ySeZHqlK1O?D&WTd)!cz8H8ToX#7_^WH^>FNE%fWskd2?#ZkNW(-xh*ZU2 z2q-+&ml8;(1(JxspNJSNDU4}dHxusu%c6R^2 zD1q>gG?nIr|F6IQCot6|l8je(!c$3M6kqni`6>Q1MMj!Y@E972;zA;Y{EDJu0EtGT z29U@=GiL-4>>5bKk;17ef63d~A+3m18iweLw?Y|$*a*%bxs0d_0lOb_$!; z<_mg70D$1A70SdV;@y&GY>4w)iDz+-XGX2k#euWIir%`8l9rBEXdecG;Z!Efvy4FL zaC41kKDbQZ6tPqdh_N(dJ`&>N)8e6mJmV)>SexGKwUV2XRuVU(gY@28b+$lRpub#wWiovEo2L`e=Yv$$X!Mf6I~rt|NZOykr^2QJ@fCfvuZoy)?S z8Ige1lD(hzVqSeY?)RywsXnyiNpi7pI+l_C?AA4b%U;!27gttJ`sN-?RBHsf>ULDB zM1oFLk4iDg*?kWgOOWz|<%!x%B*itUp`qdEW1Dp2-=B$*Z{^%ms4YhWBl{*ShlL&t z(fj!2$)~R8535;>y?ggm`^1S)4o>NaqlQsm18KCc(;wcNv^_HLFIE+JG%#+#k#5Q) zXx!R5b)#B0K_lPxXh&Dqk2AgbfbQ*3ESBXa2~&JQ8U+fTzUDp^ta2)Edk9V?T{n6g zMx1W>?iCdDNdv3rbbP$hqhaKJNr`6(V1{rgc)`k#TilIX1bohzAtol)%WmP!Z!r&s z0^rg~sS0tf$Kz^^ImY=Ton)RY?-dspS09wV_*(+kq^$Ous)|YvJbHr_F zRXC3;7g(Kgk;$z-jz*E|vxn>53!n}XGY)6V>p@`fB0Ew-g(baYf?hyUUPUsruhf*IB=tmRnc*_*9ywV{#f z-2SX^`+XCbMSXAN<$<(x$!j9+UmiVo->=c+uz|tBCy!q;6>D!0?m4Ko(5H0hq*k+L ztyi&CKTNd^p6aV1UmSJDq*e8hWZGQISO`K$P>?1pEWF8jrkdR=ngSLQT~jjV$mP0T zzH)}k;@gQy_wV1oYeOKA+_JK=%Q}R2wEJ~+RM8;;SK)X9Oq$V^am2>X!NL1Caq*2f zGWkn|D}nW4WG3{<9vY-jQrX=6y{*J1H)DQw_BCi=-oVR8^uAo&`fShJvlZyI-ISD+ z+h!=GRCm#CjVts%$(OIvRg+s2eCl)n7S@;=5j9rt{`cMZ%hz%51JtBoE7(+H^`Le1 zh!y=Cy`WlXHXhXN59-lo3CkO7*Hu+jnX1HCfN~QTPp0_X$t><*#teCRdF^*qR8&;! zhKs#*7iCq0Us=B++9|<0n>OZO>wa`~{`rS9Uru^@y87nIWRo06%4!L6z_KknF?uIzjn$#)vj=9ywWUV$AFB=Q>PAu^ zhK&e$#S{Aa$sP-N{J20ryhYfTrSA__j6i}^p6&1TK0-Vh`Z#YLMtW*j#sxiPaondb4XPXZu#%FJ&po%= zcrOQg1%!m`Nr0D=vobLC?w@64Wk<;Ks$OI!3&E`ggN?=W`F3PljYhggC)9?09$73b zEY#2`e6DRzoOlv?1E9+Tvaw~?0~lit&Z^F+_sm3Y6wIHqA%oLeS}BiDGOYwFcl)Bs zI6J-3TGmq3vXVuPzT}W09N@K$j*gb^yYx{H+K*B3cpQ={6fYtUZ}4oki?KVYKU>!C){9 zYieqOW~U(SE8Rz9{`~rJ!KuvWPX=-FX;^{sy>Q$tH#fH`!x~>(J?B{dMpHmQAQv={ zq%Wnbt*!l*^RE0w)vH-qStyG1g@u3s>$7C!wR}O#?)>(a_*KvM+L66**4`X{cb(lAMdU}E`cj#_^ zc%FPVMbJ)sD!UaI<^AB(6E>IfTb}Bd^7}}CzhjnsEi&qe9K|#uHolGX;@dlP>$cF) z&{S+6;~?E&_v1PEh3p1i4p_6n8H;4Bk7=ROW83#gp_FI(2Um55#YIn#!H!{u_&Bb8 z_=FTUI(N3zMZt9S=8Jh^4gP|LesY$cmS_UsLZkkX*Ao*l4k7t$e%KmF-UaU1Z_{~? z>+0&B26Mb?iH+u*B3YnxM(PRUkE1z=Hg|>s4k;VJo)E%5y)Lyz#g{STHsxmR1e?o7rbzEVuGmpkRw6D!I&c%<|E<0w1pb7S4q z#>qGCjI6BFqeDZXov!dl_4NhlCuEG+dWd8A0qVC;Z}fnlZR6+Qaiu=z!@@ArFQJT@ zrejAgmCg0#c5Qw8b|pGcrpm20VCe2q#R1a-k2^~{8w-i9?wy|Ijg5^PS;m1f5xw&- zZaN!h&CT%jCJ7yO8cZWfPrZV3fd$Cp9>$N=P0%sid|JMuZojK}JXZTKR_A}e3n_nY z#@o%!ZCI??v$s-)6R?LYI=sI!)x7JWG}uD-+Ke*yd=?kG%&rn9!BK)MUQOqha~JV- z$R^F=|58K(as9-6~o zR1ANlX`l|gBsxqb8FhQSTJxVH{*c;1No;@5zVC3mQy>q4TIGJf(qERIntFP5tWG&} zb7^d?D5mSV#rq4$;Gm!z=a)u9pOutcY94PFU7v_x#QL6iTkTzKM49b2PicRJvVBro zg6}a%N{o&2^P9H_Uzzy0a3FaXm)DikcfRcTL|Vh&v9 zUkwi%2@45%UQ()+X_&nSCv^wakI>Ga_X;2q_k}OeS6d~Q z-`K<7mFiupb#LG5p8_aeobh+0C$wHKKQod)z$u-kQ`Nt$1Wbu=$G_RpHK`kO7yZt4 zTnZ-dAMkB!%eA{~1Csw@p3+WLlQdfk%gCN!2Wg}BQ;ZF&>or8FaU7*5vKbnpC~}s1 z=H5`ZGIl%Rp~b79`c;Lkb$U6meQ;iCecs2!q*WP}EN!X?vGjz~qJEQ%I?c?#k(=8fZCmYh z_H$nJyAu>DRSL9K>CSbv4SVtVWX_}I@{tZOoz{zsKl>D`_{VL0Ec3cE;? z;B@lv*j;5;3#6X4)&YyfC7+cr91Ha%rKJusU3mNa4bw;wu=(Dk3LMtP z9DEvi_H!^LkLl&;Ncab9+>ksn3P~0k*w=^#A|> literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/scroll_tip.png b/TMessagesProj/src/main/res/drawable-xxhdpi/scroll_tip.png new file mode 100755 index 0000000000000000000000000000000000000000..e736cedf84246be5edc8a4b7aa6347a2d0a0ad04 GIT binary patch literal 1531 zcmeAS@N?(olHy`uVBq!ia0vp^(m-s?!3HGLw2S$Glw^r(L`iUdT1k0gQ7VIDN`6wR zf@f}GdTLN=VoGJ<$y6H#24v4 zq}24xJX@vryZ0+8WTx0Eg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i={n4aiL` zNmQuF&B-gas<2f8n`;GRgM{^!6u?SKvTcwn`GuBNuFf>#!Gt)CP zF*P$Y)KM@pFf`IP03tJ8LlY}gGbUo-h6WQb!1OB;3-k^33_xCjDfIQluQWFouDZA+C>7yetOgf{R2HP_ z2c;J0mlh=hBQ8xDWL1Hcb5UwyNq$jCetr%t6azByOY(~|@(UE4gUu8)!ZY(y^2>`g z!Rmc|tvvIJOA_;vQ$1a5m4GJbWoD*WnHd{7JG!|U85)~g8XCHqIa(SyyOv@OxC}mUbZnXFy(u?IEGZ* zdNV!VTO?58SijQi`3}>L>MXp?5p>5#JLZ($n?tvLG2C;jJ(QunsGH+p{)JYZlEgQw zb}w4ZojUvS=VQeQQqOJE|K-G;<+i^z^Ygst=El`&$7DI14lG$?xq)Td!Ngge%9^Q4 zRb0m$_-okK{#V@1H!mnW*USYx(6r)~PW zCoi=(=BustQM)GGap=%4?-eJtY%Ue1#zcoco)hk=HTl@{1SifvjCP-l*Qr_Te>8b& zrDn{c?GL2wV-Hzk+Q&6$&ojRD{Z99~=q3F3PRzV2vnc+-UiTD>e|>)oRezdJG@aVU$b5_Q z{bt*5{8K(IJ5hFE`W&U`Tvv^s)@6+~tkH@W+fCN@d2F?~&$WF<{X@~>X78T|o~&P8 zv*=A)ZRN97fyJGnC%7UNwNqNp=tr|(RZI<^DgNua*m{XiTyvW3BDmk4KO|MG@_y^x zeD?UF1J^VwW(2By_H&ASe&)aSZIYvf`{x%bzuBJb^XzW#>C&|u zM9*_w4tv)%?d9%rxg`Q}8}Fw~PEqO465V|9>A!xz>1SYOh(A)k<#YIADNu3j>FVdQ&MBb@0M8jt Ai~s-t literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxxhdpi/book_logo.png b/TMessagesProj/src/main/res/drawable-xxxhdpi/book_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1dce719a5b4ad3ddaee95cc7267939be6b7fb2af GIT binary patch literal 2434 zcmaJ@dpK148lDldZP(+r%WX_5skvg9F~h;kFf^{ipi@jUt1&UN#>~jL)mEG?_DRn+ zq3C9ZkhF^sX^x)KCgdJbW7~yl7u9Zva+Y@5=a17_&sytudEWQ^zVG+F>v`7U;6Pt} z-L<+f7)+nxM-7EW^75mj4ZTT=!xCs%Bcnyi!oXd!WR3)YdGWxV0D>Xp@PSZ(!%Ip0 z1%McbGY$<46zg73q%y;tEQVsgn&muvR#;1 zrq~CF6ZoY{fUwj+7B@AXOXMLvJrHC$2@((jG7ds6Ob|&)atiX3E(w}1Z)1>%PY_u= z1^H)Ek<4I(4=4c;E@&qd7mLFo2n00F*@ZyBZ9(9%I6MXm-2@cQndCws;aw4*FC-LA z!rMg(rP4pgf>sn{oJ=MrVKB+b$>?M!G$`R?a6}?;Sp$zpK?syIMI__MQ6lNO6$L6F zm@M7&3D0Db7$T{RBjN%KDg_BCpalXRiAEqe(_LwJ9Fc~@;b;UR&fA&le*djK9?0tykIN+t=uo(uM?dY`$x zujhjMDi;GKgIR9ue>Hk#3v$o$^ownw#TVlPBFOC$$kx$jr*vR2t#=Hn7fU`eTC{Jg zBFU=FbMzZ4E1g3>7-@y|^k3}UtBNy=%QLU`3;5Tw+}#mHISHNNMf=pb@m#H__$N=x zP<0N>;J%{@vQGRe=YY$wd6cJ_fq}u)hsLx^))wS8iu!|1a&x`fQ$4Ye9@9O!@a9eW znPW|cF30Rq-#e-Pm~oVAHR}*s6K6C>R~sr(VoJLHN9z_Hb=Pmmx|E6?;k!@A-sQ>N zoAvj?1}hc`R5%}#>DrL)s?BcoHLX8fc{VVZSfmOQn=INDzvK?5mL{1hT~)R!-&OEA z+}z*N&AZIytH#EqDtv=RSFygzbk1_lch2MWnT98Ysv3nzWuYS5Y*wM1(?;M!%I4RhjUtX4P)=>7r?GB?Y=3-cFu-YJ`Dxu^W%nU6sO&cyIY90ru^LR*Vs|=f;q$vPmcOs5EwH!95<+~-G%TP4xo%A&_lDV z@zO-i>}l_;F=fE1h?pFeCjGj}gN-QXDZ#dAXDiyQ_6>z7WY}X1&-J#lD&vIzj%}7c zjU7K_(fhXF7)-q0%QXw>_t)N02GnLuUz%6!1Gi*9f09W8M3yD9-`LSgapIEtrBvg~ zX@hO^!Y+@Y1mkelmB5a!xbTDvk7Ip}MqUrvh1_1V=D~BtbFD| z^DlVoUtYB3?eLSmAs_t1`6st{cW`5ww_-R<+uz;B=w(msP9A$u+&yNbOI9i=(MuV1 z{H)jA$-VD-32C>f2bT632@(wlTbkhrCcC3epR^Qo- z-y*gtSxI^w4R1Y$>T1K%FP`(>qZoMqI@9w!wO+5Mx>&ccj=e__V7aY=jC$T^5cqow%u}Oa z!8pI>cK6l!v#Cw_8Zr&X8SJ9bD+B^OQMZYdcd+-(}?qK8heA`LF?M8&W|P77NQd(??l%ReWdpuZTelq+arl-Txnsua2uvB&||Q+Zg|M|YxP`^U0#X6C#1p8NaV=Q-aA zQPfsPoCgkr!8i&ccroZ1VfpNB(SO|R*IUuU9pT3#v6@6AO`?Y|Y`I1X0RojI35tOv z^8LC#C=`RS@==Q8k$B;DhD@U(N-RD^qe_dSF_=)6Q7e%pLkJ*+l9XyD{%LzB9#G1e z_?=W?pis+&6v~KnJrtWBC6=Wp%jj}E>kmMvk%1DZAVdNfRViwiVPxXp@-k4_Vg~WR zTNfmmiGP<=yif#iHF^l35`zL{fg}<@qY+6IDvd_+2grdWG8l-yv;Y!?L8URsTY&cu z9*w4#Co*Dq;qPOi8WXQT5G?}))6&w2X+cDdJ_#hz>2wPRnH+$61i<^%h{PD6hP{^< zcn~bpE47GHqXsOD5~(H?VdBwD-<6=!exOyu@7shn3^Yo#Ac+`gDQU@3DE$AfD%A&X z7>R*?&G&y2!{Yr~2#kSXO{!jo4ldE#5=zV9>LCfD(Tg>jl%+0;6dFVWD>Pbw8%qN= z3MDe7+G5!B79kWe1Zo(OsAZ6V$Hb!?M5R*BpoeoQq;Ni+OrldrBtDhS;RbX0d=7^e z&LIVcQWy`XB@3Hdb za|vD+3!=$@meKxZ)JrCGe=OpM#YL45s}HHseb=LlJ^r=(0tREP6Y$t#~j!WGqGpgeHn-#K_O`q_yySE1PcP9*Srq~Vq(kGJA zys8-q(jgEjPMoQ}H~Wi@Rap;D4vrKD!GlKD&y`O`+Tb^3Ps^UZ9Hh&xKhm9P8A_Df z+`?6thxwX3{jqQuX(J||?e*t5tNF|Z0LNh4DeTsFZ@3#QT46ZkxVNVN#{g*rR#ZGOu4*HJI@Q5zFs#zGOG?p9u%9$)*gmyAH>D)EljJny{wSys>pO z<@Ayu>pJXbUZ=o4SH53y*P}ViHO%$Fg(v<8J{me?2CNC#`(4r-+~Uc-E1xXJ0xpl& zeo-&D`rJK5-dMe|*?Pn(ma_6>XXb)uN~Eh|g|RETAt(Iai+z5~z7t^-E^~*XMjzA@ z@TlUf1I0OXXm6N-tZl%sW+w!OuSmS{Q`^bHJqsoRh$Zq>n-#FP$g9XB@rc;u6?mTz zAefZ)a{P0~E`JLRXC`LeU$Nor!40j@SjRuSkodgd(xZ3c%#^|&Rq9YCZdbJXf@fy_ z2-j5j>FVLE2QsPQFQS&*Pi8#i|Em4wRSg#VSYN7IF#Y5St&K=`>zUA$hIK@&na^wj z%Y0YWPP3DJPUhcIA9^v?s*P^fDTjW@c*4UQoU%%I*B2E7$nczBj615A#v5Gch|z z9rW^ZBF%I@a1AeJVb1sTHuX&Bv*esU)*RKfHgA+Le($yISLvdL@NQ88Qf@bO{Mhr^ z7MG2*nOOjL zWo}6EXRN|Dw}+#-gHeVQ@pl8(`yB6<%ik$+yA~a~%ku^nn^y1E>Q=SJHtmjB>le4$ zrMu!>QfE>v;!yY3b4gb&@uioZbA0MD2*9Z* z%_#cOcHUg#^!)5={6OQT5Z|4{Hp8AAUCFHVjVM|k%jHC7t0eH&gQAt|*7cQp@;BQ4 z(JUUgQJFu40Dk)>rG6JY)|(k;f}N@^EWqok46HHa;W6C4WOhZ?_jo9تعطيل الأوسمة حديث + المحادثات + هل ترغب في حذف %1$s من الاقتراحات؟ معاينة الرابط نوع المجموعة @@ -206,6 +208,7 @@ %1$s أرسل رسالة صوتية للقناة %2$s %1$s قام بإرسال مقطع صوتي للقناة %2$s %1$s قام بإرسال ملصق للقناة %2$s + %1$s أرسل %3$s ملصق للقناة %2$s %1$s أرسل رسالة %1$s أرسل صورة %1$s أرسل مقطع مرئي @@ -216,6 +219,7 @@ %1$s أرسل رسالة صوتية %1$s أرسل مقطع صوتي %1$s أرسل ملصق + %1$s أرسل %2$s ملصق من يستطيع إضافة أعضاء؟ جميع الأعضاء فقط المشرفون @@ -301,7 +305,7 @@ عداد التدمير الذاتي إشعارات الخدمة جاري جلب معلومات الرابط... - فتح في المتصفح + فتح بواسطة... انسخ الرابط أرسل %1$s هل ترغب في فتح الرابط باستخدام %1$s ؟ @@ -333,6 +337,12 @@ أخرى الوصف رسالة مثبتة + معدلة + انتقل إلى الأسفل للبوتات + %1$s + المعذرة، انتهت مدة التعديل على الرسائل. + إضافة اختصار + تمت إضافة اختصار للشاشة الرئيسية %1$s قام بتعيين عداد التدمير الذاتي إلى to %2$s لقد قمت بتعيين التدمير الذاتي إلى %1$s @@ -350,6 +360,7 @@ %1$s قام بإرسال رسالة صوتية %1$s أرسل مقطع صوتي لك %1$s قام بإرسال ملصق + %1$s قام بإرسال %2$s ملصق %1$s @ %2$s: %3$s %1$s قام بإرسال رسالة للمجموعة %2$s %1$s قام بإرسال صورة للمجموعة %2$s @@ -361,6 +372,7 @@ %1$s أرسل رسالة صوتية للمجموعة %2$s %1$s أرسل مقطع صوتي للمجموعة %2$s %1$s قام بإرسال ملصق للمجموعة %2$s + %1$s أرسل %3$s ملصق للمجموعة %2$s %1$s قام بدعوتك للمجموعة %2$s %1$s قام بتعديل اسم المجموعة %2$s %1$s قام بتغيير صورة المجموعة %2$s @@ -384,6 +396,7 @@ %1$s ثبت فيديو في المجموعة %2$s %1$s ثبت ملف في المجموعة %2$s %1$s ثبت ملصق في المجموعة %2$s + %1$s قام بتثبيت %3$s ملصق في المجموعة %2$s %1$s ثبت رسالة صوتية في المجموعة %2$s %1$s ثبت جهة اتصال في المجموعة %2$s %1$s ثبت خريطة في المجموعة %2$s @@ -395,6 +408,7 @@ %1$s ثبت فيديو %1$s ثبت ملف %1$s ثبت ملصق + %1$s قام بتثبيت %2$s ملصق %1$s ثبت رسالة صوتية %1$s ثبت جهة اتصال %1$s ثبت خريطة diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index 453f2a486..f82376c81 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -66,6 +66,8 @@ Dauerhaft Stumm HASHTAGS LETZTE + CHATS + Lösche %1$s aus Vorschlägen? Linkvorschau Gruppenart @@ -206,6 +208,7 @@ %1$s hat eine Sprachnachricht an den Kanal %2$s gesendet %1$s hat ein Musikstück an den Kanal %2$s gesendet %1$s hat einen Sticker an den Kanal %2$s gesendet + %1$s hat einen %3$s Sticker an den Kanal %2$s gesendet %1$s hat eine Nachricht gesendet %1$s hat ein Bild gesendet %1$s hat ein Video gesendet @@ -216,6 +219,7 @@ %1$s hat eine Sprachnachricht gesendet %1$s hat ein Musikstück gesendet %1$s hat einen Sticker gesendet + %1$s hat einen %2$s Sticker gesendet Wer kann Mitglieder einladen? Alle Mitglieder Nur Admins @@ -301,7 +305,7 @@ Selbstzerstörungs-Timer setzen Servicemeldungen Lade Linkvorschau... - Im Browser öffnen + ÖFFNEN MIT... URL kopieren %1$s senden URL %1$s öffnen? @@ -333,6 +337,12 @@ Sonstiges Beschreibung Angeheftete Nachricht + bearbeitet + Runterscrollen für Bots + %1$s + Bearbeitungszeit leider abgelaufen. + Verknüpfung hinzufügen + Zum Startbildschirm hinzugefügt %1$s hat den Selbstzerstörungs-Timer auf %2$s gesetzt Du hast den Selbstzerstörungs-Timer auf %1$s gesetzt @@ -350,6 +360,7 @@ %1$s hat dir eine Sprachnachricht gesendet %1$s hat dir ein Musikstück gesendet %1$s hat dir einen Sticker gesendet + %1$s hat dir einen %2$s Sticker gesendet %1$s @ %2$s: %3$s %1$s hat eine Nachricht an die Gruppe %2$s gesendet %1$s hat ein Bild an die Gruppe %2$s gesendet @@ -361,6 +372,7 @@ %1$s hat eine Sprachnachricht an die Gruppe %2$s gesendet %1$s hat ein Musikstück an die Gruppe %2$s gesendet %1$s hat einen Sticker an die Gruppe %2$s gesendet + %1$s hat einen %3$s Sticker an die Gruppe %2$s gesendet %1$s hat dich in die Gruppe %2$s eingeladen %1$s hat den Namen der Gruppe %2$s geändert %1$s hat das Bild der Gruppe %2$s geändert @@ -384,6 +396,7 @@ %1$s hat ein Video in der Gruppe %2$s angeheftet %1$s hat eine Datei in der Gruppe %2$s angeheftet %1$s hat einen Sticker in der Gruppe %2$s angeheftet + %1$s hat einen %3$s Sticker in der Gruppe %2$s angeheftet %1$s hat eine Sprachnachricht in der Gruppe %2$s angeheftet %1$s hat einen Kontakt in der Gruppe %2$s angeheftet %1$s hat einen Standort in der Gruppe %2$s angeheftet @@ -395,6 +408,7 @@ %1$s hat ein Video angeheftet %1$s hat eine Datei angeheftet %1$s hat einen Sticker angeheftet + %1$s hat einen %2$s Sticker angeheftet %1$s hat eine Sprachnachricht angeheftet %1$s hat einen Kontakt angeheftet %1$s hat einen Stamdort angeheftet diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index fc5140f97..bdcb4f49d 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -66,6 +66,8 @@ Desactivar HASHTAGS RECIENTES + CHATS + ¿Eliminar a %1$s de las sugerencias? Vista previa del enlace Tipo de grupo @@ -206,6 +208,7 @@ %1$s envió un mensaje de voz al canal %2$s %1$s envió una pista al canal %2$s %1$s envió un sticker al canal %2$s + %1$s envió un %3$s sticker al canal %2$s %1$s publicó un mensaje %1$s publicó una foto %1$s publicó un vídeo @@ -216,6 +219,7 @@ %1$s publicó un mensaje de voz %1$s publicó una pista %1$s publicó un sticker + %1$s publicó un %2$s sticker ¿Quién puede invitar? Todos Administradores @@ -301,7 +305,7 @@ Establecer autodestrucción Servicio de notificaciones Obteniendo información... - Abrir en el navegador + ABRIR EN... Copiar URL Enviar %1$s ¿Abrir %1$s? @@ -333,6 +337,12 @@ Otros Descripción Mensaje anclado + editado + Ve abajo para los bots + %1$s + Terminó el tiempo de edición. + Añadir acceso directo + Acceso directo añadido al escritorio %1$s activó la autodestrucción en %2$s Activaste la autodestrucción en %1$s @@ -350,6 +360,7 @@ %1$s te envió un mensaje de voz %1$s te envió una pista %1$s te envió un sticker + %1$s te envió un %2$s sticker %1$s @ %2$s: %3$s %1$s envió un mensaje al grupo %2$s %1$s envió una foto al grupo %2$s @@ -361,6 +372,7 @@ %1$s envió un mensaje de voz al grupo %2$s %1$s envió una pista al grupo %2$s %1$s envió un sticker al grupo %2$s + %1$s envió un %3$s sticker al grupo %2$s %1$s te invitó al grupo %2$s %1$s cambió el nombre del grupo %2$s %1$s cambió la foto del grupo %2$s @@ -384,6 +396,7 @@ %1$s ancló un vídeo en el grupo %2$s %1$s ancló un archivo en el grupo %2$s %1$s ancló un sticker en el grupo %2$s + %1$s ancló un %3$s sticker en el grupo %2$s %1$s ancló un mensaje de voz en el grupo %2$s %1$s ancló un contacto en el grupo %2$s %1$s ancló un mapa en el grupo %2$s @@ -395,6 +408,7 @@ %1$s ancló un vídeo %1$s ancló un archivo %1$s ancló un sticker + %1$s ancló un %2$s sticker %1$s ancló un mensaje de voz %1$s ancló un contacto %1$s ancló un mapa @@ -876,7 +890,7 @@ Desde la cámara Desde la galería Eliminar foto - Establecer + OK OK RECORTAR @@ -1006,12 +1020,12 @@ %1$d miembros %1$d miembros %1$d miembros - y %1$d personas más están escribiendo + y %1$d más están escribiendo y %1$d más están escribiendo - y %1$d personas más están escribiendo - y %1$d personas más están escribiendo - y %1$d personas más están escribiendo - y %1$d personas más están escribiendo + y %1$d más están escribiendo + y %1$d más están escribiendo + y %1$d más están escribiendo + y %1$d más están escribiendo Sin mensajes nuevos %1$d nuevo mensaje %1$d nuevos mensajes diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index 3588972a6..61e3c426c 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -66,6 +66,8 @@ Disabilita HASHTAG RECENTI + CHAT + Eliminare %1$s dai suggerimenti? Anteprima link Tipo di gruppo @@ -164,7 +166,7 @@ Foto del canale cambiata Foto del canale rimossa Nome del canale cambiato in un2 - Spiacenti, hai creato troppi canali pubblici. Puoi creare un canale privato o eliminare un tuo canale pubblico. + Spiacenti, hai creato troppi canali pubblici. Puoi creare un canale privato o eliminare un tuo canale esistente. Spiacenti, hai creato troppi link pubblici. Prova a eliminarne o a rendere qualche gruppo o canale privato. Moderatore Creatore @@ -206,6 +208,7 @@ %1$s ha inviato un messaggio vocale al canale %2$s %1$s ha inviato una traccia al canale %2$s %1$s ha inviato uno sticker al canale %2$s + %1$s ha inviato uno %3$s sticker al canale %2$s %1$s ha pubblicato un messaggio %1$s ha pubblicato una foto %1$s ha pubblicato un video @@ -216,6 +219,7 @@ %1$s ha pubblicato un messaggio vocale %1$s ha pubblicato una traccia %1$s ha pubblicato uno sticker + %1$s ha pubblicato uno %2$s sticker Chi può aggiungere nuovi membri? Tutti i membri Solo gli amministratori @@ -301,7 +305,7 @@ Timer di autodistruzione Notifiche di servizio Recupero le info del link... - Apri nel Browser + APRI IN... Copia URL Invia %1$s Aprire url %1$s? @@ -314,7 +318,7 @@ Spiacenti, ma al momento puoi scrivere solo ai contatti reciproci. Spiacenti, ma al momento puoi aggiungere ai gruppi solo contatti reciproci. https://telegram.org/faq/it#non-posso-inviare-messaggi-a-chi-non-far-parte-dei-miei-contatti - Più info + Maggiori info Invia a... Premi qui per accedere alle GIF salvate Fissa @@ -333,6 +337,12 @@ Altro Descrizione Messaggio fissato + modificato + Scorri verso il basso per i bot + %1$s + Spiacenti, tempo di modifica scaduto. + Aggiungi scorciatoia + Scorciatoia aggiunta alla schermata home %1$s ha impostato il timer di autodistruzione a %2$s Hai impostato il timer di autodistruzione a %1$s @@ -350,6 +360,7 @@ %1$s ti ha inviato un messaggio vocale %1$s ti ha inviato una traccia %1$s ti ha inviato uno sticker + %1$s ti ha inviato uno %2$s sticker %1$s @ %2$s: %3$s %1$s ha inviato un messaggio al gruppo %2$s %1$s ha inviato una foto al gruppo %2$s @@ -361,6 +372,7 @@ %1$s ha inviato un messaggio vocale al gruppo %2$s %1$s ha inviato una traccia al gruppo %2$s %1$s ha inviato uno sticker al gruppo %2$s + %1$s ha inviato uno %3$s sticker al gruppo %2$s %1$s ti ha invitato nel gruppo %2$s %1$s ha modificato il nome del gruppo %2$s %1$s ha modificato la foto del gruppo %2$s @@ -384,6 +396,7 @@ %1$s ha fissato un video nel gruppo %2$s %1$s ha fissato un file nel gruppo %2$s %1$s ha fissato uno sticker nel gruppo %2$s + %1$s ha fissato uno %3$s sticker nel gruppo %2$s %1$s ha fissato un messaggio vocale nel gruppo %2$s %1$s ha fissato un contatto nel gruppo %2$s %1$s ha fissato una posizione nel gruppo %2$s @@ -395,6 +408,7 @@ %1$s ha fissato un video %1$s ha fissato un file %1$s ha fissato uno sticker + %1$s ha fissato uno %2$s sticker %1$s ha fissato un messaggio vocale %1$s ha fissato un contatto %1$s ha fissato una posizione @@ -544,7 +558,7 @@ Un contatto si è unito a Telegram Messaggi fissati Lingua - Nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.
    ]]>Dai un\'occhiata alle Domande frequenti
    ]]>di Telegram: contengono risposte a quasi tutte le domande e suggerimenti importanti per risolvere i problemi]]> + Nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.
    ]]>Dai un\'occhiata alle Domande frequenti di Telegram]]>: contengono risposte a quasi tutte le domande e suggerimenti importanti per risolvere i problemi]]>.
    Chiedi a un volontario Domande frequenti https://telegram.org/faq/it @@ -632,7 +646,7 @@ Altri file Vuota Mantieni media - Foto, video e altri file dalle chat nel cloud che non hai aperto]]> in questo periodo verranno eliminati dal dispositivo per preservare la spazio sul disco.\n\nTutti i media rimarranno nel cloud di Telegram e potranno essere riscaricati ogni volta che ne avrai bisogno. + Foto, video e altri file dalle chat nel cloud che non hai aperto]]> in questo periodo verranno eliminati dal dispositivo per preservare lo spazio sul disco.\n\nTutti i media rimarranno nel cloud di Telegram e potranno essere riscaricati ogni volta che ne avrai bisogno. Per sempre Sessioni attive @@ -754,7 +768,7 @@ Reinserisci la tua password E-mail di recupero La tua e-mail - Inserisci un\'e-mail valida. È l\'unico modo di recuperare una password dimenticata. + Inserisci un\'e-mail valida. È l\'unico modo per recuperare una password dimenticata. Salta Attenzione No, seriamente.\n\nSe dimentichi la tua password, perderai l\'accesso al tuo account Telegram. Non ci sarà modo di ripristinarlo. @@ -1006,12 +1020,12 @@ %1$d membri %1$d membri %1$d membri - e altre %1$d persone stanno scrivendo - e %1$d altra persona stanno scrivendo - e altre %1$d persone stanno scrivendo - e altre %1$d persone stanno scrivendo - e altre %1$d persone stanno scrivendo - e altre %1$d persone stanno scrivendo + e altri %1$d stanno scrivendo + e %1$d altro stanno scrivendo + e altri %1$d stanno scrivendo + e altri %1$d stanno scrivendo + e altri %1$d stanno scrivendo + e altri %1$d stanno scrivendo nessun nuovo messaggio %1$d nuovo messaggio %1$d nuovi messaggi diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index 6ebbc45f8..66a24bfaf 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -66,6 +66,8 @@ 비활성화 해시태그 최신 + 대화 + %1$s를 추천에서 삭제하겠습니까? 링크 미리복 그룹 종류 @@ -206,6 +208,7 @@ %1$s님이 %2$s 채널에 음성메시지를 보냈습니다 %1$s님이 %2$s 채널에 트랙을 보냈습니다 %1$s님이 %2$s 채널에 스티커를 보냈습니다 + %1$s님이 %2$s 채널에 %3$s 스티커를 보냈습니다 %1$s 님이 메시지를 보냈습니다 %1$s 님이 사진을 보냈습니다 %1$s 님이 동영상을 보냈습니다 @@ -216,6 +219,7 @@ %1$s님이 음성메시지를 보냈습니다 %1$s님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 + %1$s님이 %2$s스티커를 작성하였습니다 초대가 가능한 구성원 모든 구성원 관리자만 가능 @@ -301,7 +305,7 @@ 자동삭제 타이머 설정 서비스 알림 링크 정보를 가져오는 중... - 브라우져에서 열기 + 다음으로 열기.. URL 복사 %1$s 전송 %1$s 링크를 여시겠습니까? @@ -333,6 +337,12 @@ 기타 설명 메시지 고정 + 수정됨 + 봇을 보려면 스크롤 다운하세요 + %1$s + 죄송합니다, 수정가능시간이 만료되었습니다. + 바로 가기 추가 + 홈에 바로가기 추가 %1$s님이 자동삭제를 %2$s 후로 설정했습니다 자동삭제를 %1$s 후로 설정했습니다 @@ -350,6 +360,7 @@ %1$s님이 음성메시지를 보냈습니다 %1$s 님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 + %1$s님이 %2$s스티커를 보냈습니다 %1$s @ %2$s: %3$s %1$s님이 %2$s 그룹에 메시지를 보냈습니다 %1$s님이 %2$s 그룹에 사진을 보냈습니다 @@ -361,6 +372,7 @@ %1$s님이 %2$s 그룹에 음성메시지를 보냈습니다 %1$s님이 %2$s 그룹에 트랙을 보냈습니다 %1$s님이 %2$s 그룹에 스티커를 보냈습니다 + %1$s님이 %2$s 그룹에 %3$s 스티커를 보냈습니다 %1$s님이 %2$s 그룹에 초대했습니다 %1$s님이 그룹 이름을 %2$s 그룹으로 변경했습니다 %1$s님이 %2$s 그룹 사진을 변경했습니다 @@ -384,6 +396,7 @@ %1$s 님이 비디오를 %2$s 그룹방에 고정함 %1$s 님이 파일을 %2$s 그룹방에 고정함 %1$s 님이 스티커를 %2$s 그룹방에 고정함 + %1$s 님이 %3$s 스티커를 %2$s 그룹방에 고정함 %1$s 님이 음성메시지를 %2$s 그룹방에 고정함 %1$s 님이 연락처를 %2$s 그룹방에 고정함 %1$s 님이 지도를 %2$s 그룹방에 고정함 @@ -395,6 +408,7 @@ %1$s 님이 비디오를 고정함 %1$s 님이 파일을 고정함 %1$s 님이 스티커를 고정함 + %1$s 님이 %2$s 스티커를 고정함 %1$s 님이 음성메시지를 고정함 %1$s 님이 연락처를 고정함 %1$s 님이 지도를 고정함 diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index bc38eea8a..bebae46c6 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -66,6 +66,8 @@ Uitschakelen HASHTAGS RECENT + CHATS + %1$s uit suggesties verwijderen? Link-voorvertoning Groepsvorm @@ -206,6 +208,7 @@ %1$s heeft een spraakbericht gestuurd naar het kanaal %2$s %1$s heeft een muziekbestand gestuurd naar het kanaal %2$s %1$s heeft een sticker gestuurd naar het kanaal %2$s + %1$s heeft een %3$s sticker gestuurd naar kanaal %2$s %1$s plaatste een bericht %1$s plaatste een foto %1$s plaatste een video @@ -216,6 +219,7 @@ %1$s plaatste een spraakbericht %1$s plaatste een muziekbestand %1$s plaatste een sticker + %1$s plaatste een %2$s sticker Wie kan leden toevoegen? Alle leden Alleen beheerders @@ -301,7 +305,7 @@ Zelfvernietigingstimer instellen Servicemeldingen Link-preview ophalen... - Openen in browser + OPENEN IN... Link kopiëren %1$s versturen URL %1$s openen? @@ -333,6 +337,12 @@ Overig Beschrijving Vastgezet bericht + bewerkt + Scrollen voor bots + %1$s + Bewerken kan niet meer. + Snelkoppeling toevoegen + Snelkoppeling aan startscherm toegevoegd %1$s heeft de zelfvernietigingstimer ingesteld op %2$s Je hebt de zelfvernietigingstimer ingesteld op %1$s @@ -350,6 +360,7 @@ %1$s heeft je een spraakbericht gestuurd %1$s heeft je een muziekbestand gestuurd %1$s heeft je een sticker gestuurd + %1$s heeft je een %2$s sticker gestuurd %1$s @ %2$s: %3$s %1$s heeft een bericht gestuurd naar de groep %2$s %1$s heeft een foto gestuurd naar de groep %2$s @@ -361,6 +372,7 @@ %1$s heeft een spraakbericht gestuurd naar de groep %2$s %1$s heeft een muziekbestand gestuurd naar de groep %2$s %1$s heeft een sticker gestuurd naar de groep %2$s + %1$s heeft een %3$s sticker gestuurd naar de groep %2$s %1$s heeft je uitgenodigd voor de groep %2$s %1$s heeft de naam van de groep %2$s gewijzigd %1$s heeft de afbeelding van de groep %2$s gewijzigd @@ -384,6 +396,7 @@ %1$s heeft video vastgezet in de groep %2$s %1$s heeft bestand vastgezet in de groep %2$s %1$s heeft sticker vastgezet in de groep %2$s + %1$s heeft een %3$s sticker vastgezet in de groep %2$s %1$s heeft spraakbericht vastgezet in de groep %2$s %1$s heeft contact vastgezet in de groep %2$s %1$s heeft locatie vastgezet in de groep %2$s @@ -395,6 +408,7 @@ %1$s heeft video vastgezet %1$s heeft bestand vastgezet %1$s heeft sticker vastgezet + %1$s heeft een %2$s sticker vastgezet %1$s heeft spraakbericht vastgezet %1$s heeft contact vastgezet %1$s heeft locatie vastgezet @@ -1006,12 +1020,12 @@ %1$d leden %1$d leden %1$d leden - en nog %1$d personen zijn aan het typen - en nog %1$d persoon is aan het typen - en nog %1$d personen zijn aan het typen - en nog %1$d personen zijn aan het typen - en nog %1$d personen zijn aan het typen - en nog %1$d personen zijn aan het typen + en %1$d meer zijn aan het typen + en %1$d meer is aan het typen + en %1$d meer zijn aan het typen + en %1$d meer zijn aan het typen + en %1$d meer zijn aan het typen + en %1$d meer zijn aan het typen geen nieuwe berichten %1$d nieuw bericht %1$d nieuwe berichten diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index a69732ef5..0c0c6abff 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -66,6 +66,8 @@ Desativar HASHTAGS RECENTE + CONVERSAS + Apagar %1$s das sugestões? Prévia do link Tipo de Grupo @@ -206,6 +208,7 @@ %1$s enviou uma mensagem ao canal %2$s %1$s enviou uma música ao canal %2$s %1$s enviou um sticker ao canal %2$s + %1$s enviou um %3$s sticker ao canal %2$s %1$s postou uma mensagem %1$s postou uma foto %1$s postou um vídeo @@ -216,6 +219,7 @@ %1$s postou uma mensagem de voz %1$s postou uma música %1$s postou um sticker + %1$s postou um %2$s sticker Quem pode adicionar novos membros? Todos os Membros Somente Administradores @@ -301,7 +305,7 @@ Definir timer de autodestruição Notificações de serviço Obtendo informações... - Abrir no Navegador + ABRIR EM... Copiar URL Enviar %1$s Abrir URL em %1$s? @@ -333,6 +337,12 @@ Outro Descrição Mensagem Fixada + editado + Desça para ver os bots + %1$s + Desculpe, o tempo para editar expirou. + Adicionar atalho + Atalho adicionado à tela de início %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -350,6 +360,7 @@ %1$s enviou uma mensagem de voz %1$s enviou uma música %1$s lhe enviou um sticker + %1$s lhe enviou um %2$s sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma foto para o grupo %2$s @@ -361,6 +372,7 @@ %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma música ao grupo %2$s %1$s enviou um sticker ao grupo %2$s + %1$s enviou um %3$s sticker ao grupo %2$s %1$s convidou você para o grupo %2$s %1$s editou o nome do grupo %2$s %1$s editou a foto do grupo %2$s @@ -384,6 +396,7 @@ %1$s fixou um vídeo no grupo %2$s %1$s fixou um arquivo no grupo %2$s %1$s fixou um sticker no grupo %2$s + %1$s fixou um %3$s sticker no grupo %2$s %1$s fixou uma mensagem de voz no grupo %2$s %1$s fixou um contato no grupo %2$s %1$s fixou um mapa no grupo %2$s @@ -395,6 +408,7 @@ %1$s fixou um vídeo %1$s fixou um arquivo %1$s fixou um sticker + %1$s fixou um %2$s sticker %1$s fixou uma mensagem de voz %1$s fixou um contato %1$s fixou um mapa @@ -1006,12 +1020,12 @@ %1$d membros %1$d membros %1$d membros - e mais %1$d pessoas estão escrevendo - e mais %1$d estão digitando - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo sem novas mensagens %1$d nova mensagem %1$d novas mensagens diff --git a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml index fa030cb07..f2ed133fa 100644 --- a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml @@ -66,6 +66,8 @@ Desativar HASHTAGS RECENTE + CONVERSAS + Apagar %1$s das sugestões? Prévia do link Tipo de Grupo @@ -206,6 +208,7 @@ %1$s enviou uma mensagem ao canal %2$s %1$s enviou uma música ao canal %2$s %1$s enviou um sticker ao canal %2$s + %1$s enviou um %3$s sticker ao canal %2$s %1$s postou uma mensagem %1$s postou uma foto %1$s postou um vídeo @@ -216,6 +219,7 @@ %1$s postou uma mensagem de voz %1$s postou uma música %1$s postou um sticker + %1$s postou um %2$s sticker Quem pode adicionar novos membros? Todos os Membros Somente Administradores @@ -301,7 +305,7 @@ Definir timer de autodestruição Notificações de serviço Obtendo informações... - Abrir no Navegador + ABRIR EM... Copiar URL Enviar %1$s Abrir URL em %1$s? @@ -333,6 +337,12 @@ Outro Descrição Mensagem Fixada + editado + Desça para ver os bots + %1$s + Desculpe, o tempo para editar expirou. + Adicionar atalho + Atalho adicionado à tela de início %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -350,6 +360,7 @@ %1$s enviou uma mensagem de voz %1$s enviou uma música %1$s lhe enviou um sticker + %1$s lhe enviou um %2$s sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma foto para o grupo %2$s @@ -361,6 +372,7 @@ %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma música ao grupo %2$s %1$s enviou um sticker ao grupo %2$s + %1$s enviou um %3$s sticker ao grupo %2$s %1$s convidou você para o grupo %2$s %1$s editou o nome do grupo %2$s %1$s editou a foto do grupo %2$s @@ -384,6 +396,7 @@ %1$s fixou um vídeo no grupo %2$s %1$s fixou um arquivo no grupo %2$s %1$s fixou um sticker no grupo %2$s + %1$s fixou um %3$s sticker no grupo %2$s %1$s fixou uma mensagem de voz no grupo %2$s %1$s fixou um contato no grupo %2$s %1$s fixou um mapa no grupo %2$s @@ -395,6 +408,7 @@ %1$s fixou um vídeo %1$s fixou um arquivo %1$s fixou um sticker + %1$s fixou um %2$s sticker %1$s fixou uma mensagem de voz %1$s fixou um contato %1$s fixou um mapa @@ -1006,12 +1020,12 @@ %1$d membros %1$d membros %1$d membros - e mais %1$d pessoas estão escrevendo - e mais %1$d estão digitando - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo - e mais %1$d pessoas estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo + e mais %1$d estão escrevendo sem novas mensagens %1$d nova mensagem %1$d novas mensagens diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index bce1ee1f0..bcbfeafd0 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -66,6 +66,8 @@ Disable HASHTAGS RECENT + CHATS + Delete %1$s from suggestions? Link preview Group Type @@ -206,6 +208,7 @@ %1$s sent a voice message to the channel %2$s %1$s sent a track to the channel %2$s %1$s sent a sticker to the channel %2$s + %1$s sent a %3$s sticker to the channel %2$s %1$s posted a message %1$s posted a photo %1$s posted a video @@ -216,6 +219,7 @@ %1$s posted a voice message %1$s posted a track %1$s posted a sticker + %1$s posted a %2$s sticker Who can add new members? All Members Only Admins @@ -333,6 +337,12 @@ Other Description Pinned Message + edited + Scroll down for bots + %1$s + Sorry, editing time expired. + Add shortcut + Shortcut added to home screen %1$s set the self-destruct timer to %2$s You set the self-destruct timer to %1$s @@ -350,6 +360,7 @@ %1$s sent you a voice message %1$s sent you a track %1$s sent you a sticker + %1$s sent you a %2$s sticker %1$s @ %2$s: %3$s %1$s sent a message to the group %2$s %1$s sent a photo to the group %2$s @@ -361,6 +372,7 @@ %1$s sent a voice message to the group %2$s %1$s sent a track to the group %2$s %1$s sent a sticker to the group %2$s + %1$s sent a %3$s sticker to the group %2$s %1$s invited you to the group %2$s %1$s edited the group\'s %2$s name %1$s edited the group\'s %2$s photo @@ -384,6 +396,7 @@ %1$s pinned a video in the group %2$s %1$s pinned a file in the group %2$s %1$s pinned a sticker in the group %2$s + %1$s pinned a %3$s sticker in the group %2$s %1$s pinned a voice message in the group %2$s %1$s pinned a contact in the group %2$s %1$s pinned a map in the group %2$s @@ -395,6 +408,7 @@ %1$s pinned a video %1$s pinned a file %1$s pinned a sticker + %1$s pinned a %2$s sticker %1$s pinned a voice message %1$s pinned a contact %1$s pinned a map @@ -1006,12 +1020,12 @@ %1$d members %1$d members %1$d members - and %1$d more people are typing - and %1$d more person are typing - and %1$d more people are typing - and %1$d more people are typing - and %1$d more people are typing - and %1$d more people are typing + and %1$d more are typing + and %1$d more are typing + and %1$d more are typing + and %1$d more are typing + and %1$d more are typing + and %1$d more are typing no new messages %1$d new message %1$d new messages diff --git a/TMessagesProj/src/main/res/xml/automotive_app_desc.xml b/TMessagesProj/src/main/res/xml/automotive_app_desc.xml index b6df3b517..79ec6f5e2 100644 --- a/TMessagesProj/src/main/res/xml/automotive_app_desc.xml +++ b/TMessagesProj/src/main/res/xml/automotive_app_desc.xml @@ -1,4 +1,5 @@ + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7f37cedee..525ae5bde 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0-beta3' - classpath 'com.google.gms:google-services:2.1.0-alpha3' + classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.google.gms:google-services:2.1.0' } } \ No newline at end of file