summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--cmake/QtBuildHelpers.cmake3
-rw-r--r--cmake/QtBuildInformation.cmake5
-rw-r--r--cmake/QtBuildRepoExamplesHelpers.cmake31
-rw-r--r--cmake/QtConfigDependencies.cmake.in4
-rw-r--r--cmake/QtModuleDependencies.cmake.in14
-rw-r--r--cmake/QtModuleToolsDependencies.cmake.in8
-rw-r--r--cmake/QtPluginDependencies.cmake.in10
-rw-r--r--cmake/QtProcessConfigureArgs.cmake29
-rw-r--r--cmake/QtPublicSbomCommonGenerationHelpers.cmake668
-rw-r--r--cmake/QtPublicSbomCycloneDXHelpers.cmake382
-rw-r--r--cmake/QtPublicSbomDepHelpers.cmake75
-rw-r--r--cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake425
-rw-r--r--cmake/QtPublicSbomGenerationHelpers.cmake426
-rw-r--r--cmake/QtPublicSbomHelpers.cmake568
-rw-r--r--cmake/QtPublicSbomLicenseHelpers.cmake18
-rw-r--r--cmake/QtPublicSbomOpsHelpers.cmake324
-rw-r--r--cmake/QtPublicSbomPurlHelpers.cmake79
-rw-r--r--cmake/QtPublicSbomPythonHelpers.cmake19
-rw-r--r--cmake/QtPublicSbomQtEntityHelpers.cmake7
-rw-r--r--cmake/QtSbomHelpers.cmake98
-rw-r--r--cmake/QtWrapperScriptHelpers.cmake18
-rw-r--r--cmake/configure-cmake-mapping.md15
-rw-r--r--coin/instructions/prepare_building_env.yaml22
-rw-r--r--doc/global/compat.qdocconf6
-rw-r--r--doc/global/disabledwarnings.qdocconf6
-rw-r--r--licenseRule.json5
-rw-r--r--src/3rdparty/pcre2/qt_attribution.json2
-rw-r--r--src/3rdparty/sqlite/qt_attribution.json4
-rw-r--r--src/3rdparty/sqlite/sqlite3.c5511
-rw-r--r--src/3rdparty/sqlite/sqlite3.h413
-rwxr-xr-xsrc/3rdparty/sqlite/update_sqlite.sh4
-rw-r--r--src/3rdparty/wayland/protocols/color-management/REUSE.toml5
-rw-r--r--src/3rdparty/wayland/protocols/color-management/color-management-v1.xml (renamed from src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml)638
-rw-r--r--src/3rdparty/wayland/protocols/color-management/qt_attribution.json8
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/itemmodels/qrangemodel.cpp12
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h70
-rw-r--r--src/corelib/kernel/qcore_mac.mm65
-rw-r--r--src/corelib/kernel/qcore_mac_p.h1
-rw-r--r--src/corelib/kernel/qcoreapplication.cpp2
-rw-r--r--src/corelib/kernel/qvariant.cpp152
-rw-r--r--src/corelib/kernel/qvariant.h132
-rw-r--r--src/corelib/platform/windows/quniquehandle_types_windows.cpp17
-rw-r--r--src/corelib/platform/windows/quniquehandle_types_windows_p.h65
-rw-r--r--src/corelib/time/qdatetime.cpp2
-rw-r--r--src/corelib/time/qtimezonelocale.cpp64
-rw-r--r--src/corelib/time/qtimezoneprivate.cpp12
-rw-r--r--src/corelib/time/qtimezoneprivate_p.h16
-rw-r--r--src/corelib/tools/quniquehandle_p.h103
-rw-r--r--src/gui/kernel/qevent.cpp75
-rw-r--r--src/gui/kernel/qguiapplication.cpp4
-rw-r--r--src/gui/kernel/qinputdevice.cpp5
-rw-r--r--src/gui/kernel/qpointingdevice.cpp23
-rw-r--r--src/gui/rhi/qrhi.cpp20
-rw-r--r--src/gui/rhi/qrhi.h4
-rw-r--r--src/gui/rhi/qrhid3d11.cpp2
-rw-r--r--src/gui/rhi/qrhid3d12.cpp2
-rw-r--r--src/gui/rhi/qrhigles2.cpp20
-rw-r--r--src/gui/rhi/qrhigles2_p.h2
-rw-r--r--src/gui/rhi/qrhimetal.mm8
-rw-r--r--src/gui/rhi/qrhimetal_p.h1
-rw-r--r--src/gui/rhi/qrhivulkan.cpp4
-rw-r--r--src/gui/rhi/qrhivulkan_p.h1
-rw-r--r--src/gui/text/qfont.cpp13
-rw-r--r--src/gui/text/qfont_p.h7
-rw-r--r--src/gui/text/qfontmetrics.cpp44
-rw-r--r--src/gui/text/qfontvariableaxis.cpp38
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp14
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp14
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.mm5
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp133
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.h9
-rw-r--r--src/plugins/platforms/wayland/CMakeLists.txt2
-rw-r--r--src/plugins/platforms/wayland/global/qwaylandclientextension.cpp5
-rw-r--r--src/plugins/platforms/wayland/qwaylandcolormanagement.cpp115
-rw-r--r--src/plugins/platforms/wayland/qwaylandcolormanagement_p.h52
-rw-r--r--src/plugins/platforms/wayland/qwaylanddisplay.cpp12
-rw-r--r--src/plugins/platforms/wayland/qwaylandinputcontext.cpp10
-rw-r--r--src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp4
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow.cpp10
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow_p.h4
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp23
-rw-r--r--src/plugins/sqldrivers/sqlite/CMakeLists.txt4
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style.cpp102
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style_p.h1
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle.cpp5
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h4
-rw-r--r--src/testlib/qtestcase.cpp13
-rw-r--r--src/widgets/accessible/itemviews.cpp11
-rw-r--r--src/widgets/accessible/itemviews_p.h5
-rw-r--r--src/widgets/dialogs/qfiledialog.cpp3
-rw-r--r--src/widgets/dialogs/qfontdialog.cpp2
-rw-r--r--src/widgets/dialogs/qsidebar.cpp5
-rw-r--r--src/widgets/kernel/qlayout.cpp11
-rw-r--r--src/widgets/styles/qfusionstyle.cpp4
-rw-r--r--src/widgets/styles/qstylesheetstyle_default.cpp2
-rw-r--r--src/widgets/styles/qwindowsstyle.cpp4
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt6
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake63
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/check.cmake63
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake67
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake12
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/full.cmake17
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/minimal.cmake11
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt5
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt51
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp4
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt46
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp4
-rw-r--r--tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp70
-rw-r--r--tests/auto/corelib/time/CMakeLists.txt5
-rw-r--r--tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp798
-rw-r--r--tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt32
-rw-r--r--tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp784
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp6
-rw-r--r--tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp2
-rw-r--r--tests/auto/other/qaccessibility/tst_qaccessibility.cpp7
-rw-r--r--tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp4
-rw-r--r--tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp12
-rw-r--r--util/sbom/cyclonedx/.gitignore6
-rw-r--r--util/sbom/cyclonedx/Makefile14
-rw-r--r--util/sbom/cyclonedx/README.md54
-rw-r--r--util/sbom/cyclonedx/pyproject.toml54
-rw-r--r--util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py2
-rw-r--r--util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py881
-rw-r--r--util/sbom/cyclonedx/uv.lock625
130 files changed, 11811 insertions, 3326 deletions
diff --git a/.gitignore b/.gitignore
index 83c3424e9ae..20aff5f53d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -224,6 +224,7 @@ qtc-qmldbg/
callgrind.out.*
core
Makefile*
+!/util/sbom/cyclonedx/Makefile
!/qmake/Makefile.win32*
!/qmake/Makefile.unix
pcviewer.cfg
diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake
index b6132ddae7e..7205bad5253 100644
--- a/cmake/QtBuildHelpers.cmake
+++ b/cmake/QtBuildHelpers.cmake
@@ -295,10 +295,13 @@ function(qt_internal_get_qt_build_public_helpers out_var)
QtPublicPluginHelpers
QtPublicPluginHelpers_v2
QtPublicSbomAttributionHelpers
+ QtPublicSbomCommonGenerationHelpers
QtPublicSbomCpeHelpers
+ QtPublicSbomCycloneDXHelpers
QtPublicSbomDepHelpers
QtPublicSbomFileHelpers
QtPublicSbomGenerationHelpers
+ QtPublicSbomGenerationCycloneDXHelpers
QtPublicSbomHelpers
QtPublicSbomLicenseHelpers
QtPublicSbomOpsHelpers
diff --git a/cmake/QtBuildInformation.cmake b/cmake/QtBuildInformation.cmake
index 77c6d8184b3..5c44a7e5f48 100644
--- a/cmake/QtBuildInformation.cmake
+++ b/cmake/QtBuildInformation.cmake
@@ -47,6 +47,11 @@ function(qt_print_feature_summary)
endforeach()
endif()
+ # Print an SBOM section for a top-level build, or a single repo.
+ if(NOT QT_NO_SBOM_SUMMARY_INFO)
+ qt_internal_add_sbom_summary_info()
+ endif()
+
# Show which packages were found.
feature_summary(INCLUDE_QUIET_PACKAGES
WHAT PACKAGES_FOUND
diff --git a/cmake/QtBuildRepoExamplesHelpers.cmake b/cmake/QtBuildRepoExamplesHelpers.cmake
index a6d7c2b144b..1292844f132 100644
--- a/cmake/QtBuildRepoExamplesHelpers.cmake
+++ b/cmake/QtBuildRepoExamplesHelpers.cmake
@@ -90,6 +90,10 @@ macro(qt_examples_build_begin)
add_link_options(${active_linker_flags})
endif()
+ if(QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ add_compile_definitions(${QT_EXTRA_EXAMPLE_TARGET_DEFINES})
+ endif()
+
# Marker for warnings_as_errors.
set(QT_INTERNAL_IS_EXAMPLE_IN_TREE_BUILD ON)
endif()
@@ -200,8 +204,11 @@ function(qt_internal_add_example subdir)
# Don't show warnings for examples that were added via qt_internal_add_example.
# Those that are added via add_subdirectory will see the warning, due to the parent scope
# having the variable set to TRUE.
- if(QT_FEATURE_developer_build AND NOT QT_NO_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY_WARNING)
- set(QT_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY FALSE)
+ if(QT_FEATURE_developer_build)
+ set(QT_LINT_EXAMPLES ON)
+ if(NOT QT_NO_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY_WARNING)
+ set(QT_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY FALSE)
+ endif()
endif()
# Pre-compute unique example name based on the subdir, in case of target name clashes.
@@ -354,6 +361,10 @@ function(qt_internal_add_example_external_project subdir)
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}")
+ if(QT_FEATURE_developer_build)
+ set(QT_LINT_EXAMPLES ON)
+ endif()
+
_qt_internal_get_build_vars_for_external_projects(
CMAKE_DIR_VAR qt_cmake_dir
PREFIXES_VAR qt_prefixes
@@ -368,6 +379,10 @@ function(qt_internal_add_example_external_project subdir)
list(APPEND var_defs
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${qt_cmake_dir}/qt.toolchain.cmake
)
+ if(QT_LINT_EXAMPLES)
+ list(APPEND var_defs -DQT_LINT_EXAMPLES=ON)
+ endif()
+
else()
list(PREPEND CMAKE_PREFIX_PATH ${qt_prefixes})
@@ -519,6 +534,18 @@ function(qt_internal_add_example_external_project subdir)
"-DCMAKE_${item_type}_LINKER_FLAGS_INIT:STRING=${active_linker_flags}")
endforeach()
endif()
+ if(QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ unset(extra_args)
+ foreach(def IN LISTS QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ list(APPEND extra_args "-D${def}")
+ endforeach()
+ list(JOIN extra_args " " extra_defines_str)
+
+ foreach(language IN ITEMS C CXX OBJC OBJCXX)
+ list(APPEND var_defs
+ "-DCMAKE_${language}_FLAGS_INIT:STRING=${extra_defines_str}")
+ endforeach()
+ endif()
set(deps "")
list(REMOVE_DUPLICATES QT_EXAMPLE_DEPENDENCIES)
diff --git a/cmake/QtConfigDependencies.cmake.in b/cmake/QtConfigDependencies.cmake.in
index b006f052f7f..9144512a8f3 100644
--- a/cmake/QtConfigDependencies.cmake.in
+++ b/cmake/QtConfigDependencies.cmake.in
@@ -31,6 +31,8 @@ set(__qt_third_party_deps "@third_party_deps@")
# set _NOT_FOUND_MESSAGE which will be displayed by the includer of the Dependencies file.
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED FALSE)
-_qt_internal_find_third_party_dependencies(@INSTALL_CMAKE_NAMESPACE@ __qt_third_party_deps)
+if(__qt_third_party_deps)
+ _qt_internal_find_third_party_dependencies(@INSTALL_CMAKE_NAMESPACE@ __qt_third_party_deps)
+endif()
set(@INSTALL_CMAKE_NAMESPACE@_FOUND TRUE)
diff --git a/cmake/QtModuleDependencies.cmake.in b/cmake/QtModuleDependencies.cmake.in
index ff84817ecf9..78ada0a7425 100644
--- a/cmake/QtModuleDependencies.cmake.in
+++ b/cmake/QtModuleDependencies.cmake.in
@@ -31,19 +31,25 @@ endif()
# note: _third_party_deps example: "ICU\\;FALSE\\;1.0\\;i18n uc data;ZLIB\\;FALSE\\;\\;"
set(__qt_@target@_third_party_deps "@third_party_deps@")
@third_party_deps_extra_info@
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+if(__qt_@target@_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+endif()
unset(__qt_@target@_third_party_deps)
# Find Qt tool package.
set(__qt_@target@_tool_deps "@main_module_tool_deps@")
-_qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+if(__qt_@target@_tool_deps)
+ _qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+endif()
unset(__qt_@target@_tool_deps)
# note: target_deps example: "Qt6Core\;5.12.0;Qt6Gui\;5.12.0"
set(__qt_@target@_target_deps "@target_deps@")
set(__qt_@target@_find_dependency_paths "${CMAKE_CURRENT_LIST_DIR}/.." "${_qt_cmake_dir}")
-_qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
- __qt_@target@_find_dependency_paths)
+if(__qt_@target@_target_deps)
+ _qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
+ __qt_@target@_find_dependency_paths)
+endif()
unset(__qt_@target@_target_deps)
unset(__qt_@target@_find_dependency_paths)
diff --git a/cmake/QtModuleToolsDependencies.cmake.in b/cmake/QtModuleToolsDependencies.cmake.in
index 14af9c90e38..51e0636fa91 100644
--- a/cmake/QtModuleToolsDependencies.cmake.in
+++ b/cmake/QtModuleToolsDependencies.cmake.in
@@ -5,7 +5,11 @@
set(@INSTALL_CMAKE_NAMESPACE@@target@_FOUND TRUE)
set(__qt_@target@_tool_third_party_deps "@third_party_deps@")
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_tool_third_party_deps)
+if(__qt_@target@_tool_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_tool_third_party_deps)
+endif()
set(__qt_@target@_tool_deps "@package_deps@")
-_qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+if(__qt_@target@_tool_deps)
+ _qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+endif()
diff --git a/cmake/QtPluginDependencies.cmake.in b/cmake/QtPluginDependencies.cmake.in
index d5d3398e9e3..2ea6a19a732 100644
--- a/cmake/QtPluginDependencies.cmake.in
+++ b/cmake/QtPluginDependencies.cmake.in
@@ -5,7 +5,9 @@ set(@target@_FOUND TRUE)
# note: _third_party_deps example: "ICU\\;FALSE\\;1.0\\;i18n uc data;ZLIB\\;FALSE\\;\\;"
set(__qt_@target@_third_party_deps "@third_party_deps@")
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+if(__qt_@target@_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+endif()
unset(__qt_@target@_third_party_deps)
set(__qt_use_no_default_path_for_qt_packages "NO_DEFAULT_PATH")
@@ -16,8 +18,10 @@ endif()
# note: target_deps example: "Qt6Core\;5.12.0;Qt6Gui\;5.12.0"
set(__qt_@target@_target_deps "@target_deps@")
set(__qt_@target@_find_dependency_paths "@find_dependency_paths@")
-_qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
- __qt_@target@_find_dependency_paths)
+if(__qt_@target@_target_deps)
+ _qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
+ __qt_@target@_find_dependency_paths)
+endif()
unset(__qt_@target@_target_deps)
unset(__qt_@target@_find_dependency_paths)
diff --git a/cmake/QtProcessConfigureArgs.cmake b/cmake/QtProcessConfigureArgs.cmake
index 7118f1f6f92..05e179ba3b3 100644
--- a/cmake/QtProcessConfigureArgs.cmake
+++ b/cmake/QtProcessConfigureArgs.cmake
@@ -351,12 +351,33 @@ endfunction()
macro(qt_add_common_commandline_options)
qt_commandline_option(headersclean TYPE boolean)
qt_commandline_option(sbom TYPE boolean CMAKE_VARIABLE QT_GENERATE_SBOM)
- qt_commandline_option(sbom-json TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_JSON)
+
+ # Semi-public, undocumented.
+ qt_commandline_option(sbom-all TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_AND_VERIFY_ALL)
+
+ qt_commandline_option(sbom-spdx-v2 TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_GENERATE_SPDX_V2)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6 TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_GENERATE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-verify-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-verbose TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_VERBOSE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-json TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_SPDX_V2_JSON)
qt_commandline_option(sbom-json-required TYPE boolean
- CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_JSON
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON
)
- qt_commandline_option(sbom-verify TYPE boolean CMAKE_VARIABLE QT_SBOM_VERIFY)
- qt_commandline_option(sbom-verify-required TYPE boolean CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY)
+
+ qt_commandline_option(sbom-verify TYPE boolean CMAKE_VARIABLE QT_SBOM_VERIFY_SPDX_V2)
+ qt_commandline_option(sbom-verify-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY_SPDX_V2)
endmacro()
function(qt_commandline_prefix arg var)
diff --git a/cmake/QtPublicSbomCommonGenerationHelpers.cmake b/cmake/QtPublicSbomCommonGenerationHelpers.cmake
new file mode 100644
index 00000000000..9de2055b5b6
--- /dev/null
+++ b/cmake/QtPublicSbomCommonGenerationHelpers.cmake
@@ -0,0 +1,668 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Helper function to get common project related variables for SBOM generation for both SPDX and
+# CycloneDX formats.
+function(_qt_internal_sbom_get_common_project_variables)
+ set(opt_args "")
+ set(single_args
+ # Forwarded
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ COPYRIGHT
+ PROJECT
+ PROJECT_FOR_SPDX_ID
+ SUPPLIER
+ SUPPLIER_URL
+
+ # Custom inputs
+ DEFAULT_SBOM_FILE_NAME_EXTENSION
+
+ # Out vars
+ OUT_VAR_PROJECT_NAME
+ OUT_VAR_CURRENT_UTC
+ OUT_VAR_CURRENT_YEAR
+ OUT_VAR_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID
+ OUT_VAR_COPYRIGHT
+ OUT_VAR_SUPPLIER
+ OUT_VAR_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(QT_SBOM_FAKE_TIMESTAMP)
+ set(current_utc "2590-01-01T11:33:55Z")
+ set(current_year "2590")
+ else()
+ string(TIMESTAMP current_utc UTC)
+ string(TIMESTAMP current_year "%Y" UTC)
+ endif()
+
+ set(${arg_OUT_VAR_CURRENT_UTC} "${current_utc}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_CURRENT_YEAR} "${current_year}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
+ set(${arg_OUT_VAR_PROJECT_NAME} "${arg_PROJECT}" PARENT_SCOPE)
+
+ _qt_internal_sbom_get_git_version_vars()
+
+ _qt_internal_path_join(default_sbom_file_name
+ "${arg_PROJECT}"
+ "${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.${arg_DEFAULT_SBOM_FILE_NAME_EXTENSION}")
+ _qt_internal_path_join(default_install_sbom_path
+ "\${CMAKE_INSTALL_PREFIX}/" "${CMAKE_INSTALL_DATAROOTDIR}" "${default_sbom_file_name}"
+ )
+
+ _qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
+ _qt_internal_sbom_set_default_option_value(OUTPUT_RELATIVE_PATH
+ "${default_sbom_file_name}")
+
+ set(${arg_OUT_VAR_OUTPUT} "${arg_OUTPUT}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_OUTPUT_RELATIVE_PATH} "${arg_OUTPUT_RELATIVE_PATH}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX_ID "Package-${arg_PROJECT}")
+ string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+ string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+
+ # Prevent collision with other generated SPDXID with -[0-9]+ suffix.
+ string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+
+ set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
+ set(${arg_OUT_VAR_PROJECT_FOR_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
+ set(${arg_OUT_VAR_SUPPLIER} "${arg_SUPPLIER}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
+ "${PROJECT_HOMEPAGE_URL}")
+ set(${arg_OUT_VAR_SUPPLIER_URL} "${arg_SUPPLIER_URL}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
+ set(${arg_OUT_VAR_COPYRIGHT} "${arg_COPYRIGHT}" PARENT_SCOPE)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
+ else()
+ set(cmake_configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ set(cmake_version "Built by CMake ${CMAKE_VERSION}")
+ set(system_name_and_processor "${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})")
+ set(default_project_comment
+ "${cmake_version} with ${cmake_configs} configuration for ${system_name_and_processor}")
+ set(${arg_OUT_VAR_DEFAULT_PROJECT_COMMENT} "${default_project_comment}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to save SBOM project path values like relative build and install dirs,
+# in global properties.
+function(_qt_internal_sbom_save_common_path_variables_in_global_properties)
+ set(opt_args "")
+ set(single_args
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ SBOM_DIR
+ PROPERTY_SUFFIX
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_filename_component(output_file_name_without_ext "${arg_OUTPUT}" NAME_WLE)
+ get_filename_component(output_file_ext "${arg_OUTPUT}" LAST_EXT)
+
+ set(computed_sbom_file_name_without_ext "${output_file_name_without_ext}${multi_config_suffix}")
+ set(computed_sbom_file_name "${output_file_name_without_ext}${output_file_ext}")
+
+ # In a super build and in a no-prefix build, put all the build time sboms into the same dir in,
+ # in the qtbase build dir.
+ if(QT_BUILDING_QT AND (QT_SUPERBUILD OR (NOT QT_WILL_INSTALL)))
+ set(build_sbom_root_dir "${QT_BUILD_DIR}")
+ else()
+ set(build_sbom_root_dir "${arg_SBOM_DIR}")
+ endif()
+
+ get_filename_component(output_relative_dir "${arg_OUTPUT_RELATIVE_PATH}" DIRECTORY)
+
+ set(build_sbom_dir "${build_sbom_root_dir}/${output_relative_dir}")
+ set(build_sbom_path "${build_sbom_dir}/${computed_sbom_file_name}")
+ set(build_sbom_path_without_ext
+ "${build_sbom_dir}/${computed_sbom_file_name_without_ext}")
+
+ set(install_sbom_path "${arg_OUTPUT}")
+
+ get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
+ set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
+
+ set(suffix "${arg_PROPERTY_SUFFIX}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_path${suffix} "${build_sbom_path}")
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext${suffix}
+ "${build_sbom_path_without_ext}")
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_dir${suffix} "${build_sbom_dir}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_path${suffix} "${install_sbom_path}")
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_path_without_ext${suffix}
+ "${install_sbom_path_without_ext}")
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_dir${suffix} "${install_sbom_dir}")
+endfunction()
+
+# Helper function to get SBOM project path values like relative build and install dirs,
+function(_qt_internal_sbom_get_common_path_variables_from_global_properties)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path${suffix})
+ get_property(sbom_build_output_path_without_ext GLOBAL PROPERTY
+ _qt_sbom_build_output_path_without_ext${suffix})
+ get_property(sbom_build_output_dir GLOBAL PROPERTY _qt_sbom_build_output_dir${suffix})
+
+ get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path${suffix})
+ get_property(sbom_install_output_path_without_ext GLOBAL PROPERTY
+ _qt_sbom_install_output_path_without_ext${suffix})
+ get_property(sbom_install_output_dir GLOBAL PROPERTY _qt_sbom_install_output_dir${suffix})
+
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH} "${sbom_build_output_path}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT}
+ "${sbom_build_output_path_without_ext}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_DIR)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_DIR} "${sbom_build_output_dir}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH} "${sbom_install_output_path}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT}
+ "${sbom_install_output_path_without_ext}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_DIR)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_DIR} "${sbom_install_output_dir}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Helper function to create a staging file for SBOM generation.
+# It is the file that will be incrementally assembled by having content appended to it.
+# Also creates the intro file that will add the assembled content for the SBOM project, aka for the
+# main SPDX package or root CycloneDX component.
+function(_qt_internal_sbom_create_sbom_staging_file)
+ set(opt_args "")
+ set(single_args
+ CONTENT
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ OUT_VAR_CREATE_STAGING_FILE
+ OUT_VAR_SBOM_DIR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ # Create the directory that will contain all sbom related files.
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ file(MAKE_DIRECTORY "${sbom_dir}")
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(doc_base_name "SPDXRef-DOCUMENT")
+ set(doc_extension "spdx.in")
+ set(suffix "")
+ set(extra_content "
+ set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
+")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(starting_message "Starting SPDX SBOM generation in build dir: ${staging_area_file}")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(doc_base_name "cydx-document")
+ set(doc_extension "cdx.in.toml")
+ set(suffix "_cydx")
+ set(extra_content "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ set(starting_message
+ "Starting CycloneDX SBOM TOML file generation in build dir: ${staging_area_file}")
+ endif()
+
+ # Generate project document intro spdx file.
+ set(document_intro_file_name
+ "${sbom_dir}/${doc_base_name}-${arg_REPO_PROJECT_NAME_LOWERCASE}.${doc_extension}")
+ file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$<CONFIG>")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ # Create cmake file to append the document intro spdx to the staging file.
+ set(create_staging_file
+ "${sbom_dir}/append_document_to_staging${suffix}${multi_config_suffix}.cmake")
+
+ set(content "
+ cmake_minimum_required(VERSION 3.16)
+ message(STATUS \"${starting_message}\")
+ ${extra_content}
+ file(READ \"${document_intro_file_name}\" content)
+ # Override any previous file because we're starting from scratch.
+ file(WRITE \"${staging_area_file}\" \"\${content}\")
+")
+ file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
+
+ set(${arg_OUT_VAR_CREATE_STAGING_FILE} "${create_staging_file}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SBOM_DIR} "${sbom_dir}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to save common project info like supplier, project name, spdx id in global
+# properties.
+function(_qt_internal_sbom_save_project_info_in_global_properties)
+ set(opt_args "")
+ set(single_args
+ SUPPLIER
+ SUPPLIER_URL
+ NAMESPACE
+ PROJECT
+ PROJECT_SPDX_ID
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set_property(GLOBAL PROPERTY _qt_sbom_project_supplier "${arg_SUPPLIER}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_supplier_url "${arg_SUPPLIER_URL}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_namespace "${arg_NAMESPACE}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${arg_PROJECT_SPDX_ID}")
+endfunction()
+
+# Helper function to get cmake include files for SBOM generation from global properties.
+function(_qt_internal_sbom_get_cmake_include_files)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ OUT_VAR_INCLUDES
+ OUT_VAR_BEFORE_CHECKSUM_INCLUDES
+ OUT_VAR_AFTER_CHECKSUM_INCLUDES
+ OUT_VAR_POST_GENERATION_INCLUDES
+ OUT_VAR_VERIFY_INCLUDES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ _qt_internal_sbom_collect_cmake_include_files(includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_include_files${suffix} _qt_sbom_cmake_end_include_files${suffix}
+ )
+
+ # Before checksum includes are included after the verification codes have been collected
+ # and before their merged checksum(s) has been computed.
+ _qt_internal_sbom_collect_cmake_include_files(before_checksum_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_before_checksum_include_files${suffix}
+ )
+
+ # After checksum includes are included after the checksum has been computed and written to the
+ # QT_SBOM_VERIFICATION_CODE variable.
+ _qt_internal_sbom_collect_cmake_include_files(after_checksum_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_after_checksum_include_files${suffix}
+ )
+
+ # Post generation includes are included for both build and install time sboms, after
+ # sbom generation has finished.
+ _qt_internal_sbom_collect_cmake_include_files(post_generation_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_post_generation_include_files${suffix}
+ )
+
+ # Verification only makes sense on installation, where the checksums are present.
+ _qt_internal_sbom_collect_cmake_include_files(verify_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_verify_include_files${suffix}
+ )
+
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_INCLUDES} "${includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_BEFORE_CHECKSUM_INCLUDES} "${before_checksum_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_AFTER_CHECKSUM_INCLUDES} "${after_checksum_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_POST_GENERATION_INCLUDES} "${post_generation_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_VERIFY_INCLUDES} "${verify_includes}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Clears cmake include files for the current project from the global properties.
+function(_qt_internal_sbom_clear_cmake_include_files)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ # Clean up properties, so that they are empty for possible next repo in a top-level build.
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_before_checksum_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_after_checksum_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_post_generation_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files${suffix} "")
+endfunction()
+
+# Creates cmake build targets to create build-time SBOMs (for testing purposes only, because they
+# lack checksums for installed files).
+# Also creates the assemble_sbom cmake file that is used by both build-time and install-time
+# sbom generation.
+function(_qt_internal_sbom_create_build_time_sbom_targets)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE
+ SBOM_BUILD_OUTPUT_PATH
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT
+ SBOM_BUILD_OUTPUT_DIR
+ INCLUDES
+ POST_GENERATION_INCLUDES
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ set(extra_content "
+ set(QT_SBOM_PACKAGES \"\")
+ set(QT_SBOM_PACKAGES_WITH_VERIFICATION_CODES \"\")
+")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(final_message "Finalizing SPDX SBOM generation in build dir")
+ set(build_comment "SPDX document")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ set(extra_content "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ set(final_message "Finalizing Cyclone DX SBOM TOML generation in build dir")
+ set(build_comment "Cyclone DX document")
+ endif()
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$<CONFIG>")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(content "
+ # QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
+ # This allows reusing the same cmake file for both build and install.
+ if(NOT DEFINED QT_SBOM_BUILD_TIME)
+ set(QT_SBOM_BUILD_TIME TRUE)
+ endif()
+ if(NOT QT_SBOM_OUTPUT_PATH)
+ set(QT_SBOM_OUTPUT_DIR \"${arg_SBOM_BUILD_OUTPUT_DIR}\")
+ set(QT_SBOM_OUTPUT_PATH \"${arg_SBOM_BUILD_OUTPUT_PATH}\")
+ set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${arg_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT}\")
+ file(MAKE_DIRECTORY \"${arg_SBOM_BUILD_OUTPUT_DIR}\")
+ endif()
+ ${extra_content}
+ ${arg_INCLUDES}
+ if(QT_SBOM_BUILD_TIME)
+ message(STATUS \"${final_message}: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ ${arg_POST_GENERATION_INCLUDES}
+ endif()
+")
+ set(assemble_sbom "${sbom_dir}/assemble_sbom${suffix}${multi_config_suffix}.cmake")
+ file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
+
+ if(NOT TARGET sbom)
+ add_custom_target(sbom)
+ endif()
+
+ # Create a build target to create a build-time sbom (no verification codes or sha1s).
+ set(repo_sbom_target "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}${suffix}")
+ set(comment "")
+ string(APPEND comment "Assembling build time ${build_comment} without checksums for "
+ "${arg_REPO_PROJECT_NAME_LOWERCASE}. Just for testing.")
+ add_custom_target(${repo_sbom_target}
+ COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
+ COMMENT "${comment}"
+ VERBATIM
+ USES_TERMINAL # To avoid running two configs of the command in parallel
+ )
+
+ get_cmake_property(qt_repo_deps _qt_repo_deps_${arg_REAL_QT_REPO_PROJECT_NAME_LOWERCASE})
+ if(qt_repo_deps)
+ foreach(repo_dep IN LISTS qt_repo_deps)
+ set(repo_dep_sbom "sbom_${repo_dep}${suffix}")
+ if(TARGET "${repo_dep_sbom}")
+ add_dependencies(${repo_sbom_target} ${repo_dep_sbom})
+ endif()
+ endforeach()
+ endif()
+
+ add_dependencies(sbom ${repo_sbom_target})
+
+ set(${arg_OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH} "${assemble_sbom}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup install markers for multi-config generators.
+# Makes sure to wait for all configurations to finish installation before actually generating
+# the SBOM and removing the markers.
+function(_qt_internal_sbom_setup_multi_config_install_markers)
+ set(opt_args "")
+ set(single_args
+ SBOM_DIR
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ OUT_VAR_EXTRA_CODE_BEGIN
+ OUT_VAR_EXTRA_CODE_INNER_END
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(extra_code_begin "")
+ set(extra_code_inner_end "")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(NOT is_multi_config)
+ set(${arg_OUT_VAR_EXTRA_CODE_BEGIN} "${extra_code_begin}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_EXTRA_CODE_INNER_END} "${extra_code_inner_end}" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "_spdx")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+
+ set(install_markers_dir "${arg_SBOM_DIR}")
+ set(install_marker_path "${install_markers_dir}/finished_install${suffix}-$<CONFIG>.cmake")
+
+ set(install_marker_code "
+ message(STATUS \"Writing install marker for config $<CONFIG>: ${install_marker_path} \")
+ file(WRITE \"${install_marker_path}\" \"\")
+")
+
+ install(CODE "${install_marker_code}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${install_marker_code}"
+ COMPONENT "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}${suffix}"
+ EXCLUDE_FROM_ALL)
+ endif()
+
+ set(install_markers "")
+ foreach(config IN LISTS configs)
+ set(marker_path "${install_markers_dir}/finished_install${suffix}-${config}.cmake")
+ list(APPEND install_markers "${marker_path}")
+ # Remove the markers on reconfiguration, just in case there are stale ones.
+ if(EXISTS "${marker_path}")
+ file(REMOVE "${marker_path}")
+ endif()
+ endforeach()
+
+ # Escape the semicolons in install_makers, so they don't break argument parsing in
+ # _qt_internal_sbom_setup_sbom_install_code when they are forwarded there.
+ string(REPLACE ";" "\\;" install_markers "${install_markers}")
+
+ set(extra_code_begin "
+ set(QT_SBOM_INSTALL_MARKERS${suffix} \"${install_markers}\")
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS${suffix})
+ if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix} FALSE)
+ endif()
+ endforeach()
+")
+ set(extra_code_inner_end "
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS${suffix})
+ message(STATUS
+ \"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
+ file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
+ endforeach()
+")
+
+ set(${arg_OUT_VAR_EXTRA_CODE_BEGIN} "${extra_code_begin}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_EXTRA_CODE_INNER_END} "${extra_code_inner_end}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup the fake checksum code snippet.
+function(_qt_internal_sbom_setup_fake_checksum)
+ set(opt_args "")
+ set(single_args
+ OUT_VAR_FAKE_CHECKSUM_CODE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ # Allow skipping checksum computation for testing purposes, while installing just the sbom
+ # documents, without requiring to build and install all the actual files.
+ set(extra_code "")
+ if(QT_SBOM_FAKE_CHECKSUM)
+ string(APPEND extra_code "
+ set(QT_SBOM_FAKE_CHECKSUM TRUE)")
+ endif()
+
+ set(${arg_OUT_VAR_FAKE_CHECKSUM_CODE} "${extra_code}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup the install-time SBOM generation code.
+function(_qt_internal_sbom_setup_sbom_install_code)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+
+ SBOM_INSTALL_OUTPUT_PATH
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT
+ SBOM_INSTALL_OUTPUT_DIR
+
+ ASSEMBLE_SBOM_INCLUDE_PATH
+
+ EXTRA_CODE_BEGIN
+ EXTRA_CODE_INNER_END
+ PROCESS_VERIFICATION_CODES
+
+ BEFORE_CHECKSUM_INCLUDES
+ AFTER_CHECKSUM_INCLUDES
+ POST_GENERATION_INCLUDES
+ VERIFY_INCLUDES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "_spdx")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(final_message "Finalizing SBOM generation in install dir")
+ set(process_verification_codes "
+ include(\"${arg_PROCESS_VERIFICATION_CODES}\")
+")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ set(final_message "Finalizing intermediate TOML generation in install dir")
+
+ set(process_verification_codes "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ endif()
+
+ set(assemble_sbom_install "
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix} TRUE)
+ ${arg_EXTRA_CODE_BEGIN}
+ if(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix})
+ set(QT_SBOM_BUILD_TIME FALSE)
+ set(QT_SBOM_OUTPUT_DIR \"${arg_SBOM_INSTALL_OUTPUT_DIR}\")
+ set(QT_SBOM_OUTPUT_PATH \"${arg_SBOM_INSTALL_OUTPUT_PATH}\")
+ set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${arg_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT}\")
+ file(MAKE_DIRECTORY \"${arg_SBOM_INSTALL_OUTPUT_DIR}\")
+ include(\"${arg_ASSEMBLE_SBOM_INCLUDE_PATH}\")
+ ${arg_BEFORE_CHECKSUM_INCLUDES}
+ ${process_verification_codes}
+ ${arg_AFTER_CHECKSUM_INCLUDES}
+ message(STATUS \"${final_message}: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ ${arg_POST_GENERATION_INCLUDES}
+ ${arg_VERIFY_INCLUDES}
+ ${arg_EXTRA_CODE_INNER_END}
+ else()
+ message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
+ endif()
+")
+
+ install(CODE "${assemble_sbom_install}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}"
+ EXCLUDE_FROM_ALL)
+ endif()
+endfunction()
diff --git a/cmake/QtPublicSbomCycloneDXHelpers.cmake b/cmake/QtPublicSbomCycloneDXHelpers.cmake
new file mode 100644
index 00000000000..a3dc52d4e39
--- /dev/null
+++ b/cmake/QtPublicSbomCycloneDXHelpers.cmake
@@ -0,0 +1,382 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Gets the helper python script name and relative dir in the source dir.
+function(_qt_internal_sbom_get_cyclone_dx_generator_script_name
+ out_var_generator_name
+ out_var_generator_relative_dir)
+ set(generator_name "qt_cyclonedx_generator.py")
+
+ _qt_internal_path_join(generator_relative_dir
+ "util" "sbom" "cyclonedx" "qt_cyclonedx_generator")
+
+ set(${out_var_generator_name} "${generator_name}" PARENT_SCOPE)
+ set(${out_var_generator_relative_dir} "${generator_relative_dir}" PARENT_SCOPE)
+endfunction()
+
+# Ges the path to the helper python script, which should be used to generate CycloneDX document.
+# Prefers the source path over the installed path, for easier development of the script.
+function(_qt_internal_sbom_get_cyclone_dx_generator_path out_var)
+ _qt_internal_sbom_get_cyclone_dx_generator_script_name(generator_name generator_relative_dir)
+
+ _qt_internal_path_join(qtbase_script_path
+ "${QT_SOURCE_TREE}" "${generator_relative_dir}" "${generator_name}")
+ _qt_internal_path_join(installed_script_path
+ "${QT6_INSTALL_PREFIX}" "${QT6_INSTALL_LIBEXECS}" "${generator_name}")
+
+ # qtbase sources available, always use them, regardless if it's a prefix or non-prefix build.
+ # Makes development easier.
+ if(EXISTS "${qtbase_script_path}")
+ set(script_path "${qtbase_script_path}")
+
+ # qtbase sources unavailable, use installed files.
+ elseif(EXISTS "${installed_script_path}")
+ set(script_path "${installed_script_path}")
+ else()
+ message(FATAL_ERROR "Can't find ${generator_name} file.")
+ endif()
+
+ set(${out_var} "${script_path}" PARENT_SCOPE)
+endfunction()
+
+# Parses the options for a single CYDX_PROPERTY_ENTRY, and creates a toml snippet to add a
+# CycloneDX property to the final toml document.
+function(_qt_internal_sbom_parse_cydx_property_entry_options)
+ set(opt_args "")
+ set(single_args
+ CYDX_PROPERTY_NAME
+ CYDX_PROPERTY_VALUE
+ OUT_VAR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_CYDX_PROPERTY_NAME)
+ message(FATAL_ERROR "CYDX_PROPERTY_NAME is required.")
+ endif()
+
+ if(NOT arg_CYDX_PROPERTY_VALUE)
+ message(FATAL_ERROR "CYDX_PROPERTY_VALUE is required.")
+ endif()
+
+ if(NOT arg_OUT_VAR)
+ message(FATAL_ERROR "OUT_VAR is required.")
+ endif()
+
+ set(${arg_OUT_VAR} "
+[[components.properties]]
+name = \\\"${arg_CYDX_PROPERTY_NAME}\\\"
+value = \\\"${arg_CYDX_PROPERTY_VALUE}\\\"
+" PARENT_SCOPE)
+endfunction()
+
+# Processes a list of CycloneDX property entries, and creates their toml representation as output.
+function(_qt_internal_sbom_handle_cydx_properties)
+ set(opt_args "")
+ set(single_args
+ OUT_VAR_CYDX_PROPERTIES_STRING
+ )
+ set(multi_args
+ CYDX_PROPERTIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_OUT_VAR_CYDX_PROPERTIES_STRING)
+ message(FATAL_ERROR "OUT_VAR_CYDX_PROPERTIES_STRING is required.")
+ endif()
+
+ # Collect each CYDX_PROPERTY_ENTRY args into a separate variable.
+ set(prop_idx -1)
+ set(prop_entry_indices "")
+
+ foreach(prop_arg IN LISTS arg_CYDX_PROPERTIES)
+ if(prop_arg STREQUAL "CYDX_PROPERTY_ENTRY")
+ math(EXPR prop_idx "${prop_idx}+1")
+ list(APPEND prop_entry_indices "${prop_idx}")
+ elseif(prop_idx GREATER_EQUAL 0)
+ list(APPEND prop_${prop_idx}_args "${prop_arg}")
+ else()
+ message(FATAL_ERROR "Missing CYDX_PROPERTY_ENTRY keyword.")
+ endif()
+ endforeach()
+
+ set(properties_string "")
+
+ foreach(prop_idx IN LISTS prop_entry_indices)
+ _qt_internal_sbom_parse_cydx_property_entry_options(
+ ${prop_${prop_idx}_args}
+ OUT_VAR property_tuple
+ )
+
+ string(APPEND properties_string "${property_tuple}")
+ endforeach()
+
+ set(${arg_OUT_VAR_CYDX_PROPERTIES_STRING} "${properties_string}" PARENT_SCOPE)
+endfunction()
+
+# Outputs extra Cyclone DX properties based on the sbom entity type.
+function(_qt_internal_sbom_handle_qt_entity_cydx_properties)
+ set(opt_args "")
+ set(single_args
+ SBOM_ENTITY_TYPE
+ OUT_CYDX_PROPERTIES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_SBOM_ENTITY_TYPE)
+ message(FATAL_ERROR "SBOM_ENTITY_TYPE is required.")
+ endif()
+
+ if(NOT arg_OUT_CYDX_PROPERTIES)
+ message(FATAL_ERROR "OUT_CYDX_PROPERTIES is required.")
+ endif()
+
+ set(cydx_properties "")
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:entity_type"
+ CYDX_PROPERTY_VALUE "${arg_SBOM_ENTITY_TYPE}"
+ )
+
+ _qt_internal_sbom_is_qt_entity_type("${arg_SBOM_ENTITY_TYPE}" is_qt_entity_type)
+ if(is_qt_entity_type)
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:is_qt_entity_type"
+ CYDX_PROPERTY_VALUE "true"
+ )
+ endif()
+ _qt_internal_sbom_is_qt_3rd_party_entity_type("${arg_SBOM_ENTITY_TYPE}"
+ is_qt_3rd_party_entity_type)
+ if(is_qt_3rd_party_entity_type)
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:is_qt_3rd_party_entity_type"
+ CYDX_PROPERTY_VALUE "true"
+ )
+ endif()
+
+ set(${arg_OUT_CYDX_PROPERTIES} "${cydx_properties}" PARENT_SCOPE)
+endfunction()
+
+# Maps an sbom entity type to a cyclone dx component type.
+function(_qt_internal_sbom_get_cyclone_component_type out_var sbom_entity_type)
+ set(library_types
+ "QT_MODULE"
+ "QT_PLUGIN"
+ "QML_PLUGIN"
+ "QT_THIRD_PARTY_MODULE"
+ "QT_THIRD_PARTY_SOURCES"
+ "SYSTEM_LIBRARY"
+ "LIBRARY"
+ "THIRD_PARTY_LIBRARY"
+ "THIRD_PARTY_LIBRARY_WITH_FILES"
+ "THIRD_PARTY_SOURCES"
+ )
+
+ set(application_types
+ "QT_TOOL"
+ "QT_APP"
+ "EXECUTABLE"
+ )
+
+ if(sbom_entity_type IN_LIST library_types)
+ set(component_type "library")
+ elseif(sbom_entity_type IN_LIST application_types)
+ set(component_type "application")
+ else()
+ # Default to library for now, because it's unclear what would be a better default.
+ set(component_type "library")
+ endif()
+
+ set(${out_var} "${component_type}" PARENT_SCOPE)
+endfunction()
+
+# Generates a pseudo-unique serial number for a CycloneDX sbom document.
+#
+# The spec says that a BOM serial number must conform to RFC 4122, but doesn't specify which
+# kind of uuid version should be generated.
+# The upstream python library generates a version 4 uuid, which is fully random.
+# CMake can only generate version 3 and 5 uuids, which are fully deterministic based on the given
+# NAMESPACE and NAME values.
+# Generating a fully random uuid prevents build reproducibility. The maintainer of the Cyclone DX
+# spec even mentions that here:
+# https://github.com/CycloneDX/specification/issues/97#issuecomment-955904904
+# And yet to to do component-wise inter-document linking using the BOM-Link mechanism, you have to
+# use serial numbers.
+#
+# Because the spec doesn't explicitly prohibit it, we will generate a version 5 uuid based on the
+# SPDX_NAMESPACE passed to the function, which is supposed to be unique enough, because it contains
+# the project / document name (e.g. qtbase) and its version or git version.
+# This should alleviate the reproducibility problem as well.
+function(_qt_internal_sbom_get_cyclone_bom_serial_number)
+ set(opt_args "")
+ set(single_args
+ SPDX_NAMESPACE
+ OUT_VAR_UUID
+ OUT_VAR_SERIAL_NUMBER
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SPDX_NAMESPACE "")
+
+ # This is a randomly generated uuid v4 value. To be used for all eternity. Until we change the
+ # implementation of the function.
+ set(uuid_namespace "c024642f-9853-45b2-9bfd-ab3f061a05bb")
+
+ string(UUID uuid NAMESPACE "${uuid_namespace}" NAME "${arg_SPDX_NAMESPACE}" TYPE SHA1)
+ set(cyclone_dx_serial_number "urn:cdx:${uuid}")
+
+ if(arg_OUT_VAR_UUID)
+ set("${arg_OUT_VAR_UUID}" "${uuid}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SERIAL_NUMBER)
+ set("${arg_OUT_VAR_SERIAL_NUMBER}" "${cyclone_dx_serial_number}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# See https://github.com/CycloneDX/guides/blob/main/SBOM/en/0x52-Linking.md
+function(_qt_internal_sbom_get_cydx_external_bom_link target out_var)
+ get_target_property(spdx_id "${target}" _qt_sbom_spdx_id)
+ get_target_property(bom_serial_number "${target}" _qt_sbom_cydx_bom_serial_number_uuid)
+
+ set(bom_version "1")
+ set(bom_link "urn:cdx:${bom_serial_number}/${bom_version}#${spdx_id}")
+
+ set(${out_var} "${bom_link}" PARENT_SCOPE)
+endfunction()
+
+# Records necessary details of external target dependencies in global properties, to later create
+# the CycloneDX packages for them. The info collection needs to be done immediately in the directory
+# scope where the targets were found, because they might not be global, and thus can't be accessed
+# later.
+function(_qt_internal_sbom_record_external_target_dependecies)
+ set(opt_args "")
+ set(single_args "")
+ set(multi_args
+ TARGETS
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_TARGETS)
+ return()
+ endif()
+
+ get_property(existing_ids GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids)
+ if(NOT existing_ids)
+ set(existing_ids "")
+ endif()
+
+ foreach(target IN LISTS arg_TARGETS)
+ # Use the full spdx id (one prefixed with the containing DocumentRef-) because that's what
+ # our spdx dependency relationships use at the moment.
+ # Both Foo and FooPrivate map to the same spdx_id, so we need to avoid duplicates on spdx id
+ # level.
+ get_target_property(spdx_id "${target}" _qt_sbom_spdx_id)
+
+ if(spdx_id IN_LIST existing_ids)
+ continue()
+ endif()
+
+ list(APPEND existing_ids "${spdx_id}")
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_external_target_dep_ids "${spdx_id}")
+
+ # This is checked in _qt_internal_sbom_add_target, to prevent duplicate creation of
+ # system library targets.
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_external_target_dependencies
+ "${target}")
+
+ get_target_property(package_name "${target}" _qt_sbom_package_name)
+ get_target_property(sbom_entity_type "${target}" _qt_sbom_entity_type)
+ get_target_property(package_version "${target}" _qt_sbom_package_version)
+ _qt_internal_sbom_get_cydx_external_bom_link("${target}" external_bom_link)
+
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_target"
+ "${target}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_package_name"
+ "${package_name}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_sbom_entity_type"
+ "${sbom_entity_type}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_package_version"
+ "${package_version}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_external_bom_link"
+ "${external_bom_link}")
+ endforeach()
+endfunction()
+
+# Goes through the list of recorded external target dependencies collected during target
+# dependency analysis, and adds them as CycloneDX packages to the CycloneDX document.
+# This is different from SPDX v2.3, which doesn't require creating a package for dependencies that
+# are defined in a different document.
+function(_qt_internal_sbom_add_cydx_external_target_dependencies)
+ get_property(spdx_ids GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids)
+ if(NOT spdx_ids)
+ # Clean up external target dependencies, before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies "")
+ return()
+ endif()
+
+ # Just in case, don't add duplicates.
+ set(visited_spdx_ids "")
+
+ foreach(spdx_id IN LISTS spdx_ids)
+ if(spdx_id IN_LIST visited_spdx_ids)
+ continue()
+ endif()
+
+ get_cmake_property(package_name
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_package_name")
+ get_cmake_property(sbom_entity_type
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_sbom_entity_type")
+ get_cmake_property(package_version
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_package_version")
+ get_cmake_property(external_bom_link
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_external_bom_link")
+
+ _qt_internal_sbom_generate_cyclone_add_package(
+ PACKAGE "${package_name}"
+ SPDXID "${spdx_id}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ VERSION "${package_version}"
+ EXTERNAL_BOM_LINK "${external_bom_link}"
+ )
+
+ list(APPEND visited_spdx_ids "${spdx_id}")
+ endforeach()
+
+ # Clean up external target dependencies, before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies "")
+endfunction()
+
+# Records a license id and its text in global properties, to be added to the CycloneDX document
+# later.
+function(_qt_internal_sbom_record_license_cydx)
+ set(opt_args "")
+ set(single_args
+ LICENSE_ID
+ EXTRACTED_TEXT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_internal_sbom_cydx_licenses "${arg_LICENSE_ID}")
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_cydx_licenses_${arg_LICENSE_ID}_text "${arg_EXTRACTED_TEXT}"
+ )
+endfunction()
diff --git a/cmake/QtPublicSbomDepHelpers.cmake b/cmake/QtPublicSbomDepHelpers.cmake
index 32e3b6b832c..794f3f77db2 100644
--- a/cmake/QtPublicSbomDepHelpers.cmake
+++ b/cmake/QtPublicSbomDepHelpers.cmake
@@ -4,11 +4,17 @@
# Walks a target's direct dependencies and assembles a list of relationships between the packages
# of the target dependencies.
# Currently handles various Qt targets and system libraries.
+# For SPDX documents, it collects the relationships in OUT_SPDX_RELATIONSHIPS.
+# For CYDX documents, it collects the dependencies (not relationship info) in OUT_CYDX_DEPENDENCIES.
+# If a CYDX dependency is in an external docuemnt, the dependency is added to
+# OUT_EXTERNAL_TARGET_DEPENDENCIES instead.
function(_qt_internal_sbom_handle_target_dependencies target)
set(opt_args "")
set(single_args
SPDX_ID
- OUT_RELATIONSHIPS
+ OUT_CYDX_DEPENDENCIES
+ OUT_SPDX_RELATIONSHIPS
+ OUT_EXTERNAL_TARGET_DEPENDENCIES
)
set(multi_args
LIBRARIES
@@ -75,8 +81,12 @@ function(_qt_internal_sbom_handle_target_dependencies target)
set(all_direct_libraries ${libraries} ${public_libraries} ${sbom_dependencies})
list(REMOVE_DUPLICATES all_direct_libraries)
- set(spdx_dependencies "")
+ set(regular_cydx_dependencies "")
+ set(regular_spdx_dependencies "")
+
+ set(external_cydx_dependencies "")
set(external_spdx_dependencies "")
+ set(external_target_dependencies "")
# Go through each direct linked lib.
foreach(direct_lib IN LISTS all_direct_libraries)
@@ -108,7 +118,8 @@ function(_qt_internal_sbom_handle_target_dependencies target)
# Add a dependency on the vendored lib instead of the Wrap target.
if(is_3rdparty_bundled_lib AND lib_spdx_id)
- list(APPEND spdx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
set(bundled_targets_found TRUE)
endif()
endforeach()
@@ -148,30 +159,38 @@ function(_qt_internal_sbom_handle_target_dependencies target)
if(NOT is_dependency_in_external_document)
# If the target is not in the external document, it must be one built as part of the
# current project.
- list(APPEND spdx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
else()
# Refer to the package in the external document. This can be the case
# in a top-level build, where a system library is reused across repos, or for any
# regular dependency that was built as part of a different project.
_qt_internal_sbom_add_external_target_dependency("${direct_lib}"
- extra_spdx_dependencies
+ OUT_CYDX_DEPENDENCIES extra_cydx_dependencies
+ OUT_SPDX_DEPENDENCIES extra_spdx_dependencies
+ OUT_TARGET_DEPENDENCIES extra_target_dependencies
)
if(extra_spdx_dependencies)
+ list(APPEND external_cydx_dependencies ${extra_cydx_dependencies})
list(APPEND external_spdx_dependencies ${extra_spdx_dependencies})
+ list(APPEND external_target_dependencies ${extra_target_dependencies})
endif()
endif()
endforeach()
- set(relationships "")
+ set(spdx_relationships "")
+
# Keep the external dependencies first, so they are neatly ordered.
- foreach(dep_spdx_id IN LISTS external_spdx_dependencies spdx_dependencies)
- set(relationship
- "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}"
- )
- list(APPEND relationships "${relationship}")
+ foreach(dep_spdx_id IN LISTS external_spdx_dependencies regular_spdx_dependencies)
+ set(relationship "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}")
+ list(APPEND spdx_relationships "${relationship}")
endforeach()
- set(${arg_OUT_RELATIONSHIPS} "${relationships}" PARENT_SCOPE)
+ set(all_cydx_dependencies ${external_cydx_dependencies} ${regular_cydx_dependencies})
+
+ set(${arg_OUT_CYDX_DEPENDENCIES} "${all_cydx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_RELATIONSHIPS} "${spdx_relationships}" PARENT_SCOPE)
+ set(${arg_OUT_EXTERNAL_TARGET_DEPENDENCIES} "${external_target_dependencies}" PARENT_SCOPE)
endfunction()
# Checks whether the current target will have its sbom generated into the current repo sbom
@@ -205,18 +224,34 @@ function(_qt_internal_sbom_is_external_target_dependency target)
endfunction()
# Handles generating an external document reference SDPX element for each target package that is
-# located in a different spdx document.
-function(_qt_internal_sbom_add_external_target_dependency target out_spdx_dependencies)
+# located in a different spdx document, and collects the reference in OUT_SPDX_DEPENDENCIES.
+# In case of CycloneDX, we just collect the dependency spdx id (bom-ref) and the target name,
+# which are added to OUT_CYDX_DEPENDENCIES and OUT_TARGET_DEPENDENCIES respectively.
+function(_qt_internal_sbom_add_external_target_dependency target)
+ set(opt_args "")
+ set(single_args
+ OUT_CYDX_DEPENDENCIES
+ OUT_SPDX_DEPENDENCIES
+ OUT_TARGET_DEPENDENCIES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
_qt_internal_sbom_get_spdx_id_for_target("${target}" dep_spdx_id)
if(NOT dep_spdx_id)
message(DEBUG "Could not add external target dependency on ${target} "
"because no spdx id could be found")
- set(${out_spdx_dependencies} "" PARENT_SCOPE)
+ set(${arg_OUT_CYDX_DEPENDENCIES} "" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_DEPENDENCIES} "" PARENT_SCOPE)
+ set(${arg_OUT_TARGET_DEPENDENCIES} "" PARENT_SCOPE)
return()
endif()
+ set(cydx_dependencies "")
set(spdx_dependencies "")
+ set(target_depdendencies "")
# Get the external document path and the repo it belongs to for the given target.
get_property(relative_installed_repo_document_path TARGET ${target}
@@ -232,9 +267,11 @@ function(_qt_internal_sbom_add_external_target_dependency target out_spdx_depend
get_cmake_property(known_external_document
_qt_known_external_documents_${external_document_ref})
- set(dependency "${external_document_ref}:${dep_spdx_id}")
+ set(spdx_dependency "${external_document_ref}:${dep_spdx_id}")
- list(APPEND spdx_dependencies "${dependency}")
+ list(APPEND cydx_dependencies "${dep_spdx_id}")
+ list(APPEND spdx_dependencies "${spdx_dependency}")
+ list(APPEND target_depdendencies "${target}")
# Only add a reference to the external document package, if we haven't done so already.
if(NOT known_external_document)
@@ -261,5 +298,7 @@ function(_qt_internal_sbom_add_external_target_dependency target out_spdx_depend
"Missing spdx document path for external target dependency: ${target}")
endif()
- set(${out_spdx_dependencies} "${spdx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_CYDX_DEPENDENCIES} "${cydx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_DEPENDENCIES} "${spdx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_TARGET_DEPENDENCIES} "${target_depdendencies}" PARENT_SCOPE)
endfunction()
diff --git a/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake b/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake
new file mode 100644
index 00000000000..59797d24b7d
--- /dev/null
+++ b/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake
@@ -0,0 +1,425 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Helper to return the path to staging cydx file, where content will be incrementally appended to.
+function(_qt_internal_get_staging_area_cydx_file_path out_var)
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(staging_area_cydx_file "${sbom_dir}/staging-${repo_project_name_lowercase}.cdx.in.toml")
+ set(${out_var} "${staging_area_cydx_file}" PARENT_SCOPE)
+endfunction()
+
+# Starts recording information for the generation of a CycloneDX sbom for a project.
+# Similar to _qt_internal_sbom_begin_project_generate which is the SPDX variant.
+function(_qt_internal_sbom_begin_project_generate_cyclone)
+ set(opt_args "")
+ set(single_args
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ LICENSE
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ PROJECT
+ PROJECT_COMMENT
+ PROJECT_FOR_SPDX_ID
+ SUPPLIER
+ SUPPLIER_URL
+ NAMESPACE
+ BOM_SERIAL_NUMBER_UUID
+ CPE
+ OUT_VAR_PROJECT_SPDX_ID
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_forward_function_args(
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR common_project_args
+ FORWARD_SINGLE
+ ${single_args}
+ )
+
+ _qt_internal_sbom_get_common_project_variables(
+ ${common_project_args}
+ OUT_VAR_PROJECT_NAME arg_PROJECT
+ OUT_VAR_CURRENT_UTC current_utc
+ OUT_VAR_CURRENT_YEAR current_year
+ DEFAULT_SBOM_FILE_NAME_EXTENSION "cdx"
+ OUT_VAR_OUTPUT arg_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH arg_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID project_spdx_id
+ OUT_VAR_COPYRIGHT arg_COPYRIGHT
+ OUT_VAR_SUPPLIER arg_SUPPLIER
+ OUT_VAR_SUPPLIER_URL arg_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT project_comment
+ )
+ if(arg_OUT_VAR_PROJECT_SPDX_ID)
+ set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+ endif()
+
+ _qt_internal_sbom_get_git_version_vars()
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(BOM_SERIAL_NUMBER_UUID "")
+
+ if(arg_PROJECT_COMMENT)
+ string(APPEND project_comment "${arg_PROJECT_COMMENT}")
+ endif()
+
+ _qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "")
+ set(download_location_field "")
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location_field "download_location = \"${arg_DOWNLOAD_LOCATION}\"")
+ endif()
+
+ set(content "
+[root_component]
+name = \"${arg_PROJECT}\" # Required
+spdx_id = \"${project_spdx_id}\" # Required
+version = \"${QT_SBOM_GIT_VERSION}\" # Required
+supplier = '${arg_SUPPLIER}' # Required
+supplier_url = \"${arg_SUPPLIER_URL}\" # Required
+${download_location_field}
+build_date = \"${current_utc}\"
+serial_number_uuid = \"${arg_BOM_SERIAL_NUMBER_UUID}\" # Required
+description = '''${project_comment}
+'''
+
+[[project_build_tools]]
+name = \"cmake\"
+version = \"${CMAKE_VERSION}\"
+component_type = \"application\"
+description = \"Build system tool used to build the project.\"
+")
+
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_create_sbom_staging_file(
+ CONTENT "${content}"
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_CREATE_STAGING_FILE create_staging_file
+ OUT_VAR_SBOM_DIR sbom_dir
+ )
+
+ _qt_internal_sbom_save_project_info_in_global_properties(
+ SUPPLIER "${arg_SUPPLIER}"
+ SUPPLIER_URL "${arg_SUPPLIER_URL}"
+ NAMESPACE "${arg_NAMESPACE}"
+ PROJECT "${arg_PROJECT}"
+ PROJECT_SPDX_ID "${project_spdx_id}"
+ )
+
+ _qt_internal_sbom_save_common_path_variables_in_global_properties(
+ OUTPUT "${arg_OUTPUT}"
+ OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
+ SBOM_DIR "${sbom_dir}"
+ PROPERTY_SUFFIX _cydx
+ )
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files_cydx "${create_staging_file}")
+endfunction()
+
+# Finalizes the CycloneDX sbom generation for a project.
+function(_qt_internal_sbom_end_project_generate_cyclone)
+ _qt_internal_sbom_get_common_path_variables_from_global_properties(
+ SBOM_FORMAT "CYDX_V1_6"
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH sbom_install_output_path
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT sbom_install_output_path_without_ext
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR sbom_install_output_dir
+ )
+
+ if(NOT sbom_build_output_path)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
+
+ # Process licenses before getting the includes.
+ _qt_internal_sbom_add_recorded_licenses_cydx()
+
+ _qt_internal_sbom_get_cmake_include_files(
+ SBOM_FORMAT "CYDX_V1_6"
+ OUT_VAR_INCLUDES includes
+ OUT_VAR_POST_GENERATION_INCLUDES post_generation_includes
+ )
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+
+ set(build_time_args "")
+ if(includes)
+ list(APPEND build_time_args INCLUDES "${includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ _qt_internal_sbom_create_build_time_sbom_targets(
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
+ SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT "${sbom_build_output_path_without_ext}"
+ SBOM_BUILD_OUTPUT_DIR "${sbom_build_output_dir}"
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH assemble_sbom
+ ${build_time_args}
+ )
+
+ _qt_internal_sbom_setup_multi_config_install_markers(
+ SBOM_DIR "${sbom_dir}"
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
+ OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
+ )
+
+ _qt_internal_sbom_setup_fake_checksum(
+ OUT_VAR_FAKE_CHECKSUM_CODE extra_code_begin_fake_checksum
+ )
+ if(extra_code_begin_fake_checksum)
+ string(APPEND extra_code_begin "${extra_code_begin_fake_checksum}")
+ endif()
+
+ set(setup_sbom_install_args "")
+ if(extra_code_begin)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_BEGIN "${extra_code_begin}")
+ endif()
+ if(extra_code_inner_end)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_INNER_END "${extra_code_inner_end}")
+ endif()
+ if(before_checksum_includes)
+ list(APPEND setup_sbom_install_args BEFORE_CHECKSUM_INCLUDES "${before_checksum_includes}")
+ endif()
+ if(after_checksum_includes)
+ list(APPEND setup_sbom_install_args AFTER_CHECKSUM_INCLUDES "${after_checksum_includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND setup_sbom_install_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ if(verify_includes)
+ list(APPEND setup_sbom_install_args VERIFY_INCLUDES "${verify_includes}")
+ endif()
+
+ _qt_internal_sbom_setup_sbom_install_code(
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
+ SBOM_INSTALL_OUTPUT_DIR "${sbom_install_output_dir}"
+ ASSEMBLE_SBOM_INCLUDE_PATH "${assemble_sbom}"
+ ${setup_sbom_install_args}
+ )
+
+ _qt_internal_sbom_clear_cmake_include_files(
+ SBOM_FORMAT "CYDX_V1_6"
+ )
+endfunction()
+
+# Helper to add info about a package to the sbom. Usually a package is a mapping to a cmake target.
+function(_qt_internal_sbom_generate_cyclone_add_package)
+ set(opt_args "")
+ set(single_args
+ PACKAGE
+ VERSION
+ LICENSE_DECLARED
+ LICENSE_CONCLUDED
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ SPDXID
+ COMMENT
+
+ # Additions compared to spdx function signature.
+ CYDX_SUPPLIER
+ SBOM_ENTITY_TYPE
+ CONTAINING_COMPONENT
+ EXTERNAL_BOM_LINK
+ )
+ set(multi_args
+ CPE
+
+ # Additions compared to spdx function signature.
+ PURL_VALUES
+ DEPENDENCIES
+ CYDX_PROPERTIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(PACKAGE "")
+
+ set(check_option "")
+ if(arg_SPDXID)
+ set(check_option "CHECK" "${arg_SPDXID}")
+ endif()
+
+ _qt_internal_sbom_get_and_check_spdx_id(
+ VARIABLE arg_SPDXID
+ ${check_option}
+ HINTS "SPDXRef-${arg_PACKAGE}"
+ )
+
+ _qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "")
+ _qt_internal_sbom_set_default_option_value(VERSION "")
+ _qt_internal_sbom_set_default_option_value(SUPPLIER "")
+ _qt_internal_sbom_set_default_option_value(LICENSE_CONCLUDED "")
+ _qt_internal_sbom_set_default_option_value(COPYRIGHT "")
+
+ set(cpe_field "")
+ set(cpe_list ${arg_CPE})
+ if(cpe_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM cpe_list PREPEND "\\\"")
+ list(TRANSFORM cpe_list APPEND "\\\"")
+ list(JOIN cpe_list ", " cpe_string)
+ set(cpe_field "cpe_list = [${cpe_string}]")
+ endif()
+
+ set(purl_field "")
+ set(purl_list ${arg_PURL_VALUES})
+ if(purl_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM purl_list PREPEND "\\\"")
+ list(TRANSFORM purl_list APPEND "\\\"")
+ list(JOIN purl_list ", " purl_string)
+ set(purl_field "purl_list = [${purl_string}]")
+ endif()
+
+ set(name_field "name = \\\"${arg_PACKAGE}\\\"")
+ set(spdx_id_field "spdx_id = \\\"${arg_SPDXID}\\\"")
+ set(sbom_entity_type_field "sbom_entity_type = \\\"${arg_SBOM_ENTITY_TYPE}\\\"")
+
+ _qt_internal_sbom_get_cyclone_component_type(component_type "${arg_SBOM_ENTITY_TYPE}")
+ set(component_type_field "component_type = \\\"${component_type}\\\"")
+
+ set(version_field "")
+ if(arg_VERSION)
+ set(version_field "version = \\\"${arg_VERSION}\\\"")
+ endif()
+
+ set(dependency_field "")
+ set(dependency_list ${arg_DEPENDENCIES})
+ if(dependency_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM dependency_list PREPEND "\\\"")
+ list(TRANSFORM dependency_list APPEND "\\\"")
+ list(JOIN dependency_list ", " dependency_string)
+ set(dependency_field "dependencies = [${dependency_string}]")
+ endif()
+
+ set(copyright_field "")
+ if(arg_COPYRIGHT)
+ set(copyright_field "copyright = '''${arg_COPYRIGHT}'''")
+ endif()
+
+ set(download_location_field "")
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location_field "download_location = \\\"${arg_DOWNLOAD_LOCATION}\\\"")
+ endif()
+
+ set(license_concluded_field "")
+ if(arg_LICENSE_CONCLUDED)
+ set(license_concluded_field
+ "license_concluded_expression = \\\"${arg_LICENSE_CONCLUDED}\\\"")
+ endif()
+
+ set(supplier_field "")
+ if(arg_CYDX_SUPPLIER)
+ set(supplier_field "supplier = '${arg_CYDX_SUPPLIER}'")
+ endif()
+
+ set(containing_component_field "")
+ if(arg_CONTAINING_COMPONENT)
+ set(containing_component_field "containing_component = \\\"${arg_CONTAINING_COMPONENT}\\\"")
+ endif()
+
+ set(external_bom_link_field "")
+ if(arg_EXTERNAL_BOM_LINK)
+ set(external_bom_link_field "external_bom_link = \\\"${arg_EXTERNAL_BOM_LINK}\\\"")
+ endif()
+
+ set(properties_field "")
+ if(arg_CYDX_PROPERTIES)
+ _qt_internal_sbom_handle_cydx_properties(
+ CYDX_PROPERTIES ${arg_CYDX_PROPERTIES}
+ OUT_VAR_CYDX_PROPERTIES_STRING properties_field
+ )
+ endif()
+
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_spdx_file)
+
+ set(content "
+file(APPEND \"${staging_area_spdx_file}\"
+\"
+
+[[components]]
+${name_field}
+${spdx_id_field}
+${sbom_entity_type_field}
+${component_type_field}
+${version_field}
+${download_location_field}
+${copyright_field}
+${supplier_field}
+${containing_component_field}
+${external_bom_link_field}
+${cpe_field}
+${purl_field}
+${license_concluded_field}
+${dependency_field}
+${properties_field}
+\"
+)
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(package_sbom "${sbom_dir}/cydx_${arg_SPDXID}.cmake")
+ file(GENERATE OUTPUT "${package_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files_cydx "${package_sbom}")
+endfunction()
+
+# Adds all recorded custom licenses to the toml file.
+function(_qt_internal_sbom_add_recorded_licenses_cydx)
+ get_property(license_ids GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses)
+ if(NOT license_ids)
+ return()
+ endif()
+
+ set(content "")
+
+ foreach(license_id IN LISTS license_ids)
+ get_property(license_text GLOBAL PROPERTY
+ _qt_internal_sbom_cydx_licenses_${license_id}_text)
+
+ string(APPEND content "
+[[licenses]]
+license_id = \\\"${license_id}\\\"
+text = '''${license_text}
+'''
+")
+ endforeach()
+
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_spdx_file)
+
+ set(content "
+file(APPEND \"${staging_area_spdx_file}\"
+\"
+${content}
+\"
+)
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(snippet "${sbom_dir}/cydx_license_info.cmake")
+ file(GENERATE OUTPUT "${snippet}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files_cydx "${snippet}")
+
+ # Clean up before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses "")
+ foreach(license_id IN LISTS license_ids)
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses_${license_id}_text "")
+ endforeach()
+endfunction()
diff --git a/cmake/QtPublicSbomGenerationHelpers.cmake b/cmake/QtPublicSbomGenerationHelpers.cmake
index 89179e77a1b..1ade46c950e 100644
--- a/cmake/QtPublicSbomGenerationHelpers.cmake
+++ b/cmake/QtPublicSbomGenerationHelpers.cmake
@@ -74,37 +74,36 @@ function(_qt_internal_sbom_begin_project_generate)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
- if(QT_SBOM_FAKE_TIMESTAMP)
- set(current_utc "2590-01-01T11:33:55Z")
- set(current_year "2590")
- else()
- string(TIMESTAMP current_utc UTC)
- string(TIMESTAMP current_year "%Y" UTC)
- endif()
-
- _qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
-
- _qt_internal_sbom_get_git_version_vars()
-
- _qt_internal_path_join(default_sbom_file_name
- "${arg_PROJECT}" "${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.spdx")
+ _qt_internal_forward_function_args(
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR common_project_args
+ FORWARD_SINGLE
+ ${single_args}
+ )
- _qt_internal_path_join(default_install_sbom_path
- "\${CMAKE_INSTALL_PREFIX}/" "${CMAKE_INSTALL_DATAROOTDIR}" "${default_sbom_file_name}"
+ _qt_internal_sbom_get_common_project_variables(
+ ${common_project_args}
+ OUT_VAR_PROJECT_NAME arg_PROJECT
+ OUT_VAR_CURRENT_UTC current_utc
+ OUT_VAR_CURRENT_YEAR current_year
+ DEFAULT_SBOM_FILE_NAME_EXTENSION "spdx"
+ OUT_VAR_OUTPUT arg_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH arg_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID project_spdx_id
+ OUT_VAR_COPYRIGHT arg_COPYRIGHT
+ OUT_VAR_SUPPLIER arg_SUPPLIER
+ OUT_VAR_SUPPLIER_URL arg_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT project_comment
)
+ if(arg_OUT_VAR_PROJECT_SPDX_ID)
+ set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+ endif()
- _qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
- _qt_internal_sbom_set_default_option_value(OUTPUT_RELATIVE_PATH
- "${default_sbom_file_name}")
+ _qt_internal_sbom_get_git_version_vars()
- _qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
- _qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX_ID "Package-${arg_PROJECT}")
- _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
- _qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
- _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
- "${PROJECT_HOMEPAGE_URL}")
_qt_internal_sbom_set_default_option_value(NAMESPACE
"${arg_SUPPLIER}/spdxdocs/${arg_PROJECT}-${QT_SBOM_GIT_VERSION}")
+ _qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
_qt_internal_sbom_set_default_option_value(DOCUMENT_CREATOR_TOOL "Qt Build System")
if(arg_DOCUMENT_CREATOR_TOOL)
@@ -132,34 +131,10 @@ ExternalRef: PACKAGE-MANAGER purl ${purl_generic_id}")
PackageVersion: ${QT_SBOM_GIT_VERSION}")
endif()
- string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
- string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
- # Prevent collision with other generated SPDXID with -[0-9]+ suffix.
- string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
-
- set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
- if(arg_OUT_VAR_PROJECT_SPDX_ID)
- set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
- endif()
-
get_filename_component(doc_name "${arg_OUTPUT}" NAME_WLE)
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
- else()
- set(cmake_configs "${CMAKE_BUILD_TYPE}")
- endif()
-
_qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
- set(cmake_version "Built by CMake ${CMAKE_VERSION}")
- set(system_name_and_processor "${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})")
- set(default_project_comment
- "${cmake_version} with ${cmake_configs} configuration for ${system_name_and_processor}")
-
- set(project_comment "${default_project_comment}")
-
if(arg_PROJECT_COMMENT)
string(APPEND project_comment "${arg_PROJECT_COMMENT}")
endif()
@@ -206,82 +181,30 @@ BuiltDate: ${current_utc}
Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
")
- # Create the directory that will contain all sbom related files.
- _qt_internal_get_current_project_sbom_dir(sbom_dir)
- file(MAKE_DIRECTORY "${sbom_dir}")
- set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
-
- # Generate project document intro spdx file.
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- set(document_intro_file_name
- "${sbom_dir}/SPDXRef-DOCUMENT-${repo_project_name_lowercase}.spdx.in")
- file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
-
- # This is the file that will be incrementally assembled by having content appended to it.
- _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
-
- get_filename_component(output_file_name_without_ext "${arg_OUTPUT}" NAME_WLE)
- get_filename_component(output_file_ext "${arg_OUTPUT}" LAST_EXT)
-
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(multi_config_suffix "-$<CONFIG>")
- else()
- set(multi_config_suffix "")
- endif()
-
- set(computed_sbom_file_name_without_ext "${output_file_name_without_ext}${multi_config_suffix}")
- set(computed_sbom_file_name "${output_file_name_without_ext}${output_file_ext}")
-
- # In a super build and in a no-prefix build, put all the build time sboms into the same dir in,
- # in the qtbase build dir.
- if(QT_BUILDING_QT AND (QT_SUPERBUILD OR (NOT QT_WILL_INSTALL)))
- set(build_sbom_root_dir "${QT_BUILD_DIR}")
- else()
- set(build_sbom_root_dir "${sbom_dir}")
- endif()
-
- get_filename_component(output_relative_dir "${arg_OUTPUT_RELATIVE_PATH}" DIRECTORY)
-
- set(build_sbom_dir "${build_sbom_root_dir}/${output_relative_dir}")
- set(build_sbom_path "${build_sbom_dir}/${computed_sbom_file_name}")
- set(build_sbom_path_without_ext
- "${build_sbom_dir}/${computed_sbom_file_name_without_ext}")
-
- set(install_sbom_path "${arg_OUTPUT}")
-
- get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
- set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
-
- # Create cmake file to append the document intro spdx to the staging file.
- set(create_staging_file "${sbom_dir}/append_document_to_staging${multi_config_suffix}.cmake")
- set(content "
- cmake_minimum_required(VERSION 3.16)
- message(STATUS \"Starting SBOM generation in build dir: ${staging_area_spdx_file}\")
- set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
- file(READ \"${document_intro_file_name}\" content)
- # Override any previous file because we're starting from scratch.
- file(WRITE \"${staging_area_spdx_file}\" \"\${content}\")
-")
- file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
-
-
- set_property(GLOBAL PROPERTY _qt_sbom_project_supplier "${arg_SUPPLIER}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_supplier_url "${arg_SUPPLIER_URL}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_namespace "${arg_NAMESPACE}")
+ _qt_internal_sbom_create_sbom_staging_file(
+ CONTENT "${content}"
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_CREATE_STAGING_FILE create_staging_file
+ OUT_VAR_SBOM_DIR sbom_dir
+ )
- set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${project_spdx_id}")
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_path "${build_sbom_path}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext
- "${build_sbom_path_without_ext}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_dir "${build_sbom_dir}")
+ _qt_internal_sbom_save_project_info_in_global_properties(
+ SUPPLIER "${arg_SUPPLIER}"
+ SUPPLIER_URL "${arg_SUPPLIER_URL}"
+ NAMESPACE "${arg_NAMESPACE}"
+ PROJECT "${arg_PROJECT}"
+ PROJECT_SPDX_ID "${project_spdx_id}"
+ )
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_path "${install_sbom_path}")
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_path_without_ext
- "${install_sbom_path_without_ext}")
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_dir "${install_sbom_dir}")
+ _qt_internal_sbom_save_common_path_variables_in_global_properties(
+ OUTPUT "${arg_OUTPUT}"
+ OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
+ SBOM_DIR "${sbom_dir}"
+ )
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${create_staging_file}")
@@ -293,116 +216,51 @@ endfunction()
# Creates an 'sbom' custom target to generate an incomplete sbom at build time (no checksums).
# Creates install rules to install a complete (with checksums) sbom.
function(_qt_internal_sbom_end_project_generate)
- get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path)
- get_property(sbom_build_output_path_without_ext GLOBAL PROPERTY
- _qt_sbom_build_output_path_without_ext)
- get_property(sbom_build_output_dir GLOBAL PROPERTY _qt_sbom_build_output_dir)
-
- get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path)
- get_property(sbom_install_output_path_without_ext GLOBAL PROPERTY
- _qt_sbom_install_output_path_without_ext)
- get_property(sbom_install_output_dir GLOBAL PROPERTY _qt_sbom_install_output_dir)
+ _qt_internal_sbom_get_common_path_variables_from_global_properties(
+ SBOM_FORMAT "SPDX_V2"
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH sbom_install_output_path
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT sbom_install_output_path_without_ext
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR sbom_install_output_dir
+ )
if(NOT sbom_build_output_path)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
- _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
-
- _qt_internal_sbom_collect_cmake_include_files(includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_include_files _qt_sbom_cmake_end_include_files
- )
-
- # Before checksum includes are included after the verification codes have been collected
- # and before their merged checksum(s) has been computed.
- _qt_internal_sbom_collect_cmake_include_files(before_checksum_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_before_checksum_include_files
- )
-
- # After checksum includes are included after the checksum has been computed and written to the
- # QT_SBOM_VERIFICATION_CODE variable.
- _qt_internal_sbom_collect_cmake_include_files(after_checksum_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_after_checksum_include_files
- )
-
- # Post generation includes are included for both build and install time sboms, after
- # sbom generation has finished.
- _qt_internal_sbom_collect_cmake_include_files(post_generation_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_post_generation_include_files
- )
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
- # Verification only makes sense on installation, where the checksums are present.
- _qt_internal_sbom_collect_cmake_include_files(verify_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_verify_include_files
+ _qt_internal_sbom_get_cmake_include_files(
+ SBOM_FORMAT "SPDX_V2"
+ OUT_VAR_INCLUDES includes
+ OUT_VAR_BEFORE_CHECKSUM_INCLUDES before_checksum_includes
+ OUT_VAR_AFTER_CHECKSUM_INCLUDES after_checksum_includes
+ OUT_VAR_POST_GENERATION_INCLUDES post_generation_includes
+ OUT_VAR_VERIFY_INCLUDES verify_includes
)
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(multi_config_suffix "-$<CONFIG>")
- else()
- set(multi_config_suffix "")
- endif()
-
_qt_internal_get_current_project_sbom_dir(sbom_dir)
- set(content "
- # QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
- # This allows reusing the same cmake file for both build and install.
- if(NOT DEFINED QT_SBOM_BUILD_TIME)
- set(QT_SBOM_BUILD_TIME TRUE)
- endif()
- if(NOT QT_SBOM_OUTPUT_PATH)
- set(QT_SBOM_OUTPUT_DIR \"${sbom_build_output_dir}\")
- set(QT_SBOM_OUTPUT_PATH \"${sbom_build_output_path}\")
- set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_build_output_path_without_ext}\")
- file(MAKE_DIRECTORY \"${sbom_build_output_dir}\")
- endif()
- set(QT_SBOM_PACKAGES \"\")
- set(QT_SBOM_PACKAGES_WITH_VERIFICATION_CODES \"\")
- ${includes}
- if(QT_SBOM_BUILD_TIME)
- message(STATUS \"Finalizing SBOM generation in build dir: \${QT_SBOM_OUTPUT_PATH}\")
- configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
- ${post_generation_includes}
- endif()
-")
- set(assemble_sbom "${sbom_dir}/assemble_sbom${multi_config_suffix}.cmake")
- file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
- if(NOT TARGET sbom)
- add_custom_target(sbom)
+ set(build_time_args "")
+ if(includes)
+ list(APPEND build_time_args INCLUDES "${includes}")
endif()
-
- _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
-
- # Create a build target to create a build-time sbom (no verification codes or sha1s).
- set(repo_sbom_target "sbom_${repo_project_name_lowercase}")
- set(comment "")
- string(APPEND comment "Assembling build time SPDX document without checksums for "
- "${repo_project_name_lowercase}. Just for testing.")
- add_custom_target(${repo_sbom_target}
- COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
- COMMENT "${comment}"
- VERBATIM
- USES_TERMINAL # To avoid running two configs of the command in parallel
- )
-
- get_cmake_property(qt_repo_deps _qt_repo_deps_${real_qt_repo_project_name_lowercase})
- if(qt_repo_deps)
- foreach(repo_dep IN LISTS qt_repo_deps)
- set(repo_dep_sbom "sbom_${repo_dep}")
- if(TARGET "${repo_dep_sbom}")
- add_dependencies(${repo_sbom_target} ${repo_dep_sbom})
- endif()
- endforeach()
+ if(post_generation_includes)
+ list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
endif()
-
- add_dependencies(sbom ${repo_sbom_target})
+ _qt_internal_sbom_create_build_time_sbom_targets(
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
+ SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT "${sbom_build_output_path_without_ext}"
+ SBOM_BUILD_OUTPUT_DIR "${sbom_build_output_dir}"
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH assemble_sbom
+ ${build_time_args}
+ )
# Add 'reuse lint' per-repo custom targets.
if(arg_LINT_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
@@ -420,59 +278,19 @@ function(_qt_internal_sbom_end_project_generate)
add_dependencies(reuse_lint ${repo_sbom_target}_reuse_lint)
endif()
- set(extra_code_begin "")
- set(extra_code_inner_end "")
-
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(configs ${CMAKE_CONFIGURATION_TYPES})
-
- set(install_markers_dir "${sbom_dir}")
- set(install_marker_path "${install_markers_dir}/finished_install-$<CONFIG>.cmake")
-
- set(install_marker_code "
- message(STATUS \"Writing install marker for config $<CONFIG>: ${install_marker_path} \")
- file(WRITE \"${install_marker_path}\" \"\")
-")
-
- install(CODE "${install_marker_code}" COMPONENT sbom)
- if(QT_SUPERBUILD)
- install(CODE "${install_marker_code}" COMPONENT "sbom_${repo_project_name_lowercase}"
- EXCLUDE_FROM_ALL)
- endif()
-
- set(install_markers "")
- foreach(config IN LISTS configs)
- set(marker_path "${install_markers_dir}/finished_install-${config}.cmake")
- list(APPEND install_markers "${marker_path}")
- # Remove the markers on reconfiguration, just in case there are stale ones.
- if(EXISTS "${marker_path}")
- file(REMOVE "${marker_path}")
- endif()
- endforeach()
-
- set(extra_code_begin "
- set(QT_SBOM_INSTALL_MARKERS \"${install_markers}\")
- foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
- if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
- set(QT_SBOM_INSTALLED_ALL_CONFIGS FALSE)
- endif()
- endforeach()
-")
- set(extra_code_inner_end "
- foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
- message(STATUS
- \"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
- file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
- endforeach()
-")
- endif()
+ _qt_internal_sbom_setup_multi_config_install_markers(
+ SBOM_DIR "${sbom_dir}"
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
+ OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
+ )
- # Allow skipping checksum computation for testing purposes, while installing just the sbom
- # documents, without requiring to build and install all the actual files.
- if(QT_SBOM_FAKE_CHECKSUM)
- string(APPEND extra_code_begin "
- set(QT_SBOM_FAKE_CHECKSUM TRUE)")
+ _qt_internal_sbom_setup_fake_checksum(
+ OUT_VAR_FAKE_CHECKSUM_CODE extra_code_begin_fake_checksum
+ )
+ if(extra_code_begin_fake_checksum)
+ string(APPEND extra_code_begin "${extra_code_begin_fake_checksum}")
endif()
set(verification_codes_content "
@@ -496,42 +314,40 @@ unset(_verification_code)
set(process_verification_codes "${sbom_dir}/process_verification_codes.cmake")
file(GENERATE OUTPUT "${process_verification_codes}" CONTENT "${verification_codes_content}")
- set(assemble_sbom_install "
- set(QT_SBOM_INSTALLED_ALL_CONFIGS TRUE)
- ${extra_code_begin}
- if(QT_SBOM_INSTALLED_ALL_CONFIGS)
- set(QT_SBOM_BUILD_TIME FALSE)
- set(QT_SBOM_OUTPUT_DIR \"${sbom_install_output_dir}\")
- set(QT_SBOM_OUTPUT_PATH \"${sbom_install_output_path}\")
- set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_install_output_path_without_ext}\")
- file(MAKE_DIRECTORY \"${sbom_install_output_dir}\")
- include(\"${assemble_sbom}\")
- ${before_checksum_includes}
- include(\"${process_verification_codes}\")
- ${after_checksum_includes}
- message(STATUS \"Finalizing SBOM generation in install dir: \${QT_SBOM_OUTPUT_PATH}\")
- configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
- ${post_generation_includes}
- ${verify_includes}
- ${extra_code_inner_end}
- else()
- message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
- endif()
-")
-
- install(CODE "${assemble_sbom_install}" COMPONENT sbom)
- if(QT_SUPERBUILD)
- install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${repo_project_name_lowercase}"
- EXCLUDE_FROM_ALL)
+ set(setup_sbom_install_args "")
+ if(extra_code_begin)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_BEGIN "${extra_code_begin}")
+ endif()
+ if(extra_code_inner_end)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_INNER_END "${extra_code_inner_end}")
+ endif()
+ if(before_checksum_includes)
+ list(APPEND setup_sbom_install_args BEFORE_CHECKSUM_INCLUDES "${before_checksum_includes}")
+ endif()
+ if(after_checksum_includes)
+ list(APPEND setup_sbom_install_args AFTER_CHECKSUM_INCLUDES "${after_checksum_includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND setup_sbom_install_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ if(verify_includes)
+ list(APPEND setup_sbom_install_args VERIFY_INCLUDES "${verify_includes}")
endif()
- # Clean up properties, so that they are empty for possible next repo in a top-level build.
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_before_checksum_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_after_checksum_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_post_generation_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files "")
+ _qt_internal_sbom_setup_sbom_install_code(
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
+ SBOM_INSTALL_OUTPUT_DIR "${sbom_install_output_dir}"
+ ASSEMBLE_SBOM_INCLUDE_PATH "${assemble_sbom}"
+ PROCESS_VERIFICATION_CODES "${process_verification_codes}"
+ ${setup_sbom_install_args}
+ )
+
+ _qt_internal_sbom_clear_cmake_include_files(
+ SBOM_FORMAT "SPDX_V2"
+ )
endfunction()
# Gets a list of cmake include file paths, joins them as include() statements and returns the
diff --git a/cmake/QtPublicSbomHelpers.cmake b/cmake/QtPublicSbomHelpers.cmake
index 1a8702bf594..3d66e7ff783 100644
--- a/cmake/QtPublicSbomHelpers.cmake
+++ b/cmake/QtPublicSbomHelpers.cmake
@@ -85,7 +85,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_root_project_name_for_spdx_id(repo_project_name_for_spdx_id)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- set(begin_project_generate_args "")
+ set(begin_project_generate_args_spdx "")
+ set(begin_project_generate_args_cydx "")
if(arg_SUPPLIER_URL)
set(repo_supplier_url "${arg_SUPPLIER_URL}")
@@ -93,7 +94,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_default_supplier_url(repo_supplier_url)
endif()
if(repo_supplier_url)
- list(APPEND begin_project_generate_args SUPPLIER_URL "${repo_supplier_url}")
+ list(APPEND begin_project_generate_args_spdx SUPPLIER_URL "${repo_supplier_url}")
+ list(APPEND begin_project_generate_args_cydx SUPPLIER_URL "${repo_supplier_url}")
endif()
set(sbom_project_version_args "")
@@ -121,6 +123,15 @@ function(_qt_internal_sbom_begin_project)
)
endif()
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_get_cyclone_bom_serial_number(
+ SPDX_NAMESPACE "${repo_spdx_namespace}"
+ OUT_VAR_UUID cyclone_dx_bom_serial_number_uuid
+ )
+ list(APPEND begin_project_generate_args_cydx
+ BOM_SERIAL_NUMBER_UUID "${cyclone_dx_bom_serial_number_uuid}")
+ endif()
+
if(arg_INSTALL_SBOM_DIR)
set(install_sbom_dir "${arg_INSTALL_SBOM_DIR}")
elseif(INSTALL_SBOMDIR)
@@ -143,17 +154,28 @@ function(_qt_internal_sbom_begin_project)
list(APPEND compute_project_file_name_args VERSION_SUFFIX "${explicit_version}")
endif()
- _qt_internal_sbom_compute_project_file_name(repo_project_file_name
+ _qt_internal_sbom_compute_project_file_name(repo_project_file_name_spdx
+ SPDX_TAG_VALUE
+ PROJECT_NAME "${repo_project_name_lowercase}"
+ ${compute_project_file_name_args}
+ )
+
+ _qt_internal_sbom_compute_project_file_name(repo_project_file_name_cydx
+ CYCLONEDX_TOML
PROJECT_NAME "${repo_project_name_lowercase}"
${compute_project_file_name_args}
)
- _qt_internal_path_join(repo_spdx_relative_install_path
- "${arg_INSTALL_SBOM_DIR}" "${repo_project_file_name}")
+ _qt_internal_path_join(repo_spdx_relative_install_path_spdx
+ "${install_sbom_dir}" "${repo_project_file_name_spdx}")
+ _qt_internal_path_join(repo_spdx_relative_install_path_cydx
+ "${install_sbom_dir}" "${repo_project_file_name_cydx}")
# Prepend DESTDIR, to allow relocating installed sbom. Needed for CI.
- _qt_internal_path_join(repo_spdx_install_path
- "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path}")
+ _qt_internal_path_join(repo_spdx_install_path_spdx
+ "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path_spdx}")
+ _qt_internal_path_join(repo_spdx_install_path_cydx
+ "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path_cydx}")
if(arg_LICENSE_EXPRESSION)
set(repo_license "${arg_LICENSE_EXPRESSION}")
@@ -164,7 +186,8 @@ function(_qt_internal_sbom_begin_project)
set(repo_license "")
endif()
if(repo_license)
- list(APPEND begin_project_generate_args LICENSE "${repo_license}")
+ list(APPEND begin_project_generate_args_spdx LICENSE "${repo_license}")
+ list(APPEND begin_project_generate_args_cydx LICENSE "${repo_license}")
endif()
if(arg_COPYRIGHTS)
@@ -174,7 +197,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_default_qt_copyright_header(repo_copyright)
endif()
if(repo_copyright)
- list(APPEND begin_project_generate_args COPYRIGHT "${repo_copyright}")
+ list(APPEND begin_project_generate_args_spdx COPYRIGHT "${repo_copyright}")
+ list(APPEND begin_project_generate_args_cydx COPYRIGHT "${repo_copyright}")
endif()
if(arg_SUPPLIER)
@@ -184,7 +208,8 @@ function(_qt_internal_sbom_begin_project)
endif()
if(repo_supplier)
# This must not contain spaces!
- list(APPEND begin_project_generate_args SUPPLIER "${repo_supplier}")
+ list(APPEND begin_project_generate_args_spdx SUPPLIER "${repo_supplier}")
+ list(APPEND begin_project_generate_args_cydx SUPPLIER "${repo_supplier}")
endif()
if(arg_CPE)
@@ -195,7 +220,8 @@ function(_qt_internal_sbom_begin_project)
set(qt_cpe "")
endif()
if(qt_cpe)
- list(APPEND begin_project_generate_args CPE "${qt_cpe}")
+ list(APPEND begin_project_generate_args_spdx CPE "${qt_cpe}")
+ list(APPEND begin_project_generate_args_cydx CPE "${qt_cpe}")
endif()
if(arg_DOWNLOAD_LOCATION)
@@ -204,11 +230,12 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_qt_repo_source_download_location(download_location)
endif()
if(download_location)
- list(APPEND begin_project_generate_args DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND begin_project_generate_args_spdx DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND begin_project_generate_args_cydx DOWNLOAD_LOCATION "${download_location}")
endif()
if(arg_DOCUMENT_CREATOR_TOOL)
- list(APPEND begin_project_generate_args
+ list(APPEND begin_project_generate_args_spdx
DOCUMENT_CREATOR_TOOL "${arg_DOCUMENT_CREATOR_TOOL}")
endif()
@@ -229,24 +256,42 @@ function(_qt_internal_sbom_begin_project)
set(project_comment PROJECT_COMMENT "${project_comment}")
endif()
- _qt_internal_sbom_begin_project_generate(
- OUTPUT "${repo_spdx_install_path}"
- OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path}"
- PROJECT "${repo_project_name_lowercase}"
- ${project_comment}
- PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
- NAMESPACE "${repo_spdx_namespace}"
- ${begin_project_generate_args}
- OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_begin_project_generate(
+ OUTPUT "${repo_spdx_install_path_spdx}"
+ OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path_spdx}"
+ PROJECT "${repo_project_name_lowercase}"
+ ${project_comment}
+ PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
+ NAMESPACE "${repo_spdx_namespace}"
+ ${begin_project_generate_args_spdx}
+ OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_begin_project_generate_cyclone(
+ OUTPUT "${repo_spdx_install_path_cydx}"
+ OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path_cydx}"
+ PROJECT "${repo_project_name_lowercase}"
+ ${project_comment}
+ PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
+ NAMESPACE "${repo_spdx_namespace}"
+ ${begin_project_generate_args_cydx}
+ OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
+ )
+ endif()
set_property(GLOBAL PROPERTY _qt_internal_project_attribution_files "")
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace
"${repo_spdx_namespace}")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid
+ "${cyclone_dx_bom_serial_number_uuid}")
+
set_property(GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path
- "${repo_spdx_relative_install_path}")
+ "${repo_spdx_relative_install_path_spdx}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase
"${repo_project_name_lowercase}")
@@ -312,7 +357,10 @@ endfunction()
function(_qt_internal_sbom_setup_project_ops)
set(options "")
- if(QT_SBOM_GENERATE_JSON OR QT_INTERNAL_SBOM_GENERATE_JSON OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_GENERATE_JSON
+ OR QT_SBOM_GENERATE_SPDX_V2_JSON
+ OR QT_INTERNAL_SBOM_GENERATE_JSON
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options GENERATE_JSON)
endif()
@@ -320,20 +368,48 @@ function(_qt_internal_sbom_setup_project_ops)
# The user can explicitly request to fail the build if dependencies are not found.
# error out. For internal options that the CI uses, we always want to fail the build if the
# deps are not found.
- if(QT_SBOM_REQUIRE_GENERATE_JSON OR QT_INTERNAL_SBOM_GENERATE_JSON
+ if(QT_SBOM_REQUIRE_GENERATE_JSON
+ OR QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON
+ OR QT_INTERNAL_SBOM_GENERATE_JSON
OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options GENERATE_JSON_REQUIRED)
endif()
- if(QT_SBOM_VERIFY OR QT_INTERNAL_SBOM_VERIFY OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_VERIFY
+ OR QT_SBOM_VERIFY_SPDX_V2
+ OR QT_INTERNAL_SBOM_VERIFY
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_SBOM)
endif()
# Do the same requirement check for SBOM verification.
- if(QT_SBOM_REQUIRE_VERIFY OR QT_INTERNAL_SBOM_VERIFY OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_REQUIRE_VERIFY
+ OR QT_SBOM_REQUIRE_VERIFY_SPDX_V2
+ OR QT_INTERNAL_SBOM_VERIFY
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_SBOM_REQUIRED)
endif()
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ list(APPEND options GENERATE_CYCLONE_DX_V1_6)
+ endif()
+
+ if(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+ list(APPEND options GENERATE_CYCLONE_DX_V1_6_REQUIRED)
+ endif()
+
+ if(QT_SBOM_VERIFY_CYDX_V1_6)
+ list(APPEND options VERIFY_CYCLONE_DX_V1_6)
+ endif()
+
+ if(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6)
+ list(APPEND options VERIFY_CYCLONE_DX_V1_6_REQUIRED)
+ endif()
+
+ if(QT_SBOM_VERBOSE_CYDX_V1_6)
+ list(APPEND options VERBOSE_CYCLONE_DX_V1_6)
+ endif()
+
if(QT_SBOM_VERIFY_NTIA_COMPLIANT
OR QT_INTERNAL_SBOM_VERIFY_NTIA_COMPLIANT OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_NTIA_COMPLIANT)
@@ -367,12 +443,20 @@ function(_qt_internal_sbom_setup_project_ops)
endfunction()
# Sets up SBOM generation and verification options.
-# By default SBOM generation is disabled.
-# By default JSON generation and SBOM verification are enabled by default, if the dependencies
-# are present, otherwise they will be silently skipped. Unless the user explicitly requests to
-# fail the build if the dependencies are not found.
#
-# The QT_GENERATE_SBOM_DEFAULT option can be set by a project to change the default value.
+# By default, the main toggle for SBOM generation is disabled. The GENERATE_SBOM_DEFAULT option
+# overrides that value and can be set by a project that sets up SBOM generation.
+#
+# If the main toggle gets enabled, we enable SPDX V2.3 tag:value generation, and try to enable
+# CycloneDX V1.6 generation. Try, because CDX generation needs python dependencies. If they are
+# not found, the generation is silently skipped.
+#
+# By default SPDX v2.3 JSON generation and verification is enabled, if the python dependencies
+# are found. Otherwise they will be silently skipped.
+# Unless the user explicitly requests to fail the build if the dependencies are not found.
+# The same can be done for CycloneDX generation.
+#
+# Some older variables that were added pre-CycloneDX generation are deprecated.
function(_qt_internal_setup_sbom)
set(opt_args "")
set(single_args
@@ -388,22 +472,159 @@ function(_qt_internal_setup_sbom)
set(default_value "${arg_GENERATE_SBOM_DEFAULT}")
endif()
- option(QT_GENERATE_SBOM "Generate SBOM documents in SPDX v2.3 tag:value format."
- "${default_value}")
+ # Main SBOM toggle. Used to be the toggle for SPDX v2.3 only, but now would also enable Cyclone
+ # DX as well.
+ set(sbom_help_string "Generate SBOM.")
+ option(QT_GENERATE_SBOM "${sbom_help_string}" "${default_value}")
+
+
+ # Toggle for SPDX V2.3 generation.
+ set(spdx_v2_help_string "Generate SBOM documents in SPDX v2.3 tag:value format.")
+ option(QT_SBOM_GENERATE_SPDX_V2 "${spdx_v2_help_string}" ON)
+
+
+ # Toggles for CycloneDX V1.6 generation.
+ set(cydx_help_string "Generate SBOM documents in CycloneDX v1.6 JSON format.")
+ option(QT_SBOM_GENERATE_CYDX_V1_6 "${cydx_help_string}" ON)
- string(CONCAT help_string
+ set(cydx_require_help_string
+ "Error out if CycloneDX SBOM generation dependencies are not found.")
+ option(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 "${cydx_require_help_string}" OFF)
+
+
+ # Options for SPDX v2.3 JSON generation and verification.
+
+ string(CONCAT spdx_v23_json_help_string
"Generate SBOM documents in SPDX v2.3 JSON format if required python dependency "
- "spdx-tools is available"
+ "spdx-tools is available."
)
- option(QT_SBOM_GENERATE_JSON
- "${help_string}" ON)
- option(QT_SBOM_REQUIRE_GENERATE_JSON
- "Error out if JSON SBOM generation depdendency is not found." OFF)
+ set(spdx_v23_json_require_help_string
+ "Error out if JSON SBOM generation depdendency is not found.")
+
+ set(spdx_v23_verify_help_string
+ "Verify generated SBOM documents using python spdx-tools package.")
- option(QT_SBOM_VERIFY "Verify generated SBOM documents using python spdx-tools package." ON)
- option(QT_SBOM_REQUIRE_VERIFY
- "Error out if SBOM verification dependencies are not found." OFF)
+ set(spdx_v23_verify_require_help_string
+ "Error out if SBOM verification dependencies are not found.")
+
+ option(QT_SBOM_GENERATE_SPDX_V2_JSON "${spdx_v23_json_help_string}" ON)
+ option(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON "${spdx_v23_json_require_help_string}" OFF)
+
+ option(QT_SBOM_VERIFY_SPDX_V2 "${spdx_v23_verify_help_string}" ON)
+ option(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 "${spdx_v23_verify_require_help_string}" OFF)
+
+
+ # Options for CycloneDX verification and verbosity.
+
+ set(cydx_verify_help_string
+ "Verify generated CycloneDX document against its json schema.")
+ option(QT_SBOM_VERIFY_CYDX_V1_6 "${cydx_verify_help_string}" ON)
+
+ set(cydx_verify_require_help_string
+ "Error out if SBOM verification dependencies are not found.")
+ option(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 "${cydx_verify_require_help_string}" OFF)
+
+ set(cydx_verbose_help_string
+ "Enable verbose output for CycloneDX generation.")
+ option(QT_SBOM_VERBOSE_CYDX_V1_6 "${cydx_verbose_help_string}" OFF)
+
+
+ # Deprecated options, superseded by the options above.
+ # Only add them if the values was previously defined, but update the doc string.
+
+ if(DEFINED QT_SBOM_GENERATE_JSON)
+ option(QT_SBOM_GENERATE_JSON "Deprecated: ${spdx_v23_json_help_string}" ON)
+ endif()
+ if(DEFINED QT_SBOM_REQUIRE_GENERATE_JSON)
+ option(QT_SBOM_REQUIRE_GENERATE_JSON "Deprecated: ${spdx_v23_json_require_help_string}" OFF)
+ endif()
+ if(DEFINED QT_SBOM_VERIFY)
+ option(QT_SBOM_VERIFY "Deprecated: ${spdx_v23_verify_help_string}" ON)
+ endif()
+ if(DEFINED QT_SBOM_REQUIRE_VERIFY)
+ option(QT_SBOM_REQUIRE_VERIFY "Deprecated: ${spdx_v23_verify_require_help_string}" OFF)
+ endif()
+
+ # Semi-public, undocumented options to allow enabling all SBOM stuff, for easier testing.
+ if(QT_SBOM_GENERATE_AND_VERIFY_ALL)
+ set(QT_SBOM_GENERATE_ALL ON)
+ set(QT_SBOM_GENERATE_REQUIRED_ALL ON)
+ set(QT_SBOM_VERIFY_REQUIRED_ALL ON)
+ endif()
+
+ if(QT_SBOM_GENERATE_ALL)
+ set(QT_GENERATE_SBOM ON CACHE BOOL "${sbom_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_SPDX_V2 ON CACHE BOOL "${spdx_v2_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_SPDX_V2_JSON ON CACHE BOOL "${spdx_v23_json_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_CYDX_V1_6 ON CACHE BOOL "${cydx_help_string}" FORCE)
+ unset(QT_SBOM_GENERATE_ALL CACHE)
+ unset(QT_SBOM_GENERATE_ALL)
+ endif()
+
+ if(QT_SBOM_GENERATE_REQUIRED_ALL)
+ set(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON ON CACHE BOOL
+ "${spdx_v23_json_require_help_string}" FORCE)
+ set(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 ON CACHE BOOL "${cydx_require_help_string}" FORCE)
+
+ unset(QT_SBOM_GENERATE_REQUIRED_ALL CACHE)
+ unset(QT_SBOM_GENERATE_REQUIRED_ALL)
+ endif()
+
+ if(QT_SBOM_VERIFY_REQUIRED_ALL)
+ set(QT_SBOM_VERIFY_SPDX_V2 ON CACHE BOOL "${spdx_v23_verify_help_string}" FORCE)
+ set(QT_SBOM_VERIFY_CYDX_V1_6 ON CACHE BOOL "${cydx_verify_help_string}" FORCE)
+
+ set(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 ON CACHE BOOL "${spdx_v23_verify_require_help_string}"
+ FORCE)
+ set(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 ON CACHE BOOL "${cydx_verify_require_help_string}"
+ FORCE)
+
+ unset(QT_SBOM_VERIFY_ALL CACHE)
+ unset(QT_SBOM_VERIFY_ALL)
+ endif()
+
+ # Various sanity checks.
+
+ # Disable SPDX v2.3 JSON generation if tag:value generation is disabled.
+ if(QT_GENERATE_SBOM
+ AND QT_SBOM_GENERATE_SPDX_V2_JSON
+ AND NOT QT_SBOM_GENERATE_SPDX_V2)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling SPDX v2.3 SBOM JSON generation because tag:value generation is "
+ "disabled and that is a requirement for JSON generation.")
+ endif()
+ set(QT_SBOM_GENERATE_SPDX_V2_JSON OFF CACHE BOOL "${spdx_v23_json_help_string}" FORCE)
+ set(QT_SBOM_VERIFY_SPDX_V2 OFF CACHE BOOL "${spdx_v23_verify_help_string}" FORCE)
+ endif()
+
+ # Disable CycloneDX generation if dependencies are not found and it wasn't required.
+ if(QT_GENERATE_SBOM
+ AND QT_SBOM_GENERATE_CYDX_V1_6
+ AND NOT QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_find_cydx_dependencies(OUT_VAR_DEPS_FOUND deps_found)
+ if(NOT deps_found)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling Cyclone DX SBOM generation because dependencies were not found, "
+ "and generation was not marked as required.")
+ endif()
+ set(QT_SBOM_GENERATE_CYDX_V1_6 OFF CACHE BOOL "${cydx_help_string}" FORCE)
+ endif()
+ endif()
+
+ # Disable sbom generation if none of the formats are enabled. Failing to do so will cause
+ # errors in _qt_internal_sbom_begin_project.
+ if(QT_GENERATE_SBOM
+ AND NOT QT_SBOM_GENERATE_SPDX_V2
+ AND NOT QT_SBOM_GENERATE_CYDX_V1_6)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling SBOM generation because none of the supported formats were enabled.")
+ endif()
+ set(QT_GENERATE_SBOM OFF CACHE BOOL "${sbom_help_string}" FORCE)
+ endif()
endfunction()
# Ends repo sbom project generation.
@@ -415,10 +636,6 @@ function(_qt_internal_sbom_end_project)
return()
endif()
- # Now that we know which system libraries are linked against because we added all
- # subdirectories, we can add the recorded system libs to the sbom.
- _qt_internal_sbom_add_recorded_system_libraries()
-
# Run sbom finalization for targets that had it scheduled, but haven't run yet.
# This can happen when _qt_internal_sbom_end_project is called within the same
# subdirectory scope as where the targets are meant to be finalized, but that would be too late
@@ -462,7 +679,24 @@ function(_qt_internal_sbom_end_project)
endif()
endwhile()
- _qt_internal_sbom_end_project_generate()
+ # Now that we know which system libraries are linked against because we added all
+ # subdirectories and finalized all targets, we can add the recorded system libs to the sbom.
+ _qt_internal_sbom_add_recorded_system_libraries()
+
+ # Add any external target dependencies, for CycloneDX generation.
+ # E.g. For QtSvg, we need to create a QtCore component in the QtSvg document, so that we
+ # can declare a dependency on it.
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_add_cydx_external_target_dependencies()
+ endif()
+
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_end_project_generate()
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_end_project_generate_cyclone()
+ endif()
# Clean up external document ref properties, because each repo needs to start from scratch
# in a top-level build.
@@ -777,7 +1011,8 @@ function(_qt_internal_sbom_add_target target)
set_target_properties(${target} PROPERTIES _qt_sbom_is_qt_module TRUE)
endif()
- set(project_package_options "")
+ set(project_package_options_spdx "")
+ set(project_package_options_cydx "")
if(arg_FRIENDLY_PACKAGE_NAME)
set(package_name_for_spdx_id "${arg_FRIENDLY_PACKAGE_NAME}")
@@ -887,7 +1122,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(license_expression)
- list(APPEND project_package_options LICENSE_CONCLUDED "${license_expression}")
+ list(APPEND project_package_options_spdx LICENSE_CONCLUDED "${license_expression}")
+ list(APPEND project_package_options_cydx LICENSE_CONCLUDED "${license_expression}")
endif()
if(license_expression AND
@@ -898,7 +1134,9 @@ function(_qt_internal_sbom_add_target target)
LICENSE_CONCLUDED_EXPRESSION "${license_expression}"
OUT_VAR qt_entity_license_declared_expression)
if(qt_entity_license_declared_expression)
- list(APPEND project_package_options
+ list(APPEND project_package_options_spdx
+ LICENSE_DECLARED "${qt_entity_license_declared_expression}")
+ list(APPEND project_package_options_cydx
LICENSE_DECLARED "${qt_entity_license_declared_expression}")
endif()
endif()
@@ -923,7 +1161,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(copyrights)
list(JOIN copyrights "\n" copyrights)
- list(APPEND project_package_options COPYRIGHT "<text>${copyrights}</text>")
+ list(APPEND project_package_options_spdx COPYRIGHT "<text>${copyrights}</text>")
+ list(APPEND project_package_options_cydx COPYRIGHT "${copyrights}")
endif()
set(package_version "")
@@ -943,7 +1182,12 @@ function(_qt_internal_sbom_add_target target)
endif()
if(package_version)
- list(APPEND project_package_options VERSION "${package_version}")
+ list(APPEND project_package_options_spdx VERSION "${package_version}")
+ list(APPEND project_package_options_cydx VERSION "${package_version}")
+
+ # Also export the value in a target property, to make it available for cydx generation.
+ set_property(TARGET "${target}" PROPERTY _qt_sbom_package_version "${package_version}")
+ set_property(TARGET "${target}" APPEND PROPERTY EXPORT_PROPERTIES _qt_sbom_package_version)
endif()
set(supplier "")
@@ -961,7 +1205,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(supplier)
- list(APPEND project_package_options SUPPLIER "Organization: ${supplier}")
+ list(APPEND project_package_options_spdx SUPPLIER "Organization: ${supplier}")
+ list(APPEND project_package_options_cydx CYDX_SUPPLIER "${supplier}")
endif()
set(download_location "")
@@ -1002,11 +1247,12 @@ function(_qt_internal_sbom_add_target target)
endif()
if(download_location)
- list(APPEND project_package_options DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND project_package_options_spdx DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND project_package_options_cydx DOWNLOAD_LOCATION "${download_location}")
endif()
_qt_internal_sbom_get_package_purpose("${sbom_entity_type}" package_purpose)
- list(APPEND project_package_options PURPOSE "${package_purpose}")
+ list(APPEND project_package_options_spdx PURPOSE "${package_purpose}")
set(cpe_values "")
@@ -1046,7 +1292,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(cpe_values)
- list(APPEND project_package_options CPE ${cpe_values})
+ list(APPEND project_package_options_spdx CPE ${cpe_values})
+ list(APPEND project_package_options_cydx CPE ${cpe_values})
endif()
# Assemble arguments to forward to the function that handles purl options.
@@ -1088,12 +1335,16 @@ function(_qt_internal_sbom_add_target target)
list(APPEND purl_args PURL_VALUES ${qa_purls_replaced})
endif()
- list(APPEND purl_args OUT_VAR purl_package_options)
+ list(APPEND purl_args
+ OUT_VAR_PURL_VALUES purl_values
+ OUT_VAR_SPDX_EXT_REF_VALUES spdx_ext_ref_values
+ )
_qt_internal_sbom_handle_purl_values(${target} ${purl_args})
- if(purl_package_options)
- list(APPEND project_package_options ${purl_package_options})
+ if(spdx_ext_ref_values)
+ list(APPEND project_package_options_spdx ${spdx_ext_ref_values})
+ list(APPEND project_package_options_cydx PURL_VALUES ${purl_values})
endif()
if(arg_USE_ATTRIBUTION_FILES)
@@ -1136,32 +1387,75 @@ function(_qt_internal_sbom_add_target target)
endif()
if(package_comment)
- list(APPEND project_package_options COMMENT "<text>\n${package_comment}</text>")
+ list(APPEND project_package_options_spdx COMMENT "<text>\n${package_comment}</text>")
+ list(APPEND project_package_options_cydx COMMENT "\n${package_comment}")
endif()
_qt_internal_sbom_handle_target_dependencies("${target}"
SPDX_ID "${package_spdx_id}"
LIBRARIES "${arg_LIBRARIES}"
PUBLIC_LIBRARIES "${arg_PUBLIC_LIBRARIES}"
- OUT_RELATIONSHIPS relationships
+ OUT_CYDX_DEPENDENCIES cydx_dependencies
+ OUT_SPDX_RELATIONSHIPS spdx_relationships
+ OUT_EXTERNAL_TARGET_DEPENDENCIES external_target_dependencies
)
+ if(cydx_dependencies)
+ list(APPEND project_package_options_cydx DEPENDENCIES ${cydx_dependencies})
+ endif()
+
+ # These are processed at the end of document generation.
+ if(external_target_dependencies)
+ _qt_internal_sbom_record_external_target_dependecies(
+ TARGETS ${external_target_dependencies}
+ )
+ endif()
get_cmake_property(project_spdx_id _qt_internal_sbom_project_spdx_id)
- list(APPEND relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
+ list(APPEND spdx_relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
if(arg_SBOM_RELATIONSHIPS)
- list(APPEND relationships "${arg_SBOM_RELATIONSHIPS}")
+ list(APPEND spdx_relationships "${arg_SBOM_RELATIONSHIPS}")
endif()
- list(REMOVE_DUPLICATES relationships)
- list(JOIN relationships "\nRelationship: " relationships)
- list(APPEND project_package_options RELATIONSHIP "${relationships}")
+ list(REMOVE_DUPLICATES spdx_relationships)
+ list(JOIN spdx_relationships "\nRelationship: " relationships)
+ list(APPEND project_package_options_spdx RELATIONSHIP "${relationships}")
- _qt_internal_sbom_generate_add_package(
- PACKAGE "${package_name_for_spdx_id}"
- SPDXID "${package_spdx_id}"
- ${project_package_options}
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_generate_add_package(
+ PACKAGE "${package_name_for_spdx_id}"
+ SPDXID "${package_spdx_id}"
+ ${project_package_options_spdx}
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ get_property(external_targets
+ GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies)
+
+ # Prevent against case when a system library is also an external target dependency,
+ # which would lead to its creation twice, once via
+ # _qt_internal_sbom_add_recorded_system_libraries
+ # and second time via
+ # _qt_internal_sbom_add_cydx_external_target_dependencies.
+ # Skip the case when it's done via the first function, and only allow the second.
+ # TODO: Can this be done better somehow?
+ if(NOT target IN_LIST external_targets)
+ _qt_internal_sbom_handle_qt_entity_cydx_properties(
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ OUT_CYDX_PROPERTIES cydx_properties
+ )
+
+ _qt_internal_sbom_generate_cyclone_add_package(
+ PACKAGE "${package_name_for_spdx_id}"
+ SPDXID "${package_spdx_id}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ ${project_package_options_cydx}
+ CONTAINING_COMPONENT "${project_spdx_id}"
+ CYDX_PROPERTIES ${cydx_properties}
+ )
+ endif()
+ endif()
set(no_install_option "")
if(arg_NO_INSTALL)
@@ -1199,25 +1493,27 @@ function(_qt_internal_sbom_add_target target)
set(license_option LICENSE_EXPRESSION "${license_expression}")
endif()
- _qt_internal_sbom_handle_target_binary_files("${target}"
- ${no_install_option}
- ${framework_option}
- ${install_prefix_option}
- SBOM_ENTITY_TYPE "${sbom_entity_type}"
- ${target_binary_multi_config_args}
- SPDX_ID "${package_spdx_id}"
- ${copyrights_option}
- ${license_option}
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_handle_target_binary_files("${target}"
+ ${no_install_option}
+ ${framework_option}
+ ${install_prefix_option}
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ ${target_binary_multi_config_args}
+ SPDX_ID "${package_spdx_id}"
+ ${copyrights_option}
+ ${license_option}
+ )
- _qt_internal_sbom_handle_target_custom_files("${target}"
- ${no_install_option}
- ${install_prefix_option}
- PACKAGE_TYPE "${sbom_entity_type}"
- PACKAGE_SPDX_ID "${package_spdx_id}"
- ${copyrights_option}
- ${license_option}
- )
+ _qt_internal_sbom_handle_target_custom_files("${target}"
+ ${no_install_option}
+ ${install_prefix_option}
+ PACKAGE_TYPE "${sbom_entity_type}"
+ PACKAGE_SPDX_ID "${package_spdx_id}"
+ ${copyrights_option}
+ ${license_option}
+ )
+ endif()
endfunction()
# Helper to add sbom information for a possibly non-existing target.
@@ -1576,7 +1872,11 @@ function(_qt_internal_sbom_record_target_spdx_id target)
SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
PACKAGE_NAME "${package_name_for_spdx_id}"
)
- _qt_internal_sbom_save_spdx_id_for_target("${target}" "${package_spdx_id}")
+ _qt_internal_sbom_save_spdx_id_for_target("${target}"
+ SPDX_ID "${package_spdx_id}"
+ PACKAGE_NAME "${package_name_for_spdx_id}"
+ SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
+ )
_qt_internal_sbom_is_qt_entity_type("${arg_SBOM_ENTITY_TYPE}" is_qt_entity_type)
_qt_internal_sbom_save_spdx_id_for_qt_entity_type(
@@ -1615,10 +1915,36 @@ function(_qt_internal_sbom_generate_target_package_spdx_id out_var)
endfunction()
# Save a spdx id for a target inside its target properties.
-# Also saves the repo document namespace and relative installed repo document path.
# These are used when generating a SPDX external document reference for exported targets, to
# include them in relationships.
-function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
+# Also saves the repo document namespace and relative installed repo document path.
+# Also saves the sbom entity type and package name, because it's needed when creating CycloneDX
+# components where the target is in an external document.
+function(_qt_internal_sbom_save_spdx_id_for_target target)
+ set(opt_args "")
+ set(single_args
+ SPDX_ID
+ PACKAGE_NAME
+ SBOM_ENTITY_TYPE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_SPDX_ID)
+ message(FATAL_ERROR "arg_SPDX_ID must be set")
+ endif()
+
+ if(NOT arg_PACKAGE_NAME)
+ message(FATAL_ERROR "PACKAGE_NAME must be set")
+ endif()
+
+ if(NOT arg_SBOM_ENTITY_TYPE)
+ message(FATAL_ERROR "SBOM_ENTITY_TYPE must be set")
+ endif()
+
+ set(spdx_id "${arg_SPDX_ID}")
+
message(DEBUG "Saving spdx id for target ${target}: ${spdx_id}")
set(target_unaliased "${target}")
@@ -1631,6 +1957,9 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
get_property(repo_document_namespace
GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace)
+ get_property(bom_serial_number_uuid
+ GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid)
+
get_property(relative_installed_repo_document_path
GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path)
@@ -1643,6 +1972,10 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
"${repo_document_namespace}")
set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_cydx_bom_serial_number_uuid
+ "${bom_serial_number_uuid}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_spdx_relative_installed_repo_document_path
"${relative_installed_repo_document_path}")
@@ -1650,11 +1983,22 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
_qt_sbom_spdx_repo_project_name_lowercase
"${project_name_lowercase}")
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_package_name
+ "${arg_PACKAGE_NAME}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_entity_type
+ "${arg_SBOM_ENTITY_TYPE}")
+
# Export the properties, so they can be queried by other repos.
# We also do it for versionless targets.
set(export_properties
+ _qt_sbom_entity_type
+ _qt_sbom_package_name
_qt_sbom_spdx_id
_qt_sbom_spdx_repo_document_namespace
+ _qt_sbom_cydx_bom_serial_number_uuid
_qt_sbom_spdx_relative_installed_repo_document_path
_qt_sbom_spdx_repo_project_name_lowercase
)
@@ -1719,11 +2063,18 @@ function(_qt_internal_sbom_save_spdx_id_for_qt_entity_type target is_qt_entity_t
versionless_target
versionless_private_target
)
+
+ get_property(package_name TARGET "${target}" PROPERTY _qt_sbom_package_name)
+ get_property(sbom_entity_type TARGET "${target}" PROPERTY _qt_sbom_entity_type)
endif()
foreach(target_name IN LISTS ${target_names})
if(TARGET "${target_name}")
- _qt_internal_sbom_save_spdx_id_for_target("${target_name}" "${package_spdx_id}")
+ _qt_internal_sbom_save_spdx_id_for_target("${target_name}"
+ SPDX_ID "${package_spdx_id}"
+ PACKAGE_NAME "${package_name}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ )
endif()
endforeach()
endfunction()
@@ -2025,7 +2376,12 @@ endfunction()
function(_qt_internal_sbom_compute_project_file_name out_var)
set(opt_args
- EXTENSION_JSON
+ SPDX_TAG_VALUE
+ SPDX_JSON
+ CYCLONEDX_JSON
+ CYCLONEDX_TOML
+
+ EXTENSION_JSON # deprecated, used by WebEngine
)
set(single_args
PROJECT_NAME
@@ -2040,6 +2396,16 @@ function(_qt_internal_sbom_compute_project_file_name out_var)
message(FATAL_ERROR "PROJECT_NAME must be set")
endif()
+ if(NOT arg_SPDX_JSON
+ AND NOT arg_SPDX_TAG_VALUE
+ AND NOT arg_CYCLONEDX_TOML
+ AND NOT arg_CYCLONEDX_JSON
+ AND NOT arg_EXTENSION_JSON
+ )
+ message(FATAL_ERROR "One of the following options should be set: "
+ "SPDX_TAG_VALUE, SPDX_JSON, CYCLONEDX_JSON, CYCLONEDX_TOML")
+ endif()
+
string(TOLOWER "${arg_PROJECT_NAME}" project_name_lowercase)
set(version_suffix "")
@@ -2050,10 +2416,16 @@ function(_qt_internal_sbom_compute_project_file_name out_var)
set(version_suffix "-${QT_REPO_MODULE_VERSION}")
endif()
- if(arg_EXTENSION_JSON)
+ if(arg_SPDX_TAG_VALUE)
+ set(extension "spdx")
+ elseif(arg_SPDX_JSON OR arg_EXTENSION_JSON)
set(extension "spdx.json")
+ elseif(arg_CYCLONEDX_TOML)
+ set(extension "cdx.toml")
+ elseif(arg_CYCLONEDX_JSON)
+ set(extension "cdx.json")
else()
- set(extension "spdx")
+ message(FATAL_ERROR "Unknown file extension for SBOM generation.")
endif()
set(result
diff --git a/cmake/QtPublicSbomLicenseHelpers.cmake b/cmake/QtPublicSbomLicenseHelpers.cmake
index a91f4c2cefa..a9529fbe3d7 100644
--- a/cmake/QtPublicSbomLicenseHelpers.cmake
+++ b/cmake/QtPublicSbomLicenseHelpers.cmake
@@ -44,10 +44,20 @@ function(_qt_internal_sbom_add_license)
set(license_id "LicenseRef-${license_id}")
endif()
- _qt_internal_sbom_generate_add_license(
- LICENSE_ID "${license_id}"
- EXTRACTED_TEXT "<text>${text}</text>"
- )
+
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_generate_add_license(
+ LICENSE_ID "${license_id}"
+ EXTRACTED_TEXT "<text>${text}</text>"
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_record_license_cydx(
+ LICENSE_ID "${license_id}"
+ EXTRACTED_TEXT "${text}"
+ )
+ endif()
endfunction()
# Get a qt spdx license expression given the id.
diff --git a/cmake/QtPublicSbomOpsHelpers.cmake b/cmake/QtPublicSbomOpsHelpers.cmake
index 58af2a57f1f..78c4eaaa5e6 100644
--- a/cmake/QtPublicSbomOpsHelpers.cmake
+++ b/cmake/QtPublicSbomOpsHelpers.cmake
@@ -6,14 +6,28 @@
# like NTIA validation, auditing, json generation, etc.
function(_qt_internal_sbom_setup_project_ops_generation)
set(opt_args
+ # spdx options
GENERATE_JSON
GENERATE_JSON_REQUIRED
+
+ # cydx options
+ GENERATE_CYCLONE_DX_V1_6
+ GENERATE_CYCLONE_DX_V1_6_REQUIRED
+ VERIFY_CYCLONE_DX_V1_6
+ VERIFY_CYCLONE_DX_V1_6_REQUIRED
+ VERBOSE_CYCLONE_DX_V1_6
+
+ # source spdx options
GENERATE_SOURCE_SBOM
+ LINT_SOURCE_SBOM
+ LINT_SOURCE_SBOM_NO_ERROR
+
+ # Extra spdx verification
VERIFY_SBOM
VERIFY_SBOM_REQUIRED
VERIFY_NTIA_COMPLIANT
- LINT_SOURCE_SBOM
- LINT_SOURCE_SBOM_NO_ERROR
+
+ # Extra spdx info
SHOW_TABLE
AUDIT
AUDIT_NO_ERROR
@@ -38,6 +52,33 @@ function(_qt_internal_sbom_setup_project_ops_generation)
endif()
endif()
+ if(arg_GENERATE_CYCLONE_DX_V1_6 AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ set(op_args
+ OUT_VAR_DEPS_FOUND deps_found
+ )
+ if(arg_GENERATE_CYCLONE_DX_V1_6_REQUIRED)
+ list(APPEND op_args REQUIRED)
+ endif()
+
+ set(gen_args "")
+ if(arg_VERIFY_CYCLONE_DX_V1_6)
+ list(APPEND gen_args VERIFY)
+ endif()
+
+ if(arg_VERIFY_CYCLONE_DX_V1_6_REQUIRED)
+ list(APPEND gen_args VERIFY_REQUIRED)
+ endif()
+
+ if(arg_VERBOSE_CYCLONE_DX_V1_6)
+ list(APPEND gen_args VERBOSE)
+ endif()
+
+ _qt_internal_sbom_find_cydx_dependencies(${op_args})
+ if(deps_found)
+ _qt_internal_sbom_generate_cydx_json(${gen_args})
+ endif()
+ endif()
+
if(arg_VERIFY_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
set(op_args
OP_KEY "VERIFY_SBOM"
@@ -90,12 +131,119 @@ function(_qt_internal_sbom_setup_project_ops_generation)
endif()
endfunction()
+# Tries to find the dependencies for generating CycloneDX v1.6 SBOMs.
+# Sets OUT_VAR_DEPS_FOUND to TRUE if found, FALSE otherwise.
+function(_qt_internal_sbom_find_cydx_dependencies)
+ set(opt_args
+ REQUIRED
+ )
+ set(single_args
+ OUT_VAR_DEPS_FOUND
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(NOT arg_OUT_VAR_DEPS_FOUND)
+ message(FATAL_ERROR "OUT_VAR_DEPS_FOUND is required")
+ endif()
+
+ set(op_args
+ OP_KEY "GENERATE_CYCLONE_DX_V1_6"
+ OUT_VAR_DEPS_FOUND deps_found
+ )
+
+ if(arg_REQUIRED)
+ list(APPEND op_args REQUIRED)
+ endif()
+
+ _qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
+
+ set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
+endfunction()
+
+# Helper to output a debug or error message when python or one of its dependencies are not found.
+# When found, it also caches the found python interpreter path and version, as well as the
+# DEPS_FOUND_FOR_$OP_KEY var.
+function(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found)
+ set(opt_args
+ REQUIRED
+ PYTHON_FOUND
+ DEP_FOUND
+ EVERYTHING_FOUND
+ )
+ set(single_args
+ REQUIRED_VERSION
+ PYTHON_PATH
+ PYTHON_VERSION
+ DEP_PACKAGE_NAME
+ DEP_FIND_OUTPUT
+ OP_KEY
+ )
+ set(multi_args
+ PYTHON_COMMON_ARGS
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(arg_EVERYTHING_FOUND)
+ message(DEBUG "Python Dependencies found. "
+ "Using Python '${arg_PYTHON_PATH}' for running SBOM op '${arg_OP_KEY}'.")
+
+ if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
+ message(DEBUG "Setting QT_INTERNAL_SBOM_PYTHON_EXECUTABLE to ${arg_PYTHON_PATH}")
+ message(DEBUG "Setting QT_INTERNAL_SBOM_PYTHON_VERSION to ${arg_PYTHON_VERSION}")
+ set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${arg_PYTHON_PATH}" CACHE INTERNAL
+ "Python interpeter used for SBOM generation.")
+ set(QT_INTERNAL_SBOM_PYTHON_VERSION "${arg_PYTHON_VERSION}" CACHE INTERNAL
+ "Python interpeter version used for SBOM generation.")
+ endif()
+
+ set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
+ "All dependencies found to run SBOM op '${arg_OP_KEY}'")
+ return()
+ endif()
+
+ if(arg_REQUIRED)
+ set(message_type "FATAL_ERROR")
+ else()
+ set(message_type "DEBUG")
+ endif()
+
+ if(NOT arg_PYTHON_FOUND)
+ # Look for python one more time, this time without QUIET, to show an error why it
+ # wasn't found.
+ if(arg_REQUIRED)
+ _qt_internal_sbom_find_python_helper(${arg_PYTHON_COMMON_ARGS}
+ OUT_VAR_PYTHON_PATH unused_python
+ OUT_VAR_PYTHON_FOUND unused_found
+ )
+ endif()
+ message(${message_type}
+ "Python ${arg_REQUIRED_VERSION} for running SBOM op '${arg_OP_KEY}' NOT found.")
+ elseif(NOT arg_DEP_FOUND)
+
+ set(extra_install_message "")
+ if(message_type STREQUAL "FATAL_ERROR")
+ set(extra_install_message
+ "Install it using pip install '${arg_DEP_PACKAGE_NAME}' \n")
+ endif()
+ message(${message_type}
+ "Python dependency for running SBOM op '${arg_OP_KEY}' NOT found:\n"
+ ${extra_install_message}
+ "Details:\n"
+ "Python interpreter: ${arg_PYTHON_PATH}\n"
+ "Error output: \n${arg_DEP_FIND_OUTPUT}\n\n"
+ )
+ endif()
+endfunction()
+
# Helper to find a python interpreter and a specific python dependency, e.g. to be able to generate
# a SPDX JSON SBOM, or run post-installation steps like NTIA verification.
# The exact dependency should be specified as the OP_KEY.
#
# Caches the found python executable in a separate cache var QT_INTERNAL_SBOM_PYTHON_EXECUTABLE, to
# avoid conflicts with any other found python interpreter.
+# Also caches the python version found in QT_INTERNAL_SBOM_PYTHON_VERSION, so we can show it
+# in the configure summary.
function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
set(opt_args
REQUIRED
@@ -112,12 +260,22 @@ function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
message(FATAL_ERROR "OP_KEY is required")
endif()
- set(supported_ops "GENERATE_JSON" "VERIFY_SBOM" "RUN_NTIA")
+ set(supported_ops
+ "GENERATE_JSON"
+ "GENERATE_CYCLONE_DX_V1_6"
+ "VERIFY_SBOM"
+ "RUN_NTIA"
+ )
if(arg_OP_KEY STREQUAL "GENERATE_JSON" OR arg_OP_KEY STREQUAL "VERIFY_SBOM")
set(import_statement "import spdx_tools.spdx.clitools.pyspdxtools")
+ set(dep_package_name "spdx-tools")
+ elseif(arg_OP_KEY STREQUAL "GENERATE_CYCLONE_DX_V1_6")
+ set(import_statement "from cyclonedx.output.json import JsonV1Dot6")
+ set(dep_package_name "cyclonedx-python-lib[json-validation]")
elseif(arg_OP_KEY STREQUAL "RUN_NTIA")
set(import_statement "import ntia_conformance_checker.main")
+ set(dep_package_name "ntia-conformance-checker")
else()
message(FATAL_ERROR "OP_KEY must be one of ${supported_ops}")
endif()
@@ -145,48 +303,81 @@ function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
# non-framework python found.
if(CMAKE_HOST_APPLE)
set(extra_python_args SEARCH_IN_FRAMEWORKS QUIET)
+ message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}' "
+ "in the system framework location first.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
+ if(NOT everything_found)
+ # Assemble args to show what wasn't found.
+ set(verify_args "")
+ if(python_found)
+ list(APPEND verify_args PYTHON_FOUND)
+ endif()
+ if(dep_found)
+ list(APPEND verify_args DEP_FOUND)
+ endif()
+ if(everything_found)
+ list(APPEND verify_args EVERYTHING_FOUND)
+ endif()
+ if(python_path)
+ list(APPEND verify_args PYTHON_PATH "${python_path}")
+ endif()
+ if(python_version)
+ list(APPEND verify_args PYTHON_VERSION "${python_version}")
+ endif()
+ if(dep_find_output)
+ list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
+ endif()
+
+ _qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
+ ${verify_args}
+ REQUIRED_VERSION "${required_version}"
+ OP_KEY "${arg_OP_KEY}"
+ DEP_PACKAGE_NAME "${dep_package_name}"
+ PYTHON_COMMON_ARGS ${python_common_args}
+ )
+
+ message(DEBUG "Initial Apple-specific SBOM Python search did not find all dependencies "
+ "for op ${arg_OP_KEY}, looking again outside of the system framework location.")
+ endif()
endif()
+ # Looking again if something wasn't found, or a search was not done yet.
if(NOT everything_found)
set(extra_python_args QUIET)
+ message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}'.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
endif()
- # Always save the python interpreter path if it is found, even if the dependencies are not
- # found. This improves the error message workflow.
- if(python_found AND NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
- set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${python_path}" CACHE INTERNAL
- "Python interpeter used for SBOM generation.")
+ # Assemble args to show what was or wasn't found.
+ set(verify_args "")
+ if(arg_REQUIRED)
+ list(APPEND verify_args REQUIRED)
endif()
-
- if(NOT everything_found)
- if(arg_REQUIRED)
- set(message_type "FATAL_ERROR")
- else()
- set(message_type "DEBUG")
- endif()
-
- if(NOT python_found)
- # Look for python one more time, this time without QUIET, to show an error why it
- # wasn't found.
- if(arg_REQUIRED)
- _qt_internal_sbom_find_python_helper(${python_common_args}
- OUT_VAR_PYTHON_PATH unused_python
- OUT_VAR_PYTHON_FOUND unused_found
- )
- endif()
- message(${message_type} "Python ${required_version} for running SBOM ops not found.")
- elseif(NOT dep_found)
- message(${message_type} "Python dependency for running SBOM op ${arg_OP_KEY} "
- "not found:\n Python: ${python_path} \n Output: \n${dep_find_output}")
- endif()
- else()
- message(DEBUG "Using Python ${python_path} for running SBOM ops.")
-
- set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
- "All dependencies found to run SBOM OP ${arg_OP_KEY}")
+ if(python_found)
+ list(APPEND verify_args PYTHON_FOUND)
+ endif()
+ if(dep_found)
+ list(APPEND verify_args DEP_FOUND)
+ endif()
+ if(everything_found)
+ list(APPEND verify_args EVERYTHING_FOUND)
+ endif()
+ if(python_path)
+ list(APPEND verify_args PYTHON_PATH "${python_path}")
endif()
+ if(python_version)
+ list(APPEND verify_args PYTHON_VERSION "${python_version}")
+ endif()
+ if(dep_find_output)
+ list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
+ endif()
+ _qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
+ ${verify_args}
+ REQUIRED_VERSION "${required_version}"
+ OP_KEY "${arg_OP_KEY}"
+ DEP_PACKAGE_NAME "${dep_package_name}"
+ PYTHON_COMMON_ARGS ${python_common_args}
+ )
if(arg_OUT_VAR_DEPS_FOUND)
set(${arg_OUT_VAR_DEPS_FOUND} "${QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY}}"
@@ -298,7 +489,7 @@ function(_qt_internal_sbom_check_python_dependency_available)
_qt_internal_validate_all_args_are_parsed(arg)
set(failure_message
- "Required Python dependencies not found: ")
+ "Required Python dependencies NOT found: ")
if(arg_FAILURE_MESSAGE_PREFIX)
list(PREPEND failure_message ${arg_FAILURE_MESSAGE_PREFIX})
@@ -393,6 +584,67 @@ function(_qt_internal_sbom_generate_json)
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
endfunction()
+# Helper to generate a CycloneDX JSON file from the intermediate .toml file created by the build
+# system.
+function(_qt_internal_sbom_generate_cydx_json)
+ set(opt_args
+ VERIFY
+ VERIFY_REQUIRED
+ VERBOSE
+ )
+ set(single_args "")
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(error_message_prefix "Failed to generate a CycloneDX json file.")
+ _qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
+ _qt_internal_sbom_assert_python_dependency_available(GENERATE_CYCLONE_DX_V1_6
+ "cyclonedx" ${error_message_prefix})
+
+ _qt_internal_sbom_get_cyclone_dx_generator_path(generator_path)
+
+ set(extra_args "")
+ if(arg_VERIFY)
+ list(APPEND extra_args "--validate-json")
+ endif()
+ if(arg_VERIFY_REQUIRED)
+ list(APPEND extra_args "--validate-json-required")
+ endif()
+ if(arg_VERBOSE)
+ list(APPEND extra_args "--verbose")
+ endif()
+ list(JOIN extra_args " " extra_args)
+
+ set(content "
+ message(STATUS
+ \"Generating final CycloneDX JSON: \${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.json\")
+ execute_process(
+ COMMAND \"${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE}\" \"${generator_path}\"
+ --input-path \"\${QT_SBOM_OUTPUT_PATH}\"
+ --output-path \"\${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.json\"
+ ${extra_args}
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"CycloneDX JSON generation failed: \${res}\")
+ endif()
+ # Remove the intermediate toml file, there's no point in installing it.
+ # Keep the one in the build dir, it's useful for debugging.
+ if(NOT QT_SBOM_BUILD_TIME)
+ message(STATUS \"Removing intermediate TOML file \${QT_SBOM_OUTPUT_PATH}\")
+ file(REMOVE \"\${QT_SBOM_OUTPUT_PATH}\")
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(generate_cydx "${sbom_dir}/generate_cyclone_dx.cmake")
+ file(GENERATE OUTPUT "${generate_cydx}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_sbom_cmake_post_generation_include_files_cydx "${generate_cydx}")
+endfunction()
+
# Helper to query whether the all required dependencies are available to generate a tag / value
# document from a json one.
function(_qt_internal_sbom_verify_deps_for_generate_tag_value_spdx_document)
diff --git a/cmake/QtPublicSbomPurlHelpers.cmake b/cmake/QtPublicSbomPurlHelpers.cmake
index 6f91ea36fed..f6738a2431f 100644
--- a/cmake/QtPublicSbomPurlHelpers.cmake
+++ b/cmake/QtPublicSbomPurlHelpers.cmake
@@ -86,20 +86,31 @@ endmacro()
# default purls will be generated.
#
# There is no limit to the number of purls that can be added to a target.
+# The created purls are saved in:
+# - OUT_VAR_PURL_VALUES as plain purl values, to be used for CycloneDX genereation.
+# - OUT_VAR_SPDX_EXT_REF_VALUES as SPDX ExtRef entries, to be used for SPDX v2.3 generation.
function(_qt_internal_sbom_handle_purl_values target)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
- list(APPEND single_args OUT_VAR)
+ list(APPEND single_args
+ OUT_VAR_SPDX_EXT_REF_VALUES
+ OUT_VAR_PURL_VALUES
+ )
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
- if(NOT arg_OUT_VAR)
- message(FATAL_ERROR "OUT_VAR must be set")
+ if(NOT arg_OUT_VAR_PURL_VALUES)
+ message(FATAL_ERROR "OUT_VAR_PURL_VALUES must be set")
+ endif()
+
+ if(NOT arg_OUT_VAR_SPDX_EXT_REF_VALUES)
+ message(FATAL_ERROR "OUT_VAR_SPDX_EXT_REF_VALUES must be set")
endif()
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
- set(project_package_options "")
+ set(purl_values "")
+ set(spdx_ext_ref_values "")
# Collect each PURL_ENTRY args into a separate variable.
set(purl_idx -1)
@@ -140,6 +151,16 @@ function(_qt_internal_sbom_handle_purl_values target)
endif()
endif()
+ set(qt_entity_cydx_purl_values "")
+ set(qt_entity_spdx_purl_ext_refs "")
+
+ # When generating purls for Qt entities targeting Cyclone DX, we prefer the generic purl first,
+ # otherwise DependencyTrack gets confused with lots of components having the same purls,
+ # because it doesn't take into account the '#' part of the purl.
+ # Keep these separate and append them in the right order later.
+ set(qt_entity_cydx_purl_for_github_id "")
+ set(qt_entity_cydx_purl_for_generic_id "")
+
foreach(purl_idx IN LISTS purl_entry_indices)
# Clear previous values.
foreach(option_name IN LISTS purl_opt_args purl_single_args purl_multi_args)
@@ -172,11 +193,13 @@ function(_qt_internal_sbom_handle_purl_values target)
${purl_multi_args}
)
+ set(is_qt_entity_purl FALSE)
# Qt entity types get special treatment to gather the required args.
if(arg___QT_INTERNAL_HANDLE_QT_ENTITY_TYPE_PURL
AND arg_PURL_ID
AND arg_PURL_ID IN_LIST qt_purl_ids)
+ set(is_qt_entity_purl TRUE)
_qt_internal_sbom_handle_qt_entity_purl("${target}"
${purl_handling_args}
PURL_ID "${arg_PURL_ID}"
@@ -189,29 +212,57 @@ function(_qt_internal_sbom_handle_purl_values target)
_qt_internal_sbom_assemble_purl(${target}
${purl_args}
- OUT_VAR package_manager_external_ref
+ OUT_VAR purl_bare
+ OUT_VAR_SPDX_EXT_REF package_manager_external_ref_purl
)
- list(APPEND project_package_options ${package_manager_external_ref})
+
+ if(is_qt_entity_purl)
+ if(arg_PURL_ID STREQUAL "GENERIC")
+ set(qt_entity_cydx_purl_for_generic_id "${purl_bare}")
+ elseif(arg_PURL_ID STREQUAL "GITHUB")
+ set(qt_entity_cydx_purl_for_github_id "${purl_bare}")
+ else()
+ list(APPEND purl_values "${purl_bare}")
+ endif()
+ else()
+ list(APPEND purl_values "${purl_bare}")
+ endif()
+
+ list(APPEND spdx_ext_ref_values ${package_manager_external_ref_purl})
endforeach()
+ # Add the custom qt entity purls at the front in the right order for CycloneDX.
+ # If they are empty (for non-Qt entities), nothing will be prepended.
+ set(qt_entity_cydx_purl_values
+ ${qt_entity_cydx_purl_for_generic_id}
+ ${qt_entity_cydx_purl_for_github_id}
+ )
+ list(PREPEND purl_values ${qt_entity_cydx_purl_values})
+
foreach(purl_value IN LISTS arg_PURL_VALUES)
- _qt_internal_sbom_get_purl_value_extref(
- VALUE "${purl_value}" OUT_VAR package_manager_external_ref)
+ _qt_internal_sbom_get_purl_value_extref(VALUE "${purl_value}"
+ OUT_VAR package_manager_external_ref_purl)
# The order in which the purls are generated, matters for tools that consume the SBOM.
# Some tools can only handle one PURL per package, so the first one should be the
# important one.
# For now, I deem that the directly specified ones (probably via a qt_attribution.json
# file) are the more important ones. So we prepend them.
- list(PREPEND project_package_options ${package_manager_external_ref})
+ list(PREPEND purl_values ${purl_value})
+ list(PREPEND spdx_ext_ref_values ${package_manager_external_ref_purl})
endforeach()
- set(${arg_OUT_VAR} "${project_package_options}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_PURL_VALUES} "${purl_values}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SPDX_EXT_REF_VALUES} "${spdx_ext_ref_values}" PARENT_SCOPE)
endfunction()
# Assembles an external reference purl identifier.
+#
# PURL_TYPE and PURL_NAME are required.
-# Stores the result in the OUT_VAR.
+#
+# Stores the bare purl in the OUT_VAR.
+# Stores the SPDX External Reference purl in the OUT_VAR_SPDX_EXT_REF.
+#
# Accepted options:
# PURL_TYPE
# PURL_NAME
@@ -223,6 +274,7 @@ function(_qt_internal_sbom_assemble_purl target)
set(opt_args "")
set(single_args
OUT_VAR
+ OUT_VAR_SPDX_EXT_REF
)
set(multi_args "")
@@ -273,9 +325,10 @@ function(_qt_internal_sbom_assemble_purl target)
string(APPEND purl "#${arg_PURL_SUBPATH}")
endif()
- _qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR result)
+ _qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR ext_ref_result)
- set(${arg_OUT_VAR} "${result}" PARENT_SCOPE)
+ set(${arg_OUT_VAR} "${purl}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SPDX_EXT_REF} "${ext_ref_result}" PARENT_SCOPE)
endfunction()
# Takes a PURL VALUE and returns an SBOM purl external reference in OUT_VAR.
diff --git a/cmake/QtPublicSbomPythonHelpers.cmake b/cmake/QtPublicSbomPythonHelpers.cmake
index f83314916f9..fef87af9ee4 100644
--- a/cmake/QtPublicSbomPythonHelpers.cmake
+++ b/cmake/QtPublicSbomPythonHelpers.cmake
@@ -12,6 +12,7 @@ macro(_qt_internal_sbom_find_python_and_dependency_helper_lambda)
DEPENDENCY_IMPORT_STATEMENT "${import_statement}"
OUT_VAR_PYTHON_PATH python_path
OUT_VAR_PYTHON_FOUND python_found
+ OUT_VAR_PYTHON_VERSION python_version
OUT_VAR_DEP_FOUND dep_found
OUT_VAR_PYTHON_AND_DEP_FOUND everything_found
OUT_VAR_DEP_FIND_OUTPUT dep_find_output
@@ -27,6 +28,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
set(single_args
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_FOUND
+ OUT_VAR_PYTHON_VERSION
OUT_VAR_DEP_FOUND
OUT_VAR_PYTHON_AND_DEP_FOUND
OUT_VAR_DEP_FIND_OUTPUT
@@ -65,6 +67,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
${arg_PYTHON_ARGS}
OUT_VAR_PYTHON_PATH python_path_inner
OUT_VAR_PYTHON_FOUND python_found_inner
+ OUT_VAR_PYTHON_VERSION python_version_inner
)
if(python_found_inner AND python_path_inner)
@@ -82,6 +85,9 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
set(${arg_OUT_VAR_PYTHON_PATH} "${python_path_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FOUND} "${python_found_inner}" PARENT_SCOPE)
+ if(arg_OUT_VAR_PYTHON_VERSION)
+ set(${arg_OUT_VAR_PYTHON_VERSION} "${python_version_inner}" PARENT_SCOPE)
+ endif()
set(${arg_OUT_VAR_DEP_FOUND} "${dep_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_AND_DEP_FOUND} "${everything_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_DEP_FIND_OUTPUT} "${dep_find_output_inner}" PARENT_SCOPE)
@@ -90,7 +96,8 @@ endfunction()
# Tries to find the python intrepreter, given the QT_SBOM_PYTHON_INTERP path hint, as well as
# other options.
# Ignores any previously found python.
-# Returns the python interpreter path and whether it was successfully found.
+# Returns the python interpreter path and whether it was successfully found, along with the version
+# found.
#
# This is intentionally a function, and not a macro, to prevent overriding the Python3_EXECUTABLE
# non-cache variable in a global scope in case if a different python is found and used for a
@@ -109,6 +116,7 @@ function(_qt_internal_sbom_find_python_helper)
VERSION
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_FOUND
+ OUT_VAR_PYTHON_VERSION
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -154,6 +162,9 @@ function(_qt_internal_sbom_find_python_helper)
set(${arg_OUT_VAR_PYTHON_PATH} "${Python3_EXECUTABLE}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FOUND} "${Python3_Interpreter_FOUND}" PARENT_SCOPE)
+ if(arg_OUT_VAR_PYTHON_VERSION)
+ set(${arg_OUT_VAR_PYTHON_VERSION} "${Python3_VERSION}" PARENT_SCOPE)
+ endif()
endfunction()
# Helper that takes an python import statement to run using the given python interpreter path,
@@ -186,7 +197,7 @@ function(_qt_internal_sbom_find_python_dependency_helper)
set(python_path "${arg_PYTHON_PATH}")
execute_process(
COMMAND
- ${python_path} -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
+ "${python_path}" -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
RESULT_VARIABLE res
OUTPUT_VARIABLE output
ERROR_VARIABLE output
@@ -197,7 +208,7 @@ function(_qt_internal_sbom_find_python_dependency_helper)
set(output "${output}")
else()
set(found FALSE)
- string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} not found. "
+ string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} NOT found. "
"Error:\n${output}")
endif()
@@ -245,6 +256,6 @@ function(_qt_internal_sbom_find_python_dependency_program)
set(message_type "STATUS")
set(prefix "Optional ")
endif()
- message(${message_type} "${prefix}SBOM python program '${program_name}' not found.")
+ message(${message_type} "${prefix}SBOM python program '${program_name}' NOT found.")
endif()
endfunction()
diff --git a/cmake/QtPublicSbomQtEntityHelpers.cmake b/cmake/QtPublicSbomQtEntityHelpers.cmake
index dfe3bf2be0a..d8d61eef42c 100644
--- a/cmake/QtPublicSbomQtEntityHelpers.cmake
+++ b/cmake/QtPublicSbomQtEntityHelpers.cmake
@@ -393,9 +393,11 @@ endfunction()
function(_qt_internal_sbom_handle_qt_entity_purl_entries)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
list(APPEND single_args
- OUT_VAR # This is unused, but added by the calling function.
+ OUT_VAR_SPDX_EXT_REF_VALUES # This is unused, but added by the calling function.
+ OUT_VAR_PURL_VALUES # This is unused, but added by the calling function.
OUT_VAR_IDS
)
+
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -418,7 +420,8 @@ endfunction()
function(_qt_internal_sbom_handle_qt_entity_purl target)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
list(APPEND single_args
- OUT_VAR # This is unused, but added by the calling function.
+ OUT_VAR_SPDX_EXT_REF_VALUES # This is unused, but added by the calling function.
+ OUT_VAR_PURL_VALUES # This is unused, but added by the calling function.
OUT_PURL_ARGS
PURL_ID
)
diff --git a/cmake/QtSbomHelpers.cmake b/cmake/QtSbomHelpers.cmake
index 75694179fd1..c3ddc581427 100644
--- a/cmake/QtSbomHelpers.cmake
+++ b/cmake/QtSbomHelpers.cmake
@@ -194,3 +194,101 @@ function(qt_internal_extend_qt_entity_sbom target)
qt_internal_sbom_get_default_sbom_args("${target}" sbom_extra_args ${ARGN})
_qt_internal_extend_sbom(${target} ${ARGN} ${sbom_extra_args})
endfunction()
+
+# Helper function to convert a boolean SBOM option into a "yes" / "no" string.
+function(qt_internal_is_sbom_option_enabled var_name out_var)
+ if("${${var_name}}")
+ set(value "yes")
+ else()
+ set(value "no")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to get a summary suffix for SBOM options that are enabled, but might be skipped
+# if their dependencies are missing.
+function(qt_internal_get_sbom_option_required_suffix var_name out_var)
+ if("${${var_name}}")
+ set(value "")
+ else()
+ set(value " (skipped if dependencies are missing)")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+# Adds SBOM summary info to the configuration summary.
+function(qt_internal_add_sbom_summary_info)
+ qt_configure_add_summary_section(NAME "SBOM")
+
+ # Build SBOM info.
+ qt_internal_is_sbom_option_enabled(QT_GENERATE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Generate SBOM" TYPE "message" MESSAGE "${value}")
+
+ # Only show the details if generation is enabled.
+ if(QT_GENERATE_SBOM)
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_SPDX_V2 value)
+ qt_configure_add_summary_entry(ARGS "Generate SPDX v2.3"
+ TYPE "message" MESSAGE "${value}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_SPDX_V2_JSON value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON suffix)
+ qt_configure_add_summary_entry(ARGS "Generate SPDX v2.3 JSON"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_VERIFY_SPDX_V2 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 suffix)
+ qt_configure_add_summary_entry(ARGS "Verify SPDX v2.3 JSON"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_CYDX_V1_6 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 suffix)
+ qt_configure_add_summary_entry(ARGS "Generate CyloneDX v1.6"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_VERIFY_CYDX_V1_6 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 suffix)
+ qt_configure_add_summary_entry(ARGS "Verify CyloneDX v1.6"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ # Python interpreter info.
+ if(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
+ set(value "${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE}")
+ string(APPEND value " (version ${QT_INTERNAL_SBOM_PYTHON_VERSION})")
+ else()
+ set(value "Not found")
+ endif()
+ qt_configure_add_summary_entry(ARGS "SBOM Python interpreter"
+ TYPE "message" MESSAGE "${value}")
+
+ # These are kinda internal, so only show them when found.
+ if(QT_SBOM_PROGRAM_SBOM2DOC)
+ set(value "${QT_SBOM_PROGRAM_SBOM2DOC}")
+ qt_configure_add_summary_entry(ARGS "sbom2doc path" TYPE "message" MESSAGE "${value}")
+ endif()
+
+ if(QT_SBOM_PROGRAM_SBOMAUDIT)
+ set(value "${QT_SBOM_PROGRAM_SBOMAUDIT}")
+ qt_configure_add_summary_entry(ARGS "sbomaudit path" TYPE "message" MESSAGE "${value}")
+ endif()
+ endif()
+
+ # Source SBOM info.
+ qt_internal_is_sbom_option_enabled(QT_GENERATE_SOURCE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Generate source SPDX SBOM"
+ TYPE "message" MESSAGE "${value}")
+
+ if(QT_GENERATE_SOURCE_SBOM)
+ qt_internal_is_sbom_option_enabled(QT_LINT_SOURCE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Verify source SPDX SBOM"
+ TYPE "message" MESSAGE "${value}")
+
+ if(QT_SBOM_PROGRAM_REUSE)
+ set(value "${QT_SBOM_PROGRAM_REUSE}")
+ else()
+ set(value "Not found")
+ endif()
+ qt_configure_add_summary_entry(ARGS "reuse path" TYPE "message" MESSAGE "${value}")
+ endif()
+
+ qt_configure_end_summary_section()
+endfunction()
diff --git a/cmake/QtWrapperScriptHelpers.cmake b/cmake/QtWrapperScriptHelpers.cmake
index ecdc043c49c..f76d52bca91 100644
--- a/cmake/QtWrapperScriptHelpers.cmake
+++ b/cmake/QtWrapperScriptHelpers.cmake
@@ -235,6 +235,7 @@ export CMAKE_GENERATOR=Xcode
qt_internal_create_qt_configure_part_wrapper_script("STANDALONE_TESTS")
qt_internal_create_qt_configure_part_wrapper_script("STANDALONE_EXAMPLES")
+ qt_internal_create_cyclone_dx_sbom_generator_script()
if(NOT CMAKE_CROSSCOMPILING)
qt_internal_create_qt_android_runner_wrapper_script()
@@ -381,6 +382,23 @@ function(qt_internal_create_qt_configure_redo_script)
set_property(GLOBAL PROPERTY _qt_configure_redo_script_created TRUE)
endfunction()
+function(qt_internal_create_cyclone_dx_sbom_generator_script)
+ _qt_internal_sbom_get_cyclone_dx_generator_script_name(generator_name generator_relative_dir)
+
+ qt_path_join(build_dir_destination "${QT_BUILD_DIR}" "${INSTALL_LIBEXECDIR}")
+ qt_path_join(install_destination "${QT_INSTALL_DIR}" "${INSTALL_LIBEXECDIR}")
+ qt_path_join(script_path
+ "${CMAKE_CURRENT_SOURCE_DIR}" "${generator_relative_dir}" "${generator_name}")
+
+ if(QT_WILL_INSTALL)
+ file(COPY "${script_path}"
+ DESTINATION "${build_dir_destination}"
+ )
+ endif()
+
+ qt_copy_or_install(PROGRAMS "${script_path}" DESTINATION "${install_destination}")
+endfunction()
+
function(qt_internal_create_qt_android_runner_wrapper_script)
qt_path_join(android_runner_destination "${QT_INSTALL_DIR}" "${INSTALL_LIBEXECDIR}")
qt_path_join(android_runner "${CMAKE_CURRENT_SOURCE_DIR}" "libexec" "qt-android-runner.py")
diff --git a/cmake/configure-cmake-mapping.md b/cmake/configure-cmake-mapping.md
index fadc03c831b..72a30cd87b3 100644
--- a/cmake/configure-cmake-mapping.md
+++ b/cmake/configure-cmake-mapping.md
@@ -45,11 +45,16 @@ The following table describes the mapping of configure options to CMake argument
| -device-option <key=value> | -DQT_QMAKE_DEVICE_OPTIONS=key1=value1;key2=value2 | Only used for generation qmake-compatibility files. |
| | | The device options are written into mkspecs/qdevice.pri. |
| -appstore-compliant | -DFEATURE_appstore_compliant=ON | |
-| -sbom | -DQT_GENERATE_SBOM=ON | Enables generation and installation of a SPDX SBOM documents |
-| -sbom-json | -DQT_SBOM_GENERATE_JSON=ON | Enables generation of SPDX SBOM in JSON format |
-| -sbom-json-required | -DQT_SBOM_REQUIRE_GENERATE_JSON=ON | Fails the build if Python deps are not found |
-| -sbom-verify | -DQT_SBOM_VERIFY=ON | Enables verification of generated SBOMs |
-| -sbom-verify-required | -DQT_SBOM_REQUIRE_VERIFY=ON | Fails the build if Python deps are not found |
+| -sbom | -DQT_GENERATE_SBOM=ON | Enables generation of SBOM documents (multiple formats) |
+| -sbom-spdx-v2 | -DQT_SBOM_GENERATE_SPDX_V2=ON | Enables generation of SPDX v2.3 SBOM in tag:value format |
+| -sbom-cyclonedx-v1_6 | -DQT_SBOM_GENERATE_CYDX_V1_6=ON | Enables generation of CycloneDX v1.6 SBOM in JSON format |
+| -sbom-cyclonedx-v1_6-required | -DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON | Fails the build if Cyclone DX Python deps are not found |
+| -sbom-cyclonedx-v1_6-verify | -DQT_SBOM_VERIFY_CYDX_V1_6=ON | Enables verification of generated CycloneDX v1.6 SBOMs |
+| -sbom-cyclonedx-v1_6-verify-required | -DQT_SBOM_REQUIRE_VERIFY_CYDX_V1_6=ON | Fails the build if Cyclone DX Python verify deps are not found |
+| -sbom-json | -DQT_SBOM_GENERATE_SPDX_V2_JSON=ON | Enables generation of SPDX v2.3 SBOM in JSON format |
+| -sbom-json-required | -DQT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON=ON | Fails the build if SPDX Python deps are not found |
+| -sbom-verify | -DQT_SBOM_VERIFY_SPDX_V2=ON | Enables verification of generated SPDX v2.3 SBOMs |
+| -sbom-verify-required | -DQT_SBOM_REQUIRE_VERIFY_SPDX_V2=ON | Fails the build if SPDX Python verify deps are not found |
| -sbomdir <dir> | -DINSTALL_SBOMDIR=<dir> | Installation location of SBOM files. |
| -qtinlinenamespace | -DQT_INLINE_NAMESPACE=ON | Make the namespace specified by -qtnamespace an inline one. |
| -qtnamespace <name> | -DQT_NAMESPACE=<name> | |
diff --git a/coin/instructions/prepare_building_env.yaml b/coin/instructions/prepare_building_env.yaml
index 2cc63b136e4..e1d30ce64b4 100644
--- a/coin/instructions/prepare_building_env.yaml
+++ b/coin/instructions/prepare_building_env.yaml
@@ -487,15 +487,16 @@ instructions:
property: host.os
contains_value: Windows
- # Enable warnings are errors
+ # Enable warnings are errors, including qml linting warnings for examples that should not regress
+ # in qmllint warnings
- type: Group
instructions:
- type: AppendToEnvironmentVariable
variableName: COMMON_CMAKE_ARGS
- variableValue: " -DWARNINGS_ARE_ERRORS=ON"
+ variableValue: " -DWARNINGS_ARE_ERRORS=ON -DQT_LINT_EXAMPLES=ON"
- type: AppendToEnvironmentVariable
variableName: COMMON_TARGET_CMAKE_ARGS
- variableValue: " -DWARNINGS_ARE_ERRORS=ON"
+ variableValue: " -DWARNINGS_ARE_ERRORS=ON -DQT_LINT_EXAMPLES=ON"
enable_if:
condition: property
property: features
@@ -549,18 +550,25 @@ instructions:
property: features
contains_value: GenerateSBOM
instructions:
+ - type: EnvironmentVariable
+ variableName: SBOM_COMMON_ARGS
+ variableValue: >-
+ -DQT_GENERATE_SBOM=ON
+ -DQT_GENERATE_SOURCE_SBOM=ON
+ -DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON
+ -DQT_SBOM_VERIFY_CYDX_V1_6=ON
- type: AppendToEnvironmentVariable
variableName: COMMON_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_NON_QTBASE_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_TARGET_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_NON_QTBASE_TARGET_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
# SBOM Python apps path. On Windows python-installed apps are
# in the same directory where pip is, aka Scripts sub-directory.
diff --git a/doc/global/compat.qdocconf b/doc/global/compat.qdocconf
index df3f49c653e..e2c3bce0b4c 100644
--- a/doc/global/compat.qdocconf
+++ b/doc/global/compat.qdocconf
@@ -1,10 +1,8 @@
+include(disabledwarnings.qdocconf)
+
macro.0 = "\\\\0"
macro.n = "\\\\n"
macro.r = "\\\\r"
macro.img = "\\image"
macro.endquote = "\\endquotation"
macro.relatesto = "\\relates"
-
-spurious = "Missing comma in .*" \
- "Missing pattern .*" \
- "Unable to parse (QML|JavaScript).*"
diff --git a/doc/global/disabledwarnings.qdocconf b/doc/global/disabledwarnings.qdocconf
new file mode 100644
index 00000000000..c0070fca610
--- /dev/null
+++ b/doc/global/disabledwarnings.qdocconf
@@ -0,0 +1,6 @@
+spurious = \
+ "Missing comma in .*" \
+ "Missing pattern .*" \
+ "Unable to parse (QML|JavaScript).*" \
+ "Redundant link to self in .*"
+
diff --git a/licenseRule.json b/licenseRule.json
index 3bc58bddca9..8c4029f91e1 100644
--- a/licenseRule.json
+++ b/licenseRule.json
@@ -377,6 +377,11 @@
"file type": "util",
"spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"]
},
+ "util/sbom/cyclonedx/pyproject.toml": {
+ "comment": "Default",
+ "file type": "util",
+ "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"]
+ },
"util/unicode/data/.*\\.txt": {
"comment": "Exception.",
"file type": "3rd party",
diff --git a/src/3rdparty/pcre2/qt_attribution.json b/src/3rdparty/pcre2/qt_attribution.json
index ef29ad7d951..0468bca1b3e 100644
--- a/src/3rdparty/pcre2/qt_attribution.json
+++ b/src/3rdparty/pcre2/qt_attribution.json
@@ -30,7 +30,7 @@
"Homepage": "http://www.pcre.org/",
"Version": "10.47",
"DownloadLocation": "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2",
- "PURL": "pkg:github/PCRE2Project/pcre2@$<VERSION>",
+ "PURL": "pkg:github/PCRE2Project/pcre2@pcre2-$<VERSION>",
"CPE": "cpe:2.3:a:pcre:pcre2:$<VERSION>:*:*:*:*:*:*:*",
"License": "BSD 2-clause \"Simplified\" License",
"LicenseId": "BSD-2-Clause",
diff --git a/src/3rdparty/sqlite/qt_attribution.json b/src/3rdparty/sqlite/qt_attribution.json
index e8809d2b23d..2f8bbc30a94 100644
--- a/src/3rdparty/sqlite/qt_attribution.json
+++ b/src/3rdparty/sqlite/qt_attribution.json
@@ -7,10 +7,10 @@
"Description": "SQLite is a small C library that implements a self-contained, embeddable, zero-configuration SQL database engine.",
"Homepage": "https://www.sqlite.org/",
- "Version": "3.50.4",
+ "Version": "3.51.0",
"PURL": "pkg:github/sqlite/sqlite@version-$<VERSION>",
"CPE": "cpe:2.3:a:sqlite:sqlite:$<VERSION>:*:*:*:*:*:*:*",
- "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip",
+ "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3510000.zip",
"License": "SQLite Blessing",
"LicenseId": "blessing",
"Copyright": "The authors disclaim copyright to the source code. However, a license can be obtained if needed."
diff --git a/src/3rdparty/sqlite/sqlite3.c b/src/3rdparty/sqlite/sqlite3.c
index 26a7a43d865..03d65b62820 100644
--- a/src/3rdparty/sqlite/sqlite3.c
+++ b/src/3rdparty/sqlite/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.50.4. By combining all the individual C code files into this
+** version 3.51.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
@@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
-** 4d8adfb30e03f9cf27f800a2c1ba3c48fb4c with changes in files:
+** fb2c931ae597f8d00a37574ff67aeed3eced with changes in files:
**
**
*/
@@ -170,7 +170,9 @@
#define HAVE_UTIME 1
#else
/* This is not VxWorks. */
-#define OS_VXWORKS 0
+#ifndef OS_VXWORKS
+# define OS_VXWORKS 0
+#endif
#define HAVE_FCHOWN 1
#define HAVE_READLINK 1
#define HAVE_LSTAT 1
@@ -465,9 +467,12 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.50.4"
-#define SQLITE_VERSION_NUMBER 3050004
-#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
+#define SQLITE_VERSION "3.51.0"
+#define SQLITE_VERSION_NUMBER 3051000
+#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
+#define SQLITE_SCM_BRANCH "trunk"
+#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
+#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -487,9 +492,9 @@ extern "C" {
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
-** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
-** macro. ^The sqlite3_libversion() function returns a pointer to the
-** to the sqlite3_version[] string constant. The sqlite3_libversion()
+** ^The sqlite3_version[] string constant contains the text of the
+** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a
+** pointer to the sqlite3_version[] string constant. The sqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL. ^The
** sqlite3_libversion_number() function returns an integer equal to
@@ -689,7 +694,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** without having to use a lot of C code.
**
** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
-** semicolon-separate SQL statements passed into its 2nd argument,
+** semicolon-separated SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument. ^If the callback function of the 3rd argument to
** sqlite3_exec() is not NULL, then it is invoked for each result row
@@ -722,7 +727,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** result row is NULL then the corresponding string pointer for the
** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
** sqlite3_exec() callback is an array of pointers to strings where each
-** entry represents the name of corresponding result column as obtained
+** entry represents the name of a corresponding result column as obtained
** from [sqlite3_column_name()].
**
** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
@@ -816,6 +821,9 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
+#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8))
+#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8))
+#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@@ -850,6 +858,8 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
+#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8))
+#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -908,7 +918,7 @@ SQLITE_API int sqlite3_exec(
** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into
** [sqlite3_open_v2()] does *not* cause the underlying database file
** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into
-** [sqlite3_open_v2()] has historically be a no-op and might become an
+** [sqlite3_open_v2()] has historically been a no-op and might become an
** error in future versions of SQLite.
*/
#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
@@ -1002,7 +1012,7 @@ SQLITE_API int sqlite3_exec(
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
** of an [sqlite3_io_methods] object. These values are ordered from
-** lest restrictive to most restrictive.
+** least restrictive to most restrictive.
**
** The argument to xLock() is always SHARED or higher. The argument to
** xUnlock is either SHARED or NONE.
@@ -1243,7 +1253,7 @@ struct sqlite3_io_methods {
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
** <li>[[SQLITE_FCNTL_SYNC_OMITTED]]
-** No longer in use.
+** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used.
**
** <li>[[SQLITE_FCNTL_SYNC]]
** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and
@@ -1318,7 +1328,7 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_VFSNAME]]
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
-** all [VFSes] in the VFS stack. The names are of all VFS shims and the
+** all [VFSes] in the VFS stack. The names of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
** [sqlite3_malloc()] and the result is stored in the char* variable
** that the fourth parameter of [sqlite3_file_control()] points to.
@@ -1332,7 +1342,7 @@ struct sqlite3_io_methods {
** ^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
+** of type "[sqlite3_vfs] **". This opcode 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.
@@ -1522,7 +1532,7 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
** whether or not there is a database client in another process with a wal-mode
-** transaction open on the database or not. It is only available on unix.The
+** transaction open on the database or not. It is only available on unix. The
** (void*) argument passed with this file-control should be a pointer to a
** value of type (int). The integer value is set to 1 if the database is a wal
** mode database and there exists at least one client in another process that
@@ -1540,6 +1550,15 @@ struct sqlite3_io_methods {
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
** purges the contents of the in-memory page cache. If there is an open
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
+**
+** <li>[[SQLITE_FCNTL_FILESTAT]]
+** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information
+** about the [sqlite3_file] objects used access the database and journal files
+** for the given schema. The fourth parameter to [sqlite3_file_control()]
+** should be an initialized [sqlite3_str] pointer. JSON text describing
+** various aspects of the sqlite3_file object is appended to the sqlite3_str.
+** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time
+** options are used to enable it.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1585,6 +1604,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
+#define SQLITE_FCNTL_FILESTAT 45
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1947,7 +1967,7 @@ struct sqlite3_vfs {
** SQLite interfaces so that an application usually does not need to
** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
** calls sqlite3_initialize() so the SQLite library will be automatically
-** initialized when [sqlite3_open()] is called if it has not be initialized
+** initialized when [sqlite3_open()] is called if it has not been initialized
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
** compile-time option, then the automatic calls to sqlite3_initialize()
** are omitted and the application must call sqlite3_initialize() directly
@@ -2204,21 +2224,21 @@ struct sqlite3_mem_methods {
** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
-** routines with a wrapper that simulations memory allocation failure or
+** routines with a wrapper that simulates memory allocation failure or
** tracks memory usage, for example. </dd>
**
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
-** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
+** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of
** type int, interpreted as a boolean, which if true provides a hint to
** SQLite that it should avoid large memory allocations if possible.
** SQLite will run faster if it is free to make large memory allocations,
-** but some application might prefer to run slower in exchange for
+** but some applications might prefer to run slower in exchange for
** guarantees about memory fragmentation that are possible if large
** allocations are avoided. This hint is normally off.
** </dd>
**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
-** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
+** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
@@ -2263,7 +2283,7 @@ struct sqlite3_mem_methods {
** ^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
+** 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. </dd>
@@ -2292,7 +2312,7 @@ struct sqlite3_mem_methods {
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
** pointer to an instance of the [sqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
-** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
+** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of
** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
@@ -2334,7 +2354,7 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
-** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
@@ -2351,7 +2371,7 @@ struct sqlite3_mem_methods {
** the logger function is a copy of the first parameter to the corresponding
** [sqlite3_log()] call and is intended to be a [result code] or an
** [extended result code]. ^The third parameter passed to the logger is
-** log message after formatting via [sqlite3_snprintf()].
+** a log message after formatting via [sqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
@@ -2542,7 +2562,7 @@ struct sqlite3_mem_methods {
** These constants are the available integer configuration options that
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
**
-** The [sqlite3_db_config()] interface is a var-args functions. It takes a
+** The [sqlite3_db_config()] interface is a var-args function. It takes a
** variable number of parameters, though always at least two. The number of
** parameters passed into sqlite3_db_config() depends on which of these
** constants is given as the second parameter. This documentation page
@@ -2654,17 +2674,20 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
-** <dd> ^This option is used to enable or disable the
-** [fts3_tokenizer()] function which is part of the
-** [FTS3] full-text search engine extension.
-** There must 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. </dd>
+** <dd> ^This option is used to enable or disable using the
+** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine
+** extension - without using bound parameters as the parameters. Doing so
+** is disabled by default. There must be two additional arguments. The first
+** argument is an integer. If it is passed 0, then using fts3_tokenizer()
+** without bound parameters is disabled. If it is passed a positive value,
+** then calling fts3_tokenizer without bound parameters is enabled. If it
+** is passed a negative value, this setting is not modified - this can be
+** used to query for the current setting. The second parameter is a pointer
+** to an integer into which is written 0 or 1 to indicate the current value
+** of this setting (after it is modified, if applicable). The second
+** parameter may be a NULL pointer, in which case the value of the setting
+** is not reported back. Refer to [FTS3] documentation for further details.
+** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
@@ -2676,8 +2699,8 @@ struct sqlite3_mem_methods {
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
** 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.
+** If the first argument is -1, then no changes are made to the 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
@@ -2795,7 +2818,7 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
-** the legacy behavior of the [ALTER TABLE RENAME] command such it
+** the legacy behavior of the [ALTER TABLE RENAME] command such that it
** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
** additional information. This feature can also be turned on and off
@@ -2844,7 +2867,7 @@ struct sqlite3_mem_methods {
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag. When activated, this flag causes all newly
-** created database file to have a schema format version number (the 4-byte
+** created database files to have a schema format version number (the 4-byte
** integer found at offset 44 into the database header) of 1. This in turn
** means that the resulting database file will be readable and writable by
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
@@ -2871,7 +2894,7 @@ struct sqlite3_mem_methods {
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default. <p>This option takes two arguments: an integer and a pointer to
-** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
+** an integer. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
@@ -2914,8 +2937,8 @@ struct sqlite3_mem_methods {
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
** This capability is enabled by default. Applications can disable or
-** reenable this capability using the current DBCONFIG option. If the
-** the this capability is disabled, the [ATTACH] command will still work,
+** reenable this capability using the current DBCONFIG option. If
+** this capability is disabled, the [ATTACH] command will still work,
** but the database will be opened read-only. If this option is disabled,
** then the ability to create a new database using [ATTACH] is also disabled,
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
@@ -2949,7 +2972,7 @@ struct sqlite3_mem_methods {
**
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
** overall call to [sqlite3_db_config()] has a total of four parameters.
-** The first argument (the third parameter to sqlite3_db_config()) is a integer.
+** The first argument (the third parameter to sqlite3_db_config()) is an integer.
** The second argument is a pointer to an integer. If the first argument is 1,
** then the option becomes enabled. If the first integer argument is 0, then the
** option is disabled. If the first argument is -1, then the option setting
@@ -3239,7 +3262,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*);
** ^These routines return 0 if the statement is incomplete. ^If a
** memory allocation fails, then SQLITE_NOMEM is returned.
**
-** ^These routines do not parse the SQL statements thus
+** ^These routines do not parse the SQL statements and thus
** will not detect syntactically incorrect SQL.
**
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
@@ -3356,7 +3379,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
-** Internally, each SQLite database handle store two timeout values - the
+** Internally, each SQLite database handle stores two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
@@ -3386,7 +3409,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
-** Definition: A <b>result table</b> is memory data structure created by the
+** Definition: A <b>result table</b> is a memory data structure created by the
** [sqlite3_get_table()] interface. A result table records the
** complete query results from one or more queries.
**
@@ -3529,7 +3552,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** ^Calling sqlite3_free() with a pointer previously returned
** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
** that it might be reused. ^The sqlite3_free() routine is
-** a no-op if is called with a NULL pointer. Passing a NULL pointer
+** a no-op if it is called with a NULL pointer. Passing a NULL pointer
** to sqlite3_free() is harmless. After being freed, memory
** should neither be read nor written. Even reading previously freed
** memory might result in a segmentation fault or other severe error.
@@ -3547,13 +3570,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** sqlite3_free(X).
** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
-** ^If M is the size of the prior allocation, then min(N,M) bytes
-** of the prior allocation are copied into the beginning of buffer returned
+** ^If M is the size of the prior allocation, then min(N,M) bytes of the
+** prior allocation are copied into the beginning of the buffer returned
** by sqlite3_realloc(X,N) and the prior allocation is freed.
** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
-** ^The sqlite3_realloc64(X,N) interfaces works the same as
+** ^The sqlite3_realloc64(X,N) interface works the same as
** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
@@ -3603,7 +3626,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
** was last reset. ^The values returned by [sqlite3_memory_used()] and
** [sqlite3_memory_highwater()] include any overhead
** added by SQLite in its implementation of [sqlite3_malloc()],
-** but not overhead added by the any underlying system library
+** but not overhead added by any underlying system library
** routines that [sqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
@@ -4055,7 +4078,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** there is no harm in trying.)
**
** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
-** <dd>The database is opened [shared cache] enabled, overriding
+** <dd>The database is opened with [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
** The [use of shared cache mode is discouraged] and hence shared cache
@@ -4063,7 +4086,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
-** <dd>The database is opened [shared cache] disabled, overriding
+** <dd>The database is opened with [shared cache] disabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
**
@@ -4481,7 +4504,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** subsequent calls to other SQLite interface functions.)^
**
** ^The sqlite3_errstr(E) interface returns the English-language text
-** that describes the [result code] E, as UTF-8, or NULL if E is not an
+** that describes the [result code] E, as UTF-8, or NULL if E is not a
** result code for which a text error message is available.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
@@ -4489,7 +4512,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** ^If the most recent error references a specific token in the input
** SQL, the sqlite3_error_offset() interface returns the byte offset
** of the start of that token. ^The byte offset returned by
-** sqlite3_error_offset() assumes that the input SQL is UTF8.
+** sqlite3_error_offset() assumes that the input SQL is UTF-8.
** ^If the most recent error does not reference a specific token in the input
** SQL, then the sqlite3_error_offset() function returns -1.
**
@@ -4515,6 +4538,34 @@ SQLITE_API const char *sqlite3_errstr(int);
SQLITE_API int sqlite3_error_offset(sqlite3 *db);
/*
+** CAPI3REF: Set Error Codes And Message
+** METHOD: sqlite3
+**
+** Set the error code of the database handle passed as the first argument
+** to errcode, and the error message to a copy of nul-terminated string
+** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
+** the default message associated with the supplied error code. Subsequent
+** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will
+** return the values set by this routine in place of what was previously
+** set by SQLite itself.
+**
+** This function returns SQLITE_OK if the error code and error message are
+** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
+** the database handle is NULL or invalid.
+**
+** The error code and message set by this routine remains in effect until
+** they are changed, either by another call to this routine or until they are
+** changed to by SQLite itself to reflect the result of some subsquent
+** API call.
+**
+** This function is intended for use by SQLite extensions or wrappers. The
+** idea is that an extension or wrapper can use this routine to set error
+** messages and error codes and thus behave more like a core SQLite
+** feature from the point of view of an application.
+*/
+SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);
+
+/*
** CAPI3REF: Prepared Statement Object
** KEYWORDS: {prepared statement} {prepared statements}
**
@@ -4588,8 +4639,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
**
** These constants define various performance limits
** that can be lowered at run-time using [sqlite3_limit()].
-** The synopsis of the meanings of the various limits is shown below.
-** Additional information is available at [limits | Limits in SQLite].
+** A concise description of these limits follows, and additional information
+** is available at [limits | Limits in SQLite].
**
** <dl>
** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt>
@@ -4654,7 +4705,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
/*
** CAPI3REF: Prepare Flags
**
-** These constants define various flags that can be passed into
+** These constants define various flags that can be passed into the
** "prepFlags" parameter of the [sqlite3_prepare_v3()] and
** [sqlite3_prepare16_v3()] interfaces.
**
@@ -4741,7 +4792,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** there is a small performance advantage to passing an nByte parameter that
** is the number of bytes in the input string <i>including</i>
** the nul-terminator.
-** Note that nByte measure the length of the input in bytes, not
+** Note that nByte measures the length of the input in bytes, not
** characters, even for the UTF-16 interfaces.
**
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
@@ -4875,7 +4926,7 @@ SQLITE_API int sqlite3_prepare16_v3(
**
** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
-** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
+** maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
@@ -5063,7 +5114,7 @@ typedef struct sqlite3_value sqlite3_value;
**
** The context in which an SQL function executes is stored in an
** sqlite3_context object. ^A pointer to an sqlite3_context object
-** is always first parameter to [application-defined SQL functions].
+** is always the first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
** [sqlite3_aggregate_context()], [sqlite3_user_data()],
@@ -5187,9 +5238,11 @@ typedef struct sqlite3_context sqlite3_context;
** associated with the pointer P of type T. ^D is either a NULL pointer or
** a pointer to a destructor function for P. ^SQLite will invoke the
** destructor D with a single argument of P when it is finished using
-** P. The T parameter should be a static string, preferably a string
-** literal. The sqlite3_bind_pointer() routine is part of the
-** [pointer passing interface] added for SQLite 3.20.0.
+** P, even if the call to sqlite3_bind_pointer() fails. Due to a
+** historical design quirk, results are undefined if D is
+** SQLITE_TRANSIENT. The T parameter should be a static string,
+** preferably a string literal. The sqlite3_bind_pointer() routine is
+** part of the [pointer passing interface] added for SQLite 3.20.0.
**
** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
@@ -5800,7 +5853,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
**
** ^The sqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
-** or if the statement is never been evaluated, then sqlite3_finalize() returns
+** or if the statement has never been evaluated, then sqlite3_finalize() returns
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
** sqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
@@ -6032,7 +6085,7 @@ SQLITE_API int sqlite3_create_window_function(
/*
** CAPI3REF: Text Encodings
**
-** These constant define integer codes that represent the various
+** These constants define integer codes that represent the various
** text encodings supported by SQLite.
*/
#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
@@ -6124,7 +6177,7 @@ SQLITE_API int sqlite3_create_window_function(
** result.
** Every function that invokes [sqlite3_result_subtype()] should have this
** property. If it does not, then the call to [sqlite3_result_subtype()]
-** might become a no-op if the function is used as term in an
+** might become a no-op if the function is used as a term in an
** [expression index]. On the other hand, SQL functions that never invoke
** [sqlite3_result_subtype()] should avoid setting this property, as the
** purpose of this property is to disable certain optimizations that are
@@ -6251,7 +6304,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
** that the xUpdate method call was invoked to implement and if
-** and the prior [xColumn] method call that was invoked to extracted
+** the prior [xColumn] method call that was invoked to extract
** the value for that column returned without setting a result (probably
** because it queried [sqlite3_vtab_nochange()] and found that the column
** was unchanging). ^Within an [xUpdate] method, any value for which
@@ -6524,6 +6577,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
** or a NULL pointer if there were no prior calls to
** sqlite3_set_clientdata() with the same values of D and N.
** Names are compared using strcmp() and are thus case sensitive.
+** It returns 0 on success and SQLITE_NOMEM on allocation failure.
**
** If P and X are both non-NULL, then the destructor X is invoked with
** argument P on the first of the following occurrences:
@@ -9200,9 +9254,18 @@ SQLITE_API int sqlite3_status64(
** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
+** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same
+** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H
+** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead
+** of pointers to 32-bit integers, which allows larger status values
+** to be returned. If a status value exceeds 2,147,483,647 then
+** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64()
+** will return the full value.
+**
** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
*/
SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
+SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
/*
** CAPI3REF: Status Parameters for database connections
@@ -9299,6 +9362,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** If an IO or other error occurs while writing a page to disk, the effect
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
+** <p>
+** ^(There is overlap between the quantities measured by this parameter
+** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL.
+** Resetting one will reduce the other.)^
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
@@ -9314,6 +9381,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
** resolved.)^ ^The highwater mark is always 0.
+**
+** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt>
+** <dd>^(This parameter returns the number of bytes written to temporary
+** files on disk that could have been kept in memory had sufficient memory
+** been available. This value includes writes to intermediate tables that
+** are part of complex queries, external sorts that spill to disk, and
+** writes to TEMP tables.)^
+** ^The highwater mark is always 0.
+** <p>
+** ^(There is overlap between the quantities measured by this parameter
+** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE.
+** Resetting one will reduce the other.)^
** </dd>
** </dl>
*/
@@ -9330,7 +9409,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
#define SQLITE_DBSTATUS_CACHE_SPILL 12
-#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13
+#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */
/*
@@ -10095,7 +10175,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** is the number of pages currently in the write-ahead log file,
** including those that were just committed.
**
-** The callback function should normally return [SQLITE_OK]. ^If an error
+** ^The callback function should normally return [SQLITE_OK]. ^If an error
** code is returned, that error will propagate back up through the
** SQLite code base to cause the statement that provoked the callback
** to report an error, though the commit will have still occurred. If the
@@ -10103,13 +10183,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** that does not correspond to any valid SQLite error code, the results
** are undefined.
**
-** A single database handle may have at most a single write-ahead log callback
-** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
-** previously registered write-ahead log callback. ^The return value is
-** a copy of the third parameter from the previous call, if any, or 0.
-** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
-** overwrite any prior [sqlite3_wal_hook()] settings.
+** ^A single database handle may have at most a single write-ahead log
+** callback registered at one time. ^Calling [sqlite3_wal_hook()]
+** replaces the default behavior or previously registered write-ahead
+** log callback.
+**
+** ^The return value is a copy of the third parameter from the
+** previous call, if any, or 0.
+**
+** ^The [sqlite3_wal_autocheckpoint()] interface and the
+** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and
+** will overwrite any prior [sqlite3_wal_hook()] settings.
+**
+** ^If a write-ahead log callback is set using this function then
+** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint]
+** should be invoked periodically to keep the write-ahead log file
+** from growing without bound.
+**
+** ^Passing a NULL pointer for the callback disables automatic
+** checkpointing entirely. To re-enable the default behavior, call
+** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint].
*/
SQLITE_API void *sqlite3_wal_hook(
sqlite3*,
@@ -10126,7 +10219,7 @@ SQLITE_API void *sqlite3_wal_hook(
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file. ^Passing zero or
-** a negative value as the nFrame parameter disables automatic
+** a negative value as the N parameter disables automatic
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
@@ -10142,9 +10235,10 @@ SQLITE_API void *sqlite3_wal_hook(
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
-** pages. The use of this interface
-** is only necessary if the default setting is found to be suboptimal
-** for a particular application.
+** pages.
+**
+** ^The use of this interface is only necessary if the default setting
+** is found to be suboptimal for a particular application.
*/
SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
@@ -10209,6 +10303,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
** addition that it also truncates the log file to zero bytes just prior
** to a successful return.
+**
+** <dt>SQLITE_CHECKPOINT_NOOP<dd>
+** ^This mode always checkpoints zero frames. The only reason to invoke
+** a NOOP checkpoint is to access the values returned by
+** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt.
** </dl>
**
** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
@@ -10279,6 +10378,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
+#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
@@ -11106,7 +11206,7 @@ typedef struct sqlite3_snapshot {
** The [sqlite3_snapshot_get()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
+SQLITE_API int sqlite3_snapshot_get(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot **ppSnapshot
@@ -11155,7 +11255,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** The [sqlite3_snapshot_open()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
+SQLITE_API int sqlite3_snapshot_open(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot *pSnapshot
@@ -11172,7 +11272,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
** The [sqlite3_snapshot_free()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
+SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*);
/*
** CAPI3REF: Compare the ages of two snapshot handles.
@@ -11199,7 +11299,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
+SQLITE_API int sqlite3_snapshot_cmp(
sqlite3_snapshot *p1,
sqlite3_snapshot *p2
);
@@ -11227,7 +11327,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
+SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Serialize a database
@@ -11301,12 +11401,13 @@ SQLITE_API unsigned char *sqlite3_serialize(
**
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
** [database connection] D to disconnect from database S and then
-** reopen S as an in-memory database based on the serialization contained
-** in P. The serialized database P is N bytes in size. M is the size of
-** the buffer P, which might be larger than N. If M is larger than N, and
-** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
-** permitted to add content to the in-memory database as long as the total
-** size does not exceed M bytes.
+** reopen S as an in-memory database based on the serialization
+** contained in P. If S is a NULL pointer, the main database is
+** used. The serialized database P is N bytes in size. M is the size
+** of the buffer P, which might be larger than N. If M is larger than
+** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then
+** SQLite is permitted to add content to the in-memory database as
+** long as the total size does not exceed M bytes.
**
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
** invoke sqlite3_free() on the serialization buffer when the database
@@ -11374,6 +11475,54 @@ SQLITE_API int sqlite3_deserialize(
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
/*
+** CAPI3REF: Bind array values to the CARRAY table-valued function
+**
+** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to
+** one of the first argument of the [carray() table-valued function]. The
+** S parameter is a pointer to the [prepared statement] that uses the carray()
+** functions. I is the parameter index to be bound. P is a pointer to the
+** array to be bound, and N is the number of eements in the array. The
+** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64],
+** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to
+** indicate the datatype of the array being bound. The X argument is not a
+** NULL pointer, then SQLite will invoke the function X on the P parameter
+** after it has finished using P, even if the call to
+** sqlite3_carray_bind() fails. The special-case finalizer
+** SQLITE_TRANSIENT has no effect here.
+*/
+SQLITE_API int sqlite3_carray_bind(
+ sqlite3_stmt *pStmt, /* Statement to be bound */
+ int i, /* Parameter index */
+ void *aData, /* Pointer to array data */
+ int nData, /* Number of data elements */
+ int mFlags, /* CARRAY flags */
+ void (*xDel)(void*) /* Destructor for aData */
+);
+
+/*
+** CAPI3REF: Datatypes for the CARRAY table-valued function
+**
+** The fifth argument to the [sqlite3_carray_bind()] interface musts be
+** one of the following constants, to specify the datatype of the array
+** that is being bound into the [carray table-valued function].
+*/
+#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */
+#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */
+#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */
+#define SQLITE_CARRAY_TEXT 3 /* Data is char* */
+#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */
+
+/*
+** Versions of the above #defines that omit the initial SQLITE_, for
+** legacy compatibility.
+*/
+#define CARRAY_INT32 0 /* Data is 32-bit signed integers */
+#define CARRAY_INT64 1 /* Data is 64-bit signed integers */
+#define CARRAY_DOUBLE 2 /* Data is doubles */
+#define CARRAY_TEXT 3 /* Data is char* */
+#define CARRAY_BLOB 4 /* Data is struct iovec */
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
@@ -12632,14 +12781,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** update the "main" database attached to handle db with the changes found in
** the changeset passed via the second and third arguments.
**
+** All changes made by these functions are enclosed in a savepoint transaction.
+** If any other error (aside from a constraint failure when attempting to
+** write to the target database) occurs, then the savepoint transaction is
+** rolled back, restoring the target database to its original state, and an
+** SQLite error code returned. Additionally, starting with version 3.51.0,
+** an error code and error message that may be accessed using the
+** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database
+** handle.
+**
** The fourth argument (xFilter) passed to these functions is the "filter
-** callback". If it is not NULL, then for each table affected by at least one
-** change in the changeset, the filter callback is invoked with
-** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument as the first. If the "filter callback"
-** returns zero, then no attempt is made to apply any changes to the table.
-** Otherwise, if the return value is non-zero or the xFilter argument to
-** is NULL, all changes related to the table are attempted.
+** callback". This may be passed NULL, in which case all changes in the
+** changeset are applied to the database. For sqlite3changeset_apply() and
+** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
+** for each table affected by at least one change in the changeset. In this
+** case the table name is passed as the second argument, and a copy of
+** the context pointer passed as the sixth argument to apply() or apply_v2()
+** 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, all changes related to the table are attempted.
+**
+** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
+** per change. The second argument in this case is an sqlite3_changeset_iter
+** that may be queried using the usual APIs for the details of the current
+** change. If the "filter callback" returns zero in this case, then no attempt
+** is made to apply the current change. If it returns non-zero, the change
+** is applied.
**
** 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
@@ -12660,11 +12827,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** 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.
+** to modify the table contents according to each UPDATE, INSERT or DELETE
+** change that is not excluded by a filter callback. 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
@@ -12760,12 +12927,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** This can be used to further customize the application's conflict
** resolution strategy.
**
-** All changes made by these functions are enclosed in a savepoint transaction.
-** If any other error (aside from a constraint failure when attempting to
-** write to the target database) occurs, then the savepoint transaction is
-** rolled back, restoring the target database to its original state, and an
-** SQLite error code returned.
-**
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
** may set (*ppRebase) to point to a "rebase" that may be used with the
@@ -12815,6 +12976,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
+SQLITE_API int sqlite3changeset_apply_v3(
+ 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() */
+ sqlite3_changeset_iter *p /* Handle describing change */
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
/*
** CAPI3REF: Flags for sqlite3changeset_apply_v2
@@ -13234,6 +13412,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
);
+SQLITE_API int sqlite3changeset_apply_v3_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() */
+ sqlite3_changeset_iter *p
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
SQLITE_API int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
@@ -14310,7 +14505,7 @@ struct fts5_api {
** Maximum number of pages in one database file.
**
** This is really just the default value for the max_page_count pragma.
-** This value can be lowered (or raised) at run-time using that the
+** This value can be lowered (or raised) at run-time using the
** max_page_count macro.
*/
#ifndef SQLITE_MAX_PAGE_COUNT
@@ -15178,7 +15373,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
** ourselves.
*/
#ifndef offsetof
-#define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD))
+# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
#endif
/*
@@ -15566,6 +15761,8 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace;
** 0x00020000 Transform DISTINCT into GROUP BY
** 0x00040000 SELECT tree dump after all code has been generated
** 0x00080000 NOT NULL strength reduction
+** 0x00100000 Pointers are all shown as zero
+** 0x00200000 EXISTS-to-JOIN optimization
*/
/*
@@ -15610,6 +15807,7 @@ SQLITE_PRIVATE u32 sqlite3WhereTrace;
** 0x00020000 Show WHERE terms returned from whereScanNext()
** 0x00040000 Solver overview messages
** 0x00080000 Star-query heuristic
+** 0x00100000 Pointers are all shown as zero
*/
@@ -15682,7 +15880,7 @@ struct BusyHandler {
** pointer will work here as long as it is distinct from SQLITE_STATIC
** and SQLITE_TRANSIENT.
*/
-#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear)
+#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3RowSetClear)
/*
** When SQLITE_OMIT_WSD is defined, it means that the target platform does
@@ -15903,8 +16101,8 @@ typedef int VList;
** must provide its own VFS implementation together with sqlite3_os_init()
** and sqlite3_os_end() routines.
*/
-#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \
- !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN)
+#if SQLITE_OS_KV+1<=1 && SQLITE_OS_OTHER+1<=1 && \
+ SQLITE_OS_WIN+1<=1 && SQLITE_OS_UNIX+1<=1
# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \
defined(__MINGW32__) || defined(__BORLANDC__)
# define SQLITE_OS_WIN 1
@@ -16750,6 +16948,7 @@ struct BtreePayload {
SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload,
int flags, int seekResult);
SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes);
+SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes);
SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes);
SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int flags);
SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*);
@@ -17083,20 +17282,20 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_SorterSort 34 /* jump */
#define OP_Sort 35 /* jump */
#define OP_Rewind 36 /* jump0 */
-#define OP_SorterNext 37 /* jump */
-#define OP_Prev 38 /* jump */
-#define OP_Next 39 /* jump */
-#define OP_IdxLE 40 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IdxGT 41 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IdxLT 42 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IfEmpty 37 /* jump, synopsis: if( empty(P1) ) goto P2 */
+#define OP_SorterNext 38 /* jump */
+#define OP_Prev 39 /* jump */
+#define OP_Next 40 /* jump */
+#define OP_IdxLE 41 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxGT 42 /* jump, synopsis: key=r[P3@P4] */
#define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */
#define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */
-#define OP_IdxGE 45 /* jump, synopsis: key=r[P3@P4] */
-#define OP_RowSetRead 46 /* jump, synopsis: r[P3]=rowset(P1) */
-#define OP_RowSetTest 47 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */
-#define OP_Program 48 /* jump0 */
-#define OP_FkIfZero 49 /* jump, synopsis: if fkctr[P1]==0 goto P2 */
-#define OP_IfPos 50 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
+#define OP_IdxLT 45 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxGE 46 /* jump, synopsis: key=r[P3@P4] */
+#define OP_RowSetRead 47 /* jump, synopsis: r[P3]=rowset(P1) */
+#define OP_RowSetTest 48 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */
+#define OP_Program 49 /* jump0 */
+#define OP_FkIfZero 50 /* jump, synopsis: if fkctr[P1]==0 goto P2 */
#define OP_IsNull 51 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */
#define OP_NotNull 52 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */
#define OP_Ne 53 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */
@@ -17106,49 +17305,49 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Lt 57 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */
#define OP_Ge 58 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */
#define OP_ElseEq 59 /* jump, same as TK_ESCAPE */
-#define OP_IfNotZero 60 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */
-#define OP_DecrJumpZero 61 /* jump, synopsis: if (--r[P1])==0 goto P2 */
-#define OP_IncrVacuum 62 /* jump */
-#define OP_VNext 63 /* jump */
-#define OP_Filter 64 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */
-#define OP_PureFunc 65 /* synopsis: r[P3]=func(r[P2@NP]) */
-#define OP_Function 66 /* synopsis: r[P3]=func(r[P2@NP]) */
-#define OP_Return 67
-#define OP_EndCoroutine 68
-#define OP_HaltIfNull 69 /* synopsis: if r[P3]=null halt */
-#define OP_Halt 70
-#define OP_Integer 71 /* synopsis: r[P2]=P1 */
-#define OP_Int64 72 /* synopsis: r[P2]=P4 */
-#define OP_String 73 /* synopsis: r[P2]='P4' (len=P1) */
-#define OP_BeginSubrtn 74 /* synopsis: r[P2]=NULL */
-#define OP_Null 75 /* synopsis: r[P2..P3]=NULL */
-#define OP_SoftNull 76 /* synopsis: r[P1]=NULL */
-#define OP_Blob 77 /* synopsis: r[P2]=P4 (len=P1) */
-#define OP_Variable 78 /* synopsis: r[P2]=parameter(P1) */
-#define OP_Move 79 /* synopsis: r[P2@P3]=r[P1@P3] */
-#define OP_Copy 80 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
-#define OP_SCopy 81 /* synopsis: r[P2]=r[P1] */
-#define OP_IntCopy 82 /* synopsis: r[P2]=r[P1] */
-#define OP_FkCheck 83
-#define OP_ResultRow 84 /* synopsis: output=r[P1@P2] */
-#define OP_CollSeq 85
-#define OP_AddImm 86 /* synopsis: r[P1]=r[P1]+P2 */
-#define OP_RealAffinity 87
-#define OP_Cast 88 /* synopsis: affinity(r[P1]) */
-#define OP_Permutation 89
-#define OP_Compare 90 /* synopsis: r[P1@P3] <-> r[P2@P3] */
-#define OP_IsTrue 91 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
-#define OP_ZeroOrNull 92 /* synopsis: r[P2] = 0 OR NULL */
-#define OP_Offset 93 /* synopsis: r[P3] = sqlite_offset(P1) */
-#define OP_Column 94 /* synopsis: r[P3]=PX cursor P1 column P2 */
-#define OP_TypeCheck 95 /* synopsis: typecheck(r[P1@P2]) */
-#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */
-#define OP_MakeRecord 97 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
-#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_IfPos 60 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
+#define OP_IfNotZero 61 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */
+#define OP_DecrJumpZero 62 /* jump, synopsis: if (--r[P1])==0 goto P2 */
+#define OP_IncrVacuum 63 /* jump */
+#define OP_VNext 64 /* jump */
+#define OP_Filter 65 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */
+#define OP_PureFunc 66 /* synopsis: r[P3]=func(r[P2@NP]) */
+#define OP_Function 67 /* synopsis: r[P3]=func(r[P2@NP]) */
+#define OP_Return 68
+#define OP_EndCoroutine 69
+#define OP_HaltIfNull 70 /* synopsis: if r[P3]=null halt */
+#define OP_Halt 71
+#define OP_Integer 72 /* synopsis: r[P2]=P1 */
+#define OP_Int64 73 /* synopsis: r[P2]=P4 */
+#define OP_String 74 /* synopsis: r[P2]='P4' (len=P1) */
+#define OP_BeginSubrtn 75 /* synopsis: r[P2]=NULL */
+#define OP_Null 76 /* synopsis: r[P2..P3]=NULL */
+#define OP_SoftNull 77 /* synopsis: r[P1]=NULL */
+#define OP_Blob 78 /* synopsis: r[P2]=P4 (len=P1) */
+#define OP_Variable 79 /* synopsis: r[P2]=parameter(P1) */
+#define OP_Move 80 /* synopsis: r[P2@P3]=r[P1@P3] */
+#define OP_Copy 81 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
+#define OP_SCopy 82 /* synopsis: r[P2]=r[P1] */
+#define OP_IntCopy 83 /* synopsis: r[P2]=r[P1] */
+#define OP_FkCheck 84
+#define OP_ResultRow 85 /* synopsis: output=r[P1@P2] */
+#define OP_CollSeq 86
+#define OP_AddImm 87 /* synopsis: r[P1]=r[P1]+P2 */
+#define OP_RealAffinity 88
+#define OP_Cast 89 /* synopsis: affinity(r[P1]) */
+#define OP_Permutation 90
+#define OP_Compare 91 /* synopsis: r[P1@P3] <-> r[P2@P3] */
+#define OP_IsTrue 92 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
+#define OP_ZeroOrNull 93 /* synopsis: r[P2] = 0 OR NULL */
+#define OP_Offset 94 /* synopsis: r[P3] = sqlite_offset(P1) */
+#define OP_Column 95 /* synopsis: r[P3]=PX cursor P1 column P2 */
+#define OP_TypeCheck 96 /* synopsis: typecheck(r[P1@P2]) */
+#define OP_Affinity 97 /* synopsis: affinity(r[P1@P2]) */
+#define OP_MakeRecord 98 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
+#define OP_Count 99 /* synopsis: r[P2]=count() */
+#define OP_ReadCookie 100
+#define OP_SetCookie 101
+#define OP_ReopenIdx 102 /* synopsis: root=P2 iDb=P3 */
#define OP_BitAnd 103 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
#define OP_BitOr 104 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
#define OP_ShiftLeft 105 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
@@ -17159,83 +17358,84 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Divide 110 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
#define OP_Remainder 111 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
#define OP_Concat 112 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
-#define OP_OpenWrite 113 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenDup 114
+#define OP_OpenRead 113 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenWrite 114 /* synopsis: root=P2 iDb=P3 */
#define OP_BitNot 115 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */
-#define OP_OpenAutoindex 116 /* synopsis: nColumn=P2 */
-#define OP_OpenEphemeral 117 /* synopsis: nColumn=P2 */
+#define OP_OpenDup 116
+#define OP_OpenAutoindex 117 /* synopsis: nColumn=P2 */
#define OP_String8 118 /* same as TK_STRING, synopsis: r[P2]='P4' */
-#define OP_SorterOpen 119
-#define OP_SequenceTest 120 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
-#define OP_OpenPseudo 121 /* synopsis: P3 columns in r[P2] */
-#define OP_Close 122
-#define OP_ColumnsUsed 123
-#define OP_SeekScan 124 /* synopsis: Scan-ahead up to P1 rows */
-#define OP_SeekHit 125 /* synopsis: set P2<=seekHit<=P3 */
-#define OP_Sequence 126 /* synopsis: r[P2]=cursor[P1].ctr++ */
-#define OP_NewRowid 127 /* synopsis: r[P2]=rowid */
-#define OP_Insert 128 /* synopsis: intkey=r[P3] data=r[P2] */
-#define OP_RowCell 129
-#define OP_Delete 130
-#define OP_ResetCount 131
-#define OP_SorterCompare 132 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
-#define OP_SorterData 133 /* synopsis: r[P2]=data */
-#define OP_RowData 134 /* synopsis: r[P2]=data */
-#define OP_Rowid 135 /* synopsis: r[P2]=PX rowid of P1 */
-#define OP_NullRow 136
-#define OP_SeekEnd 137
-#define OP_IdxInsert 138 /* synopsis: key=r[P2] */
-#define OP_SorterInsert 139 /* synopsis: key=r[P2] */
-#define OP_IdxDelete 140 /* synopsis: key=r[P2@P3] */
-#define OP_DeferredSeek 141 /* synopsis: Move P3 to P1.rowid if needed */
-#define OP_IdxRowid 142 /* synopsis: r[P2]=rowid */
-#define OP_FinishSeek 143
-#define OP_Destroy 144
-#define OP_Clear 145
-#define OP_ResetSorter 146
-#define OP_CreateBtree 147 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
-#define OP_SqlExec 148
-#define OP_ParseSchema 149
-#define OP_LoadAnalysis 150
-#define OP_DropTable 151
-#define OP_DropIndex 152
-#define OP_DropTrigger 153
+#define OP_OpenEphemeral 119 /* synopsis: nColumn=P2 */
+#define OP_SorterOpen 120
+#define OP_SequenceTest 121 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
+#define OP_OpenPseudo 122 /* synopsis: P3 columns in r[P2] */
+#define OP_Close 123
+#define OP_ColumnsUsed 124
+#define OP_SeekScan 125 /* synopsis: Scan-ahead up to P1 rows */
+#define OP_SeekHit 126 /* synopsis: set P2<=seekHit<=P3 */
+#define OP_Sequence 127 /* synopsis: r[P2]=cursor[P1].ctr++ */
+#define OP_NewRowid 128 /* synopsis: r[P2]=rowid */
+#define OP_Insert 129 /* synopsis: intkey=r[P3] data=r[P2] */
+#define OP_RowCell 130
+#define OP_Delete 131
+#define OP_ResetCount 132
+#define OP_SorterCompare 133 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
+#define OP_SorterData 134 /* synopsis: r[P2]=data */
+#define OP_RowData 135 /* synopsis: r[P2]=data */
+#define OP_Rowid 136 /* synopsis: r[P2]=PX rowid of P1 */
+#define OP_NullRow 137
+#define OP_SeekEnd 138
+#define OP_IdxInsert 139 /* synopsis: key=r[P2] */
+#define OP_SorterInsert 140 /* synopsis: key=r[P2] */
+#define OP_IdxDelete 141 /* synopsis: key=r[P2@P3] */
+#define OP_DeferredSeek 142 /* synopsis: Move P3 to P1.rowid if needed */
+#define OP_IdxRowid 143 /* synopsis: r[P2]=rowid */
+#define OP_FinishSeek 144
+#define OP_Destroy 145
+#define OP_Clear 146
+#define OP_ResetSorter 147
+#define OP_CreateBtree 148 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
+#define OP_SqlExec 149
+#define OP_ParseSchema 150
+#define OP_LoadAnalysis 151
+#define OP_DropTable 152
+#define OP_DropIndex 153
#define OP_Real 154 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
-#define OP_IntegrityCk 155
-#define OP_RowSetAdd 156 /* synopsis: rowset(P1)=r[P2] */
-#define OP_Param 157
-#define OP_FkCounter 158 /* synopsis: fkctr[P1]+=P2 */
-#define OP_MemMax 159 /* synopsis: r[P1]=max(r[P1],r[P2]) */
-#define OP_OffsetLimit 160 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
-#define OP_AggInverse 161 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
-#define OP_AggStep 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggStep1 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggValue 164 /* synopsis: r[P3]=value N=P2 */
-#define OP_AggFinal 165 /* synopsis: accum=r[P1] N=P2 */
-#define OP_Expire 166
-#define OP_CursorLock 167
-#define OP_CursorUnlock 168
-#define OP_TableLock 169 /* synopsis: iDb=P1 root=P2 write=P3 */
-#define OP_VBegin 170
-#define OP_VCreate 171
-#define OP_VDestroy 172
-#define OP_VOpen 173
-#define OP_VCheck 174
-#define OP_VInitIn 175 /* synopsis: r[P2]=ValueList(P1,P3) */
-#define OP_VColumn 176 /* synopsis: r[P3]=vcolumn(P2) */
-#define OP_VRename 177
-#define OP_Pagecount 178
-#define OP_MaxPgcnt 179
-#define OP_ClrSubtype 180 /* synopsis: r[P1].subtype = 0 */
-#define OP_GetSubtype 181 /* synopsis: r[P2] = r[P1].subtype */
-#define OP_SetSubtype 182 /* synopsis: r[P2].subtype = r[P1] */
-#define OP_FilterAdd 183 /* synopsis: filter(P1) += key(P3@P4) */
-#define OP_Trace 184
-#define OP_CursorHint 185
-#define OP_ReleaseReg 186 /* synopsis: release r[P1@P2] mask P3 */
-#define OP_Noop 187
-#define OP_Explain 188
-#define OP_Abortable 189
+#define OP_DropTrigger 155
+#define OP_IntegrityCk 156
+#define OP_RowSetAdd 157 /* synopsis: rowset(P1)=r[P2] */
+#define OP_Param 158
+#define OP_FkCounter 159 /* synopsis: fkctr[P1]+=P2 */
+#define OP_MemMax 160 /* synopsis: r[P1]=max(r[P1],r[P2]) */
+#define OP_OffsetLimit 161 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
+#define OP_AggInverse 162 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
+#define OP_AggStep 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggStep1 164 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggValue 165 /* synopsis: r[P3]=value N=P2 */
+#define OP_AggFinal 166 /* synopsis: accum=r[P1] N=P2 */
+#define OP_Expire 167
+#define OP_CursorLock 168
+#define OP_CursorUnlock 169
+#define OP_TableLock 170 /* synopsis: iDb=P1 root=P2 write=P3 */
+#define OP_VBegin 171
+#define OP_VCreate 172
+#define OP_VDestroy 173
+#define OP_VOpen 174
+#define OP_VCheck 175
+#define OP_VInitIn 176 /* synopsis: r[P2]=ValueList(P1,P3) */
+#define OP_VColumn 177 /* synopsis: r[P3]=vcolumn(P2) */
+#define OP_VRename 178
+#define OP_Pagecount 179
+#define OP_MaxPgcnt 180
+#define OP_ClrSubtype 181 /* synopsis: r[P1].subtype = 0 */
+#define OP_GetSubtype 182 /* synopsis: r[P2] = r[P1].subtype */
+#define OP_SetSubtype 183 /* synopsis: r[P2].subtype = r[P1] */
+#define OP_FilterAdd 184 /* synopsis: filter(P1) += key(P3@P4) */
+#define OP_Trace 185
+#define OP_CursorHint 186
+#define OP_ReleaseReg 187 /* synopsis: release r[P1@P2] mask P3 */
+#define OP_Noop 188
+#define OP_Explain 189
+#define OP_Abortable 190
/* Properties such as "out2" or "jump" that are specified in
** comments following the "case" for each opcode in the vdbe.c
@@ -17254,26 +17454,26 @@ typedef struct VdbeOpList VdbeOpList;
/* 8 */ 0x81, 0x01, 0x01, 0x81, 0x83, 0x83, 0x01, 0x01,\
/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0xc9, 0xc9, 0xc9,\
/* 24 */ 0xc9, 0x01, 0x49, 0x49, 0x49, 0x49, 0xc9, 0x49,\
-/* 32 */ 0xc1, 0x01, 0x41, 0x41, 0xc1, 0x01, 0x41, 0x41,\
-/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x23, 0x0b,\
-/* 48 */ 0x81, 0x01, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\
-/* 56 */ 0x0b, 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x01, 0x41,\
-/* 64 */ 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10,\
-/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10, 0x00,\
-/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02,\
-/* 88 */ 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x40, 0x00,\
-/* 96 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x40, 0x40, 0x26,\
+/* 32 */ 0xc1, 0x01, 0x41, 0x41, 0xc1, 0x01, 0x01, 0x41,\
+/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x41, 0x23,\
+/* 48 */ 0x0b, 0x81, 0x01, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\
+/* 56 */ 0x0b, 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x03, 0x01,\
+/* 64 */ 0x41, 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00,\
+/* 72 */ 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10,\
+/* 80 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02,\
+/* 88 */ 0x02, 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x40,\
+/* 96 */ 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x40, 0x26,\
/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\
-/* 112 */ 0x26, 0x00, 0x40, 0x12, 0x40, 0x40, 0x10, 0x00,\
-/* 120 */ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10, 0x10,\
-/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x50,\
-/* 136 */ 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50, 0x40,\
-/* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\
-/* 152 */ 0x00, 0x00, 0x10, 0x00, 0x06, 0x10, 0x00, 0x04,\
-/* 160 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x50,\
-/* 176 */ 0x40, 0x00, 0x10, 0x10, 0x02, 0x12, 0x12, 0x00,\
-/* 184 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}
+/* 112 */ 0x26, 0x40, 0x00, 0x12, 0x40, 0x40, 0x10, 0x40,\
+/* 120 */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10,\
+/* 128 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,\
+/* 136 */ 0x50, 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50,\
+/* 144 */ 0x40, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\
+/* 152 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00,\
+/* 160 */ 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10,\
+/* 176 */ 0x50, 0x40, 0x00, 0x10, 0x10, 0x02, 0x12, 0x12,\
+/* 184 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}
/* The resolve3P2Values() routine is able to run faster if it knows
** the value of the largest JUMP opcode. The smaller the maximum
@@ -17281,7 +17481,7 @@ typedef struct VdbeOpList VdbeOpList;
** generated this include file strives to group all JUMP opcodes
** together near the beginning of the list.
*/
-#define SQLITE_MX_JUMP_OPCODE 64 /* Maximum JUMP opcode */
+#define SQLITE_MX_JUMP_OPCODE 65 /* Maximum JUMP opcode */
/************** End of opcodes.h *********************************************/
/************** Continuing where we left off in vdbe.h ***********************/
@@ -17404,8 +17604,11 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*);
#endif
SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*);
+#ifdef SQLITE_ENABLE_PERCENTILE
+SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context*);
+#endif
-SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*);
+SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*);
SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*);
SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*);
@@ -17418,7 +17621,9 @@ SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe*);
SQLITE_PRIVATE void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val);
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context*);
+#endif
#ifdef SQLITE_ENABLE_BYTECODE_VTAB
SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3*);
#endif
@@ -18074,7 +18279,7 @@ struct sqlite3 {
u8 iDb; /* Which db file is being initialized */
u8 busy; /* TRUE if currently initializing */
unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
- unsigned imposterTable : 1; /* Building an imposter table */
+ unsigned imposterTable : 2; /* Building an imposter table */
unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
const char **azInit; /* "type", "name", and "tbl_name" columns */
} init;
@@ -18157,6 +18362,7 @@ struct sqlite3 {
i64 nDeferredImmCons; /* Net deferred immediate constraints */
int *pnBytesFreed; /* If not NULL, increment this in DbFree() */
DbClientData *pDbData; /* sqlite3_set_clientdata() content */
+ u64 nSpill; /* TEMP content spilled to disk */
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
/* The following variables are all protected by the STATIC_MAIN
** mutex, not by sqlite3.mutex. They are used by code in notify.c.
@@ -18300,6 +18506,7 @@ struct sqlite3 {
#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
+#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*
@@ -18538,7 +18745,7 @@ struct FuncDestructor {
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_BUILTIN|\
SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- pArg, 0, xFunc, 0, 0, 0, #zName, }
+ pArg, 0, xFunc, 0, 0, 0, #zName, {0} }
#define LIKEFUNC(zName, nArg, arg, flags) \
{nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \
(void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} }
@@ -18866,6 +19073,7 @@ struct Table {
#define TF_Ephemeral 0x00004000 /* An ephemeral table */
#define TF_Eponymous 0x00008000 /* An eponymous virtual table */
#define TF_Strict 0x00010000 /* STRICT mode */
+#define TF_Imposter 0x00020000 /* An imposter table */
/*
** Allowed values for Table.eTabType
@@ -19021,9 +19229,15 @@ struct FKey {
** 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
-** are nField slots for the columns of an index then one extra slot
-** for the rowid at the end.
+** The aSortOrder[] and aColl[] arrays have nAllField slots each. There
+** are nKeyField slots for the columns of an index then extra slots
+** for the rowid or key at the end. The aSortOrder array is located after
+** the aColl[] array.
+**
+** If SQLITE_ENABLE_PREUPDATE_HOOK is defined, then aSortFlags might be NULL
+** to indicate that this object is for use by a preupdate hook. When aSortFlags
+** is NULL, then nAllField is uninitialized and no space is allocated for
+** aColl[], so those fields may not be used.
*/
struct KeyInfo {
u32 nRef; /* Number of references to this KeyInfo object */
@@ -19035,9 +19249,18 @@ struct KeyInfo {
CollSeq *aColl[FLEXARRAY]; /* Collating sequence for each term of the key */
};
-/* The size (in bytes) of a KeyInfo object with up to N fields */
+/* The size (in bytes) of a KeyInfo object with up to N fields. This includes
+** the main body of the KeyInfo object and the aColl[] array of N elements,
+** but does not count the memory used to hold aSortFlags[]. */
#define SZ_KEYINFO(N) (offsetof(KeyInfo,aColl) + (N)*sizeof(CollSeq*))
+/* The size of a bare KeyInfo with no aColl[] entries */
+#if FLEXARRAY+1 > 1
+# define SZ_KEYINFO_0 offsetof(KeyInfo,aColl)
+#else
+# define SZ_KEYINFO_0 sizeof(KeyInfo)
+#endif
+
/*
** Allowed bit values for entries in the KeyInfo.aSortFlags[] array.
*/
@@ -19056,9 +19279,8 @@ struct KeyInfo {
**
** 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.
+** is closest to the key described by this object. This object might hold
+** just a prefix of the key. The number of fields is given by nField.
**
** 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
@@ -19068,7 +19290,7 @@ struct KeyInfo {
** 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
+** at the first nField elements) 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.
**
@@ -19080,8 +19302,8 @@ struct KeyInfo {
** b-tree.
*/
struct UnpackedRecord {
- KeyInfo *pKeyInfo; /* Collation and sort-order information */
- Mem *aMem; /* Values */
+ KeyInfo *pKeyInfo; /* Comparison info for the index that is unpacked */
+ Mem *aMem; /* Values for columns of the index */
union {
char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */
i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */
@@ -19730,6 +19952,7 @@ struct SrcItem {
unsigned rowidUsed :1; /* The ROWID of this table is referenced */
unsigned fixedSchema :1; /* Uses u4.pSchema, not u4.zDatabase */
unsigned hadSchema :1; /* Had u4.zDatabase before u4.pSchema */
+ unsigned fromExists :1; /* Comes from WHERE EXISTS(...) */
} fg;
int iCursor; /* The VDBE cursor number used to access this table */
Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */
@@ -20017,6 +20240,7 @@ struct Select {
#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */
#define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */
#define SF_Correlated 0x20000000 /* True if references the outer context */
+#define SF_OnToWhere 0x40000000 /* One or more ON clauses moved to WHERE */
/* True if SrcItem X is a subquery that has SF_NestedFrom */
#define IsNestedFrom(X) \
@@ -20260,6 +20484,7 @@ struct Parse {
u8 disableLookaside; /* Number of times lookaside has been disabled */
u8 prepFlags; /* SQLITE_PREPARE_* flags */
u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */
+ u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */
u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */
u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
u8 bReturning; /* Coding a RETURNING trigger */
@@ -20769,6 +20994,7 @@ struct Walker {
SrcItem *pSrcItem; /* A single FROM clause item */
DbFixer *pFix; /* See sqlite3FixSelect() */
Mem *aMem; /* See sqlite3BtreeCursorHint() */
+ struct CheckOnCtx *pCheckOnCtx; /* See selectCheckOnClauses() */
} u;
};
@@ -21256,6 +21482,7 @@ SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*);
SQLITE_PRIVATE void sqlite3ShowWindow(const Window*);
SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*);
#endif
+SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec*);
#endif
SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*);
@@ -21572,13 +21799,17 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void);
SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void);
SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*);
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON)
-SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*);
+SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3*,const char*);
#endif
SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*);
SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*);
SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int);
SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p);
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY)
+SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3*);
+#endif
+
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
#endif
@@ -21799,7 +22030,7 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*);
SQLITE_PRIVATE void sqlite3AlterFunctions(void);
SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*);
-SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *);
+SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *, int *);
SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...);
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int);
SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int);
@@ -22565,6 +22796,9 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_ENABLE_BYTECODE_VTAB
"ENABLE_BYTECODE_VTAB",
#endif
+#ifdef SQLITE_ENABLE_CARRAY
+ "ENABLE_CARRAY",
+#endif
#ifdef SQLITE_ENABLE_CEROD
"ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD),
#endif
@@ -22655,6 +22889,9 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK
"ENABLE_OVERSIZE_CELL_CHECK",
#endif
+#ifdef SQLITE_ENABLE_PERCENTILE
+ "ENABLE_PERCENTILE",
+#endif
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
"ENABLE_PREUPDATE_HOOK",
#endif
@@ -23869,7 +24106,7 @@ struct sqlite3_value {
** MEM_Int, MEM_Real, and MEM_IntReal.
**
** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus
-** MEM.u.i extra 0x00 bytes at the end.
+** Mem.u.nZero extra 0x00 bytes at the end.
**
** * MEM_Int Integer stored in Mem.u.i.
**
@@ -24138,7 +24375,9 @@ struct PreUpdate {
Table *pTab; /* Schema object being updated */
Index *pPk; /* PK index if pTab is WITHOUT ROWID */
sqlite3_value **apDflt; /* Array of default values, if required */
- u8 keyinfoSpace[SZ_KEYINFO(0)]; /* Space to hold pKeyinfo[0] content */
+ struct {
+ u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */
+ } uKey;
};
/*
@@ -24302,9 +24541,11 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*);
#endif
#ifndef SQLITE_OMIT_FOREIGN_KEY
-SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int);
+SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe*);
+SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe*);
#else
-# define sqlite3VdbeCheckFk(p,i) 0
+# define sqlite3VdbeCheckFkImmediate(p) 0
+# define sqlite3VdbeCheckFkDeferred(p) 0
#endif
#ifdef SQLITE_DEBUG
@@ -24513,23 +24754,25 @@ SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3 *db, int *pHighwater){
/*
** Query status information for a single database connection
*/
-SQLITE_API int sqlite3_db_status(
- sqlite3 *db, /* The database connection whose status is desired */
- int op, /* Status verb */
- int *pCurrent, /* Write current value here */
- int *pHighwater, /* Write high-water mark here */
- int resetFlag /* Reset high-water mark if true */
+SQLITE_API int sqlite3_db_status64(
+ sqlite3 *db, /* The database connection whose status is desired */
+ int op, /* Status verb */
+ sqlite3_int64 *pCurrent, /* Write current value here */
+ sqlite3_int64 *pHighwtr, /* Write high-water mark here */
+ int resetFlag /* Reset high-water mark if true */
){
int rc = SQLITE_OK; /* Return code */
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){
+ if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){
return SQLITE_MISUSE_BKPT;
}
#endif
sqlite3_mutex_enter(db->mutex);
switch( op ){
case SQLITE_DBSTATUS_LOOKASIDE_USED: {
- *pCurrent = sqlite3LookasideUsed(db, pHighwater);
+ int H = 0;
+ *pCurrent = sqlite3LookasideUsed(db, &H);
+ *pHighwtr = H;
if( resetFlag ){
LookasideSlot *p = db->lookaside.pFree;
if( p ){
@@ -24560,7 +24803,7 @@ SQLITE_API int sqlite3_db_status(
assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 );
assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 );
*pCurrent = 0;
- *pHighwater = (int)db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT];
+ *pHighwtr = db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT];
if( resetFlag ){
db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0;
}
@@ -24574,7 +24817,7 @@ SQLITE_API int sqlite3_db_status(
*/
case SQLITE_DBSTATUS_CACHE_USED_SHARED:
case SQLITE_DBSTATUS_CACHE_USED: {
- int totalUsed = 0;
+ sqlite3_int64 totalUsed = 0;
int i;
sqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
@@ -24590,18 +24833,18 @@ SQLITE_API int sqlite3_db_status(
}
sqlite3BtreeLeaveAll(db);
*pCurrent = totalUsed;
- *pHighwater = 0;
+ *pHighwtr = 0;
break;
}
/*
** *pCurrent gets an accurate estimate of the amount of memory used
** to store the schema for all databases (main, temp, and any ATTACHed
- ** databases. *pHighwater is set to zero.
+ ** databases. *pHighwtr is set to zero.
*/
case SQLITE_DBSTATUS_SCHEMA_USED: {
- int i; /* Used to iterate through schemas */
- int nByte = 0; /* Used to accumulate return value */
+ int i; /* Used to iterate through schemas */
+ int nByte = 0; /* Used to accumulate return value */
sqlite3BtreeEnterAll(db);
db->pnBytesFreed = &nByte;
@@ -24635,7 +24878,7 @@ SQLITE_API int sqlite3_db_status(
db->lookaside.pEnd = db->lookaside.pTrueEnd;
sqlite3BtreeLeaveAll(db);
- *pHighwater = 0;
+ *pHighwtr = 0;
*pCurrent = nByte;
break;
}
@@ -24643,7 +24886,7 @@ SQLITE_API int sqlite3_db_status(
/*
** *pCurrent gets an accurate estimate of the amount of memory used
** to store all prepared statements.
- ** *pHighwater is set to zero.
+ ** *pHighwtr is set to zero.
*/
case SQLITE_DBSTATUS_STMT_USED: {
struct Vdbe *pVdbe; /* Used to iterate through VMs */
@@ -24658,7 +24901,7 @@ SQLITE_API int sqlite3_db_status(
db->lookaside.pEnd = db->lookaside.pTrueEnd;
db->pnBytesFreed = 0;
- *pHighwater = 0; /* IMP: R-64479-57858 */
+ *pHighwtr = 0; /* IMP: R-64479-57858 */
*pCurrent = nByte;
break;
@@ -24666,7 +24909,7 @@ SQLITE_API int sqlite3_db_status(
/*
** Set *pCurrent to the total cache hits or misses encountered by all
- ** pagers the database handle is connected to. *pHighwater is always set
+ ** pagers the database handle is connected to. *pHighwtr is always set
** to zero.
*/
case SQLITE_DBSTATUS_CACHE_SPILL:
@@ -24686,19 +24929,39 @@ SQLITE_API int sqlite3_db_status(
sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet);
}
}
- *pHighwater = 0; /* IMP: R-42420-56072 */
+ *pHighwtr = 0; /* IMP: R-42420-56072 */
/* IMP: R-54100-20147 */
/* IMP: R-29431-39229 */
- *pCurrent = (int)nRet & 0x7fffffff;
+ *pCurrent = nRet;
+ break;
+ }
+
+ /* Set *pCurrent to the number of bytes that the db database connection
+ ** has spilled to the filesystem in temporary files that could have been
+ ** stored in memory, had sufficient memory been available.
+ ** The *pHighwater is always set to zero.
+ */
+ case SQLITE_DBSTATUS_TEMPBUF_SPILL: {
+ u64 nRet = 0;
+ if( db->aDb[1].pBt ){
+ Pager *pPager = sqlite3BtreePager(db->aDb[1].pBt);
+ sqlite3PagerCacheStat(pPager, SQLITE_DBSTATUS_CACHE_WRITE,
+ resetFlag, &nRet);
+ nRet *= sqlite3BtreeGetPageSize(db->aDb[1].pBt);
+ }
+ nRet += db->nSpill;
+ if( resetFlag ) db->nSpill = 0;
+ *pHighwtr = 0;
+ *pCurrent = nRet;
break;
}
/* Set *pCurrent to non-zero if there are unresolved deferred foreign
** key constraints. Set *pCurrent to zero if all foreign key constraints
- ** have been satisfied. The *pHighwater is always set to zero.
+ ** have been satisfied. The *pHighwtr is always set to zero.
*/
case SQLITE_DBSTATUS_DEFERRED_FKS: {
- *pHighwater = 0; /* IMP: R-11967-56545 */
+ *pHighwtr = 0; /* IMP: R-11967-56545 */
*pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0;
break;
}
@@ -24711,6 +24974,31 @@ SQLITE_API int sqlite3_db_status(
return rc;
}
+/*
+** 32-bit variant of sqlite3_db_status64()
+*/
+SQLITE_API int sqlite3_db_status(
+ sqlite3 *db, /* The database connection whose status is desired */
+ int op, /* Status verb */
+ int *pCurrent, /* Write current value here */
+ int *pHighwtr, /* Write high-water mark here */
+ int resetFlag /* Reset high-water mark if true */
+){
+ sqlite3_int64 C = 0, H = 0;
+ int rc;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
+ rc = sqlite3_db_status64(db, op, &C, &H, resetFlag);
+ if( rc==0 ){
+ *pCurrent = C & 0x7fffffff;
+ *pHighwtr = H & 0x7fffffff;
+ }
+ return rc;
+}
+
/************** End of status.c **********************************************/
/************** Begin file date.c ********************************************/
/*
@@ -24903,6 +25191,10 @@ static int parseTimezone(const char *zDate, DateTime *p){
}
zDate += 5;
p->tz = sgn*(nMn + nHr*60);
+ if( p->tz==0 ){ /* Forum post 2025-09-17T10:12:14z */
+ p->isLocal = 0;
+ p->isUtc = 1;
+ }
zulu_time:
while( sqlite3Isspace(*zDate) ){ zDate++; }
return *zDate!=0;
@@ -26098,8 +26390,8 @@ static int daysAfterSunday(DateTime *pDate){
** %l hour 1-12 (leading zero converted to space)
** %m month 01-12
** %M minute 00-59
-** %p "am" or "pm"
-** %P "AM" or "PM"
+** %p "AM" or "PM"
+** %P "am" or "pm"
** %R time as HH:MM
** %s seconds since 1970-01-01
** %S seconds 00-59
@@ -31706,6 +31998,7 @@ typedef struct et_info { /* Information about each format field */
etByte type; /* Conversion paradigm */
etByte charset; /* Offset into aDigits[] of the digits string */
etByte prefix; /* Offset into aPrefix[] of the prefix string */
+ char iNxt; /* Next with same hash, or 0 for end of chain */
} et_info;
/*
@@ -31714,44 +32007,61 @@ typedef struct et_info { /* Information about each format field */
#define FLAG_SIGNED 1 /* True if the value to convert is signed */
#define FLAG_STRING 4 /* Allow infinite precision */
-
/*
-** The following table is searched linearly, so it is good to put the
-** most frequently used conversion types first.
+** The table is searched by hash. In the case of %C where C is the character
+** and that character has ASCII value j, then the hash is j%23.
+**
+** The order of the entries in fmtinfo[] and the hash chain was entered
+** manually, but based on the output of the following TCL script:
*/
+#if 0 /***** Beginning of script ******/
+foreach c {d s g z q Q w c o u x X f e E G i n % p T S r} {
+ scan $c %c x
+ set n($c) $x
+}
+set mx [llength [array names n]]
+puts "count: $mx"
+
+set mx 27
+puts "*********** mx=$mx ************"
+for {set r 0} {$r<$mx} {incr r} {
+ puts -nonewline [format %2d: $r]
+ foreach c [array names n] {
+ if {($n($c))%$mx==$r} {puts -nonewline " $c"}
+ }
+ puts ""
+}
+#endif /***** End of script ********/
+
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
static const char aPrefix[] = "-x0\000X0";
-static const et_info fmtinfo[] = {
- { 'd', 10, 1, etDECIMAL, 0, 0 },
- { 's', 0, 4, etSTRING, 0, 0 },
- { 'g', 0, 1, etGENERIC, 30, 0 },
- { 'z', 0, 4, etDYNSTRING, 0, 0 },
- { 'q', 0, 4, etESCAPE_q, 0, 0 },
- { 'Q', 0, 4, etESCAPE_Q, 0, 0 },
- { 'w', 0, 4, etESCAPE_w, 0, 0 },
- { 'c', 0, 0, etCHARX, 0, 0 },
- { 'o', 8, 0, etRADIX, 0, 2 },
- { 'u', 10, 0, etDECIMAL, 0, 0 },
- { 'x', 16, 0, etRADIX, 16, 1 },
- { 'X', 16, 0, etRADIX, 0, 4 },
-#ifndef SQLITE_OMIT_FLOATING_POINT
- { 'f', 0, 1, etFLOAT, 0, 0 },
- { 'e', 0, 1, etEXP, 30, 0 },
- { 'E', 0, 1, etEXP, 14, 0 },
- { 'G', 0, 1, etGENERIC, 14, 0 },
-#endif
- { 'i', 10, 1, etDECIMAL, 0, 0 },
- { 'n', 0, 0, etSIZE, 0, 0 },
- { '%', 0, 0, etPERCENT, 0, 0 },
- { 'p', 16, 0, etPOINTER, 0, 1 },
-
- /* All the rest are undocumented and are for internal use only */
- { 'T', 0, 0, etTOKEN, 0, 0 },
- { 'S', 0, 0, etSRCITEM, 0, 0 },
- { 'r', 10, 1, etORDINAL, 0, 0 },
+static const et_info fmtinfo[23] = {
+ /* 0 */ { 's', 0, 4, etSTRING, 0, 0, 1 },
+ /* 1 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 0 */
+ /* 2 */ { 'u', 10, 0, etDECIMAL, 0, 0, 3 },
+ /* 3 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 2 */
+ /* 4 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 },
+ /* 5 */ { 'x', 16, 0, etRADIX, 16, 1, 0 },
+ /* 6 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, /* Hash: 7 */
+ /* 7 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 6 },
+ /* 8 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 },
+ /* 9 */ { 'e', 0, 1, etEXP, 30, 0, 0 },
+ /* 10 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 },
+ /* 11 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 },
+ /* 12 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 0 },
+ /* 13 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 },
+ /* 14 */ { '%', 0, 0, etPERCENT, 0, 0, 16 },
+ /* 15 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 },
+ /* 16 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, /* Hash: 14 */
+ /* 17 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 19 */
+ /* 18 */ { 'n', 0, 0, etSIZE, 0, 0, 0 },
+ /* 19 */ { 'o', 8, 0, etRADIX, 0, 2, 17 },
+ /* 20 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 },
+ /* 21 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 },
+ /* 22 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 }
};
-/* Notes:
+/* Additional Notes:
**
** %S Takes a pointer to SrcItem. Shows name or database.name
** %!S Like %S but prefer the zName over the zAlias
@@ -31878,7 +32188,10 @@ SQLITE_API void sqlite3_str_vappendf(
#if HAVE_STRCHRNUL
fmt = strchrnul(fmt, '%');
#else
- do{ fmt++; }while( *fmt && *fmt != '%' );
+ fmt = strchr(fmt, '%');
+ if( fmt==0 ){
+ fmt = bufpt + strlen(bufpt);
+ }
#endif
sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt));
if( *fmt==0 ) break;
@@ -31992,6 +32305,9 @@ SQLITE_API void sqlite3_str_vappendf(
}while( !done && (c=(*++fmt))!=0 );
/* Fetch the info entry for the field */
+#ifdef SQLITE_EBCDIC
+ /* The hash table only works for ASCII. For EBCDIC, we need to do
+ ** a linear search of the table */
infop = &fmtinfo[0];
xtype = etINVALID;
for(idx=0; idx<ArraySize(fmtinfo); idx++){
@@ -32001,6 +32317,20 @@ SQLITE_API void sqlite3_str_vappendf(
break;
}
}
+#else
+ /* Fast hash-table lookup */
+ assert( ArraySize(fmtinfo)==23 );
+ idx = ((unsigned)c) % 23;
+ if( fmtinfo[idx].fmttype==c
+ || fmtinfo[idx = fmtinfo[idx].iNxt].fmttype==c
+ ){
+ infop = &fmtinfo[idx];
+ xtype = infop->type;
+ }else{
+ infop = &fmtinfo[0];
+ xtype = etINVALID;
+ }
+#endif
/*
** At this point, variables are initialized as follows:
@@ -32068,6 +32398,14 @@ SQLITE_API void sqlite3_str_vappendf(
}
prefix = 0;
}
+
+#if WHERETRACE_ENABLED
+ if( xtype==etPOINTER && sqlite3WhereTrace & 0x100000 ) longvalue = 0;
+#endif
+#if TREETRACE_ENABLED
+ if( xtype==etPOINTER && sqlite3TreeTrace & 0x100000 ) longvalue = 0;
+#endif
+
if( longvalue==0 ) flag_alternateform = 0;
if( flag_zeropad && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
@@ -32180,7 +32518,21 @@ SQLITE_API void sqlite3_str_vappendf(
}
}
if( s.sign=='-' ){
- prefix = '-';
+ if( flag_alternateform
+ && !flag_prefix
+ && xtype==etFLOAT
+ && s.iDP<=iRound
+ ){
+ /* Suppress the minus sign if all of the following are true:
+ ** * The value displayed is zero
+ ** * The '#' flag is used
+ ** * The '+' flag is not used, and
+ ** * The format is %f
+ */
+ prefix = 0;
+ }else{
+ prefix = '-';
+ }
}else{
prefix = flag_prefix;
}
@@ -33391,9 +33743,13 @@ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc)
n = 0;
if( pItem->fg.isSubquery ) n++;
if( pItem->fg.isTabFunc ) n++;
- if( pItem->fg.isUsing ) n++;
+ if( pItem->fg.isUsing || pItem->u3.pOn!=0 ) n++;
if( pItem->fg.isUsing ){
sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING");
+ }else if( pItem->u3.pOn!=0 ){
+ sqlite3TreeViewItem(pView, "ON", (--n)>0);
+ sqlite3TreeViewExpr(pView, pItem->u3.pOn, 0);
+ sqlite3TreeViewPop(&pView);
}
if( pItem->fg.isSubquery ){
assert( n==1 );
@@ -37699,20 +38055,20 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 34 */ "SorterSort" OpHelp(""),
/* 35 */ "Sort" OpHelp(""),
/* 36 */ "Rewind" OpHelp(""),
- /* 37 */ "SorterNext" OpHelp(""),
- /* 38 */ "Prev" OpHelp(""),
- /* 39 */ "Next" OpHelp(""),
- /* 40 */ "IdxLE" OpHelp("key=r[P3@P4]"),
- /* 41 */ "IdxGT" OpHelp("key=r[P3@P4]"),
- /* 42 */ "IdxLT" OpHelp("key=r[P3@P4]"),
+ /* 37 */ "IfEmpty" OpHelp("if( empty(P1) ) goto P2"),
+ /* 38 */ "SorterNext" OpHelp(""),
+ /* 39 */ "Prev" OpHelp(""),
+ /* 40 */ "Next" OpHelp(""),
+ /* 41 */ "IdxLE" OpHelp("key=r[P3@P4]"),
+ /* 42 */ "IdxGT" OpHelp("key=r[P3@P4]"),
/* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"),
/* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"),
- /* 45 */ "IdxGE" OpHelp("key=r[P3@P4]"),
- /* 46 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
- /* 47 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
- /* 48 */ "Program" OpHelp(""),
- /* 49 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
- /* 50 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
+ /* 45 */ "IdxLT" OpHelp("key=r[P3@P4]"),
+ /* 46 */ "IdxGE" OpHelp("key=r[P3@P4]"),
+ /* 47 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
+ /* 48 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
+ /* 49 */ "Program" OpHelp(""),
+ /* 50 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
/* 51 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"),
/* 52 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"),
/* 53 */ "Ne" OpHelp("IF r[P3]!=r[P1]"),
@@ -37722,49 +38078,49 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 57 */ "Lt" OpHelp("IF r[P3]<r[P1]"),
/* 58 */ "Ge" OpHelp("IF r[P3]>=r[P1]"),
/* 59 */ "ElseEq" OpHelp(""),
- /* 60 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"),
- /* 61 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
- /* 62 */ "IncrVacuum" OpHelp(""),
- /* 63 */ "VNext" OpHelp(""),
- /* 64 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"),
- /* 65 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"),
- /* 66 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"),
- /* 67 */ "Return" OpHelp(""),
- /* 68 */ "EndCoroutine" OpHelp(""),
- /* 69 */ "HaltIfNull" OpHelp("if r[P3]=null halt"),
- /* 70 */ "Halt" OpHelp(""),
- /* 71 */ "Integer" OpHelp("r[P2]=P1"),
- /* 72 */ "Int64" OpHelp("r[P2]=P4"),
- /* 73 */ "String" OpHelp("r[P2]='P4' (len=P1)"),
- /* 74 */ "BeginSubrtn" OpHelp("r[P2]=NULL"),
- /* 75 */ "Null" OpHelp("r[P2..P3]=NULL"),
- /* 76 */ "SoftNull" OpHelp("r[P1]=NULL"),
- /* 77 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
- /* 78 */ "Variable" OpHelp("r[P2]=parameter(P1)"),
- /* 79 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
- /* 80 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
- /* 81 */ "SCopy" OpHelp("r[P2]=r[P1]"),
- /* 82 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
- /* 83 */ "FkCheck" OpHelp(""),
- /* 84 */ "ResultRow" OpHelp("output=r[P1@P2]"),
- /* 85 */ "CollSeq" OpHelp(""),
- /* 86 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
- /* 87 */ "RealAffinity" OpHelp(""),
- /* 88 */ "Cast" OpHelp("affinity(r[P1])"),
- /* 89 */ "Permutation" OpHelp(""),
- /* 90 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
- /* 91 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
- /* 92 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"),
- /* 93 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
- /* 94 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"),
- /* 95 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"),
- /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
- /* 97 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
- /* 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"),
+ /* 60 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
+ /* 61 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"),
+ /* 62 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
+ /* 63 */ "IncrVacuum" OpHelp(""),
+ /* 64 */ "VNext" OpHelp(""),
+ /* 65 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"),
+ /* 66 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"),
+ /* 67 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"),
+ /* 68 */ "Return" OpHelp(""),
+ /* 69 */ "EndCoroutine" OpHelp(""),
+ /* 70 */ "HaltIfNull" OpHelp("if r[P3]=null halt"),
+ /* 71 */ "Halt" OpHelp(""),
+ /* 72 */ "Integer" OpHelp("r[P2]=P1"),
+ /* 73 */ "Int64" OpHelp("r[P2]=P4"),
+ /* 74 */ "String" OpHelp("r[P2]='P4' (len=P1)"),
+ /* 75 */ "BeginSubrtn" OpHelp("r[P2]=NULL"),
+ /* 76 */ "Null" OpHelp("r[P2..P3]=NULL"),
+ /* 77 */ "SoftNull" OpHelp("r[P1]=NULL"),
+ /* 78 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
+ /* 79 */ "Variable" OpHelp("r[P2]=parameter(P1)"),
+ /* 80 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
+ /* 81 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
+ /* 82 */ "SCopy" OpHelp("r[P2]=r[P1]"),
+ /* 83 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
+ /* 84 */ "FkCheck" OpHelp(""),
+ /* 85 */ "ResultRow" OpHelp("output=r[P1@P2]"),
+ /* 86 */ "CollSeq" OpHelp(""),
+ /* 87 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
+ /* 88 */ "RealAffinity" OpHelp(""),
+ /* 89 */ "Cast" OpHelp("affinity(r[P1])"),
+ /* 90 */ "Permutation" OpHelp(""),
+ /* 91 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
+ /* 92 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
+ /* 93 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"),
+ /* 94 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
+ /* 95 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"),
+ /* 96 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"),
+ /* 97 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
+ /* 98 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
+ /* 99 */ "Count" OpHelp("r[P2]=count()"),
+ /* 100 */ "ReadCookie" OpHelp(""),
+ /* 101 */ "SetCookie" OpHelp(""),
+ /* 102 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
/* 103 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
/* 104 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
/* 105 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
@@ -37775,83 +38131,84 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 110 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
/* 111 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
/* 112 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
- /* 113 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
- /* 114 */ "OpenDup" OpHelp(""),
+ /* 113 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
+ /* 114 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
/* 115 */ "BitNot" OpHelp("r[P2]= ~r[P1]"),
- /* 116 */ "OpenAutoindex" OpHelp("nColumn=P2"),
- /* 117 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 116 */ "OpenDup" OpHelp(""),
+ /* 117 */ "OpenAutoindex" OpHelp("nColumn=P2"),
/* 118 */ "String8" OpHelp("r[P2]='P4'"),
- /* 119 */ "SorterOpen" OpHelp(""),
- /* 120 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
- /* 121 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
- /* 122 */ "Close" OpHelp(""),
- /* 123 */ "ColumnsUsed" OpHelp(""),
- /* 124 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"),
- /* 125 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"),
- /* 126 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
- /* 127 */ "NewRowid" OpHelp("r[P2]=rowid"),
- /* 128 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
- /* 129 */ "RowCell" OpHelp(""),
- /* 130 */ "Delete" OpHelp(""),
- /* 131 */ "ResetCount" OpHelp(""),
- /* 132 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
- /* 133 */ "SorterData" OpHelp("r[P2]=data"),
- /* 134 */ "RowData" OpHelp("r[P2]=data"),
- /* 135 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"),
- /* 136 */ "NullRow" OpHelp(""),
- /* 137 */ "SeekEnd" OpHelp(""),
- /* 138 */ "IdxInsert" OpHelp("key=r[P2]"),
- /* 139 */ "SorterInsert" OpHelp("key=r[P2]"),
- /* 140 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
- /* 141 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
- /* 142 */ "IdxRowid" OpHelp("r[P2]=rowid"),
- /* 143 */ "FinishSeek" OpHelp(""),
- /* 144 */ "Destroy" OpHelp(""),
- /* 145 */ "Clear" OpHelp(""),
- /* 146 */ "ResetSorter" OpHelp(""),
- /* 147 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
- /* 148 */ "SqlExec" OpHelp(""),
- /* 149 */ "ParseSchema" OpHelp(""),
- /* 150 */ "LoadAnalysis" OpHelp(""),
- /* 151 */ "DropTable" OpHelp(""),
- /* 152 */ "DropIndex" OpHelp(""),
- /* 153 */ "DropTrigger" OpHelp(""),
+ /* 119 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 120 */ "SorterOpen" OpHelp(""),
+ /* 121 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
+ /* 122 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
+ /* 123 */ "Close" OpHelp(""),
+ /* 124 */ "ColumnsUsed" OpHelp(""),
+ /* 125 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"),
+ /* 126 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"),
+ /* 127 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
+ /* 128 */ "NewRowid" OpHelp("r[P2]=rowid"),
+ /* 129 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
+ /* 130 */ "RowCell" OpHelp(""),
+ /* 131 */ "Delete" OpHelp(""),
+ /* 132 */ "ResetCount" OpHelp(""),
+ /* 133 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
+ /* 134 */ "SorterData" OpHelp("r[P2]=data"),
+ /* 135 */ "RowData" OpHelp("r[P2]=data"),
+ /* 136 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"),
+ /* 137 */ "NullRow" OpHelp(""),
+ /* 138 */ "SeekEnd" OpHelp(""),
+ /* 139 */ "IdxInsert" OpHelp("key=r[P2]"),
+ /* 140 */ "SorterInsert" OpHelp("key=r[P2]"),
+ /* 141 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
+ /* 142 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
+ /* 143 */ "IdxRowid" OpHelp("r[P2]=rowid"),
+ /* 144 */ "FinishSeek" OpHelp(""),
+ /* 145 */ "Destroy" OpHelp(""),
+ /* 146 */ "Clear" OpHelp(""),
+ /* 147 */ "ResetSorter" OpHelp(""),
+ /* 148 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
+ /* 149 */ "SqlExec" OpHelp(""),
+ /* 150 */ "ParseSchema" OpHelp(""),
+ /* 151 */ "LoadAnalysis" OpHelp(""),
+ /* 152 */ "DropTable" OpHelp(""),
+ /* 153 */ "DropIndex" OpHelp(""),
/* 154 */ "Real" OpHelp("r[P2]=P4"),
- /* 155 */ "IntegrityCk" OpHelp(""),
- /* 156 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
- /* 157 */ "Param" OpHelp(""),
- /* 158 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
- /* 159 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
- /* 160 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
- /* 161 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
- /* 162 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 163 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 164 */ "AggValue" OpHelp("r[P3]=value N=P2"),
- /* 165 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
- /* 166 */ "Expire" OpHelp(""),
- /* 167 */ "CursorLock" OpHelp(""),
- /* 168 */ "CursorUnlock" OpHelp(""),
- /* 169 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
- /* 170 */ "VBegin" OpHelp(""),
- /* 171 */ "VCreate" OpHelp(""),
- /* 172 */ "VDestroy" OpHelp(""),
- /* 173 */ "VOpen" OpHelp(""),
- /* 174 */ "VCheck" OpHelp(""),
- /* 175 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"),
- /* 176 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
- /* 177 */ "VRename" OpHelp(""),
- /* 178 */ "Pagecount" OpHelp(""),
- /* 179 */ "MaxPgcnt" OpHelp(""),
- /* 180 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"),
- /* 181 */ "GetSubtype" OpHelp("r[P2] = r[P1].subtype"),
- /* 182 */ "SetSubtype" OpHelp("r[P2].subtype = r[P1]"),
- /* 183 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"),
- /* 184 */ "Trace" OpHelp(""),
- /* 185 */ "CursorHint" OpHelp(""),
- /* 186 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
- /* 187 */ "Noop" OpHelp(""),
- /* 188 */ "Explain" OpHelp(""),
- /* 189 */ "Abortable" OpHelp(""),
+ /* 155 */ "DropTrigger" OpHelp(""),
+ /* 156 */ "IntegrityCk" OpHelp(""),
+ /* 157 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
+ /* 158 */ "Param" OpHelp(""),
+ /* 159 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
+ /* 160 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
+ /* 161 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
+ /* 162 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
+ /* 163 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 164 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 165 */ "AggValue" OpHelp("r[P3]=value N=P2"),
+ /* 166 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
+ /* 167 */ "Expire" OpHelp(""),
+ /* 168 */ "CursorLock" OpHelp(""),
+ /* 169 */ "CursorUnlock" OpHelp(""),
+ /* 170 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
+ /* 171 */ "VBegin" OpHelp(""),
+ /* 172 */ "VCreate" OpHelp(""),
+ /* 173 */ "VDestroy" OpHelp(""),
+ /* 174 */ "VOpen" OpHelp(""),
+ /* 175 */ "VCheck" OpHelp(""),
+ /* 176 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"),
+ /* 177 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
+ /* 178 */ "VRename" OpHelp(""),
+ /* 179 */ "Pagecount" OpHelp(""),
+ /* 180 */ "MaxPgcnt" OpHelp(""),
+ /* 181 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"),
+ /* 182 */ "GetSubtype" OpHelp("r[P2] = r[P1].subtype"),
+ /* 183 */ "SetSubtype" OpHelp("r[P2].subtype = r[P1]"),
+ /* 184 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"),
+ /* 185 */ "Trace" OpHelp(""),
+ /* 186 */ "CursorHint" OpHelp(""),
+ /* 187 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
+ /* 188 */ "Noop" OpHelp(""),
+ /* 189 */ "Explain" OpHelp(""),
+ /* 190 */ "Abortable" OpHelp(""),
};
return azName[i];
}
@@ -38035,7 +38392,7 @@ static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf);
#define KVSTORAGE_KEY_SZ 32
/* Expand the key name with an appropriate prefix and put the result
-** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least
+** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least
** KVSTORAGE_KEY_SZ bytes.
*/
static void kvstorageMakeKey(
@@ -38094,10 +38451,12 @@ static int kvstorageDelete(const char *zClass, const char *zKey){
**
** Return the total number of bytes in the data, without truncation, and
** not counting the final zero terminator. Return -1 if the key does
-** not exist.
+** not exist or its key cannot be read.
**
-** If nBuf<=0 then this routine simply returns the size of the data without
-** actually reading it.
+** If nBuf<=0 then this routine simply returns the size of the data
+** without actually reading it. Similarly, if nBuf==1 then it
+** zero-terminates zBuf at zBuf[0] and returns the size of the data
+** without reading it.
*/
static int kvstorageRead(
const char *zClass,
@@ -38146,11 +38505,9 @@ static int kvstorageRead(
** kvvfs i/o methods with JavaScript implementations in WASM builds.
** Maintenance reminder: if this struct changes in any way, the JSON
** rendering of its structure must be updated in
-** sqlite3_wasm_enum_json(). There are no binary compatibility
-** concerns, so it does not need an iVersion member. This file is
-** necessarily always compiled together with sqlite3_wasm_enum_json(),
-** and JS code dynamically creates the mapping of members based on
-** that JSON description.
+** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary
+** compatibility concerns, so it does not need an iVersion
+** member.
*/
typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods;
struct sqlite3_kvvfs_methods {
@@ -38167,8 +38524,8 @@ struct sqlite3_kvvfs_methods {
** the compiler can hopefully optimize this level of indirection out.
** That said, kvvfs is intended primarily for use in WASM builds.
**
-** Note that this is not explicitly flagged as static because the
-** amalgamation build will tag it with SQLITE_PRIVATE.
+** This is not explicitly flagged as static because the amalgamation
+** build will tag it with SQLITE_PRIVATE.
*/
#ifndef SQLITE_WASM
const
@@ -39341,10 +39698,11 @@ static struct unix_syscall {
#if defined(HAVE_FCHMOD)
{ "fchmod", (sqlite3_syscall_ptr)fchmod, 0 },
+#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent)
#else
{ "fchmod", (sqlite3_syscall_ptr)0, 0 },
+#define osFchmod(FID,MODE) 0
#endif
-#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent)
#if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE
{ "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 },
@@ -39438,6 +39796,119 @@ static struct unix_syscall {
}; /* End of the overrideable system calls */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+/*
+** Extract Posix Advisory Locking information about file description fd
+** from the /proc/PID/fdinfo/FD pseudo-file. Fill the string buffer a[16]
+** with characters to indicate which SQLite-relevant locks are held.
+** a[16] will be a 15-character zero-terminated string with the following
+** schema:
+**
+** AAA/B.DDD.DDDDD
+**
+** Each of character A-D will be "w" or "r" or "-" to indicate either a
+** write-lock, a read-lock, or no-lock, respectively. The "." and "/"
+** characters are delimiters intended to make the string more easily
+** readable by humans. Here are the meaning of the specific letters:
+**
+** AAA -> The main database locks. PENDING_BYTE, RESERVED_BYTE,
+** and SHARED_FIRST, respectively.
+**
+** B -> The deadman switch lock. Offset 128 of the -shm file.
+**
+** CCC -> WAL locks: WRITE, CKPT, RECOVER
+**
+** DDDDD -> WAL read-locks 0 through 5
+**
+** Note that elements before the "/" apply to the main database file and
+** elements after the "/" apply to the -shm file in WAL mode.
+**
+** Here is another way of thinking about the meaning of the result string:
+**
+** AAA/B.CCC.DDDDD
+** ||| | ||| \___/
+** PENDING--'|| | ||| `----- READ 0-5
+** RESERVED--'| | ||`---- RECOVER
+** SHARED ----' | |`----- CKPT
+** DMS ------' `------ WRITE
+**
+** Return SQLITE_OK on success and SQLITE_ERROR_UNABLE if the /proc
+** pseudo-filesystem is unavailable.
+*/
+static int unixPosixAdvisoryLocks(
+ int fd, /* The file descriptor to analyze */
+ char a[16] /* Write a text description of PALs here */
+){
+ int in;
+ ssize_t n;
+ char *p, *pNext, *x;
+ char z[2000];
+
+ /* 1 */
+ /* 012 4 678 01234 */
+ memcpy(a, "---/-.---.-----", 16);
+ sqlite3_snprintf(sizeof(z), z, "/proc/%d/fdinfo/%d", getpid(), fd);
+ in = osOpen(z, O_RDONLY, 0);
+ if( in<0 ){
+ return SQLITE_ERROR_UNABLE;
+ }
+ n = osRead(in, z, sizeof(z)-1);
+ osClose(in);
+ if( n<=0 ) return SQLITE_ERROR_UNABLE;
+ z[n] = 0;
+
+ /* We are looking for lines that begin with "lock:\t". Examples:
+ **
+ ** lock: 1: POSIX ADVISORY READ 494716 08:02:5277597 1073741826 1073742335
+ ** lock: 1: POSIX ADVISORY WRITE 494716 08:02:5282282 120 120
+ ** lock: 2: POSIX ADVISORY READ 494716 08:02:5282282 123 123
+ ** lock: 3: POSIX ADVISORY READ 494716 08:02:5282282 128 128
+ */
+ pNext = strstr(z, "lock:\t");
+ while( pNext ){
+ char cType = 0;
+ sqlite3_int64 iFirst, iLast;
+ p = pNext+6;
+ pNext = strstr(p, "lock:\t");
+ if( pNext ) pNext[-1] = 0;
+ if( (x = strstr(p, " READ "))!=0 ){
+ cType = 'r';
+ x += 6;
+ }else if( (x = strstr(p, " WRITE "))!=0 ){
+ cType = 'w';
+ x += 7;
+ }else{
+ continue;
+ }
+ x = strrchr(x, ' ');
+ if( x==0 ) continue;
+ iLast = strtoll(x+1, 0, 10);
+ *x = 0;
+ x = strrchr(p, ' ');
+ if( x==0 ) continue;
+ iFirst = strtoll(x+1, 0, 10);
+ if( iLast>=PENDING_BYTE ){
+ if( iFirst<=PENDING_BYTE && iLast>=PENDING_BYTE ) a[0] = cType;
+ if( iFirst<=PENDING_BYTE+1 && iLast>=PENDING_BYTE+1 ) a[1] = cType;
+ if( iFirst<=PENDING_BYTE+2 && iLast>=PENDING_BYTE+510 ) a[2] = cType;
+ }else if( iLast<=128 ){
+ if( iFirst<=128 && iLast>=128 ) a[4] = cType;
+ if( iFirst<=120 && iLast>=120 ) a[6] = cType;
+ if( iFirst<=121 && iLast>=121 ) a[7] = cType;
+ if( iFirst<=122 && iLast>=122 ) a[8] = cType;
+ if( iFirst<=123 && iLast>=123 ) a[10] = cType;
+ if( iFirst<=124 && iLast>=124 ) a[11] = cType;
+ if( iFirst<=125 && iLast>=125 ) a[12] = cType;
+ if( iFirst<=126 && iLast>=126 ) a[13] = cType;
+ if( iFirst<=127 && iLast>=127 ) a[14] = cType;
+ }
+ }
+ return SQLITE_OK;
+}
+#else
+# define unixPosixAdvisoryLocks(A,B) SQLITE_ERROR_UNABLE
+#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */
+
/*
** 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
@@ -39602,9 +40073,8 @@ static int robust_open(const char *z, int f, mode_t m){
/*
** Helper functions to obtain and relinquish the global mutex. The
-** global mutex is used to protect the unixInodeInfo and
-** vxworksFileId objects used by this file, all of which may be
-** shared by multiple threads.
+** global mutex is used to protect the unixInodeInfo objects used by
+** this file, all of which may be shared by multiple threads.
**
** Function unixMutexHeld() is used to assert() that the global mutex
** is held when required. This function is only used as part of assert()
@@ -39806,6 +40276,7 @@ struct vxworksFileId {
** variable:
*/
static struct vxworksFileId *vxworksFileList = 0;
+static sqlite3_mutex *vxworksMutex = 0;
/*
** Simplify a filename into its canonical form
@@ -39871,14 +40342,14 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
** If found, increment the reference count and return a pointer to
** the existing file ID.
*/
- unixEnterMutex();
+ sqlite3_mutex_enter(vxworksMutex);
for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){
if( pCandidate->nName==n
&& memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0
){
sqlite3_free(pNew);
pCandidate->nRef++;
- unixLeaveMutex();
+ sqlite3_mutex_leave(vxworksMutex);
return pCandidate;
}
}
@@ -39888,7 +40359,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
pNew->nName = n;
pNew->pNext = vxworksFileList;
vxworksFileList = pNew;
- unixLeaveMutex();
+ sqlite3_mutex_leave(vxworksMutex);
return pNew;
}
@@ -39897,7 +40368,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
** the object when the reference count reaches zero.
*/
static void vxworksReleaseFileId(struct vxworksFileId *pId){
- unixEnterMutex();
+ sqlite3_mutex_enter(vxworksMutex);
assert( pId->nRef>0 );
pId->nRef--;
if( pId->nRef==0 ){
@@ -39907,7 +40378,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){
*pp = pId->pNext;
sqlite3_free(pId);
}
- unixLeaveMutex();
+ sqlite3_mutex_leave(vxworksMutex);
}
#endif /* OS_VXWORKS */
/*************** End of Unique File ID Utility Used By VxWorks ****************
@@ -40295,6 +40766,10 @@ static int findInodeInfo(
storeLastErrno(pFile, errno);
return SQLITE_IOERR;
}
+ if( fsync(fd) ){
+ storeLastErrno(pFile, errno);
+ return SQLITE_IOERR_FSYNC;
+ }
rc = osFstat(fd, &statbuf);
if( rc!=0 ){
storeLastErrno(pFile, errno);
@@ -40464,18 +40939,42 @@ static int osSetPosixAdvisoryLock(
struct flock *pLock, /* The description of the lock */
unixFile *pFile /* Structure holding timeout value */
){
- int tm = pFile->iBusyTimeout;
- int rc = osFcntl(h,F_SETLK,pLock);
- while( rc<0 && tm>0 ){
- /* On systems that support some kind of blocking file lock with a timeout,
- ** make appropriate changes here to invoke that blocking file lock. On
- ** generic posix, however, there is no such API. So we simply try the
- ** lock once every millisecond until either the timeout expires, or until
- ** the lock is obtained. */
- unixSleep(0,1000);
+ int rc = 0;
+
+ if( pFile->iBusyTimeout==0 ){
+ /* unixFile->iBusyTimeout is set to 0. In this case, attempt a
+ ** non-blocking lock. */
+ rc = osFcntl(h,F_SETLK,pLock);
+ }else{
+ /* unixFile->iBusyTimeout is set to greater than zero. In this case,
+ ** attempt a blocking-lock with a unixFile->iBusyTimeout ms timeout.
+ **
+ ** On systems that support some kind of blocking file lock operation,
+ ** this block should be replaced by code to attempt a blocking lock
+ ** with a timeout of unixFile->iBusyTimeout ms. The code below is
+ ** placeholder code. If SQLITE_TEST is defined, the placeholder code
+ ** retries the lock once every 1ms until it succeeds or the timeout
+ ** is reached. Or, if SQLITE_TEST is not defined, the placeholder
+ ** code attempts a non-blocking lock and sets unixFile->iBusyTimeout
+ ** to 0. This causes the caller to return SQLITE_BUSY, instead of
+ ** SQLITE_BUSY_TIMEOUT to SQLite - as required by a VFS that does not
+ ** support blocking locks.
+ */
+#ifdef SQLITE_TEST
+ int tm = pFile->iBusyTimeout;
+ while( tm>0 ){
+ rc = osFcntl(h,F_SETLK,pLock);
+ if( rc==0 ) break;
+ unixSleep(0,1000);
+ tm--;
+ }
+#else
rc = osFcntl(h,F_SETLK,pLock);
- tm--;
+ pFile->iBusyTimeout = 0;
+#endif
+ /* End of code to replace with real blocking-locks code. */
}
+
return rc;
}
#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */
@@ -40533,6 +41032,13 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
return rc;
}
+#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL)
+/* Forward reference */
+static int unixIsSharingShmNode(unixFile*);
+#else
+#define unixIsSharingShmNode(pFile) (0)
+#endif
+
/*
** Lock the file with the lock specified by parameter eFileLock - one
** of the following:
@@ -40721,7 +41227,9 @@ static int unixLock(sqlite3_file *id, int eFileLock){
pInode->nLock++;
pInode->nShared = 1;
}
- }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){
+ }else if( (eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1)
+ || unixIsSharingShmNode(pFile)
+ ){
/* We are trying for an exclusive lock but another thread in this
** same process is still holding a shared lock. */
rc = SQLITE_BUSY;
@@ -42816,6 +43324,10 @@ static int unixGetTempname(int nBuf, char *zBuf);
#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL)
static int unixFcntlExternalReader(unixFile*, int*);
#endif
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+ static void unixDescribeShm(sqlite3_str*,unixShm*);
+#endif
+
/*
** Information and control of an open file handle.
@@ -42958,6 +43470,66 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
return SQLITE_OK;
#endif
}
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+ case SQLITE_FCNTL_FILESTAT: {
+ sqlite3_str *pStr = (sqlite3_str*)pArg;
+ char aLck[16];
+ unixInodeInfo *pInode;
+ static const char *azLock[] = { "SHARED", "RESERVED",
+ "PENDING", "EXCLUSIVE" };
+ sqlite3_str_appendf(pStr, "{\"h\":%d", pFile->h);
+ sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName);
+ if( pFile->eFileLock ){
+ sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"",
+ azLock[pFile->eFileLock-1]);
+ if( unixPosixAdvisoryLocks(pFile->h, aLck)==SQLITE_OK ){
+ sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck);
+ }
+ }
+ unixEnterMutex();
+ if( pFile->pShm ){
+ sqlite3_str_appendall(pStr, ",\"shm\":");
+ unixDescribeShm(pStr, pFile->pShm);
+ }
+#if SQLITE_MAX_MMAP_SIZE>0
+ if( pFile->mmapSize ){
+ sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize);
+ sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut);
+ }
+#endif
+ if( (pInode = pFile->pInode)!=0 ){
+ sqlite3_str_appendf(pStr, ",\"inode\":{\"nRef\":%d",pInode->nRef);
+ sqlite3_mutex_enter(pInode->pLockMutex);
+ sqlite3_str_appendf(pStr, ",\"nShared\":%d", pInode->nShared);
+ if( pInode->eFileLock ){
+ sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"",
+ azLock[pInode->eFileLock-1]);
+ }
+ if( pInode->pUnused ){
+ char cSep = '[';
+ UnixUnusedFd *pUFd = pFile->pInode->pUnused;
+ sqlite3_str_appendall(pStr, ",\"unusedFd\":");
+ while( pUFd ){
+ sqlite3_str_appendf(pStr, "%c{\"fd\":%d,\"flags\":%d",
+ cSep, pUFd->fd, pUFd->flags);
+ cSep = ',';
+ if( unixPosixAdvisoryLocks(pUFd->fd, aLck)==SQLITE_OK ){
+ sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck);
+ }
+ sqlite3_str_append(pStr, "}", 1);
+ pUFd = pUFd->pNext;
+ }
+ sqlite3_str_append(pStr, "]", 1);
+ }
+ sqlite3_mutex_leave(pInode->pLockMutex);
+ sqlite3_str_append(pStr, "}", 1);
+ }
+ unixLeaveMutex();
+ sqlite3_str_append(pStr, "}", 1);
+ return SQLITE_OK;
+ }
+#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */
}
return SQLITE_NOTFOUND;
}
@@ -43224,6 +43796,26 @@ struct unixShm {
#define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */
#define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+/*
+** Describe the pShm object using JSON. Used for diagnostics only.
+*/
+static void unixDescribeShm(sqlite3_str *pStr, unixShm *pShm){
+ unixShmNode *pNode = pShm->pShmNode;
+ char aLck[16];
+ sqlite3_str_appendf(pStr, "{\"h\":%d", pNode->hShm);
+ assert( unixMutexHeld() );
+ sqlite3_str_appendf(pStr, ",\"nRef\":%d", pNode->nRef);
+ sqlite3_str_appendf(pStr, ",\"id\":%d", pShm->id);
+ sqlite3_str_appendf(pStr, ",\"sharedMask\":%d", pShm->sharedMask);
+ sqlite3_str_appendf(pStr, ",\"exclMask\":%d", pShm->exclMask);
+ if( unixPosixAdvisoryLocks(pNode->hShm, aLck)==SQLITE_OK ){
+ sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck);
+ }
+ sqlite3_str_append(pStr, "}", 1);
+}
+#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */
+
/*
** Use F_GETLK to check whether or not there are any readers with open
** wal-mode transactions in other processes on database file pFile. If
@@ -43257,6 +43849,49 @@ static int unixFcntlExternalReader(unixFile *pFile, int *piOut){
return rc;
}
+/*
+** If pFile has a -shm file open and it is sharing that file with some
+** other connection, either in the same process or in a separate process,
+** then return true. Return false if either pFile does not have a -shm
+** file open or if it is the only connection to that -shm file across the
+** entire system.
+**
+** This routine is not required for correct operation. It can always return
+** false and SQLite will continue to operate according to spec. However,
+** when this routine does its job, it adds extra robustness in cases
+** where database file locks have been erroneously deleted in a WAL-mode
+** database by doing close(open(DATABASE_PATHNAME)) or similar.
+**
+** With false negatives, SQLite still operates to spec, though with less
+** robustness. With false positives, the last database connection on a
+** WAL-mode database will fail to unlink the -wal and -shm files, which
+** is annoying but harmless. False positives will also prevent a database
+** connection from running "PRAGMA journal_mode=DELETE" in order to take
+** the database out of WAL mode, which is perhaps more serious, but is
+** still not a disaster.
+*/
+static int unixIsSharingShmNode(unixFile *pFile){
+ int rc;
+ unixShmNode *pShmNode;
+ if( pFile->pShm==0 ) return 0;
+ if( pFile->ctrlFlags & UNIXFILE_EXCL ) return 0;
+ pShmNode = pFile->pShm->pShmNode;
+ rc = 1;
+ unixEnterMutex();
+ if( ALWAYS(pShmNode->nRef==1) ){
+ struct flock lock;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = UNIX_SHM_DMS;
+ lock.l_len = 1;
+ lock.l_type = F_WRLCK;
+ osFcntl(pShmNode->hShm, F_GETLK, &lock);
+ if( lock.l_type==F_UNLCK ){
+ rc = 0;
+ }
+ }
+ unixLeaveMutex();
+ return rc;
+}
/*
** Apply posix advisory locks for all bytes from ofst through ofst+n-1.
@@ -43302,7 +43937,8 @@ static int unixShmSystemLock(
/* Locks are within range */
assert( n>=1 && n<=SQLITE_SHM_NLOCK );
- assert( ofst>=UNIX_SHM_BASE && ofst<=(UNIX_SHM_DMS+SQLITE_SHM_NLOCK) );
+ assert( ofst>=UNIX_SHM_BASE && ofst<=UNIX_SHM_DMS );
+ assert( ofst+n-1<=UNIX_SHM_DMS );
if( pShmNode->hShm>=0 ){
int res;
@@ -43834,7 +44470,7 @@ static int assertLockingArrayOk(unixShmNode *pShmNode){
return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0);
#endif
}
-#endif
+#endif /* !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) */
/*
** Change the lock state for a shared-memory segment.
@@ -44796,10 +45432,17 @@ static int fillInUnixFile(
storeLastErrno(pNew, 0);
#if OS_VXWORKS
if( rc!=SQLITE_OK ){
- if( h>=0 ) robust_close(pNew, h, __LINE__);
- h = -1;
- osUnlink(zFilename);
- pNew->ctrlFlags |= UNIXFILE_DELETE;
+ if( h>=0 ){
+ robust_close(pNew, h, __LINE__);
+ h = -1;
+ }
+ if( pNew->ctrlFlags & UNIXFILE_DELETE ){
+ osUnlink(zFilename);
+ }
+ if( pNew->pId ){
+ vxworksReleaseFileId(pNew->pId);
+ pNew->pId = 0;
+ }
}
#endif
if( rc!=SQLITE_OK ){
@@ -44843,6 +45486,9 @@ static const char *unixTempFileDir(void){
while(1){
if( zDir!=0
+#if OS_VXWORKS
+ && zDir[0]=='/'
+#endif
&& osStat(zDir, &buf)==0
&& S_ISDIR(buf.st_mode)
&& osAccess(zDir, 03)==0
@@ -45157,6 +45803,12 @@ static int unixOpen(
|| eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL
);
+#if OS_VXWORKS
+ /* The file-ID mechanism used in Vxworks requires that all pathnames
+ ** provided to unixOpen must be absolute pathnames. */
+ if( zPath!=0 && zPath[0]!='/' ){ return SQLITE_CANTOPEN; }
+#endif
+
/* Detect a pid change and reset the PRNG. There is a race condition
** here such that two or more threads all trying to open databases at
** the same instant might all reset the PRNG. But multiple resets
@@ -45357,8 +46009,11 @@ static int unixOpen(
}
#endif
- assert( zPath==0 || zPath[0]=='/'
- || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL
+ assert( zPath==0
+ || zPath[0]=='/'
+ || eType==SQLITE_OPEN_SUPER_JOURNAL
+ || eType==SQLITE_OPEN_MAIN_JOURNAL
+ || eType==SQLITE_OPEN_TEMP_JOURNAL
);
rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags);
@@ -47087,6 +47742,9 @@ SQLITE_API int sqlite3_os_init(void){
sqlite3KvvfsInit();
#endif
unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
+#if OS_VXWORKS
+ vxworksMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS2);
+#endif
#ifndef SQLITE_OMIT_WAL
/* Validate lock assumptions */
@@ -47121,6 +47779,9 @@ SQLITE_API int sqlite3_os_init(void){
*/
SQLITE_API int sqlite3_os_end(void){
unixBigLock = 0;
+#if OS_VXWORKS
+ vxworksMutex = 0;
+#endif
return SQLITE_OK;
}
@@ -49793,6 +50454,7 @@ static BOOL winLockFile(
#endif
}
+#ifndef SQLITE_OMIT_WAL
/*
** Lock a region of nByte bytes starting at offset offset of file hFile.
** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock
@@ -49875,6 +50537,7 @@ static int winHandleLockTimeout(
}
return rc;
}
+#endif /* #ifndef SQLITE_OMIT_WAL */
/*
** Unlock a file region.
@@ -49909,6 +50572,7 @@ static BOOL winUnlockFile(
#endif
}
+#ifndef SQLITE_OMIT_WAL
/*
** Remove an nByte lock starting at offset iOff from HANDLE h.
*/
@@ -49916,6 +50580,7 @@ static int winHandleUnlock(HANDLE h, int iOff, int nByte){
BOOL ret = winUnlockFile(&h, iOff, 0, nByte, 0);
return (ret ? SQLITE_OK : SQLITE_IOERR_UNLOCK);
}
+#endif
/*****************************************************************************
** The next group of routines implement the I/O methods specified
@@ -50253,6 +50918,7 @@ static int winWrite(
return SQLITE_OK;
}
+#ifndef SQLITE_OMIT_WAL
/*
** Truncate the file opened by handle h to nByte bytes in size.
*/
@@ -50306,6 +50972,7 @@ static void winHandleClose(HANDLE h){
osCloseHandle(h);
}
}
+#endif /* #ifndef SQLITE_OMIT_WAL */
/*
** Truncate an open file to a specified size
@@ -51083,6 +51750,28 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
}
#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+ case SQLITE_FCNTL_FILESTAT: {
+ sqlite3_str *pStr = (sqlite3_str*)pArg;
+ sqlite3_str_appendf(pStr, "{\"h\":%llu", (sqlite3_uint64)pFile->h);
+ sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName);
+ if( pFile->locktype ){
+ static const char *azLock[] = { "SHARED", "RESERVED",
+ "PENDING", "EXCLUSIVE" };
+ sqlite3_str_appendf(pStr, ",\"locktype\":\"%s\"",
+ azLock[pFile->locktype-1]);
+ }
+#if SQLITE_MAX_MMAP_SIZE>0
+ if( pFile->mmapSize ){
+ sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize);
+ sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut);
+ }
+#endif
+ sqlite3_str_append(pStr, "}", 1);
+ return SQLITE_OK;
+ }
+#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */
+
}
OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h));
return SQLITE_NOTFOUND;
@@ -51120,6 +51809,103 @@ static int winDeviceCharacteristics(sqlite3_file *id){
*/
static SYSTEM_INFO winSysInfo;
+/*
+** Convert a UTF-8 filename into whatever form the underlying
+** operating system wants filenames in. Space to hold the result
+** is obtained from malloc and must be freed by the calling
+** function
+**
+** On Cygwin, 3 possible input forms are accepted:
+** - If the filename starts with "<drive>:/" or "<drive>:\",
+** it is converted to UTF-16 as-is.
+** - If the filename contains '/', it is assumed to be a
+** Cygwin absolute path, it is converted to a win32
+** absolute path in UTF-16.
+** - Otherwise it must be a filename only, the win32 filename
+** is returned in UTF-16.
+** Note: If the function cygwin_conv_path() fails, only
+** UTF-8 -> UTF-16 conversion will be done. This can only
+** happen when the file path >32k, in which case winUtf8ToUnicode()
+** will fail too.
+*/
+static void *winConvertFromUtf8Filename(const char *zFilename){
+ void *zConverted = 0;
+ if( osIsNT() ){
+#ifdef __CYGWIN__
+ int nChar;
+ LPWSTR zWideFilename;
+
+ if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename)
+ && winIsDirSep(zFilename[2])) ){
+ i64 nByte;
+ int convertflag = CCP_POSIX_TO_WIN_W;
+ if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE;
+ nByte = (i64)osCygwin_conv_path(convertflag,
+ zFilename, 0, 0);
+ if( nByte>0 ){
+ zConverted = sqlite3MallocZero(12+(u64)nByte);
+ if ( zConverted==0 ){
+ return zConverted;
+ }
+ zWideFilename = zConverted;
+ /* Filenames should be prefixed, except when converted
+ * full path already starts with "\\?\". */
+ if( osCygwin_conv_path(convertflag, zFilename,
+ zWideFilename+4, nByte)==0 ){
+ if( (convertflag&CCP_RELATIVE) ){
+ memmove(zWideFilename, zWideFilename+4, nByte);
+ }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){
+ memcpy(zWideFilename, L"\\\\?\\", 8);
+ }else if( zWideFilename[6]!='?' ){
+ memmove(zWideFilename+6, zWideFilename+4, nByte);
+ memcpy(zWideFilename, L"\\\\?\\UNC", 14);
+ }else{
+ memmove(zWideFilename, zWideFilename+4, nByte);
+ }
+ return zConverted;
+ }
+ sqlite3_free(zConverted);
+ }
+ }
+ nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
+ if( nChar==0 ){
+ return 0;
+ }
+ zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 );
+ if( zWideFilename==0 ){
+ return 0;
+ }
+ nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1,
+ zWideFilename, nChar);
+ if( nChar==0 ){
+ sqlite3_free(zWideFilename);
+ zWideFilename = 0;
+ }else if( nChar>MAX_PATH
+ && winIsDriveLetterAndColon(zFilename)
+ && winIsDirSep(zFilename[2]) ){
+ memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR));
+ zWideFilename[2] = '\\';
+ memcpy(zWideFilename, L"\\\\?\\", 8);
+ }else if( nChar>MAX_PATH
+ && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1])
+ && zFilename[2] != '?' ){
+ memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR));
+ memcpy(zWideFilename, L"\\\\?\\UNC", 14);
+ }
+ zConverted = zWideFilename;
+#else
+ zConverted = winUtf8ToUnicode(zFilename);
+#endif /* __CYGWIN__ */
+ }
+#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32)
+ else{
+ zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
+ }
+#endif
+ /* caller will handle out of memory */
+ return zConverted;
+}
+
#ifndef SQLITE_OMIT_WAL
/*
@@ -51156,29 +51942,35 @@ static int winShmMutexHeld(void) {
** log-summary is opened only once per process.
**
** winShmMutexHeld() must be true when creating or destroying
-** this object or while reading or writing the following fields:
+** this object, or while editing the global linked list that starts
+** at winShmNodeList.
**
-** nRef
-** pNext
+** When reading or writing the linked list starting at winShmNode.pWinShmList,
+** pShmNode->mutex must be held.
**
-** The following fields are read-only after the object is created:
+** The following fields are constant after the object is created:
**
** zFilename
+** hSharedShm
+** mutex
+** bUseSharedLockHandle
**
-** Either winShmNode.mutex must be held or winShmNode.nRef==0 and
+** Either winShmNode.mutex must be held or winShmNode.pWinShmList==0 and
** winShmMutexHeld() is true when reading or writing any other field
** in this structure.
**
-** File-handle hSharedShm is used to (a) take the DMS lock, (b) truncate
-** the *-shm file if the DMS-locking protocol demands it, and (c) map
-** regions of the *-shm file into memory using MapViewOfFile() or
-** similar. Other locks are taken by individual clients using the
-** winShm.hShm handles.
+** File-handle hSharedShm is always used to (a) take the DMS lock, (b)
+** truncate the *-shm file if the DMS-locking protocol demands it, and
+** (c) map regions of the *-shm file into memory using MapViewOfFile()
+** or similar. If bUseSharedLockHandle is true, then other locks are also
+** taken on hSharedShm. Or, if bUseSharedLockHandle is false, then other
+** locks are taken using each connection's winShm.hShm handles.
*/
struct winShmNode {
sqlite3_mutex *mutex; /* Mutex to access this object */
char *zFilename; /* Name of the file */
HANDLE hSharedShm; /* File handle open on zFilename */
+ int bUseSharedLockHandle; /* True to use hSharedShm for everything */
int isUnlocked; /* DMS lock has not yet been obtained */
int isReadonly; /* True if read-only */
@@ -51191,7 +51983,8 @@ struct winShmNode {
} *aRegion;
DWORD lastErrno; /* The Windows errno from the last I/O error */
- int nRef; /* Number of winShm objects pointing to this */
+ winShm *pWinShmList; /* List of winShm objects with ptrs to this */
+
winShmNode *pNext; /* Next in list of all winShmNode objects */
#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
u8 nextShmId; /* Next available winShm.id value */
@@ -51219,6 +52012,7 @@ struct winShm {
#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
u8 id; /* Id of this connection with its winShmNode */
#endif
+ winShm *pWinShmNext; /* Next winShm object on same winShmNode */
};
/*
@@ -51232,7 +52026,7 @@ static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*);
static int winDelete(sqlite3_vfs *,const char*,int);
/*
-** Purge the winShmNodeList list of all entries with winShmNode.nRef==0.
+** Purge the winShmNodeList list of all entries with winShmNode.pWinShmList==0.
**
** This is not a VFS shared-memory method; it is a utility function called
** by VFS shared-memory methods.
@@ -51245,7 +52039,7 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
osGetCurrentProcessId(), deleteFlag));
pp = &winShmNodeList;
while( (p = *pp)!=0 ){
- if( p->nRef==0 ){
+ if( p->pWinShmList==0 ){
int i;
if( p->mutex ){ sqlite3_mutex_free(p->mutex); }
for(i=0; i<p->nRegion; i++){
@@ -51315,103 +52109,6 @@ static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){
/*
-** Convert a UTF-8 filename into whatever form the underlying
-** operating system wants filenames in. Space to hold the result
-** is obtained from malloc and must be freed by the calling
-** function
-**
-** On Cygwin, 3 possible input forms are accepted:
-** - If the filename starts with "<drive>:/" or "<drive>:\",
-** it is converted to UTF-16 as-is.
-** - If the filename contains '/', it is assumed to be a
-** Cygwin absolute path, it is converted to a win32
-** absolute path in UTF-16.
-** - Otherwise it must be a filename only, the win32 filename
-** is returned in UTF-16.
-** Note: If the function cygwin_conv_path() fails, only
-** UTF-8 -> UTF-16 conversion will be done. This can only
-** happen when the file path >32k, in which case winUtf8ToUnicode()
-** will fail too.
-*/
-static void *winConvertFromUtf8Filename(const char *zFilename){
- void *zConverted = 0;
- if( osIsNT() ){
-#ifdef __CYGWIN__
- int nChar;
- LPWSTR zWideFilename;
-
- if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename)
- && winIsDirSep(zFilename[2])) ){
- i64 nByte;
- int convertflag = CCP_POSIX_TO_WIN_W;
- if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE;
- nByte = (i64)osCygwin_conv_path(convertflag,
- zFilename, 0, 0);
- if( nByte>0 ){
- zConverted = sqlite3MallocZero(12+(u64)nByte);
- if ( zConverted==0 ){
- return zConverted;
- }
- zWideFilename = zConverted;
- /* Filenames should be prefixed, except when converted
- * full path already starts with "\\?\". */
- if( osCygwin_conv_path(convertflag, zFilename,
- zWideFilename+4, nByte)==0 ){
- if( (convertflag&CCP_RELATIVE) ){
- memmove(zWideFilename, zWideFilename+4, nByte);
- }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){
- memcpy(zWideFilename, L"\\\\?\\", 8);
- }else if( zWideFilename[6]!='?' ){
- memmove(zWideFilename+6, zWideFilename+4, nByte);
- memcpy(zWideFilename, L"\\\\?\\UNC", 14);
- }else{
- memmove(zWideFilename, zWideFilename+4, nByte);
- }
- return zConverted;
- }
- sqlite3_free(zConverted);
- }
- }
- nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
- if( nChar==0 ){
- return 0;
- }
- zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 );
- if( zWideFilename==0 ){
- return 0;
- }
- nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1,
- zWideFilename, nChar);
- if( nChar==0 ){
- sqlite3_free(zWideFilename);
- zWideFilename = 0;
- }else if( nChar>MAX_PATH
- && winIsDriveLetterAndColon(zFilename)
- && winIsDirSep(zFilename[2]) ){
- memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR));
- zWideFilename[2] = '\\';
- memcpy(zWideFilename, L"\\\\?\\", 8);
- }else if( nChar>MAX_PATH
- && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1])
- && zFilename[2] != '?' ){
- memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR));
- memcpy(zWideFilename, L"\\\\?\\UNC", 14);
- }
- zConverted = zWideFilename;
-#else
- zConverted = winUtf8ToUnicode(zFilename);
-#endif /* __CYGWIN__ */
- }
-#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32)
- else{
- zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
- }
-#endif
- /* caller will handle out of memory */
- return zConverted;
-}
-
-/*
** This function is used to open a handle on a *-shm file.
**
** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file
@@ -51506,6 +52203,60 @@ static int winHandleOpen(
return rc;
}
+/*
+** Close pDbFd's connection to shared-memory. Delete the underlying
+** *-shm file if deleteFlag is true.
+*/
+static int winCloseSharedMemory(winFile *pDbFd, int deleteFlag){
+ winShm *p; /* The connection to be closed */
+ winShm **pp; /* Iterator for pShmNode->pWinShmList */
+ winShmNode *pShmNode; /* The underlying shared-memory file */
+
+ p = pDbFd->pShm;
+ if( p==0 ) return SQLITE_OK;
+ if( p->hShm!=INVALID_HANDLE_VALUE ){
+ osCloseHandle(p->hShm);
+ }
+
+ winShmEnterMutex();
+ pShmNode = p->pShmNode;
+
+ /* Remove this connection from the winShmNode.pWinShmList list */
+ sqlite3_mutex_enter(pShmNode->mutex);
+ for(pp=&pShmNode->pWinShmList; *pp!=p; pp=&(*pp)->pWinShmNext){}
+ *pp = p->pWinShmNext;
+ sqlite3_mutex_leave(pShmNode->mutex);
+
+ winShmPurge(pDbFd->pVfs, deleteFlag);
+ winShmLeaveMutex();
+
+ /* Free the connection p */
+ sqlite3_free(p);
+ pDbFd->pShm = 0;
+ return SQLITE_OK;
+}
+
+/*
+** testfixture builds may set this global variable to true via a
+** Tcl interface. This forces the VFS to use the locking normally
+** only used for UNC paths for all files.
+*/
+#ifdef SQLITE_TEST
+SQLITE_API int sqlite3_win_test_unc_locking = 0;
+#else
+# define sqlite3_win_test_unc_locking 0
+#endif
+
+/*
+** Return true if the string passed as the only argument is likely
+** to be a UNC path. In other words, if it starts with "\\".
+*/
+static int winIsUNCPath(const char *zFile){
+ if( zFile[0]=='\\' && zFile[1]=='\\' ){
+ return 1;
+ }
+ return sqlite3_win_test_unc_locking;
+}
/*
** Open the shared-memory area associated with database file pDbFd.
@@ -51532,15 +52283,10 @@ static int winOpenSharedMemory(winFile *pDbFd){
pNew->zFilename = (char*)&pNew[1];
pNew->hSharedShm = INVALID_HANDLE_VALUE;
pNew->isUnlocked = 1;
+ pNew->bUseSharedLockHandle = winIsUNCPath(pDbFd->zPath);
sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath);
sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename);
- /* Open a file-handle on the *-shm file for this connection. This file-handle
- ** is only used for locking. The mapping of the *-shm file is created using
- ** the shared file handle in winShmNode.hSharedShm. */
- p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0);
- rc = winHandleOpen(pNew->zFilename, &p->bReadonly, &p->hShm);
-
/* Look to see if there is an existing winShmNode that can be used.
** If no matching winShmNode currently exists, then create a new one. */
winShmEnterMutex();
@@ -51561,7 +52307,7 @@ static int winOpenSharedMemory(winFile *pDbFd){
/* Open a file-handle to use for mappings, and for the DMS lock. */
if( rc==SQLITE_OK ){
HANDLE h = INVALID_HANDLE_VALUE;
- pShmNode->isReadonly = p->bReadonly;
+ pShmNode->isReadonly = sqlite3_uri_boolean(pDbFd->zPath,"readonly_shm",0);
rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h);
pShmNode->hSharedShm = h;
}
@@ -51583,20 +52329,35 @@ static int winOpenSharedMemory(winFile *pDbFd){
/* If no error has occurred, link the winShm object to the winShmNode and
** the winShm to pDbFd. */
if( rc==SQLITE_OK ){
+ sqlite3_mutex_enter(pShmNode->mutex);
p->pShmNode = pShmNode;
- pShmNode->nRef++;
+ p->pWinShmNext = pShmNode->pWinShmList;
+ pShmNode->pWinShmList = p;
#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
p->id = pShmNode->nextShmId++;
#endif
pDbFd->pShm = p;
+ sqlite3_mutex_leave(pShmNode->mutex);
}else if( p ){
- winHandleClose(p->hShm);
sqlite3_free(p);
}
assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 );
winShmLeaveMutex();
sqlite3_free(pNew);
+
+ /* Open a file-handle on the *-shm file for this connection. This file-handle
+ ** is only used for locking. The mapping of the *-shm file is created using
+ ** the shared file handle in winShmNode.hSharedShm. */
+ if( rc==SQLITE_OK && pShmNode->bUseSharedLockHandle==0 ){
+ p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0);
+ rc = winHandleOpen(pShmNode->zFilename, &p->bReadonly, &p->hShm);
+ if( rc!=SQLITE_OK ){
+ assert( p->hShm==INVALID_HANDLE_VALUE );
+ winCloseSharedMemory(pDbFd, 0);
+ }
+ }
+
return rc;
}
@@ -51608,33 +52369,7 @@ static int winShmUnmap(
sqlite3_file *fd, /* Database holding shared memory */
int deleteFlag /* Delete after closing if true */
){
- winFile *pDbFd; /* Database holding shared-memory */
- winShm *p; /* The connection to be closed */
- winShmNode *pShmNode; /* The underlying shared-memory file */
-
- pDbFd = (winFile*)fd;
- p = pDbFd->pShm;
- if( p==0 ) return SQLITE_OK;
- if( p->hShm!=INVALID_HANDLE_VALUE ){
- osCloseHandle(p->hShm);
- }
-
- pShmNode = p->pShmNode;
- winShmEnterMutex();
-
- /* If pShmNode->nRef has reached 0, then close the underlying
- ** shared-memory file, too. */
- assert( pShmNode->nRef>0 );
- pShmNode->nRef--;
- if( pShmNode->nRef==0 ){
- winShmPurge(pDbFd->pVfs, deleteFlag);
- }
- winShmLeaveMutex();
-
- /* Free the connection p */
- sqlite3_free(p);
- pDbFd->pShm = 0;
- return SQLITE_OK;
+ return winCloseSharedMemory((winFile*)fd, deleteFlag);
}
/*
@@ -51703,6 +52438,7 @@ static int winShmLock(
|| (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask))
|| (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK))
){
+ HANDLE h = p->hShm;
if( flags & SQLITE_SHM_UNLOCK ){
/* Case (a) - unlock. */
@@ -51711,7 +52447,27 @@ static int winShmLock(
assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask );
assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask );
- rc = winHandleUnlock(p->hShm, ofst+WIN_SHM_BASE, n);
+ assert( !(flags & SQLITE_SHM_SHARED) || n==1 );
+ if( pShmNode->bUseSharedLockHandle ){
+ h = pShmNode->hSharedShm;
+ if( flags & SQLITE_SHM_SHARED ){
+ winShm *pShm;
+ sqlite3_mutex_enter(pShmNode->mutex);
+ for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){
+ if( pShm!=p && (pShm->sharedMask & mask) ){
+ /* Another connection within this process is also holding this
+ ** SHARED lock. So do not actually release the OS lock. */
+ h = INVALID_HANDLE_VALUE;
+ break;
+ }
+ }
+ sqlite3_mutex_leave(pShmNode->mutex);
+ }
+ }
+
+ if( h!=INVALID_HANDLE_VALUE ){
+ rc = winHandleUnlock(h, ofst+WIN_SHM_BASE, n);
+ }
/* If successful, also clear the bits in sharedMask/exclMask */
if( rc==SQLITE_OK ){
@@ -51721,7 +52477,32 @@ static int winShmLock(
}else{
int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0);
DWORD nMs = winFileBusyTimeout(pDbFd);
- rc = winHandleLockTimeout(p->hShm, ofst+WIN_SHM_BASE, n, bExcl, nMs);
+
+ if( pShmNode->bUseSharedLockHandle ){
+ winShm *pShm;
+ h = pShmNode->hSharedShm;
+ sqlite3_mutex_enter(pShmNode->mutex);
+ for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){
+ if( bExcl ){
+ if( (pShm->sharedMask|pShm->exclMask) & mask ){
+ rc = SQLITE_BUSY;
+ h = INVALID_HANDLE_VALUE;
+ }
+ }else{
+ if( pShm->sharedMask & mask ){
+ h = INVALID_HANDLE_VALUE;
+ }else if( pShm->exclMask & mask ){
+ rc = SQLITE_BUSY;
+ h = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ sqlite3_mutex_leave(pShmNode->mutex);
+ }
+
+ if( h!=INVALID_HANDLE_VALUE ){
+ rc = winHandleLockTimeout(h, ofst+WIN_SHM_BASE, n, bExcl, nMs);
+ }
if( rc==SQLITE_OK ){
if( bExcl ){
p->exclMask = (p->exclMask | mask);
@@ -54860,6 +55641,7 @@ struct Bitvec {
} u;
};
+
/*
** Create a new bitmap object able to handle bits between 0 and iSize,
** inclusive. Return a pointer to the new object. Return NULL if
@@ -55048,6 +55830,52 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
return p->iSize;
}
+#ifdef SQLITE_DEBUG
+/*
+** Show the content of a Bitvec option and its children. Indent
+** everything by n spaces. Add x to each bitvec value.
+**
+** From a debugger such as gdb, one can type:
+**
+** call sqlite3ShowBitvec(p)
+**
+** For some Bitvec p and see a recursive view of the Bitvec's content.
+*/
+static void showBitvec(Bitvec *p, int n, unsigned x){
+ int i;
+ if( p==0 ){
+ printf("NULL\n");
+ return;
+ }
+ printf("Bitvec 0x%p iSize=%u", p, p->iSize);
+ if( p->iSize<=BITVEC_NBIT ){
+ printf(" bitmap\n");
+ printf("%*s bits:", n, "");
+ for(i=1; i<=BITVEC_NBIT; i++){
+ if( sqlite3BitvecTest(p,i) ) printf(" %u", x+(unsigned)i);
+ }
+ printf("\n");
+ }else if( p->iDivisor==0 ){
+ printf(" hash with %u entries\n", p->nSet);
+ printf("%*s bits:", n, "");
+ for(i=0; i<BITVEC_NINT; i++){
+ if( p->u.aHash[i] ) printf(" %u", x+(unsigned)p->u.aHash[i]);
+ }
+ printf("\n");
+ }else{
+ printf(" sub-bitvec with iDivisor=%u\n", p->iDivisor);
+ for(i=0; i<BITVEC_NPTR; i++){
+ if( p->u.apSub[i]==0 ) continue;
+ printf("%*s apSub[%d]=", n, "", i);
+ showBitvec(p->u.apSub[i], n+4, i*p->iDivisor);
+ }
+ }
+}
+SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec *p){
+ showBitvec(p, 0, 0);
+}
+#endif
+
#ifndef SQLITE_UNTESTABLE
/*
** Let V[] be an array of unsigned characters sufficient to hold
@@ -55059,6 +55887,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
#define CLEARBIT(V,I) V[I>>3] &= ~(BITVEC_TELEM)(1<<(I&7))
#define TESTBIT(V,I) (V[I>>3]&(1<<(I&7)))!=0
+
/*
** This routine runs an extensive test of the Bitvec code.
**
@@ -55067,7 +55896,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
** by 0, 1, or 3 operands, depending on the opcode. Another
** opcode follows immediately after the last operand.
**
-** There are 6 opcodes numbered from 0 through 5. 0 is the
+** There are opcodes numbered starting with 0. 0 is the
** "halt" opcode and causes the test to end.
**
** 0 Halt and return the number of errors
@@ -55076,18 +55905,25 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
** 3 N Set N randomly chosen bits
** 4 N Clear N randomly chosen bits
** 5 N S X Set N bits from S increment X in array only, not in bitvec
+** 6 Invoice sqlite3ShowBitvec() on the Bitvec object so far
+** 7 X Show compile-time parameters and the hash of X
**
** The opcodes 1 through 4 perform set and clear operations are performed
** on both a Bitvec object and on a linear array of bits obtained from malloc.
** Opcode 5 works on the linear array only, not on the Bitvec.
** Opcode 5 is used to deliberately induce a fault in order to
-** confirm that error detection works.
+** confirm that error detection works. Opcodes 6 and greater are
+** state output opcodes. Opcodes 6 and greater are no-ops unless
+** SQLite has been compiled with SQLITE_DEBUG.
**
** At the conclusion of the test the linear array is compared
** against the Bitvec object. If there are any differences,
** an error is returned. If they are the same, zero is returned.
**
** If a memory allocation error occurs, return -1.
+**
+** sz is the size of the Bitvec. Or if sz is negative, make the size
+** 2*(unsigned)(-sz) and disabled the linear vector check.
*/
SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
Bitvec *pBitvec = 0;
@@ -55098,10 +55934,15 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
/* Allocate the Bitvec to be tested and a linear array of
** bits to act as the reference */
- pBitvec = sqlite3BitvecCreate( sz );
- pV = sqlite3MallocZero( (7+(i64)sz)/8 + 1 );
+ if( sz<=0 ){
+ pBitvec = sqlite3BitvecCreate( 2*(unsigned)(-sz) );
+ pV = 0;
+ }else{
+ pBitvec = sqlite3BitvecCreate( sz );
+ pV = sqlite3MallocZero( (7+(i64)sz)/8 + 1 );
+ }
pTmpSpace = sqlite3_malloc64(BITVEC_SZ);
- if( pBitvec==0 || pV==0 || pTmpSpace==0 ) goto bitvec_end;
+ if( pBitvec==0 || pTmpSpace==0 || (pV==0 && sz>0) ) goto bitvec_end;
/* NULL pBitvec tests */
sqlite3BitvecSet(0, 1);
@@ -55110,6 +55951,24 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
/* Run the program */
pc = i = 0;
while( (op = aOp[pc])!=0 ){
+ if( op>=6 ){
+#ifdef SQLITE_DEBUG
+ if( op==6 ){
+ sqlite3ShowBitvec(pBitvec);
+ }else if( op==7 ){
+ printf("BITVEC_SZ = %d (%d by sizeof)\n",
+ BITVEC_SZ, (int)sizeof(Bitvec));
+ printf("BITVEC_USIZE = %d\n", (int)BITVEC_USIZE);
+ printf("BITVEC_NELEM = %d\n", (int)BITVEC_NELEM);
+ printf("BITVEC_NBIT = %d\n", (int)BITVEC_NBIT);
+ printf("BITVEC_NINT = %d\n", (int)BITVEC_NINT);
+ printf("BITVEC_MXHASH = %d\n", (int)BITVEC_MXHASH);
+ printf("BITVEC_NPTR = %d\n", (int)BITVEC_NPTR);
+ }
+#endif
+ pc++;
+ continue;
+ }
switch( op ){
case 1:
case 2:
@@ -55131,12 +55990,12 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
pc += nx;
i = (i & 0x7fffffff)%sz;
if( (op & 1)!=0 ){
- SETBIT(pV, (i+1));
+ if( pV ) SETBIT(pV, (i+1));
if( op!=5 ){
if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end;
}
}else{
- CLEARBIT(pV, (i+1));
+ if( pV ) CLEARBIT(pV, (i+1));
sqlite3BitvecClear(pBitvec, i+1, pTmpSpace);
}
}
@@ -55146,14 +56005,18 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
** match (rc==0). Change rc to non-zero if a discrepancy
** is found.
*/
- rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1)
- + sqlite3BitvecTest(pBitvec, 0)
- + (sqlite3BitvecSize(pBitvec) - sz);
- for(i=1; i<=sz; i++){
- if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){
- rc = i;
- break;
+ if( pV ){
+ rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1)
+ + sqlite3BitvecTest(pBitvec, 0)
+ + (sqlite3BitvecSize(pBitvec) - sz);
+ for(i=1; i<=sz; i++){
+ if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){
+ rc = i;
+ break;
+ }
}
+ }else{
+ rc = 0;
}
/* Free allocated structure */
@@ -59914,7 +60777,7 @@ static void pager_unlock(Pager *pPager){
** have sqlite3WalEndReadTransaction() drop the write-lock, as it once
** did, because this would break "BEGIN EXCLUSIVE" handling for
** SQLITE_ENABLE_SETLK_TIMEOUT builds. */
- sqlite3WalEndWriteTransaction(pPager->pWal);
+ (void)sqlite3WalEndWriteTransaction(pPager->pWal);
}
sqlite3WalEndReadTransaction(pPager->pWal);
pPager->eState = PAGER_OPEN;
@@ -61670,14 +62533,27 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags(
unsigned pgFlags /* Various flags */
){
unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK;
- if( pPager->tempFile ){
+ if( pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF ){
pPager->noSync = 1;
pPager->fullSync = 0;
pPager->extraSync = 0;
}else{
- pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0;
+ pPager->noSync = 0;
pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0;
- pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0;
+
+ /* Set Pager.extraSync if "PRAGMA synchronous=EXTRA" is requested, or
+ ** if the file-system supports F2FS style atomic writes. If this flag
+ ** is set, SQLite syncs the directory to disk immediately after deleting
+ ** a journal file in "PRAGMA journal_mode=DELETE" mode. */
+ if( level==PAGER_SYNCHRONOUS_EXTRA
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_BATCH_ATOMIC)
+#endif
+ ){
+ pPager->extraSync = 1;
+ }else{
+ pPager->extraSync = 0;
+ }
}
if( pPager->noSync ){
pPager->syncFlags = 0;
@@ -65570,7 +66446,7 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint(
}
if( pPager->pWal ){
rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode,
- (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler),
+ (eMode<=SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler),
pPager->pBusyHandlerArg,
pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
pnLog, pnCkpt
@@ -66480,7 +67356,7 @@ struct WalIterator {
/* Size (in bytes) of a WalIterator object suitable for N or fewer segments */
#define SZ_WALITERATOR(N) \
- (offsetof(WalIterator,aSegment)*(N)*sizeof(struct WalSegment))
+ (offsetof(WalIterator,aSegment)+(N)*sizeof(struct WalSegment))
/*
** Define the parameters of the hash tables in the wal-index file. There
@@ -69366,7 +70242,7 @@ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){
assert( pWal->writeLock==0 || pWal->readLock<0 );
#endif
if( pWal->readLock>=0 ){
- sqlite3WalEndWriteTransaction(pWal);
+ (void)sqlite3WalEndWriteTransaction(pWal);
walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock));
pWal->readLock = -1;
}
@@ -70175,7 +71051,8 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
/* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
** in the SQLITE_CHECKPOINT_PASSIVE mode. */
- assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
+ assert( SQLITE_CHECKPOINT_NOOP<SQLITE_CHECKPOINT_PASSIVE );
+ assert( eMode>SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
if( pWal->readOnly ) return SQLITE_READONLY;
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
@@ -70192,31 +71069,35 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
** it will not be invoked in this case.
*/
- rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
- testcase( rc==SQLITE_BUSY );
- testcase( rc!=SQLITE_OK && xBusy2!=0 );
- if( rc==SQLITE_OK ){
- pWal->ckptLock = 1;
+ if( eMode!=SQLITE_CHECKPOINT_NOOP ){
+ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ testcase( rc==SQLITE_BUSY );
+ testcase( rc!=SQLITE_OK && xBusy2!=0 );
+ if( rc==SQLITE_OK ){
+ pWal->ckptLock = 1;
- /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
- ** TRUNCATE modes also obtain the exclusive "writer" lock on the database
- ** file.
- **
- ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
- ** immediately, and a busy-handler is configured, it is invoked and the
- ** writer lock retried until either the busy-handler returns 0 or the
- ** lock is successfully obtained.
- */
- if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
- rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1);
- if( rc==SQLITE_OK ){
- pWal->writeLock = 1;
- }else if( rc==SQLITE_BUSY ){
- eMode2 = SQLITE_CHECKPOINT_PASSIVE;
- xBusy2 = 0;
- rc = SQLITE_OK;
+ /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART
+ ** and TRUNCATE modes also obtain the exclusive "writer" lock on the
+ ** database file.
+ **
+ ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
+ ** immediately, and a busy-handler is configured, it is invoked and the
+ ** writer lock retried until either the busy-handler returns 0 or the
+ ** lock is successfully obtained.
+ */
+ if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
+ rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1);
+ if( rc==SQLITE_OK ){
+ pWal->writeLock = 1;
+ }else if( rc==SQLITE_BUSY ){
+ eMode2 = SQLITE_CHECKPOINT_PASSIVE;
+ xBusy2 = 0;
+ rc = SQLITE_OK;
+ }
}
}
+ }else{
+ rc = SQLITE_OK;
}
@@ -70230,7 +71111,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
** immediately and do a partial checkpoint if it cannot obtain it. */
walDisableBlocking(pWal);
rc = walIndexReadHdr(pWal, &isChanged);
- if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal);
+ if( eMode2>SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal);
if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){
sqlite3OsUnfetch(pWal->pDbFd, 0, 0);
}
@@ -70240,7 +71121,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
if( rc==SQLITE_OK ){
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
- }else{
+ }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){
rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf);
}
@@ -70268,7 +71149,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
sqlite3WalDb(pWal, 0);
/* Release the locks. */
- sqlite3WalEndWriteTransaction(pWal);
+ (void)sqlite3WalEndWriteTransaction(pWal);
if( pWal->ckptLock ){
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
pWal->ckptLock = 0;
@@ -72425,7 +73306,7 @@ static int btreeMoveto(
assert( nKey==(i64)(int)nKey );
pIdxKey = sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT;
- sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey);
+ sqlite3VdbeRecordUnpack((int)nKey, pKey, pIdxKey);
if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){
rc = SQLITE_CORRUPT_BKPT;
}else{
@@ -73482,10 +74363,10 @@ static int freeSpace(MemPage *pPage, int iStart, int iSize){
assert( pPage->pBt!=0 );
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize );
- assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize );
+ assert( CORRUPT_DB || iEnd <= (int)pPage->pBt->usableSize );
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
assert( iSize>=4 ); /* Minimum cell size is 4 */
- assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 );
+ assert( CORRUPT_DB || iStart<=(int)pPage->pBt->usableSize-4 );
/* The list of freeblocks must be in ascending order. Find the
** spot on the list where iStart should be inserted.
@@ -74409,6 +75290,7 @@ static int removeFromSharingList(BtShared *pBt){
sqlite3_mutex_leave(pMainMtx);
return removed;
#else
+ UNUSED_PARAMETER( pBt );
return 1;
#endif
}
@@ -74626,6 +75508,10 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve,
sqlite3BtreeEnter(p);
pBt->nReserveWanted = (u8)nReserve;
x = pBt->pageSize - pBt->usableSize;
+ if( x==nReserve && (pageSize==0 || (u32)pageSize==pBt->pageSize) ){
+ sqlite3BtreeLeave(p);
+ return SQLITE_OK;
+ }
if( nReserve<x ) nReserve = x;
if( pBt->btsFlags & BTS_PAGESIZE_FIXED ){
sqlite3BtreeLeave(p);
@@ -77215,6 +78101,30 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
return rc;
}
+/* Set *pRes to 1 (true) if the BTree pointed to by cursor pCur contains zero
+** rows of content. Set *pRes to 0 (false) if the table contains content.
+** Return SQLITE_OK on success or some error code (ex: SQLITE_NOMEM) if
+** something goes wrong.
+*/
+SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){
+ int rc;
+
+ assert( cursorOwnsBtShared(pCur) );
+ assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ if( pCur->eState==CURSOR_VALID ){
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ rc = moveToRoot(pCur);
+ if( rc==SQLITE_EMPTY ){
+ *pRes = 1;
+ rc = SQLITE_OK;
+ }else{
+ *pRes = 0;
+ }
+ return rc;
+}
+
#ifdef SQLITE_DEBUG
/* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that
** this flags are true for a consistent database.
@@ -77434,8 +78344,8 @@ moveto_table_finish:
}
/*
-** Compare the "idx"-th cell on the page the cursor pCur is currently
-** pointing to to pIdxKey using xRecordCompare. Return negative or
+** Compare the "idx"-th cell on the page pPage against the key
+** pointing to by pIdxKey using xRecordCompare. Return negative or
** zero if the cell is less than or equal pIdxKey. Return positive
** if unknown.
**
@@ -77450,12 +78360,11 @@ moveto_table_finish:
** a positive value as that will cause the optimization to be skipped.
*/
static int indexCellCompare(
- BtCursor *pCur,
+ MemPage *pPage,
int idx,
UnpackedRecord *pIdxKey,
RecordCompare xRecordCompare
){
- MemPage *pPage = pCur->pPage;
int c;
int nCell; /* Size of the pCell cell in bytes */
u8 *pCell = findCellPastPtr(pPage, idx);
@@ -77564,14 +78473,14 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto(
){
int c;
if( pCur->ix==pCur->pPage->nCell-1
- && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0
+ && (c = indexCellCompare(pCur->pPage,pCur->ix,pIdxKey,xRecordCompare))<=0
&& pIdxKey->errCode==SQLITE_OK
){
*pRes = c;
return SQLITE_OK; /* Cursor already pointing at the correct spot */
}
if( pCur->iPage>0
- && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0
+ && indexCellCompare(pCur->pPage, 0, pIdxKey, xRecordCompare)<=0
&& pIdxKey->errCode==SQLITE_OK
){
pCur->curFlags &= ~(BTCF_ValidOvfl|BTCF_AtLast);
@@ -77788,7 +78697,7 @@ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){
n = pCur->pPage->nCell;
for(i=0; i<pCur->iPage; i++){
- n *= pCur->apPage[i]->nCell;
+ n *= pCur->apPage[i]->nCell+1;
}
return n;
}
@@ -80245,7 +81154,12 @@ static int balance_nonroot(
** of the right-most new sibling page is set to the value that was
** originally in the same field of the right-most old sibling page. */
if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){
- MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1];
+ MemPage *pOld;
+ if( nNew>nOld ){
+ pOld = apNew[nOld-1];
+ }else{
+ pOld = apOld[nOld-1];
+ }
memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4);
}
@@ -82877,6 +83791,7 @@ SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void
*/
SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){
int rc;
+ UNUSED_PARAMETER(p); /* only used in DEBUG builds */
assert( sqlite3_mutex_held(p->db->mutex) );
sqlite3BtreeEnter(p);
rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK);
@@ -85062,6 +85977,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){
return SQLITE_NOMEM_BKPT;
}
+ assert( pMem->z!=0 );
memcpy(pMem->z, z, nAlloc);
}else{
sqlite3VdbeMemRelease(pMem);
@@ -88889,10 +89805,12 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt))
|| nTrans<=1
){
- for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
- Btree *pBt = db->aDb[i].pBt;
- if( pBt ){
- rc = sqlite3BtreeCommitPhaseOne(pBt, 0);
+ if( needXcommit ){
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( sqlite3BtreeTxnState(pBt)>=SQLITE_TXN_WRITE ){
+ rc = sqlite3BtreeCommitPhaseOne(pBt, 0);
+ }
}
}
@@ -88903,7 +89821,9 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( pBt ){
+ int txn = sqlite3BtreeTxnState(pBt);
+ if( txn!=SQLITE_TXN_NONE ){
+ assert( needXcommit || txn==SQLITE_TXN_READ );
rc = sqlite3BtreeCommitPhaseTwo(pBt, 0);
}
}
@@ -89158,28 +90078,31 @@ SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
/*
-** This function is called when a transaction opened by the database
+** These functions are called when a transaction opened by the database
** handle associated with the VM passed as an argument is about to be
-** committed. If there are outstanding deferred foreign key constraint
-** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK.
+** committed. If there are outstanding foreign key constraint violations
+** return an error code. Otherwise, SQLITE_OK.
**
** If there are outstanding FK violations and this function returns
-** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY
-** and write an error message to it. Then return SQLITE_ERROR.
+** non-zero, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY
+** and write an error message to it.
*/
#ifndef SQLITE_OMIT_FOREIGN_KEY
-SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){
+static SQLITE_NOINLINE int vdbeFkError(Vdbe *p){
+ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY;
+ p->errorAction = OE_Abort;
+ sqlite3VdbeError(p, "FOREIGN KEY constraint failed");
+ if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR;
+ return SQLITE_CONSTRAINT_FOREIGNKEY;
+}
+SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe *p){
+ if( p->nFkConstraint==0 ) return SQLITE_OK;
+ return vdbeFkError(p);
+}
+SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe *p){
sqlite3 *db = p->db;
- if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0)
- || (!deferred && p->nFkConstraint>0)
- ){
- p->rc = SQLITE_CONSTRAINT_FOREIGNKEY;
- p->errorAction = OE_Abort;
- sqlite3VdbeError(p, "FOREIGN KEY constraint failed");
- if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR;
- return SQLITE_CONSTRAINT_FOREIGNKEY;
- }
- return SQLITE_OK;
+ if( (db->nDeferredCons+db->nDeferredImmCons)==0 ) return SQLITE_OK;
+ return vdbeFkError(p);
}
#endif
@@ -89273,7 +90196,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
/* Check for immediate foreign key violations. */
if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
- (void)sqlite3VdbeCheckFk(p, 0);
+ (void)sqlite3VdbeCheckFkImmediate(p);
}
/* If the auto-commit flag is set and this is the only active writer
@@ -89287,7 +90210,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
&& db->nVdbeWrite==(p->readOnly==0)
){
if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
- rc = sqlite3VdbeCheckFk(p, 1);
+ rc = sqlite3VdbeCheckFkDeferred(p);
if( rc!=SQLITE_OK ){
if( NEVER(p->readOnly) ){
sqlite3VdbeLeave(p);
@@ -90097,30 +91020,22 @@ SQLITE_PRIVATE void sqlite3VdbeSerialGet(
return;
}
/*
-** This routine is used to allocate sufficient space for an UnpackedRecord
-** structure large enough to be used with sqlite3VdbeRecordUnpack() if
-** the first argument is a pointer to KeyInfo structure pKeyInfo.
-**
-** The space is either allocated using sqlite3DbMallocRaw() or from within
-** the unaligned buffer passed via the second and third arguments (presumably
-** stack space). If the former, then *ppFree is set to a pointer that should
-** be eventually freed by the caller using sqlite3DbFree(). Or, if the
-** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL
-** before returning.
+** Allocate sufficient space for an UnpackedRecord structure large enough
+** to hold a decoded index record for pKeyInfo.
**
-** If an OOM error occurs, NULL is returned.
+** The space is allocated using sqlite3DbMallocRaw(). If an OOM error
+** occurs, NULL is returned.
*/
SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(
KeyInfo *pKeyInfo /* Description of the record */
){
UnpackedRecord *p; /* Unpacked record to return */
- int nByte; /* Number of bytes required for *p */
+ u64 nByte; /* Number of bytes required for *p */
assert( sizeof(UnpackedRecord) + sizeof(Mem)*65536 < 0x7fffffff );
nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1);
p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte);
if( !p ) return 0;
p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))];
- assert( pKeyInfo->aSortFlags!=0 );
p->pKeyInfo = pKeyInfo;
p->nField = pKeyInfo->nKeyField + 1;
return p;
@@ -90132,7 +91047,6 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(
** contents of the decoded record.
*/
SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(
- KeyInfo *pKeyInfo, /* Information about the record format */
int nKey, /* Size of the binary record */
const void *pKey, /* The binary record */
UnpackedRecord *p /* Populate this structure before returning. */
@@ -90143,6 +91057,7 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(
u16 u; /* Unsigned loop counter */
u32 szHdr;
Mem *pMem = p->aMem;
+ KeyInfo *pKeyInfo = p->pKeyInfo;
p->default_rc = 0;
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
@@ -90160,16 +91075,18 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(
pMem->z = 0;
sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
d += sqlite3VdbeSerialTypeLen(serial_type);
- pMem++;
if( (++u)>=p->nField ) break;
+ pMem++;
}
if( d>(u32)nKey && u ){
assert( CORRUPT_DB );
/* In a corrupt record entry, the last pMem might have been set up using
** uninitialized memory. Overwrite its value with NULL, to prevent
** warnings from MSAN. */
- sqlite3VdbeMemSetNull(pMem-1);
+ sqlite3VdbeMemSetNull(pMem-(u<p->nField));
}
+ testcase( u == pKeyInfo->nKeyField + 1 );
+ testcase( u < pKeyInfo->nKeyField + 1 );
assert( u<=pKeyInfo->nKeyField + 1 );
p->nField = u;
}
@@ -90337,6 +91254,32 @@ static void vdbeAssertFieldCountWithinLimits(
** or positive value if *pMem1 is less than, equal to or greater than
** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);".
*/
+static SQLITE_NOINLINE int vdbeCompareMemStringWithEncodingChange(
+ const Mem *pMem1,
+ const Mem *pMem2,
+ const CollSeq *pColl,
+ u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */
+){
+ int rc;
+ const void *v1, *v2;
+ Mem c1;
+ Mem c2;
+ sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null);
+ sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null);
+ sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem);
+ sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem);
+ v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc);
+ v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc);
+ if( (v1==0 || v2==0) ){
+ if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT;
+ rc = 0;
+ }else{
+ rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2);
+ }
+ sqlite3VdbeMemReleaseMalloc(&c1);
+ sqlite3VdbeMemReleaseMalloc(&c2);
+ return rc;
+}
static int vdbeCompareMemString(
const Mem *pMem1,
const Mem *pMem2,
@@ -90348,25 +91291,7 @@ static int vdbeCompareMemString(
** comparison function directly */
return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z);
}else{
- int rc;
- const void *v1, *v2;
- Mem c1;
- Mem c2;
- sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null);
- sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null);
- sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem);
- sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem);
- v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc);
- v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc);
- if( (v1==0 || v2==0) ){
- if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT;
- rc = 0;
- }else{
- rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2);
- }
- sqlite3VdbeMemReleaseMalloc(&c1);
- sqlite3VdbeMemReleaseMalloc(&c2);
- return rc;
+ return vdbeCompareMemStringWithEncodingChange(pMem1,pMem2,pColl,prcErr);
}
}
@@ -91029,6 +91954,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
** The easiest way to enforce this limit is to consider only records with
** 13 fields or less. If the first field is an integer, the maximum legal
** header size is (12*5 + 1 + 1) bytes. */
+ assert( p->pKeyInfo->aSortFlags!=0 );
if( p->pKeyInfo->nAllField<=13 ){
int flags = p->aMem[0].flags;
if( p->pKeyInfo->aSortFlags[0] ){
@@ -91278,6 +92204,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
}
}
+#ifndef SQLITE_OMIT_DATETIME_FUNCS
/*
** Cause a function to throw an error if it was call from OP_PureFunc
** rather than OP_Function.
@@ -91311,6 +92238,7 @@ SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context *pCtx){
}
return 1;
}
+#endif /* SQLITE_OMIT_DATETIME_FUNCS */
#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG)
/*
@@ -91387,7 +92315,6 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
i64 iKey2;
PreUpdate preupdate;
const char *zTbl = pTab->zName;
- static const u8 fakeSortOrder = 0;
#ifdef SQLITE_DEBUG
int nRealCol;
if( pTab->tabFlags & TF_WithoutRowid ){
@@ -91422,11 +92349,11 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
preupdate.pCsr = pCsr;
preupdate.op = op;
preupdate.iNewReg = iReg;
- preupdate.pKeyinfo = (KeyInfo*)&preupdate.keyinfoSpace;
+ preupdate.pKeyinfo = (KeyInfo*)&preupdate.uKey;
preupdate.pKeyinfo->db = db;
preupdate.pKeyinfo->enc = ENC(db);
preupdate.pKeyinfo->nKeyField = pTab->nCol;
- preupdate.pKeyinfo->aSortFlags = (u8*)&fakeSortOrder;
+ preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */
preupdate.iKey1 = iKey1;
preupdate.iKey2 = iKey2;
preupdate.pTab = pTab;
@@ -91456,6 +92383,17 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+#ifdef SQLITE_ENABLE_PERCENTILE
+/*
+** Return the name of an SQL function associated with the sqlite3_context.
+*/
+SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){
+ assert( pCtx!=0 );
+ assert( pCtx->pFunc!=0 );
+ return pCtx->pFunc->zName;
+}
+#endif /* SQLITE_ENABLE_PERCENTILE */
+
/************** End of vdbeaux.c *********************************************/
/************** Begin file vdbeapi.c *****************************************/
/*
@@ -93153,8 +94091,12 @@ static int bindText(
if( zData!=0 ){
pVar = &p->aVar[i-1];
rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel);
- if( rc==SQLITE_OK && encoding!=0 ){
- rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db));
+ if( rc==SQLITE_OK ){
+ if( encoding==0 ){
+ pVar->enc = ENC(p->db);
+ }else{
+ rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db));
+ }
}
if( rc ){
sqlite3Error(p->db, rc);
@@ -93623,7 +94565,7 @@ static UnpackedRecord *vdbeUnpackRecord(
pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
if( pRet ){
memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nKeyField+1));
- sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
+ sqlite3VdbeRecordUnpack(nKey, pKey, pRet);
}
return pRet;
}
@@ -93652,6 +94594,9 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa
}
if( p->pPk ){
iStore = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ }else if( iIdx >= p->pTab->nCol ){
+ rc = SQLITE_MISUSE_BKPT;
+ goto preupdate_old_out;
}else{
iStore = sqlite3TableColumnToStorage(p->pTab, iIdx);
}
@@ -93807,6 +94752,8 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
}
if( p->pPk && p->op!=SQLITE_UPDATE ){
iStore = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ }else if( iIdx >= p->pTab->nCol ){
+ return SQLITE_MISUSE_BKPT;
}else{
iStore = sqlite3TableColumnToStorage(p->pTab, iIdx);
}
@@ -94082,10 +95029,10 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
** a host parameter. If the text contains no host parameters, return
** the total number of bytes in the text.
*/
-static int findNextHostParameter(const char *zSql, int *pnToken){
+static i64 findNextHostParameter(const char *zSql, i64 *pnToken){
int tokenType;
- int nTotal = 0;
- int n;
+ i64 nTotal = 0;
+ i64 n;
*pnToken = 0;
while( zSql[0] ){
@@ -94132,8 +95079,8 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
sqlite3 *db; /* The database connection */
int idx = 0; /* Index of a host parameter */
int nextIndex = 1; /* Index of next ? host parameter */
- int n; /* Length of a token prefix */
- int nToken; /* Length of the parameter token */
+ i64 n; /* Length of a token prefix */
+ i64 nToken; /* Length of the parameter token */
int i; /* Loop counter */
Mem *pVar; /* Value of a host parameter */
StrAccum out; /* Accumulate the output here */
@@ -95057,7 +96004,7 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){
static SQLITE_NOINLINE int vdbeColumnFromOverflow(
VdbeCursor *pC, /* The BTree cursor from which we are reading */
int iCol, /* The column to read */
- int t, /* The serial-type code for the column value */
+ u32 t, /* The serial-type code for the column value */
i64 iOffset, /* Offset to the start of the content value */
u32 cacheStatus, /* Current Vdbe.cacheCtr value */
u32 colCacheCtr, /* Current value of the column cache counter */
@@ -95132,6 +96079,36 @@ static SQLITE_NOINLINE int vdbeColumnFromOverflow(
return rc;
}
+/*
+** Send a "statement aborts" message to the error log.
+*/
+static SQLITE_NOINLINE void sqlite3VdbeLogAbort(
+ Vdbe *p, /* The statement that is running at the time of failure */
+ int rc, /* Error code */
+ Op *pOp, /* Opcode that filed */
+ Op *aOp /* All opcodes */
+){
+ const char *zSql = p->zSql; /* Original SQL text */
+ const char *zPrefix = ""; /* Prefix added to SQL text */
+ int pc; /* Opcode address */
+ char zXtra[100]; /* Buffer space to store zPrefix */
+
+ if( p->pFrame ){
+ assert( aOp[0].opcode==OP_Init );
+ if( aOp[0].p4.z!=0 ){
+ assert( aOp[0].p4.z[0]=='-'
+ && aOp[0].p4.z[1]=='-'
+ && aOp[0].p4.z[2]==' ' );
+ sqlite3_snprintf(sizeof(zXtra), zXtra,"/* %s */ ",aOp[0].p4.z+3);
+ zPrefix = zXtra;
+ }else{
+ zPrefix = "/* unknown trigger */ ";
+ }
+ }
+ pc = (int)(pOp - aOp);
+ sqlite3_log(rc, "statement aborts at %d: %s; [%s%s]",
+ pc, p->zErrMsg, zPrefix, zSql);
+}
/*
** Return the symbolic name for the data type of a pMem
@@ -95657,8 +96634,7 @@ case OP_Halt: {
}else{
sqlite3VdbeError(p, "%s", pOp->p4.z);
}
- pcx = (int)(pOp - aOp);
- sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql);
+ sqlite3VdbeLogAbort(p, pOp->p1, pOp, aOp);
}
rc = sqlite3VdbeHalt(p);
assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR );
@@ -96037,7 +97013,7 @@ case OP_IntCopy: { /* out2 */
** RETURNING clause.
*/
case OP_FkCheck: {
- if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){
+ if( (rc = sqlite3VdbeCheckFkImmediate(p))!=SQLITE_OK ){
goto abort_due_to_error;
}
break;
@@ -96129,10 +97105,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem;
flags2 = pIn2->flags & ~MEM_Str;
}
- nByte = pIn1->n + pIn2->n;
+ nByte = pIn1->n;
+ nByte += pIn2->n;
if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
}
+#if SQLITE_MAX_LENGTH>2147483645
+ if( nByte>2147483645 ){ goto too_big; }
+#endif
if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){
goto no_mem;
}
@@ -96816,6 +97796,7 @@ case OP_Compare: {
pKeyInfo = pOp->p4.pKeyInfo;
assert( n>0 );
assert( pKeyInfo!=0 );
+ assert( pKeyInfo->aSortFlags!=0 );
p1 = pOp->p1;
p2 = pOp->p2;
#ifdef SQLITE_DEBUG
@@ -97578,6 +98559,15 @@ op_column_corrupt:
** Take the affinities from the Table object in P4. If any value
** cannot be coerced into the correct type, then raise an error.
**
+** If P3==0, then omit checking of VIRTUAL columns.
+**
+** If P3==1, then omit checking of all generated column, both VIRTUAL
+** and STORED.
+**
+** If P3>=2, then only check column number P3-2 in the table (which will
+** be a VIRTUAL column) against the value in reg[P1]. In this case,
+** P2 will be 1.
+**
** This opcode is similar to OP_Affinity except that this opcode
** forces the register type to the Table column type. This is used
** to implement "strict affinity".
@@ -97591,8 +98581,8 @@ op_column_corrupt:
**
** <ul>
** <li> P2 should be the number of non-virtual columns in the
-** table of P4.
-** <li> Table P4 should be a STRICT table.
+** table of P4 unless P3>1, in which case P2 will be 1.
+** <li> Table P4 is a STRICT table.
** </ul>
**
** If any precondition is false, an assertion fault occurs.
@@ -97601,16 +98591,28 @@ case OP_TypeCheck: {
Table *pTab;
Column *aCol;
int i;
+ int nCol;
assert( pOp->p4type==P4_TABLE );
pTab = pOp->p4.pTab;
assert( pTab->tabFlags & TF_Strict );
- assert( pTab->nNVCol==pOp->p2 );
+ assert( pOp->p3>=0 && pOp->p3<pTab->nCol+2 );
aCol = pTab->aCol;
pIn1 = &aMem[pOp->p1];
- for(i=0; i<pTab->nCol; i++){
- if( aCol[i].colFlags & COLFLAG_GENERATED ){
- if( aCol[i].colFlags & COLFLAG_VIRTUAL ) continue;
+ if( pOp->p3<2 ){
+ assert( pTab->nNVCol==pOp->p2 );
+ i = 0;
+ nCol = pTab->nCol;
+ }else{
+ i = pOp->p3-2;
+ nCol = i+1;
+ assert( i<pTab->nCol );
+ assert( aCol[i].colFlags & COLFLAG_VIRTUAL );
+ assert( pOp->p2==1 );
+ }
+ for(; i<nCol; i++){
+ if( (aCol[i].colFlags & COLFLAG_GENERATED)!=0 && pOp->p3<2 ){
+ if( (aCol[i].colFlags & COLFLAG_VIRTUAL)!=0 ) continue;
if( pOp->p3 ){ pIn1++; continue; }
}
assert( pIn1 < &aMem[pOp->p1+pOp->p2] );
@@ -97932,7 +98934,7 @@ case OP_MakeRecord: {
len = (u32)pRec->n;
serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0);
if( pRec->flags & MEM_Zero ){
- serial_type += pRec->u.nZero*2;
+ serial_type += (u32)pRec->u.nZero*2;
if( nData ){
if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem;
len += pRec->u.nZero;
@@ -98199,7 +99201,7 @@ case OP_Savepoint: {
*/
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
if( isTransaction && p1==SAVEPOINT_RELEASE ){
- if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
+ if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){
goto vdbe_return;
}
db->autoCommit = 1;
@@ -98317,7 +99319,7 @@ case OP_AutoCommit: {
"SQL statements in progress");
rc = SQLITE_BUSY;
goto abort_due_to_error;
- }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
+ }else if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){
goto vdbe_return;
}else{
db->autoCommit = (u8)desiredAutoCommit;
@@ -99689,7 +100691,7 @@ case OP_Found: { /* jump, in3, ncycle */
if( rc ) goto no_mem;
pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
if( pIdxKey==0 ) goto no_mem;
- sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey);
+ sqlite3VdbeRecordUnpack(r.aMem->n, r.aMem->z, pIdxKey);
pIdxKey->default_rc = 0;
rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult);
sqlite3DbFreeNN(db, pIdxKey);
@@ -100687,6 +101689,32 @@ case OP_Rewind: { /* jump0, ncycle */
break;
}
+/* Opcode: IfEmpty P1 P2 * * *
+** Synopsis: if( empty(P1) ) goto P2
+**
+** Check to see if the b-tree table that cursor P1 references is empty
+** and jump to P2 if it is.
+*/
+case OP_IfEmpty: { /* jump */
+ VdbeCursor *pC;
+ BtCursor *pCrsr;
+ int res;
+
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( pOp->p2>=0 && pOp->p2<p->nOp );
+
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ pCrsr = pC->uc.pCursor;
+ assert( pCrsr );
+ rc = sqlite3BtreeIsEmpty(pCrsr, &res);
+ if( rc ) goto abort_due_to_error;
+ VdbeBranchTaken(res!=0,2);
+ if( res ) goto jump_to_p2;
+ break;
+}
+
/* Opcode: Next P1 P2 P3 * P5
**
** Advance cursor P1 so that it points to the next key/data pair in its
@@ -102223,6 +103251,7 @@ case OP_Checkpoint: {
|| pOp->p2==SQLITE_CHECKPOINT_FULL
|| pOp->p2==SQLITE_CHECKPOINT_RESTART
|| pOp->p2==SQLITE_CHECKPOINT_TRUNCATE
+ || pOp->p2==SQLITE_CHECKPOINT_NOOP
);
rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]);
if( rc ){
@@ -102558,7 +103587,14 @@ case OP_VOpen: { /* ncycle */
const sqlite3_module *pModule;
assert( p->bIsReader );
- pCur = 0;
+ pCur = p->apCsr[pOp->p1];
+ if( pCur!=0
+ && ALWAYS( pCur->eCurType==CURTYPE_VTAB )
+ && ALWAYS( pCur->uc.pVCur->pVtab==pOp->p4.pVtab->pVtab )
+ ){
+ /* This opcode is a no-op if the cursor is already open */
+ break;
+ }
pVCur = 0;
pVtab = pOp->p4.pVtab->pVtab;
if( pVtab==0 || NEVER(pVtab->pModule==0) ){
@@ -103500,8 +104536,7 @@ abort_due_to_error:
p->rc = rc;
sqlite3SystemError(db, rc);
testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(rc, "statement aborts at %d: %s; [%s]",
- (int)(pOp - aOp), p->zErrMsg, p->zSql);
+ sqlite3VdbeLogAbort(p, rc, pOp, aOp);
if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){
@@ -103962,7 +104997,7 @@ static int blobReadWrite(
int iOffset,
int (*xCall)(BtCursor*, u32, u32, void*)
){
- int rc;
+ int rc = SQLITE_OK;
Incrblob *p = (Incrblob *)pBlob;
Vdbe *v;
sqlite3 *db;
@@ -104002,17 +105037,32 @@ static int blobReadWrite(
** using the incremental-blob API, this works. For the sessions module
** anyhow.
*/
- sqlite3_int64 iKey;
- iKey = sqlite3BtreeIntegerKey(p->pCsr);
- assert( v->apCsr[0]!=0 );
- assert( v->apCsr[0]->eCurType==CURTYPE_BTREE );
- sqlite3VdbePreUpdateHook(
- v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
- );
+ if( sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ){
+ /* If the cursor is not currently valid, try to reseek it. This
+ ** always either fails or finds the correct row - the cursor will
+ ** have been marked permanently CURSOR_INVALID if the open row has
+ ** been deleted. */
+ int bDiff = 0;
+ rc = sqlite3BtreeCursorRestore(p->pCsr, &bDiff);
+ assert( bDiff==0 || sqlite3BtreeCursorIsValidNN(p->pCsr)==0 );
+ }
+ if( sqlite3BtreeCursorIsValidNN(p->pCsr) ){
+ sqlite3_int64 iKey;
+ iKey = sqlite3BtreeIntegerKey(p->pCsr);
+ assert( v->apCsr[0]!=0 );
+ assert( v->apCsr[0]->eCurType==CURTYPE_BTREE );
+ sqlite3VdbePreUpdateHook(
+ v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
+ );
+ }
}
+ if( rc==SQLITE_OK ){
+ rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
+ }
+#else
+ rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
#endif
- rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
sqlite3BtreeLeaveCursor(p->pCsr);
if( rc==SQLITE_ABORT ){
sqlite3VdbeFinalize(v);
@@ -104401,6 +105451,7 @@ struct SortSubtask {
SorterCompare xCompare; /* Compare function to use */
SorterFile file; /* Temp file for level-0 PMAs */
SorterFile file2; /* Space for other PMAs */
+ u64 nSpill; /* Total bytes written by this task */
};
@@ -104521,6 +105572,7 @@ struct PmaWriter {
int iBufEnd; /* Last byte of buffer to write */
i64 iWriteOff; /* Offset of start of buffer in file */
sqlite3_file *pFd; /* File handle to write to */
+ u64 nPmaSpill; /* Total number of bytes written */
};
/*
@@ -104865,7 +105917,7 @@ static int vdbeSorterCompareTail(
){
UnpackedRecord *r2 = pTask->pUnpacked;
if( *pbKey2Cached==0 ){
- sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
+ sqlite3VdbeRecordUnpack(nKey2, pKey2, r2);
*pbKey2Cached = 1;
}
return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1);
@@ -104892,7 +105944,7 @@ static int vdbeSorterCompare(
){
UnpackedRecord *r2 = pTask->pUnpacked;
if( !*pbKey2Cached ){
- sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
+ sqlite3VdbeRecordUnpack(nKey2, pKey2, r2);
*pbKey2Cached = 1;
}
return sqlite3VdbeRecordCompare(nKey1, pKey1, r2);
@@ -104932,6 +105984,7 @@ static int vdbeSorterCompareText(
);
}
}else{
+ assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 );
assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) );
if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){
res = res * -1;
@@ -104995,6 +106048,7 @@ static int vdbeSorterCompareInt(
}
}
+ assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 );
if( res==0 ){
if( pTask->pSorter->pKeyInfo->nKeyField>1 ){
res = vdbeSorterCompareTail(
@@ -105068,7 +106122,8 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
assert( pCsr->eCurType==CURTYPE_SORTER );
assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*)
< 0x7fffffff );
- szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField);
+ assert( pCsr->pKeyInfo->nKeyField<=pCsr->pKeyInfo->nAllField );
+ szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nAllField);
sz = SZ_VDBESORTER(nWorker+1);
pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo);
@@ -105082,7 +106137,12 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
pKeyInfo->db = 0;
if( nField && nWorker==0 ){
pKeyInfo->nKeyField = nField;
+ assert( nField<=pCsr->pKeyInfo->nAllField );
}
+ /* It is OK that pKeyInfo reuses the aSortFlags field from pCsr->pKeyInfo,
+ ** since the pCsr->pKeyInfo->aSortFlags[] array is invariant and lives
+ ** longer that pSorter. */
+ assert( pKeyInfo->aSortFlags==pCsr->pKeyInfo->aSortFlags );
sqlite3BtreeEnter(pBt);
pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(pBt);
sqlite3BtreeLeave(pBt);
@@ -105371,6 +106431,12 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
assert( pCsr->eCurType==CURTYPE_SORTER );
pSorter = pCsr->uc.pSorter;
if( pSorter ){
+ /* Increment db->nSpill by the total number of bytes of data written
+ ** to temp files by this sort operation. */
+ int ii;
+ for(ii=0; ii<pSorter->nTask; ii++){
+ db->nSpill += pSorter->aTask[ii].nSpill;
+ }
sqlite3VdbeSorterReset(db, pSorter);
sqlite3_free(pSorter->list.aMemory);
sqlite3DbFree(db, pSorter);
@@ -105596,6 +106662,7 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){
&p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart,
p->iWriteOff + p->iBufStart
);
+ p->nPmaSpill += (p->iBufEnd - p->iBufStart);
p->iBufStart = p->iBufEnd = 0;
p->iWriteOff += p->nBuffer;
}
@@ -105612,17 +106679,20 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){
** required. Otherwise, return an SQLite error code.
**
** Before returning, set *piEof to the offset immediately following the
-** last byte written to the file.
+** last byte written to the file. Also, increment (*pnSpill) by the total
+** number of bytes written to the file.
*/
-static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){
+static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof, u64 *pnSpill){
int rc;
if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){
p->eFWErr = sqlite3OsWrite(p->pFd,
&p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart,
p->iWriteOff + p->iBufStart
);
+ p->nPmaSpill += (p->iBufEnd - p->iBufStart);
}
*piEof = (p->iWriteOff + p->iBufEnd);
+ *pnSpill += p->nPmaSpill;
sqlite3_free(p->aBuffer);
rc = p->eFWErr;
memset(p, 0, sizeof(PmaWriter));
@@ -105702,7 +106772,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
if( pList->aMemory==0 ) sqlite3_free(p);
}
pList->pList = p;
- rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof);
+ rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof, &pTask->nSpill);
}
vdbeSorterWorkDebug(pTask, "exit");
@@ -106016,7 +107086,7 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){
rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy);
}
- rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof);
+ rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof, &pTask->nSpill);
if( rc==SQLITE_OK ) rc = rc2;
vdbeSorterPopulateDebug(pTask, "exit");
return rc;
@@ -106862,7 +107932,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare(
assert( r2->nField==nKeyCol );
pKey = vdbeSorterRowkey(pSorter, &nKey);
- sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2);
+ sqlite3VdbeRecordUnpack(nKey, pKey, r2);
for(i=0; i<nKeyCol; i++){
if( r2->aMem[i].flags & MEM_Null ){
*pRes = -1;
@@ -108407,10 +109477,13 @@ static int lookupName(
if( cnt>0 ){
if( pItem->fg.isUsing==0
|| sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0
+ || pMatch==pItem
){
/* Two or more tables have the same column name which is
- ** not joined by USING. This is an error. Signal as much
- ** by clearing pFJMatch and letting cnt go above 1. */
+ ** not joined by USING. Or, a single table has two columns
+ ** that match a USING term (if pMatch==pItem). These are both
+ ** "ambiguous column name" errors. Signal as much by clearing
+ ** pFJMatch and letting cnt go above 1. */
sqlite3ExprListDelete(db, pFJMatch);
pFJMatch = 0;
}else
@@ -108960,8 +110033,8 @@ static void notValidImpl(
/*
** Expression p should encode a floating point value between 1.0 and 0.0.
-** Return 1024 times this value. Or return -1 if p is not a floating point
-** value between 1.0 and 0.0.
+** Return 134,217,728 (2^27) times this value. Or return -1 if p is not
+** a floating point value between 1.0 and 0.0.
*/
static int exprProbability(Expr *p){
double r = -1.0;
@@ -109392,11 +110465,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
return WRC_Prune;
}
#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_EXISTS:
case TK_SELECT:
- case TK_EXISTS: testcase( pExpr->op==TK_EXISTS );
#endif
case TK_IN: {
testcase( pExpr->op==TK_IN );
+ testcase( pExpr->op==TK_EXISTS );
+ testcase( pExpr->op==TK_SELECT );
if( ExprUseXSelect(pExpr) ){
int nRef = pNC->nRef;
testcase( pNC->ncFlags & NC_IsCheck );
@@ -109404,6 +110479,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
testcase( pNC->ncFlags & NC_IdxExpr );
testcase( pNC->ncFlags & NC_GenCol );
assert( pExpr->x.pSelect );
+ if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1;
if( pNC->ncFlags & NC_SelfRef ){
notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr);
}else{
@@ -110314,14 +111390,17 @@ SQLITE_PRIVATE int sqlite3ResolveSelfReference(
SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */
NameContext sNC; /* Name context for pParse->pNewTable */
int rc;
- u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */
+ union {
+ SrcList sSrc;
+ u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */
+ } uSrc;
assert( type==0 || pTab!=0 );
assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr
|| type==NC_GenCol || pTab==0 );
memset(&sNC, 0, sizeof(sNC));
- pSrc = (SrcList*)srcSpace;
- memset(pSrc, 0, SZ_SRCLIST_1);
+ memset(&uSrc, 0, sizeof(uSrc));
+ pSrc = &uSrc.sSrc;
if( pTab ){
pSrc->nSrc = 1;
pSrc->a[0].zName = pTab->zName;
@@ -111584,6 +112663,11 @@ SQLITE_PRIVATE void sqlite3ExprAddFunctionOrderBy(
sqlite3ExprListDelete(db, pOrderBy);
return;
}
+ if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause");
+ sqlite3ExprListDelete(db, pOrderBy);
+ return;
+ }
pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0);
if( pOB==0 ){
@@ -112719,6 +113803,85 @@ SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){
}
/*
+** Return true if it might be advantageous to compute the right operand
+** of expression pExpr first, before the left operand.
+**
+** Normally the left operand is computed before the right operand. But if
+** the left operand contains a subquery and the right does not, then it
+** might be more efficient to compute the right operand first.
+*/
+static int exprEvalRhsFirst(Expr *pExpr){
+ if( ExprHasProperty(pExpr->pLeft, EP_Subquery)
+ && !ExprHasProperty(pExpr->pRight, EP_Subquery)
+ ){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Compute the two operands of a binary operator.
+**
+** If either operand contains a subquery, then the code strives to
+** compute the operand containing the subquery second. If the other
+** operand evalutes to NULL, then a jump is made. The address of the
+** IsNull operand that does this jump is returned. The caller can use
+** this to optimize the computation so as to avoid doing the potentially
+** expensive subquery.
+**
+** If no optimization opportunities exist, return 0.
+*/
+static int exprComputeOperands(
+ Parse *pParse, /* Parsing context */
+ Expr *pExpr, /* The comparison expression */
+ int *pR1, /* OUT: Register holding the left operand */
+ int *pR2, /* OUT: Register holding the right operand */
+ int *pFree1, /* OUT: Temp register to free if not zero */
+ int *pFree2 /* OUT: Another temp register to free if not zero */
+){
+ int addrIsNull;
+ int r1, r2;
+ Vdbe *v = pParse->pVdbe;
+
+ assert( v!=0 );
+ /*
+ ** If the left operand contains a (possibly expensive) subquery and the
+ ** right operand does not and the right operation might be NULL,
+ ** then compute the right operand first and do an IsNull jump if the
+ ** right operand evalutes to NULL.
+ */
+ if( exprEvalRhsFirst(pExpr) && sqlite3ExprCanBeNull(pExpr->pRight) ){
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2);
+ addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r2);
+ VdbeComment((v, "skip left operand"));
+ VdbeCoverage(v);
+ }else{
+ r2 = 0; /* Silence a false-positive uninit-var warning in MSVC */
+ addrIsNull = 0;
+ }
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1);
+ if( addrIsNull==0 ){
+ /*
+ ** If the right operand contains a subquery and the left operand does not
+ ** and the left operand might be NULL, then do an IsNull check
+ ** check on the left operand before computing the right operand.
+ */
+ if( ExprHasProperty(pExpr->pRight, EP_Subquery)
+ && sqlite3ExprCanBeNull(pExpr->pLeft)
+ ){
+ addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1);
+ VdbeComment((v, "skip right operand"));
+ VdbeCoverage(v);
+ }
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2);
+ }
+ *pR1 = r1;
+ *pR2 = r2;
+ return addrIsNull;
+}
+
+/*
** pExpr is a TK_FUNCTION node. Try to determine whether or not the
** function is a constant function. A function is constant if all of
** the following are true:
@@ -114162,17 +115325,23 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
VdbeComment((v, "Init EXISTS result"));
}
if( pSel->pLimit ){
- /* The subquery already has a limit. If the pre-existing limit is X
- ** then make the new limit X<>0 so that the new limit is either 1 or 0 */
- sqlite3 *db = pParse->db;
- pLimit = sqlite3Expr(db, TK_INTEGER, "0");
- if( pLimit ){
- pLimit->affExpr = SQLITE_AFF_NUMERIC;
- pLimit = sqlite3PExpr(pParse, TK_NE,
- sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit);
+ /* The subquery already has a limit. If the pre-existing limit X is
+ ** not already integer value 1 or 0, then make the new limit X<>0 so that
+ ** the new limit is either 1 or 0 */
+ Expr *pLeft = pSel->pLimit->pLeft;
+ if( ExprHasProperty(pLeft, EP_IntValue)==0
+ || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0)
+ ){
+ sqlite3 *db = pParse->db;
+ pLimit = sqlite3Expr(db, TK_INTEGER, "0");
+ if( pLimit ){
+ pLimit->affExpr = SQLITE_AFF_NUMERIC;
+ pLimit = sqlite3PExpr(pParse, TK_NE,
+ sqlite3ExprDup(db, pLeft, 0), pLimit);
+ }
+ sqlite3ExprDeferredDelete(pParse, pLeft);
+ pSel->pLimit->pLeft = pLimit;
}
- sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft);
- pSel->pLimit->pLeft = pLimit;
}else{
/* If there is no pre-existing limit add a limit of 1 */
pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1");
@@ -114260,7 +115429,6 @@ static void sqlite3ExprCodeIN(
int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */
int eType; /* Type of the RHS */
int rLhs; /* Register(s) holding the LHS values */
- int rLhsOrig; /* LHS values prior to reordering by aiMap[] */
Vdbe *v; /* Statement under construction */
int *aiMap = 0; /* Map from vector field to index column */
char *zAff = 0; /* Affinity string for comparisons */
@@ -114323,19 +115491,8 @@ static void sqlite3ExprCodeIN(
** by code generated below. */
assert( pParse->okConstFactor==okConstFactor );
pParse->okConstFactor = 0;
- rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy);
+ rLhs = exprCodeVector(pParse, pLeft, &iDummy);
pParse->okConstFactor = okConstFactor;
- for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */
- if( i==nVector ){
- /* LHS fields are not reordered */
- rLhs = rLhsOrig;
- }else{
- /* Need to reorder the LHS fields according to aiMap */
- rLhs = sqlite3GetTempRange(pParse, nVector);
- for(i=0; i<nVector; i++){
- sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0);
- }
- }
/* If sqlite3FindInIndex() did not find or create an index that is
** suitable for evaluating the IN operator, then evaluate using a
@@ -114350,6 +115507,7 @@ static void sqlite3ExprCodeIN(
int r2, regToFree;
int regCkNull = 0;
int ii;
+ assert( nVector==1 );
assert( ExprUseXList(pExpr) );
pList = pExpr->x.pList;
pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
@@ -114391,6 +115549,26 @@ static void sqlite3ExprCodeIN(
goto sqlite3ExprCodeIN_finished;
}
+ if( eType!=IN_INDEX_ROWID ){
+ /* If this IN operator will use an index, then the order of columns in the
+ ** vector might be different from the order in the index. In that case,
+ ** we need to reorder the LHS values to be in index order. Run Affinity
+ ** before reordering the columns, so that the affinity is correct.
+ */
+ sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
+ for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */
+ if( i!=nVector ){
+ /* Need to reorder the LHS fields according to aiMap */
+ int rLhsOrig = rLhs;
+ rLhs = sqlite3GetTempRange(pParse, nVector);
+ for(i=0; i<nVector; i++){
+ sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0);
+ }
+ sqlite3ReleaseTempReg(pParse, rLhsOrig);
+ }
+ }
+
+
/* Step 2: Check to see if the LHS contains any NULL columns. If the
** LHS does contain NULLs then the result must be either FALSE or NULL.
** We will then skip the binary search of the RHS.
@@ -114417,11 +115595,11 @@ static void sqlite3ExprCodeIN(
/* In this case, the RHS is the ROWID of table b-tree and so we also
** know that the RHS is non-NULL. Hence, we combine steps 3 and 4
** into a single opcode. */
+ assert( nVector==1 );
sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs);
VdbeCoverage(v);
addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */
}else{
- sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
if( destIfFalse==destIfNull ){
/* Combine Step 3 and Step 5 into a single opcode */
if( ExprHasProperty(pExpr, EP_Subrtn) ){
@@ -114499,7 +115677,6 @@ static void sqlite3ExprCodeIN(
sqlite3VdbeJumpHere(v, addrTruthOp);
sqlite3ExprCodeIN_finished:
- if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs);
VdbeComment((v, "end IN expr"));
sqlite3ExprCodeIN_oom_error:
sqlite3DbFree(pParse->db, aiMap);
@@ -114614,7 +115791,12 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(
iAddr = 0;
}
sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut);
- if( pCol->affinity>=SQLITE_AFF_TEXT ){
+ if( (pCol->colFlags & COLFLAG_VIRTUAL)!=0
+ && (pTab->tabFlags & TF_Strict)!=0
+ ){
+ int p3 = 2+(int)(pCol - pTab->aCol);
+ sqlite3VdbeAddOp4(v, OP_TypeCheck, regOut, 1, p3, (char*)pTab, P4_TABLE);
+ }else if( pCol->affinity>=SQLITE_AFF_TEXT ){
sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
}
if( iAddr ) sqlite3VdbeJumpHere(v, iAddr);
@@ -115052,6 +116234,80 @@ static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){
return 0;
}
+/*
+** Generate code that evaluates an AND or OR operator leaving a
+** boolean result in a register. pExpr is the AND/OR expression.
+** Store the result in the "target" register. Use short-circuit
+** evaluation to avoid computing both operands, if possible.
+**
+** The code generated might require the use of a temporary register.
+** If it does, then write the number of that temporary register
+** into *pTmpReg. If not, leave *pTmpReg unchanged.
+*/
+static SQLITE_NOINLINE int exprCodeTargetAndOr(
+ Parse *pParse, /* Parsing context */
+ Expr *pExpr, /* AND or OR expression to be coded */
+ int target, /* Put result in this register, guaranteed */
+ int *pTmpReg /* Write a temporary register here */
+){
+ int op; /* The opcode. TK_AND or TK_OR */
+ int skipOp; /* Opcode for the branch that skips one operand */
+ int addrSkip; /* Branch instruction that skips one of the operands */
+ int regSS = 0; /* Register holding computed operand when other omitted */
+ int r1, r2; /* Registers for left and right operands, respectively */
+ Expr *pAlt; /* Alternative, simplified expression */
+ Vdbe *v; /* statement being coded */
+
+ assert( pExpr!=0 );
+ op = pExpr->op;
+ assert( op==TK_AND || op==TK_OR );
+ assert( TK_AND==OP_And ); testcase( op==TK_AND );
+ assert( TK_OR==OP_Or ); testcase( op==TK_OR );
+ assert( pParse->pVdbe!=0 );
+ v = pParse->pVdbe;
+ pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
+ if( pAlt!=pExpr ){
+ r1 = sqlite3ExprCodeTarget(pParse, pAlt, target);
+ sqlite3VdbeAddOp3(v, OP_And, r1, r1, target);
+ return target;
+ }
+ skipOp = op==TK_AND ? OP_IfNot : OP_If;
+ if( exprEvalRhsFirst(pExpr) ){
+ /* Compute the right operand first. Skip the computation of the left
+ ** operand if the right operand fully determines the result */
+ r2 = regSS = sqlite3ExprCodeTarget(pParse, pExpr->pRight, target);
+ addrSkip = sqlite3VdbeAddOp1(v, skipOp, r2);
+ VdbeComment((v, "skip left operand"));
+ VdbeCoverage(v);
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pTmpReg);
+ }else{
+ /* Compute the left operand first */
+ r1 = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ if( ExprHasProperty(pExpr->pRight, EP_Subquery) ){
+ /* Skip over the computation of the right operand if the right
+ ** operand is a subquery and the left operand completely determines
+ ** the result */
+ regSS = r1;
+ addrSkip = sqlite3VdbeAddOp1(v, skipOp, r1);
+ VdbeComment((v, "skip right operand"));
+ VdbeCoverage(v);
+ }else{
+ addrSkip = regSS = 0;
+ }
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pTmpReg);
+ }
+ sqlite3VdbeAddOp3(v, op, r2, r1, target);
+ testcase( (*pTmpReg)==0 );
+ if( addrSkip ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ sqlite3VdbeJumpHere(v, addrSkip);
+ sqlite3VdbeAddOp3(v, OP_Or, regSS, regSS, target);
+ VdbeComment((v, "short-circut value"));
+ }
+ return target;
+}
+
+
/*
** Generate code into the current Vdbe to evaluate the given
@@ -115307,11 +116563,17 @@ expr_code_doover:
case TK_NE:
case TK_EQ: {
Expr *pLeft = pExpr->pLeft;
+ int addrIsNull = 0;
if( sqlite3ExprIsVector(pLeft) ){
codeVectorCompare(pParse, pExpr, target, op, p5);
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && p5!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ }
sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg);
codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2,
sqlite3VdbeCurrentAddr(v)+2, p5,
@@ -115326,6 +116588,11 @@ expr_code_doover:
sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg);
}else{
sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2);
+ if( addrIsNull ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, inReg);
+ }
}
testcase( regFree1==0 );
testcase( regFree2==0 );
@@ -115333,7 +116600,10 @@ expr_code_doover:
break;
}
case TK_AND:
- case TK_OR:
+ case TK_OR: {
+ inReg = exprCodeTargetAndOr(pParse, pExpr, target, &regFree1);
+ break;
+ }
case TK_PLUS:
case TK_STAR:
case TK_MINUS:
@@ -115344,8 +116614,7 @@ expr_code_doover:
case TK_LSHIFT:
case TK_RSHIFT:
case TK_CONCAT: {
- assert( TK_AND==OP_And ); testcase( op==TK_AND );
- assert( TK_OR==OP_Or ); testcase( op==TK_OR );
+ int addrIsNull;
assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS );
assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS );
assert( TK_REM==OP_Remainder ); testcase( op==TK_REM );
@@ -115355,11 +116624,23 @@ expr_code_doover:
assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT );
assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT );
assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
sqlite3VdbeAddOp3(v, op, r2, r1, target);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ VdbeComment((v, "short-circut value"));
+ }
break;
}
case TK_UMINUS: {
@@ -116227,17 +117508,27 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
if( pAlt!=pExpr ){
sqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull);
- }else if( op==TK_AND ){
- int d2 = sqlite3VdbeMakeLabel(pParse);
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,
- jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
}else{
- testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ Expr *pFirst, *pSecond;
+ if( exprEvalRhsFirst(pExpr) ){
+ pFirst = pExpr->pRight;
+ pSecond = pExpr->pLeft;
+ }else{
+ pFirst = pExpr->pLeft;
+ pSecond = pExpr->pRight;
+ }
+ if( op==TK_AND ){
+ int d2 = sqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, pFirst, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull);
+ sqlite3VdbeResolveLabel(v, d2);
+ }else{
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfTrue(pParse, pFirst, dest, jumpIfNull);
+ sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull);
+ }
}
break;
}
@@ -116276,10 +117567,16 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
case TK_GE:
case TK_NE:
case TK_EQ: {
+ int addrIsNull;
if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
- testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
@@ -116294,6 +117591,13 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ if( jumpIfNull ){
+ sqlite3VdbeChangeP2(v, addrIsNull, dest);
+ }else{
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ }
+ }
break;
}
case TK_ISNULL:
@@ -116401,17 +117705,27 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
if( pAlt!=pExpr ){
sqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull);
- }else if( pExpr->op==TK_AND ){
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
}else{
- int d2 = sqlite3VdbeMakeLabel(pParse);
- testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2,
- jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
+ Expr *pFirst, *pSecond;
+ if( exprEvalRhsFirst(pExpr) ){
+ pFirst = pExpr->pRight;
+ pSecond = pExpr->pLeft;
+ }else{
+ pFirst = pExpr->pLeft;
+ pSecond = pExpr->pRight;
+ }
+ if( pExpr->op==TK_AND ){
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfFalse(pParse, pFirst, dest, jumpIfNull);
+ sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull);
+ }else{
+ int d2 = sqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ sqlite3ExprIfTrue(pParse, pFirst, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull);
+ sqlite3VdbeResolveLabel(v, d2);
+ }
}
break;
}
@@ -116453,10 +117767,16 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
case TK_GE:
case TK_NE:
case TK_EQ: {
+ int addrIsNull;
if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
- testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){
+ addrIsNull = exprComputeOperands(pParse, pExpr,
+ &r1, &r2, &regFree1, &regFree2);
+ }else{
+ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ addrIsNull = 0;
+ }
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
@@ -116471,6 +117791,13 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ);
testcase( regFree1==0 );
testcase( regFree2==0 );
+ if( addrIsNull ){
+ if( jumpIfNull ){
+ sqlite3VdbeChangeP2(v, addrIsNull, dest);
+ }else{
+ sqlite3VdbeJumpHere(v, addrIsNull);
+ }
+ }
break;
}
case TK_ISNULL:
@@ -123436,6 +124763,16 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){
pMod = sqlite3PragmaVtabRegister(db, zName);
}
+#ifndef SQLITE_OMIT_JSON
+ if( pMod==0 && sqlite3_strnicmp(zName, "json", 4)==0 ){
+ pMod = sqlite3JsonVtabRegister(db, zName);
+ }
+#endif
+#ifdef SQLITE_ENABLE_CARRAY
+ if( pMod==0 && sqlite3_stricmp(zName, "carray")==0 ){
+ pMod = sqlite3CarrayRegister(db);
+ }
+#endif
if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){
testcase( pMod->pEpoTab==0 );
return pMod->pEpoTab;
@@ -124074,7 +125411,7 @@ SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){
int i;
i16 iCol16;
assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN );
- assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 );
+ assert( pIdx->nColumn<=SQLITE_MAX_COLUMN*2 );
iCol16 = iCol;
for(i=0; i<pIdx->nColumn; i++){
if( iCol16==pIdx->aiColumn[i] ){
@@ -124371,6 +125708,9 @@ SQLITE_PRIVATE void sqlite3StartTable(
sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
sqlite3VdbeAddOp0(v, OP_Close);
+ }else if( db->init.imposterTable ){
+ pTable->tabFlags |= TF_Imposter;
+ if( db->init.imposterTable>=2 ) pTable->tabFlags |= TF_Readonly;
}
/* Normal (non-error) return. */
@@ -128140,16 +129480,22 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI
** are deleted by this function.
*/
SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){
- assert( p1 && p1->nSrc==1 );
+ assert( p1 );
+ assert( p2 || pParse->nErr );
+ assert( p2==0 || p2->nSrc>=1 );
+ testcase( p1->nSrc==0 );
if( p2 ){
- SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, 1);
+ int nOld = p1->nSrc;
+ SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld);
if( pNew==0 ){
sqlite3SrcListDelete(pParse->db, p2);
}else{
p1 = pNew;
- memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem));
+ memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem));
+ assert( nOld==1 || (p2->a[0].fg.jointype & JT_LTORJ)==0 );
+ assert( p1->nSrc>=1 );
+ p1->a[0].fg.jointype |= (JT_LTORJ & p2->a[0].fg.jointype);
sqlite3DbFree(pParse->db, p2);
- p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype);
}
}
return p1;
@@ -128660,14 +130006,19 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
}
if( pParse->nErr ){
assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ );
- if( pIdx->bNoQuery==0 ){
+ if( pIdx->bNoQuery==0
+ && sqlite3HashFind(&pIdx->pSchema->idxHash, pIdx->zName)
+ ){
/* Deactivate the index because it contains an unknown collating
** sequence. The only way to reactive the index is to reload the
** schema. Adding the missing collating sequence later does not
** reactive the index. The application had the chance to register
** the missing index using the collation-needed callback. For
** simplicity, SQLite will not give the application a second chance.
- */
+ **
+ ** Except, do not do this if the index is not in the schema hash
+ ** table. In this case the index is currently being constructed
+ ** by a CREATE INDEX statement, and retrying will not help. */
pIdx->bNoQuery = 1;
pParse->rc = SQLITE_ERROR_RETRY;
}
@@ -130864,7 +132215,7 @@ static void *contextMalloc(sqlite3_context *context, i64 nByte){
sqlite3 *db = sqlite3_context_db_handle(context);
assert( nByte>0 );
testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH] );
- testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 );
+ testcase( nByte==(i64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 );
if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
sqlite3_result_error_toobig(context);
z = 0;
@@ -131535,7 +132886,7 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int
*/
static int isNHex(const char *z, int N, u32 *pVal){
int i;
- int v = 0;
+ u32 v = 0;
for(i=0; i<N; i++){
if( !sqlite3Isxdigit(z[i]) ) return 0;
v = (v<<4) + sqlite3HexToInt(z[i]);
@@ -132048,6 +133399,7 @@ static void concatFuncCore(
){
i64 j, n = 0;
int i;
+ int bNotNull = 0; /* True after at least NOT NULL argument seen */
char *z;
for(i=0; i<argc; i++){
n += sqlite3_value_bytes(argv[i]);
@@ -132064,12 +133416,13 @@ static void concatFuncCore(
int k = sqlite3_value_bytes(argv[i]);
const char *v = (const char*)sqlite3_value_text(argv[i]);
if( v!=0 ){
- if( j>0 && nSep>0 ){
+ if( bNotNull && nSep>0 ){
memcpy(&z[j], zSep, nSep);
j += nSep;
}
memcpy(&z[j], v, k);
j += k;
+ bNotNull = 1;
}
}
}
@@ -133015,6 +134368,502 @@ static void signFunc(
sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0);
}
+#if defined(SQLITE_ENABLE_PERCENTILE)
+/***********************************************************************
+** This section implements the percentile(Y,P) SQL function and similar.
+** Requirements:
+**
+** (1) The percentile(Y,P) function is an aggregate function taking
+** exactly two arguments.
+**
+** (2) If the P argument to percentile(Y,P) is not the same for every
+** row in the aggregate then an error is thrown. The word "same"
+** in the previous sentence means that the value differ by less
+** than 0.001.
+**
+** (3) If the P argument to percentile(Y,P) evaluates to anything other
+** than a number in the range of 0.0 to 100.0 inclusive then an
+** error is thrown.
+**
+** (4) If any Y argument to percentile(Y,P) evaluates to a value that
+** is not NULL and is not numeric then an error is thrown.
+**
+** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus
+** infinity then an error is thrown. (SQLite always interprets NaN
+** values as NULL.)
+**
+** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions,
+** including CASE WHEN expressions.
+**
+** (7) The percentile(Y,P) aggregate is able to handle inputs of at least
+** one million (1,000,000) rows.
+**
+** (8) If there are no non-NULL values for Y, then percentile(Y,P)
+** returns NULL.
+**
+** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P)
+** returns the one Y value.
+**
+** (10) If there N non-NULL values of Y where N is two or more and
+** the Y values are ordered from least to greatest and a graph is
+** drawn from 0 to N-1 such that the height of the graph at J is
+** the J-th Y value and such that straight lines are drawn between
+** adjacent Y values, then the percentile(Y,P) function returns
+** the height of the graph at P*(N-1)/100.
+**
+** (11) The percentile(Y,P) function always returns either a floating
+** point number or NULL.
+**
+** (12) The percentile(Y,P) is implemented as a single C99 source-code
+** file that compiles into a shared-library or DLL that can be loaded
+** into SQLite using the sqlite3_load_extension() interface.
+**
+** (13) A separate median(Y) function is the equivalent percentile(Y,50).
+**
+** (14) A separate percentile_cont(Y,P) function is equivalent to
+** percentile(Y,P/100.0). In other words, the fraction value in
+** the second argument is in the range of 0 to 1 instead of 0 to 100.
+**
+** (15) A separate percentile_disc(Y,P) function is like
+** percentile_cont(Y,P) except that instead of returning the weighted
+** average of the nearest two input values, it returns the next lower
+** value. So the percentile_disc(Y,P) will always return a value
+** that was one of the inputs.
+**
+** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and
+** percentile_disc(Y,P) can be used as window functions.
+**
+** Differences from standard SQL:
+**
+** * The percentile_cont(X,P) function is equivalent to the following in
+** standard SQL:
+**
+** (percentile_cont(P) WITHIN GROUP (ORDER BY X))
+**
+** The SQLite syntax is much more compact. The standard SQL syntax
+** is also supported if SQLite is compiled with the
+** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option.
+**
+** * No median(X) function exists in the SQL standard. App developers
+** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)".
+**
+** * No percentile(Y,P) function exists in the SQL standard. Instead of
+** percential(Y,P), developers must write this:
+** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that
+** the fraction parameter to percentile() goes from 0 to 100 whereas
+** the fraction parameter in SQL standard percentile_cont() goes from
+** 0 to 1.
+**
+** Implementation notes as of 2024-08-31:
+**
+** * The regular aggregate-function versions of these routines work
+** by accumulating all values in an array of doubles, then sorting
+** that array using quicksort before computing the answer. Thus
+** the runtime is O(NlogN) where N is the number of rows of input.
+**
+** * For the window-function versions of these routines, the array of
+** inputs is sorted as soon as the first value is computed. Thereafter,
+** the array is kept in sorted order using an insert-sort. This
+** results in O(N*K) performance where K is the size of the window.
+** One can imagine alternative implementations that give O(N*logN*logK)
+** performance, but they require more complex logic and data structures.
+** The developers have elected to keep the asymptotically slower
+** algorithm for now, for simplicity, under the theory that window
+** functions are seldom used and when they are, the window size K is
+** often small. The developers might revisit that decision later,
+** should the need arise.
+*/
+
+/* The following object is the group context for a single percentile()
+** aggregate. Remember all input Y values until the very end.
+** Those values are accumulated in the Percentile.a[] array.
+*/
+typedef struct Percentile Percentile;
+struct Percentile {
+ u64 nAlloc; /* Number of slots allocated for a[] */
+ u64 nUsed; /* Number of slots actually used in a[] */
+ char bSorted; /* True if a[] is already in sorted order */
+ char bKeepSorted; /* True if advantageous to keep a[] sorted */
+ char bPctValid; /* True if rPct is valid */
+ double rPct; /* Fraction. 0.0 to 1.0 */
+ double *a; /* Array of Y values */
+};
+
+/*
+** Return TRUE if the input floating-point number is an infinity.
+*/
+static int percentIsInfinity(double r){
+ sqlite3_uint64 u;
+ assert( sizeof(u)==sizeof(r) );
+ memcpy(&u, &r, sizeof(u));
+ return ((u>>52)&0x7ff)==0x7ff;
+}
+
+/*
+** Return TRUE if two doubles differ by 0.001 or less.
+*/
+static int percentSameValue(double a, double b){
+ a -= b;
+ return a>=-0.001 && a<=0.001;
+}
+
+/*
+** Search p (which must have p->bSorted) looking for an entry with
+** value y. Return the index of that entry.
+**
+** If bExact is true, return -1 if the entry is not found.
+**
+** If bExact is false, return the index at which a new entry with
+** value y should be insert in order to keep the values in sorted
+** order. The smallest return value in this case will be 0, and
+** the largest return value will be p->nUsed.
+*/
+static i64 percentBinarySearch(Percentile *p, double y, int bExact){
+ i64 iFirst = 0; /* First element of search range */
+ i64 iLast = (i64)p->nUsed - 1; /* Last element of search range */
+ while( iLast>=iFirst ){
+ i64 iMid = (iFirst+iLast)/2;
+ double x = p->a[iMid];
+ if( x<y ){
+ iFirst = iMid + 1;
+ }else if( x>y ){
+ iLast = iMid - 1;
+ }else{
+ return iMid;
+ }
+ }
+ if( bExact ) return -1;
+ return iFirst;
+}
+
+/*
+** Generate an error for a percentile function.
+**
+** The error format string must have exactly one occurrence of "%%s()"
+** (with two '%' characters). That substring will be replaced by the name
+** of the function.
+*/
+static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){
+ char *zMsg1;
+ char *zMsg2;
+ va_list ap;
+
+ va_start(ap, zFormat);
+ zMsg1 = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, sqlite3VdbeFuncName(pCtx)) : 0;
+ sqlite3_result_error(pCtx, zMsg2, -1);
+ sqlite3_free(zMsg1);
+ sqlite3_free(zMsg2);
+}
+
+/*
+** The "step" function for percentile(Y,P) is called once for each
+** input row.
+*/
+static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
+ Percentile *p;
+ double rPct;
+ int eType;
+ double y;
+ assert( argc==2 || argc==1 );
+
+ if( argc==1 ){
+ /* Requirement 13: median(Y) is the same as percentile(Y,50). */
+ rPct = 0.5;
+ }else{
+ /* P must be a number between 0 and 100 for percentile() or between
+ ** 0.0 and 1.0 for percentile_cont() and percentile_disc().
+ **
+ ** The user-data is an integer which is 10 times the upper bound.
+ */
+ double mxFrac = (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&2)? 100.0 : 1.0;
+ eType = sqlite3_value_numeric_type(argv[1]);
+ rPct = sqlite3_value_double(argv[1])/mxFrac;
+ if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT)
+ || rPct<0.0 || rPct>1.0
+ ){
+ percentError(pCtx, "the fraction argument to %%s()"
+ " is not between 0.0 and %.1f",
+ (double)mxFrac);
+ return;
+ }
+ }
+
+ /* Allocate the session context. */
+ p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p==0 ) return;
+
+ /* Remember the P value. Throw an error if the P value is different
+ ** from any prior row, per Requirement (2). */
+ if( !p->bPctValid ){
+ p->rPct = rPct;
+ p->bPctValid = 1;
+ }else if( !percentSameValue(p->rPct,rPct) ){
+ percentError(pCtx, "the fraction argument to %%s()"
+ " is not the same for all input rows");
+ return;
+ }
+
+ /* Ignore rows for which Y is NULL */
+ eType = sqlite3_value_type(argv[0]);
+ if( eType==SQLITE_NULL ) return;
+
+ /* If not NULL, then Y must be numeric. Otherwise throw an error.
+ ** Requirement 4 */
+ if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){
+ percentError(pCtx, "input to %%s() is not numeric");
+ return;
+ }
+
+ /* Throw an error if the Y value is infinity or NaN */
+ y = sqlite3_value_double(argv[0]);
+ if( percentIsInfinity(y) ){
+ percentError(pCtx, "Inf input to %%s()");
+ return;
+ }
+
+ /* Allocate and store the Y */
+ if( p->nUsed>=p->nAlloc ){
+ u64 n = p->nAlloc*2 + 250;
+ double *a = sqlite3_realloc64(p->a, sizeof(double)*n);
+ if( a==0 ){
+ sqlite3_free(p->a);
+ memset(p, 0, sizeof(*p));
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ p->nAlloc = n;
+ p->a = a;
+ }
+ if( p->nUsed==0 ){
+ p->a[p->nUsed++] = y;
+ p->bSorted = 1;
+ }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){
+ p->a[p->nUsed++] = y;
+ }else if( p->bKeepSorted ){
+ i64 i;
+ i = percentBinarySearch(p, y, 0);
+ if( i<(int)p->nUsed ){
+ memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0]));
+ }
+ p->a[i] = y;
+ p->nUsed++;
+ }else{
+ p->a[p->nUsed++] = y;
+ p->bSorted = 0;
+ }
+}
+
+/*
+** Interchange two doubles.
+*/
+#define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;}
+
+/*
+** Sort an array of doubles.
+**
+** Algorithm: quicksort
+**
+** This is implemented separately rather than using the qsort() routine
+** from the standard library because:
+**
+** (1) To avoid a dependency on qsort()
+** (2) To avoid the function call to the comparison routine for each
+** comparison.
+*/
+static void percentSort(double *a, unsigned int n){
+ int iLt; /* Entries before a[iLt] are less than rPivot */
+ int iGt; /* Entries at or after a[iGt] are greater than rPivot */
+ int i; /* Loop counter */
+ double rPivot; /* The pivot value */
+
+ assert( n>=2 );
+ if( a[0]>a[n-1] ){
+ SWAP_DOUBLE(a[0],a[n-1])
+ }
+ if( n==2 ) return;
+ iGt = n-1;
+ i = n/2;
+ if( a[0]>a[i] ){
+ SWAP_DOUBLE(a[0],a[i])
+ }else if( a[i]>a[iGt] ){
+ SWAP_DOUBLE(a[i],a[iGt])
+ }
+ if( n==3 ) return;
+ rPivot = a[i];
+ iLt = i = 1;
+ do{
+ if( a[i]<rPivot ){
+ if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt])
+ iLt++;
+ i++;
+ }else if( a[i]>rPivot ){
+ do{
+ iGt--;
+ }while( iGt>i && a[iGt]>rPivot );
+ SWAP_DOUBLE(a[i],a[iGt])
+ }else{
+ i++;
+ }
+ }while( i<iGt );
+ if( iLt>=2 ) percentSort(a, iLt);
+ if( n-iGt>=2 ) percentSort(a+iGt, n-iGt);
+
+/* Uncomment for testing */
+#if 0
+ for(i=0; i<n-1; i++){
+ assert( a[i]<=a[i+1] );
+ }
+#endif
+}
+
+
+/*
+** The "inverse" function for percentile(Y,P) is called to remove a
+** row that was previously inserted by "step".
+*/
+static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){
+ Percentile *p;
+ int eType;
+ double y;
+ i64 i;
+ assert( argc==2 || argc==1 );
+
+ /* Allocate the session context. */
+ p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p));
+ assert( p!=0 );
+
+ /* Ignore rows for which Y is NULL */
+ eType = sqlite3_value_type(argv[0]);
+ if( eType==SQLITE_NULL ) return;
+
+ /* If not NULL, then Y must be numeric. Otherwise throw an error.
+ ** Requirement 4 */
+ if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){
+ return;
+ }
+
+ /* Ignore the Y value if it is infinity or NaN */
+ y = sqlite3_value_double(argv[0]);
+ if( percentIsInfinity(y) ){
+ return;
+ }
+ if( p->bSorted==0 ){
+ assert( p->nUsed>1 );
+ percentSort(p->a, p->nUsed);
+ p->bSorted = 1;
+ }
+ p->bKeepSorted = 1;
+
+ /* Find and remove the row */
+ i = percentBinarySearch(p, y, 1);
+ if( i>=0 ){
+ p->nUsed--;
+ if( i<(int)p->nUsed ){
+ memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0]));
+ }
+ }
+}
+
+/*
+** Compute the final output of percentile(). Clean up all allocated
+** memory if and only if bIsFinal is true.
+*/
+static void percentCompute(sqlite3_context *pCtx, int bIsFinal){
+ Percentile *p;
+ int settings = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&1; /* Discrete? */
+ unsigned i1, i2;
+ double v1, v2;
+ double ix, vx;
+ p = (Percentile*)sqlite3_aggregate_context(pCtx, 0);
+ if( p==0 ) return;
+ if( p->a==0 ) return;
+ if( p->nUsed ){
+ if( p->bSorted==0 ){
+ assert( p->nUsed>1 );
+ percentSort(p->a, p->nUsed);
+ p->bSorted = 1;
+ }
+ ix = p->rPct*(p->nUsed-1);
+ i1 = (unsigned)ix;
+ if( settings & 1 ){
+ vx = p->a[i1];
+ }else{
+ i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1;
+ v1 = p->a[i1];
+ v2 = p->a[i2];
+ vx = v1 + (v2-v1)*(ix-i1);
+ }
+ sqlite3_result_double(pCtx, vx);
+ }
+ if( bIsFinal ){
+ sqlite3_free(p->a);
+ memset(p, 0, sizeof(*p));
+ }else{
+ p->bKeepSorted = 1;
+ }
+}
+static void percentFinal(sqlite3_context *pCtx){
+ percentCompute(pCtx, 1);
+}
+static void percentValue(sqlite3_context *pCtx){
+ percentCompute(pCtx, 0);
+}
+/****** End of percentile family of functions ******/
+#endif /* SQLITE_ENABLE_PERCENTILE */
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+/*
+** Implementation of sqlite_filestat(SCHEMA).
+**
+** Return JSON text that describes low-level debug/diagnostic information
+** about the sqlite3_file object associated with SCHEMA.
+*/
+static void filestatFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ const char *zDbName;
+ sqlite3_str *pStr;
+ Btree *pBtree;
+
+ zDbName = (const char*)sqlite3_value_text(argv[0]);
+ pBtree = sqlite3DbNameToBtree(db, zDbName);
+ if( pBtree ){
+ Pager *pPager;
+ sqlite3_file *fd;
+ int rc;
+ sqlite3BtreeEnter(pBtree);
+ pPager = sqlite3BtreePager(pBtree);
+ assert( pPager!=0 );
+ fd = sqlite3PagerFile(pPager);
+ pStr = sqlite3_str_new(db);
+ if( pStr==0 ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_str_append(pStr, "{\"db\":", 6);
+ rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr);
+ if( rc ) sqlite3_str_append(pStr, "null", 4);
+ fd = sqlite3PagerJrnlFile(pPager);
+ if( fd && fd->pMethods!=0 ){
+ sqlite3_str_appendall(pStr, ",\"journal\":");
+ rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr);
+ if( rc ) sqlite3_str_append(pStr, "null", 4);
+ }
+ sqlite3_str_append(pStr, "}", 1);
+ sqlite3_result_text(context, sqlite3_str_finish(pStr), -1,
+ sqlite3_free);
+ }
+ sqlite3BtreeLeave(pBtree);
+ }else{
+ sqlite3_result_text(context, "{}", 2, SQLITE_STATIC);
+ }
+}
+#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */
+
#ifdef SQLITE_DEBUG
/*
** Implementation of fpdecode(x,y,z) function.
@@ -133173,6 +135022,9 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ),
#endif
+#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT)
+ FUNCTION(sqlite_filestat, 1, 0, 0, filestatFunc ),
+#endif
FUNCTION(ltrim, 1, 1, 0, trimFunc ),
FUNCTION(ltrim, 2, 1, 0, trimFunc ),
FUNCTION(rtrim, 1, 2, 0, trimFunc ),
@@ -133245,6 +135097,21 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep,
groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
+#ifdef SQLITE_ENABLE_PERCENTILE
+ WAGGREGATE(median, 1, 0,0, percentStep,
+ percentFinal, percentValue, percentInverse,
+ SQLITE_INNOCUOUS|SQLITE_SELFORDER1),
+ WAGGREGATE(percentile, 2, 0x2,0, percentStep,
+ percentFinal, percentValue, percentInverse,
+ SQLITE_INNOCUOUS|SQLITE_SELFORDER1),
+ WAGGREGATE(percentile_cont, 2, 0,0, percentStep,
+ percentFinal, percentValue, percentInverse,
+ SQLITE_INNOCUOUS|SQLITE_SELFORDER1),
+ WAGGREGATE(percentile_disc, 2, 0x1,0, percentStep,
+ percentFinal, percentValue, percentInverse,
+ SQLITE_INNOCUOUS|SQLITE_SELFORDER1),
+#endif /* SQLITE_ENABLE_PERCENTILE */
+
LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE),
#ifdef SQLITE_CASE_SENSITIVE_LIKE
LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE),
@@ -134999,12 +136866,15 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
** by one slot and insert a new OP_TypeCheck where the current
** OP_MakeRecord is found */
VdbeOp *pPrev;
+ int p3;
sqlite3VdbeAppendP4(v, pTab, P4_TABLE);
pPrev = sqlite3VdbeGetLastOp(v);
assert( pPrev!=0 );
assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed );
pPrev->opcode = OP_TypeCheck;
- sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, pPrev->p3);
+ p3 = pPrev->p3;
+ pPrev->p3 = 0;
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, p3);
}else{
/* Insert an isolated OP_Typecheck */
sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol);
@@ -138739,6 +140609,10 @@ struct sqlite3_api_routines {
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
+ /* Version 3.51.0 and later */
+ int (*set_errmsg)(sqlite3*,int,const char*);
+ int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
+
};
/*
@@ -139074,6 +140948,9 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
+/* Version 3.51.0 and later */
+#define sqlite3_set_errmsg sqlite3_api->set_errmsg
+#define sqlite3_db_status64 sqlite3_api->db_status64
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
@@ -139597,7 +141474,10 @@ static const sqlite3_api_routines sqlite3Apis = {
sqlite3_get_clientdata,
sqlite3_set_clientdata,
/* Version 3.50.0 and later */
- sqlite3_setlk_timeout
+ sqlite3_setlk_timeout,
+ /* Version 3.51.0 and later */
+ sqlite3_set_errmsg,
+ sqlite3_db_status64
};
/* True if x is the directory separator character
@@ -141060,6 +142940,22 @@ static int integrityCheckResultRow(Vdbe *v){
}
/*
+** Should table pTab be skipped when doing an integrity_check?
+** Return true or false.
+**
+** If pObjTab is not null, the return true if pTab matches pObjTab.
+**
+** If pObjTab is null, then return true only if pTab is an imposter table.
+*/
+static int tableSkipIntegrityCheck(const Table *pTab, const Table *pObjTab){
+ if( pObjTab ){
+ return pTab!=pObjTab;
+ }else{
+ return (pTab->tabFlags & TF_Imposter)!=0;
+ }
+}
+
+/*
** Process a pragma statement.
**
** Pragmas are of this form:
@@ -142404,7 +144300,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
Table *pTab = sqliteHashData(x); /* Current table */
Index *pIdx; /* An index on pTab */
int nIdx; /* Number of indexes on pTab */
- if( pObjTab && pObjTab!=pTab ) continue;
+ if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue;
if( HasRowid(pTab) ) cnt++;
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; }
}
@@ -142417,7 +144313,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
Index *pIdx;
- if( pObjTab && pObjTab!=pTab ) continue;
+ if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue;
if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum;
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
aRoot[++cnt] = pIdx->tnum;
@@ -142448,7 +144344,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
int iTab = 0;
Table *pTab = sqliteHashData(x);
Index *pIdx;
- if( pObjTab && pObjTab!=pTab ) continue;
+ if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue;
if( HasRowid(pTab) ){
iTab = cnt++;
}else{
@@ -142484,7 +144380,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
int r2; /* Previous key for WITHOUT ROWID tables */
int mxCol; /* Maximum non-virtual column number */
- if( pObjTab && pObjTab!=pTab ) continue;
+ if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue;
if( !IsOrdinaryTable(pTab) ) continue;
if( isQuick || HasRowid(pTab) ){
pPk = 0;
@@ -142808,7 +144704,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
Table *pTab = sqliteHashData(x);
sqlite3_vtab *pVTab;
int a1;
- if( pObjTab && pObjTab!=pTab ) continue;
+ if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue;
if( IsOrdinaryTable(pTab) ) continue;
if( !IsVirtual(pTab) ) continue;
if( pTab->nCol<=0 ){
@@ -143040,6 +144936,8 @@ SQLITE_PRIVATE void sqlite3Pragma(
eMode = SQLITE_CHECKPOINT_RESTART;
}else if( sqlite3StrICmp(zRight, "truncate")==0 ){
eMode = SQLITE_CHECKPOINT_TRUNCATE;
+ }else if( sqlite3StrICmp(zRight, "noop")==0 ){
+ eMode = SQLITE_CHECKPOINT_NOOP;
}
}
pParse->nMem = 3;
@@ -144606,9 +146504,11 @@ static int sqlite3LockAndPrepare(
rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);
assert( rc==SQLITE_OK || *ppStmt==0 );
if( rc==SQLITE_OK || db->mallocFailed ) break;
- }while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY)
- || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) );
+ cnt++;
+ }while( (rc==SQLITE_ERROR_RETRY && ALWAYS(cnt<=SQLITE_MAX_PREPARE_RETRY))
+ || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt)==1) );
sqlite3BtreeLeaveAll(db);
+ assert( rc!=SQLITE_ERROR_RETRY );
rc = sqlite3ApiExit(db, rc);
assert( (rc&db->errMask)==rc );
db->busyHandler.nBusy = 0;
@@ -145223,7 +147123,7 @@ static int tableAndColumnIndex(
int iEnd, /* Last member of pSrc->a[] to check */
const char *zCol, /* Name of the column we are looking for */
int *piTab, /* Write index of pSrc->a[] here */
- int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */
+ int *piCol, /* Write index of pSrc->a[*piTab].pSTab->aCol[] here */
int bIgnoreHidden /* Ignore hidden columns */
){
int i; /* For looping over tables in pSrc */
@@ -145282,8 +147182,7 @@ SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
ExprSetVVAProperty(p, EP_NoReduce);
p->w.iJoin = iTable;
- if( p->op==TK_FUNCTION ){
- assert( ExprUseXList(p) );
+ if( ExprUseXList(p) ){
if( p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
@@ -145499,6 +147398,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn);
pRight->u3.pOn = 0;
pRight->fg.isOn = 1;
+ p->selFlags |= SF_OnToWhere;
}
}
return 0;
@@ -146385,7 +148285,10 @@ static void selectInnerLoop(
*/
SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){
int nExtra = (N+X)*(sizeof(CollSeq*)+1);
- KeyInfo *p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra);
+ KeyInfo *p;
+ assert( X>=0 );
+ if( NEVER(N+X>0xffff) ) return (KeyInfo*)sqlite3OomFault(db);
+ p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra);
if( p ){
p->aSortFlags = (u8*)&p->aColl[N+X];
p->nKeyField = (u16)N;
@@ -146952,6 +148855,10 @@ static void generateColumnTypes(
#endif
sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT);
}
+#else
+ UNUSED_PARAMETER(pParse);
+ UNUSED_PARAMETER(pTabList);
+ UNUSED_PARAMETER(pEList);
#endif /* !defined(SQLITE_OMIT_DECLTYPE) */
}
@@ -147871,8 +149778,10 @@ static int multiSelect(
int priorOp; /* The SRT_ operation to apply to prior selects */
Expr *pLimit; /* Saved values of p->nLimit */
int addr;
+ int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */
SelectDest uniondest;
+
testcase( p->op==TK_EXCEPT );
testcase( p->op==TK_UNION );
priorOp = SRT_Union;
@@ -147910,6 +149819,8 @@ static int multiSelect(
*/
if( p->op==TK_EXCEPT ){
op = SRT_Except;
+ emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab);
+ VdbeCoverage(v);
}else{
assert( p->op==TK_UNION );
op = SRT_Union;
@@ -147930,6 +149841,7 @@ static int multiSelect(
if( p->op==TK_UNION ){
p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
}
+ if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass);
sqlite3ExprDelete(db, p->pLimit);
p->pLimit = pLimit;
p->iLimit = 0;
@@ -147960,9 +149872,10 @@ static int multiSelect(
int tab1, tab2;
int iCont, iBreak, iStart;
Expr *pLimit;
- int addr;
+ int addr, iLimit, iOffset;
SelectDest intersectdest;
int r1;
+ int emptyBypass;
/* INTERSECT is different from the others since it requires
** two temporary tables. Hence it has its own case. Begin
@@ -147987,14 +149900,28 @@ static int multiSelect(
goto multi_select_end;
}
+ /* Initialize LIMIT counters before checking to see if the LHS
+ ** is empty, in case the jump is taken */
+ iBreak = sqlite3VdbeMakeLabel(pParse);
+ computeLimitRegisters(pParse, p, iBreak);
+ emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v);
+
/* Code the current SELECT into temporary table "tab2"
*/
addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
assert( p->addrOpenEphm[1] == -1 );
p->addrOpenEphm[1] = addr;
- p->pPrior = 0;
+
+ /* Disable prior SELECTs and the LIMIT counters during the computation
+ ** of the RHS select */
pLimit = p->pLimit;
+ iLimit = p->iLimit;
+ iOffset = p->iOffset;
+ p->pPrior = 0;
p->pLimit = 0;
+ p->iLimit = 0;
+ p->iOffset = 0;
+
intersectdest.iSDParm = tab2;
ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
sqlite3SelectOpName(p->op)));
@@ -148007,19 +149934,21 @@ static int multiSelect(
p->nSelectRow = pPrior->nSelectRow;
}
sqlite3ExprDelete(db, p->pLimit);
+
+ /* Reinstate the LIMIT counters prior to running the final intersect */
p->pLimit = pLimit;
+ p->iLimit = iLimit;
+ p->iOffset = iOffset;
/* Generate code to take the intersection of the two temporary
** tables.
*/
if( rc ) break;
assert( p->pEList );
- iBreak = sqlite3VdbeMakeLabel(pParse);
- iCont = sqlite3VdbeMakeLabel(pParse);
- computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
+ sqlite3VdbeAddOp1(v, OP_Rewind, tab1);
r1 = sqlite3GetTempReg(pParse);
iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1);
+ iCont = sqlite3VdbeMakeLabel(pParse);
sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0);
VdbeCoverage(v);
sqlite3ReleaseTempReg(pParse, r1);
@@ -148029,6 +149958,7 @@ static int multiSelect(
sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v);
sqlite3VdbeResolveLabel(v, iBreak);
sqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
+ sqlite3VdbeJumpHere(v, emptyBypass);
sqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
break;
}
@@ -148677,7 +150607,7 @@ static int multiSelectOrderBy(
** ## About "isOuterJoin":
**
** The isOuterJoin column indicates that the replacement will occur into a
-** position in the parent that NULL-able due to an OUTER JOIN. Either the
+** position in the parent that is NULL-able due to an OUTER JOIN. Either the
** target slot in the parent is the right operand of a LEFT JOIN, or one of
** the left operands of a RIGHT JOIN. In either case, we need to potentially
** bypass the substituted expression with OP_IfNullRow.
@@ -148707,6 +150637,7 @@ typedef struct SubstContext {
int iTable; /* Replace references to this table */
int iNewTable; /* New table number */
int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
+ int nSelDepth; /* Depth of sub-query recursion. Top==1 */
ExprList *pEList; /* Replacement expressions */
ExprList *pCList; /* Collation sequences for replacement expr */
} SubstContext;
@@ -148814,6 +150745,9 @@ static Expr *substExpr(
if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
pExpr->iTable = pSubst->iNewTable;
}
+ if( pExpr->op==TK_AGG_FUNCTION && pExpr->op2>=pSubst->nSelDepth ){
+ pExpr->op2--;
+ }
pExpr->pLeft = substExpr(pSubst, pExpr->pLeft);
pExpr->pRight = substExpr(pSubst, pExpr->pRight);
if( ExprUseXSelect(pExpr) ){
@@ -148851,6 +150785,7 @@ static void substSelect(
SrcItem *pItem;
int i;
if( !p ) return;
+ pSubst->nSelDepth++;
do{
substExprList(pSubst, p->pEList);
substExprList(pSubst, p->pGroupBy);
@@ -148868,6 +150803,7 @@ static void substSelect(
}
}
}while( doPrior && (p = p->pPrior)!=0 );
+ pSubst->nSelDepth--;
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
@@ -149479,7 +151415,7 @@ static int flattenSubquery(
** complete, since there may still exist Expr.pTab entries that
** refer to the subquery even after flattening. Ticket #3346.
**
- ** pSubitem->pTab is always non-NULL by test restrictions and tests above.
+ ** pSubitem->pSTab is always non-NULL by test restrictions and tests above.
*/
if( ALWAYS(pSubitem->pSTab!=0) ){
Table *pTabToDel = pSubitem->pSTab;
@@ -149509,17 +151445,12 @@ static int flattenSubquery(
pSub = pSub1;
for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){
int nSubSrc;
- u8 jointype = 0;
- u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ;
+ u8 jointype = pSubitem->fg.jointype;
assert( pSub!=0 );
pSubSrc = pSub->pSrc; /* FROM clause of subquery */
nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */
pSrc = pParent->pSrc; /* FROM clause of the outer query */
- if( pParent==p ){
- jointype = pSubitem->fg.jointype; /* First time through the loop */
- }
-
/* The subquery uses a single slot of the FROM clause of the outer
** query. If the subquery has more than one element in its FROM clause,
** then expand the outer query to make space for it to hold all elements
@@ -149539,6 +151470,7 @@ static int flattenSubquery(
pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1);
if( pSrc==0 ) break;
pParent->pSrc = pSrc;
+ pSubitem = &pSrc->a[iFrom];
}
/* Transfer the FROM clause terms from the subquery into the
@@ -149553,11 +151485,10 @@ static int flattenSubquery(
|| pItem->u4.zDatabase==0 );
if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
*pItem = pSubSrc->a[i];
- pItem->fg.jointype |= ltorj;
+ pItem->fg.jointype |= (jointype & JT_LTORJ);
memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
}
- pSrc->a[iFrom].fg.jointype &= JT_LTORJ;
- pSrc->a[iFrom].fg.jointype |= jointype | ltorj;
+ pSubitem->fg.jointype |= jointype;
/* Now begin substituting subquery result set expressions for
** references to the iParent in the outer query.
@@ -149609,6 +151540,7 @@ static int flattenSubquery(
x.iTable = iParent;
x.iNewTable = iNewParent;
x.isOuterJoin = isOuterJoin;
+ x.nSelDepth = 0;
x.pEList = pSub->pEList;
x.pCList = findLeftmostExprlist(pSub);
substSelect(&x, pParent, 0);
@@ -150194,6 +152126,7 @@ static int pushDownWhereTerms(
x.iTable = pSrc->iCursor;
x.iNewTable = pSrc->iCursor;
x.isOuterJoin = 0;
+ x.nSelDepth = 0;
x.pEList = pSubq->pEList;
x.pCList = findLeftmostExprlist(pSubq);
pNew = substExpr(&x, pNew);
@@ -150591,7 +152524,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
** CTE expression, through routine checks to see if the reference is
** a recursive reference to the CTE.
**
-** If pFrom matches a CTE according to either of these two above, pFrom->pTab
+** If pFrom matches a CTE according to either of these two above, pFrom->pSTab
** and other fields are populated accordingly.
**
** Return 0 if no match is found.
@@ -151629,6 +153562,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
if( pFunc->bOBPayload ){
/* extra columns for the function arguments */
assert( ExprUseXList(pFunc->pFExpr) );
+ assert( pFunc->pFExpr->x.pList!=0 );
nExtra += pFunc->pFExpr->x.pList->nExpr;
}
if( pFunc->bUseSubtype ){
@@ -152219,6 +154153,193 @@ static int fromClauseTermCanBeCoroutine(
}
/*
+** Argument pWhere is the WHERE clause belonging to SELECT statement p. This
+** function attempts to transform expressions of the form:
+**
+** EXISTS (SELECT ...)
+**
+** into joins. For example, given
+**
+** CREATE TABLE sailors(sid INTEGER PRIMARY KEY, name TEXT);
+** CREATE TABLE reserves(sid INT, day DATE, PRIMARY KEY(sid, day));
+**
+** SELECT name FROM sailors AS S WHERE EXISTS (
+** SELECT * FROM reserves AS R WHERE S.sid = R.sid AND R.day = '2022-10-25'
+** );
+**
+** the SELECT statement may be transformed as follows:
+**
+** SELECT name FROM sailors AS S, reserves AS R
+** WHERE S.sid = R.sid AND R.day = '2022-10-25';
+**
+** **Approximately**. Really, we have to ensure that the FROM-clause term
+** that was formerly inside the EXISTS is only executed once. This is handled
+** by setting the SrcItem.fg.fromExists flag, which then causes code in
+** the where.c file to exit the corresponding loop after the first successful
+** match (if any).
+*/
+static SQLITE_NOINLINE void existsToJoin(
+ Parse *pParse, /* Parsing context */
+ Select *p, /* The SELECT statement being optimized */
+ Expr *pWhere /* part of the WHERE clause currently being examined */
+){
+ if( pParse->nErr==0
+ && pWhere!=0
+ && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON)
+ && ALWAYS(p->pSrc!=0)
+ && p->pSrc->nSrc<BMS
+ ){
+ if( pWhere->op==TK_AND ){
+ Expr *pRight = pWhere->pRight;
+ existsToJoin(pParse, p, pWhere->pLeft);
+ existsToJoin(pParse, p, pRight);
+ }
+ else if( pWhere->op==TK_EXISTS ){
+ Select *pSub = pWhere->x.pSelect;
+ Expr *pSubWhere = pSub->pWhere;
+ if( pSub->pSrc->nSrc==1
+ && (pSub->selFlags & SF_Aggregate)==0
+ && !pSub->pSrc->a[0].fg.isSubquery
+ && pSub->pLimit==0
+ ){
+ memset(pWhere, 0, sizeof(*pWhere));
+ pWhere->op = TK_INTEGER;
+ pWhere->u.iValue = 1;
+ ExprSetProperty(pWhere, EP_IntValue);
+
+ assert( p->pWhere!=0 );
+ pSub->pSrc->a[0].fg.fromExists = 1;
+ pSub->pSrc->a[0].fg.jointype |= JT_CROSS;
+ p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc);
+ if( pSubWhere ){
+ p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere);
+ pSub->pWhere = 0;
+ }
+ pSub->pSrc = 0;
+ sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub);
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100000 ){
+ TREETRACE(0x100000,pParse,p,
+ ("After EXISTS-to-JOIN optimization:\n"));
+ sqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+ existsToJoin(pParse, p, pSubWhere);
+ }
+ }
+ }
+}
+
+/*
+** Type used for Walker callbacks by selectCheckOnClauses().
+*/
+typedef struct CheckOnCtx CheckOnCtx;
+struct CheckOnCtx {
+ SrcList *pSrc; /* SrcList for this context */
+ int iJoin; /* Cursor numbers must be =< than this */
+ CheckOnCtx *pParent; /* Parent context */
+};
+
+/*
+** True if the SrcList passed as the only argument contains at least
+** one RIGHT or FULL JOIN. False otherwise.
+*/
+#define hasRightJoin(pSrc) (((pSrc)->a[0].fg.jointype & JT_LTORJ)!=0)
+
+/*
+** The xExpr callback for the search of invalid ON clause terms.
+*/
+static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){
+ CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx;
+
+ /* Check if pExpr is root or near-root of an ON clause constraint that needs
+ ** to be checked to ensure that it does not refer to tables in its FROM
+ ** clause to the right of itself. i.e. it is either:
+ **
+ ** + an ON clause on an OUTER join, or
+ ** + an ON clause on an INNER join within a FROM that features at
+ ** least one RIGHT or FULL join.
+ */
+ if( (ExprHasProperty(pExpr, EP_OuterON))
+ || (ExprHasProperty(pExpr, EP_InnerON) && hasRightJoin(pCtx->pSrc))
+ ){
+ /* If CheckOnCtx.iJoin is already set, then fall through and process
+ ** this expression node as normal. Or, if CheckOnCtx.iJoin is still 0,
+ ** set it to the cursor number of the RHS of the join to which this
+ ** ON expression was attached and then iterate through the entire
+ ** expression. */
+ assert( pCtx->iJoin==0 || pCtx->iJoin==pExpr->w.iJoin );
+ if( pCtx->iJoin==0 ){
+ pCtx->iJoin = pExpr->w.iJoin;
+ sqlite3WalkExprNN(pWalker, pExpr);
+ pCtx->iJoin = 0;
+ return WRC_Prune;
+ }
+ }
+
+ if( pExpr->op==TK_COLUMN ){
+ /* A column expression. Find the SrcList (if any) to which it refers.
+ ** Then, if CheckOnCtx.iJoin indicates that this expression is part of an
+ ** ON clause from that SrcList (i.e. if iJoin is non-zero), check that it
+ ** does not refer to a table to the right of CheckOnCtx.iJoin. */
+ do {
+ SrcList *pSrc = pCtx->pSrc;
+ int iTab = pExpr->iTable;
+ if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){
+ if( pCtx->iJoin && iTab>pCtx->iJoin ){
+ sqlite3ErrorMsg(pWalker->pParse,
+ "ON clause references tables to its right");
+ return WRC_Abort;
+ }
+ break;
+ }
+ pCtx = pCtx->pParent;
+ }while( pCtx );
+ }
+ return WRC_Continue;
+}
+
+/*
+** The xSelect callback for the search of invalid ON clause terms.
+*/
+static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){
+ CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx;
+ if( pSelect->pSrc==pCtx->pSrc || pSelect->pSrc->nSrc==0 ){
+ return WRC_Continue;
+ }else{
+ CheckOnCtx sCtx;
+ memset(&sCtx, 0, sizeof(sCtx));
+ sCtx.pSrc = pSelect->pSrc;
+ sCtx.pParent = pCtx;
+ pWalker->u.pCheckOnCtx = &sCtx;
+ sqlite3WalkSelect(pWalker, pSelect);
+ pWalker->u.pCheckOnCtx = pCtx;
+ pSelect->selFlags &= ~SF_OnToWhere;
+ return WRC_Prune;
+ }
+}
+
+/*
+** Check all ON clauses in pSelect to verify that they do not reference
+** columns to the right.
+*/
+static void selectCheckOnClauses(Parse *pParse, Select *pSelect){
+ Walker w;
+ CheckOnCtx sCtx;
+ assert( pSelect->selFlags & SF_OnToWhere );
+ assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 );
+ memset(&w, 0, sizeof(w));
+ w.pParse = pParse;
+ w.xExprCallback = selectCheckOnClausesExpr;
+ w.xSelectCallback = selectCheckOnClausesSelect;
+ w.u.pCheckOnCtx = &sCtx;
+ memset(&sCtx, 0, sizeof(sCtx));
+ sCtx.pSrc = pSelect->pSrc;
+ sqlite3WalkExprNN(&w, pSelect->pWhere);
+ pSelect->selFlags &= ~SF_OnToWhere;
+}
+
+/*
** Generate byte-code for the SELECT statement given in the p argument.
**
** The results are returned according to the SelectDest structure.
@@ -152345,6 +154466,18 @@ SQLITE_PRIVATE int sqlite3Select(
}
#endif
+ /* If the SELECT statement contains ON clauses that were moved into
+ ** the WHERE clause, go through and verify that none of the terms
+ ** in the ON clauses reference tables to the right of the ON clause.
+ ** Do this now, after name resolution, but before query flattening
+ */
+ if( p->selFlags & SF_OnToWhere ){
+ selectCheckOnClauses(pParse, p);
+ if( pParse->nErr ){
+ goto select_end;
+ }
+ }
+
/* If the SF_UFSrcCheck flag is set, then this function is being called
** as part of populating the temp table for an UPDATE...FROM statement.
** In this case, it is an error if the target object (pSrc->a[0]) name
@@ -152586,6 +154719,13 @@ SQLITE_PRIVATE int sqlite3Select(
}
#endif
+ /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt
+ ** to change it into a join. */
+ if( pParse->bHasExists && OptimizationEnabled(db,SQLITE_ExistsToJoin) ){
+ existsToJoin(pParse, p, p->pWhere);
+ pTabList = p->pSrc;
+ }
+
/* Do the WHERE-clause constant propagation optimization if this is
** a join. No need to spend time on this operation for non-join queries
** as the equivalent optimization will be handled by query planner in
@@ -153373,12 +155513,12 @@ SQLITE_PRIVATE int sqlite3Select(
** for the next GROUP BY batch.
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
- VdbeComment((v, "output one row"));
+ VdbeComment((v, "output one row of %d", p->selId));
sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr);
sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v);
VdbeComment((v, "check abort flag"));
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
- VdbeComment((v, "reset accumulator"));
+ VdbeComment((v, "reset accumulator %d", p->selId));
/* Update the aggregate accumulators based on the content of
** the current row
@@ -153386,7 +155526,7 @@ SQLITE_PRIVATE int sqlite3Select(
sqlite3VdbeJumpHere(v, addr1);
updateAccumulator(pParse, iUseFlag, pAggInfo, eDist);
sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
- VdbeComment((v, "indicate data in accumulator"));
+ VdbeComment((v, "indicate data in accumulator %d", p->selId));
/* End of the loop
*/
@@ -153403,7 +155543,7 @@ SQLITE_PRIVATE int sqlite3Select(
/* Output the final row of result
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
- VdbeComment((v, "output final row"));
+ VdbeComment((v, "output final row of %d", p->selId));
/* Jump over the subroutines
*/
@@ -153424,7 +155564,7 @@ SQLITE_PRIVATE int sqlite3Select(
addrOutputRow = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2);
VdbeCoverage(v);
- VdbeComment((v, "Groupby result generator entry point"));
+ VdbeComment((v, "Groupby result generator entry point %d", p->selId));
sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
finalizeAggFunctions(pParse, pAggInfo);
sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL);
@@ -153432,14 +155572,14 @@ SQLITE_PRIVATE int sqlite3Select(
&sDistinct, pDest,
addrOutputRow+1, addrSetAbort);
sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
- VdbeComment((v, "end groupby result generator"));
+ VdbeComment((v, "end groupby result generator %d", p->selId));
/* Generate a subroutine that will reset the group-by accumulator
*/
sqlite3VdbeResolveLabel(v, addrReset);
resetAccumulator(pParse, pAggInfo);
sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag);
- VdbeComment((v, "indicate accumulator empty"));
+ VdbeComment((v, "indicate accumulator %d empty", p->selId));
sqlite3VdbeAddOp1(v, OP_Return, regReset);
if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){
@@ -154903,7 +157043,10 @@ static void codeReturningTrigger(
Returning *pReturning;
Select sSelect;
SrcList *pFrom;
- u8 fromSpace[SZ_SRCLIST_1];
+ union {
+ SrcList sSrc;
+ u8 fromSpace[SZ_SRCLIST_1];
+ } uSrc;
assert( v!=0 );
if( !pParse->bReturning ){
@@ -154919,8 +157062,8 @@ static void codeReturningTrigger(
return;
}
memset(&sSelect, 0, sizeof(sSelect));
- pFrom = (SrcList*)fromSpace;
- memset(pFrom, 0, SZ_SRCLIST_1);
+ memset(&uSrc, 0, sizeof(uSrc));
+ pFrom = &uSrc.sSrc;
sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0);
sSelect.pSrc = pFrom;
pFrom->nSrc = 1;
@@ -157327,7 +159470,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum(
saved_nChange = db->nChange;
saved_nTotalChange = db->nTotalChange;
saved_mTrace = db->mTrace;
- db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_Comments;
+ db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_Comments
+ | SQLITE_AttachCreate | SQLITE_AttachWrite;
db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder
| SQLITE_Defensive | SQLITE_CountRows);
@@ -159030,6 +161174,7 @@ struct WhereLevel {
int iTabCur; /* The VDBE cursor used to access the table */
int iIdxCur; /* The VDBE cursor used to access pIdx */
int addrBrk; /* Jump here to break out of the loop */
+ int addrHalt; /* Abort the query due to empty table or similar */
int addrNxt; /* Jump here to start the next IN combination */
int addrSkip; /* Jump here for next iteration of skip-scan */
int addrCont; /* Jump here to continue with the next loop cycle */
@@ -159235,6 +161380,9 @@ struct WhereTerm {
u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */
int iParent; /* Disable pWC->a[iParent] when this term disabled */
int leftCursor; /* Cursor number of X in "X <op> <expr>" */
+#ifdef SQLITE_DEBUG
+ int iTerm; /* Which WhereTerm is this, for debug purposes */
+#endif
union {
struct {
int leftColumn; /* Column number of X in "X <op> <expr>" */
@@ -159727,7 +161875,6 @@ SQLITE_PRIVATE void sqlite3WhereAddExplainText(
#endif
{
VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr);
-
SrcItem *pItem = &pTabList->a[pLevel->iFrom];
sqlite3 *db = pParse->db; /* Database handle */
int isSearch; /* True for a SEARCH. False for SCAN. */
@@ -159750,7 +161897,10 @@ SQLITE_PRIVATE void sqlite3WhereAddExplainText(
sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
str.printfFlags = SQLITE_PRINTF_INTERNAL;
- sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem);
+ sqlite3_str_appendf(&str, "%s %S%s",
+ isSearch ? "SEARCH" : "SCAN",
+ pItem,
+ pItem->fg.fromExists ? " EXISTS" : "");
if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
const char *zFmt = 0;
Index *pIdx;
@@ -160994,6 +163144,7 @@ static SQLITE_NOINLINE void filterPullDown(
int addrNxt, /* Jump here to bypass inner loops */
Bitmask notReady /* Loops that are not ready */
){
+ int saved_addrBrk;
while( ++iLevel < pWInfo->nLevel ){
WhereLevel *pLevel = &pWInfo->a[iLevel];
WhereLoop *pLoop = pLevel->pWLoop;
@@ -161002,7 +163153,7 @@ static SQLITE_NOINLINE void filterPullDown(
/* ,--- Because sqlite3ConstructBloomFilter() has will not have set
** vvvvv--' pLevel->regFilter if this were true. */
if( NEVER(pLoop->prereq & notReady) ) continue;
- assert( pLevel->addrBrk==0 );
+ saved_addrBrk = pLevel->addrBrk;
pLevel->addrBrk = addrNxt;
if( pLoop->wsFlags & WHERE_IPK ){
WhereTerm *pTerm = pLoop->aLTerm[0];
@@ -161032,7 +163183,7 @@ static SQLITE_NOINLINE void filterPullDown(
VdbeCoverage(pParse->pVdbe);
}
pLevel->regFilter = 0;
- pLevel->addrBrk = 0;
+ pLevel->addrBrk = saved_addrBrk;
}
}
@@ -161079,7 +163230,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3 *db; /* Database connection */
SrcItem *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
- int addrHalt; /* addrBrk for the outermost loop */
int addrCont; /* Jump here to continue with next cycle */
int iRowidReg = 0; /* Rowid is stored in this register, if not zero */
int iReleaseReg = 0; /* Temp register to free before returning */
@@ -161123,7 +163273,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** there are no IN operators in the constraints, the "addrNxt" label
** is the same as "addrBrk".
*/
- addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse);
+ addrBrk = pLevel->addrNxt = pLevel->addrBrk;
addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse);
/* If this is the right table of a LEFT OUTER JOIN, allocate and
@@ -161139,14 +163289,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
VdbeComment((v, "init LEFT JOIN match flag"));
}
- /* Compute a safe address to jump to if we discover that the table for
- ** this loop is empty and can never contribute content. */
- for(j=iLevel; j>0; j--){
- if( pWInfo->a[j].iLeftJoin ) break;
- if( pWInfo->a[j].pRJ ) break;
- }
- addrHalt = pWInfo->a[j].addrBrk;
-
/* Special case of a FROM clause subquery implemented as a co-routine */
if( pTabItem->fg.viaCoroutine ){
int regYield;
@@ -161385,7 +163527,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
VdbeCoverageIf(v, pX->op==TK_GE);
sqlite3ReleaseTempReg(pParse, rTemp);
}else{
- sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt);
+ sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, pLevel->addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
}
@@ -161425,36 +163567,36 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL);
}
}else if( pLoop->wsFlags & WHERE_INDEXED ){
- /* Case 4: A scan using an index.
+ /* Case 4: Search using an index.
**
- ** The WHERE clause may contain zero or more equality
- ** terms ("==" or "IN" operators) that refer to the N
- ** left-most columns of the index. It may also contain
- ** inequality constraints (>, <, >= or <=) on the indexed
- ** column that immediately follows the N equalities. Only
- ** the right-most column can be an inequality - the rest must
- ** use the "==" and "IN" operators. For example, if the
- ** index is on (x,y,z), then the following clauses are all
- ** optimized:
+ ** The WHERE clause may contain zero or more equality
+ ** terms ("==" or "IN" or "IS" operators) that refer to the N
+ ** left-most columns of the index. It may also contain
+ ** inequality constraints (>, <, >= or <=) on the indexed
+ ** column that immediately follows the N equalities. Only
+ ** the right-most column can be an inequality - the rest must
+ ** use the "==", "IN", or "IS" operators. For example, if the
+ ** index is on (x,y,z), then the following clauses are all
+ ** optimized:
**
- ** x=5
- ** x=5 AND y=10
- ** x=5 AND y<10
- ** x=5 AND y>5 AND y<10
- ** x=5 AND y=5 AND z<=10
+ ** x=5
+ ** x=5 AND y=10
+ ** x=5 AND y<10
+ ** x=5 AND y>5 AND y<10
+ ** x=5 AND y=5 AND z<=10
**
- ** The z<10 term of the following cannot be used, only
- ** the x=5 term:
+ ** The z<10 term of the following cannot be used, only
+ ** the x=5 term:
**
- ** x=5 AND z<10
+ ** x=5 AND z<10
**
- ** N may be zero if there are inequality constraints.
- ** If there are no inequality constraints, then N is at
- ** least one.
+ ** N may be zero if there are inequality constraints.
+ ** If there are no inequality constraints, then N is at
+ ** least one.
**
- ** This case is also used when there are no WHERE clause
- ** constraints but an index is selected anyway, in order
- ** to force the output order to conform to an ORDER BY.
+ ** This case is also used when there are no WHERE clause
+ ** constraints but an index is selected anyway, in order
+ ** to force the output order to conform to an ORDER BY.
*/
static const u8 aStartOp[] = {
0,
@@ -162180,7 +164322,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
codeCursorHint(pTabItem, pWInfo, pLevel, 0);
pLevel->op = aStep[bRev];
pLevel->p1 = iCur;
- pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt);
+ pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev],iCur,pLevel->addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP;
@@ -162452,7 +164594,10 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
WhereLoop *pLoop = pLevel->pWLoop;
SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
SrcList *pFrom;
- u8 fromSpace[SZ_SRCLIST_1];
+ union {
+ SrcList sSrc;
+ u8 fromSpace[SZ_SRCLIST_1];
+ } uSrc;
Bitmask mAll = 0;
int k;
@@ -162496,7 +164641,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
sqlite3ExprDup(pParse->db, pTerm->pExpr, 0));
}
}
- pFrom = (SrcList*)fromSpace;
+ pFrom = &uSrc.sSrc;
pFrom->nSrc = 1;
pFrom->nAlloc = 1;
memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem));
@@ -163491,7 +165636,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){
if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */
assert( pSrc!=0 );
if( pExpr->op==TK_IS
- && pSrc->nSrc
+ && pSrc->nSrc>=2
&& (pSrc->a[0].fg.jointype & JT_LTORJ)!=0
){
return 0; /* (4) */
@@ -163667,6 +165812,9 @@ static void exprAnalyze(
}
assert( pWC->nTerm > idxTerm );
pTerm = &pWC->a[idxTerm];
+#ifdef SQLITE_DEBUG
+ pTerm->iTerm = idxTerm;
+#endif
pMaskSet = &pWInfo->sMaskSet;
pExpr = pTerm->pExpr;
assert( pExpr!=0 ); /* Because malloc() has not failed */
@@ -163710,21 +165858,7 @@ static void exprAnalyze(
prereqAll |= x;
extraRight = x-1; /* ON clause terms may not be used with an index
** on left table of a LEFT JOIN. Ticket #3015 */
- if( (prereqAll>>1)>=x ){
- sqlite3ErrorMsg(pParse, "ON clause references tables to its right");
- return;
- }
}else if( (prereqAll>>1)>=x ){
- /* The ON clause of an INNER JOIN references a table to its right.
- ** Most other SQL database engines raise an error. But SQLite versions
- ** 3.0 through 3.38 just put the ON clause constraint into the WHERE
- ** clause and carried on. Beginning with 3.39, raise an error only
- ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite
- ** more like other systems, and also preserves legacy. */
- if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
- sqlite3ErrorMsg(pParse, "ON clause references tables to its right");
- return;
- }
ExprClearProperty(pExpr, EP_InnerON);
}
}
@@ -164081,7 +166215,7 @@ static void exprAnalyze(
idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
pNewTerm = &pWC->a[idxNew];
- pNewTerm->prereqRight = prereqExpr;
+ pNewTerm->prereqRight = prereqExpr | extraRight;
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.x.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_AUX;
@@ -164192,7 +166326,7 @@ static void whereAddLimitExpr(
**
** 1. The SELECT statement has a LIMIT clause, and
** 2. The SELECT statement is not an aggregate or DISTINCT query, and
-** 3. The SELECT statement has exactly one object in its from clause, and
+** 3. The SELECT statement has exactly one object in its FROM clause, and
** that object is a virtual table, and
** 4. There are no terms in the WHERE clause that will not be passed
** to the virtual table xBestIndex method.
@@ -164229,8 +166363,22 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Selec
** (leftCursor==iCsr) test below. */
continue;
}
- if( pWC->a[ii].leftCursor!=iCsr ) return;
- if( pWC->a[ii].prereqRight!=0 ) return;
+ if( pWC->a[ii].leftCursor==iCsr && pWC->a[ii].prereqRight==0 ) continue;
+
+ /* If this term has a parent with exactly one child, and the parent will
+ ** be passed through to xBestIndex, then this term can be ignored. */
+ if( pWC->a[ii].iParent>=0 ){
+ WhereTerm *pParent = &pWC->a[ pWC->a[ii].iParent ];
+ if( pParent->leftCursor==iCsr
+ && pParent->prereqRight==0
+ && pParent->nChild==1
+ ){
+ continue;
+ }
+ }
+
+ /* This term will not be passed through. Do not add a LIMIT clause. */
+ return;
}
/* Check condition (5). Return early if it is not met. */
@@ -164894,11 +167042,11 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
pScan->pWC = pWC;
pScan->k = k+1;
#ifdef WHERETRACE_ENABLED
- if( sqlite3WhereTrace & 0x20000 ){
+ if( (sqlite3WhereTrace & 0x20000)!=0 && pScan->nEquiv>1 ){
int ii;
- sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d",
- pTerm, pScan->nEquiv);
- for(ii=0; ii<pScan->nEquiv; ii++){
+ sqlite3DebugPrintf("EQUIVALENT TO {%d:%d} (due to TERM-%d):",
+ pScan->aiCur[0], pScan->aiColumn[0], pTerm->iTerm);
+ for(ii=1; ii<pScan->nEquiv; ii++){
sqlite3DebugPrintf(" {%d:%d}",
pScan->aiCur[ii], pScan->aiColumn[ii]);
}
@@ -165669,7 +167817,9 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
VdbeCoverage(v);
VdbeComment((v, "next row of %s", pSrc->pSTab->zName));
}else{
- addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
+ assert( pLevel->addrHalt );
+ addrTop = sqlite3VdbeAddOp2(v, OP_Rewind,pLevel->iTabCur,pLevel->addrHalt);
+ VdbeCoverage(v);
}
if( pPartial ){
iContinue = sqlite3VdbeMakeLabel(pParse);
@@ -165697,11 +167847,14 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
pSrc->u4.pSubq->regResult, pLevel->iIdxCur);
sqlite3VdbeGoto(v, addrTop);
pSrc->fg.viaCoroutine = 0;
+ sqlite3VdbeJumpHere(v, addrTop);
}else{
sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v);
sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX);
+ if( (pSrc->fg.jointype & JT_LEFT)!=0 ){
+ sqlite3VdbeJumpHere(v, addrTop);
+ }
}
- sqlite3VdbeJumpHere(v, addrTop);
sqlite3ReleaseTempReg(pParse, regRecord);
/* Jump here when skipping the initialization */
@@ -166853,6 +169006,7 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
}else{
sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor);
}
+ iTerm = pTerm->iTerm = MAX(iTerm,pTerm->iTerm);
sqlite3DebugPrintf(
"TERM-%-3d %p %s %-12s op=%03x wtFlags=%04x",
iTerm, pTerm, zType, zLeft, pTerm->eOperator, pTerm->wtFlags);
@@ -167994,6 +170148,7 @@ static int whereLoopAddBtreeIndex(
&& pProbe->hasStat1!=0
&& OptimizationEnabled(db, SQLITE_SkipScan)
&& pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */
+ && pSrc->fg.fromExists==0
&& (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK
){
LogEst nIter;
@@ -169565,6 +171720,10 @@ static i8 wherePathSatisfiesOrderBy(
&& ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY)
){
obSat = obDone;
+ }else{
+ /* No further ORDER BY terms may be matched. So this call should
+ ** return >=0, not -1. Clear isOrderDistinct to ensure it does so. */
+ isOrderDistinct = 0;
}
break;
}
@@ -170310,8 +172469,15 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** mxChoice best-so-far paths.
**
** First look for an existing path among best-so-far paths
- ** that covers the same set of loops and has the same isOrdered
- ** setting as the current path candidate.
+ ** that:
+ ** (1) covers the same set of loops, and
+ ** (2) has a compatible isOrdered value.
+ **
+ ** "Compatible isOrdered value" means either
+ ** (A) both have isOrdered==-1, or
+ ** (B) both have isOrder>=0, or
+ ** (C) ordering does not matter because this is the last round
+ ** of the solver.
**
** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent
** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range
@@ -170320,7 +172486,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
testcase( nTo==0 );
for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){
if( pTo->maskLoop==maskNew
- && ((pTo->isOrdered^isOrdered)&0x80)==0
+ && ( ((pTo->isOrdered^isOrdered)&0x80)==0 || iLoop==nLoop-1 )
){
testcase( jj==nTo-1 );
break;
@@ -170475,11 +172641,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
return SQLITE_ERROR;
}
- /* Find the lowest cost path. pFrom will be left pointing to that path */
+ /* Only one path is available, which is the best path */
+ assert( nFrom==1 );
pFrom = aFrom;
- for(ii=1; ii<nFrom; ii++){
- if( pFrom->rCost>aFrom[ii].rCost ) pFrom = &aFrom[ii];
- }
+
assert( pWInfo->nLevel==nLoop );
/* Load the lowest cost path into pWInfo */
for(iLoop=0; iLoop<nLoop; iLoop++){
@@ -170612,7 +172777,10 @@ static SQLITE_NOINLINE void whereInterstageHeuristic(WhereInfo *pWInfo){
for(i=0; i<pWInfo->nLevel; i++){
WhereLoop *p = pWInfo->a[i].pWLoop;
if( p==0 ) break;
- if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue;
+ if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ){
+ /* Treat a vtab scan as similar to a full-table scan */
+ break;
+ }
if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){
u8 iTab = p->iTab;
WhereLoop *pLoop;
@@ -171550,6 +173718,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
pTab = pTabItem->pSTab;
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
pLoop = pLevel->pWLoop;
+ pLevel->addrBrk = sqlite3VdbeMakeLabel(pParse);
+ if( ii==0 || (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){
+ pLevel->addrHalt = pLevel->addrBrk;
+ }else if( pWInfo->a[ii-1].pRJ ){
+ pLevel->addrHalt = pWInfo->a[ii-1].addrBrk;
+ }else{
+ pLevel->addrHalt = pWInfo->a[ii-1].addrHalt;
+ }
if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){
/* Do nothing */
}else
@@ -171601,6 +173777,13 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0,
(const u8*)&pTabItem->colUsed, P4_INT64);
#endif
+ if( ii>=2
+ && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0
+ && pLevel->addrHalt==pWInfo->a[0].addrHalt
+ ){
+ sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pWInfo->iBreak);
+ VdbeCoverage(v);
+ }
}else{
sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
}
@@ -171857,6 +174040,9 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
}
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
+ if( pTabList->a[pLevel->iFrom].fg.fromExists ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ }
/* The common case: Advance to the next row */
if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
@@ -174707,7 +176893,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){
**
** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING
**
-** ... loop started by sqlite3WhereBegin() ...
+** ... loop started by sqlite3WhereBegin() ...
** if( new partition ){
** Gosub flush
** }
@@ -175225,6 +177411,12 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1);
}else{
assert( pMWin->eEnd==TK_FOLLOWING );
+ /* assert( regStart>=0 );
+ ** regEnd = regEnd - regStart;
+ ** regStart = 0; */
+ sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd);
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, regStart);
+
addrStart = sqlite3VdbeCurrentAddr(v);
addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1);
addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1);
@@ -181620,8 +183812,9 @@ static int analyzeFilterKeyword(const unsigned char *z, int lastToken){
** Return the length (in bytes) of the token that begins at z[0].
** Store the token type in *tokenType before returning.
*/
-SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
- int i, c;
+SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *z, int *tokenType){
+ i64 i;
+ int c;
switch( aiClass[*z] ){ /* Switch on the character-class of the first byte
** of the token. See the comment on the CC_ defines
** above. */
@@ -181949,7 +184142,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){
int nErr = 0; /* Number of errors encountered */
void *pEngine; /* The LEMON-generated LALR(1) parser */
- int n = 0; /* Length of the next token token */
+ i64 n = 0; /* Length of the next token token */
int tokenType; /* type of the next token */
int lastTokenParsed = -1; /* type of the previous token */
sqlite3 *db = pParse->db; /* The database connection */
@@ -182052,13 +184245,13 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){
}else if( tokenType!=TK_QNUMBER ){
Token x;
x.z = zSql;
- x.n = n;
+ x.n = (u32)n;
sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x);
break;
}
}
pParse->sLastToken.z = zSql;
- pParse->sLastToken.n = n;
+ pParse->sLastToken.n = (u32)n;
sqlite3Parser(pEngine, tokenType, pParse->sLastToken);
lastTokenParsed = tokenType;
zSql += n;
@@ -182134,7 +184327,7 @@ SQLITE_PRIVATE char *sqlite3Normalize(
){
sqlite3 *db; /* The database connection */
int i; /* Next unread byte of zSql[] */
- int n; /* length of current token */
+ i64 n; /* length of current token */
int tokenType; /* type of current token */
int prevType = 0; /* Previous non-whitespace token */
int nParen; /* Number of nested levels of parentheses */
@@ -182712,9 +184905,6 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = {
sqlite3DbstatRegister,
#endif
sqlite3TestExtInit,
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON)
- sqlite3JsonTableFunctions,
-#endif
#ifdef SQLITE_ENABLE_STMTVTAB
sqlite3StmtVtabInit,
#endif
@@ -184170,6 +186360,9 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
case SQLITE_OK: zName = "SQLITE_OK"; break;
case SQLITE_ERROR: zName = "SQLITE_ERROR"; break;
case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break;
+ case SQLITE_ERROR_RETRY: zName = "SQLITE_ERROR_RETRY"; break;
+ case SQLITE_ERROR_MISSING_COLLSEQ:
+ zName = "SQLITE_ERROR_MISSING_COLLSEQ"; break;
case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break;
case SQLITE_PERM: zName = "SQLITE_PERM"; break;
case SQLITE_ABORT: zName = "SQLITE_ABORT"; break;
@@ -185352,6 +187545,29 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
}
/*
+** Set the error code and error message associated with the database handle.
+**
+** This routine is intended to be called by outside extensions (ex: the
+** Session extension). Internal logic should invoke sqlite3Error() or
+** sqlite3ErrorWithMsg() directly.
+*/
+SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){
+ int rc = SQLITE_OK;
+ if( !sqlite3SafetyCheckSickOrOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+ sqlite3_mutex_enter(db->mutex);
+ if( zMsg ){
+ sqlite3ErrorWithMsg(db, errcode, "%s", zMsg);
+ }else{
+ sqlite3Error(db, errcode);
+ }
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
** Return the byte offset of the most recent error
*/
SQLITE_API int sqlite3_error_offset(sqlite3 *db){
@@ -187175,13 +189391,15 @@ SQLITE_API int sqlite3_test_control(int op, ...){
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum);
+ /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, mode, tnum);
**
** This test control is used to create imposter tables. "db" is a pointer
** to the database connection. dbName is the database name (ex: "main" or
- ** "temp") which will receive the imposter. "onOff" turns imposter mode on
- ** or off. "tnum" is the root page of the b-tree to which the imposter
- ** table should connect.
+ ** "temp") which will receive the imposter. "mode" turns imposter mode on
+ ** or off. mode==0 means imposter mode is off. mode==1 means imposter mode
+ ** is on. mode==2 means imposter mode is on but results in an imposter
+ ** table that is read-only unless writable_schema is on. "tnum" is the
+ ** root page of the b-tree to which the imposter table should connect.
**
** Enable imposter mode only when the schema has already been parsed. Then
** run a single CREATE TABLE statement to construct the imposter table in
@@ -188418,6 +190636,13 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){
#ifndef _FTSINT_H
#define _FTSINT_H
+/*
+** Activate assert() only if SQLITE_TEST is enabled.
+*/
+#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
+# define NDEBUG 1
+#endif
+
/* #include <assert.h> */
/* #include <stdlib.h> */
/* #include <stddef.h> */
@@ -188425,10 +190650,6 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){
/* #include <string.h> */
/* #include <stdarg.h> */
-#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
-# define NDEBUG 1
-#endif
-
/* FTS3/FTS4 require virtual tables */
#ifdef SQLITE_OMIT_VIRTUALTABLE
# undef SQLITE_ENABLE_FTS3
@@ -188872,13 +191093,6 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */
#define UNUSED_PARAMETER(x) (void)(x)
/*
-** Activate assert() only if SQLITE_TEST is enabled.
-*/
-#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
-# define NDEBUG 1
-#endif
-
-/*
** The TESTONLY macro is used to enclose variable declarations or
** other bits of code that are needed to support the arguments
** within testcase() and assert() macros.
@@ -188898,7 +191112,7 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */
** Macros needed to provide flexible arrays in a portable way
*/
#ifndef offsetof
-# define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD))
+# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define FLEXARRAY
@@ -203152,8 +205366,8 @@ struct NodeWriter {
** to an appendable b-tree segment.
*/
struct IncrmergeWriter {
- int nLeafEst; /* Space allocated for leaf blocks */
- int nWork; /* Number of leaf pages flushed */
+ i64 nLeafEst; /* Space allocated for leaf blocks */
+ i64 nWork; /* Number of leaf pages flushed */
sqlite3_int64 iAbsLevel; /* Absolute level of input segments */
int iIdx; /* Index of *output* segment in iAbsLevel+1 */
sqlite3_int64 iStart; /* Block number of first allocated block */
@@ -203899,7 +206113,7 @@ static int fts3IncrmergeWriter(
){
int rc; /* Return Code */
int i; /* Iterator variable */
- int nLeafEst = 0; /* Blocks allocated for leaf nodes */
+ i64 nLeafEst = 0; /* Blocks allocated for leaf nodes */
sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */
sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */
@@ -203909,7 +206123,7 @@ static int fts3IncrmergeWriter(
sqlite3_bind_int64(pLeafEst, 1, iAbsLevel);
sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment);
if( SQLITE_ROW==sqlite3_step(pLeafEst) ){
- nLeafEst = sqlite3_column_int(pLeafEst, 0);
+ nLeafEst = sqlite3_column_int64(pLeafEst, 0);
}
rc = sqlite3_reset(pLeafEst);
}
@@ -205292,10 +207506,6 @@ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){
/* #include <string.h> */
/* #include <assert.h> */
-#ifndef SQLITE_AMALGAMATION
-typedef sqlite3_int64 i64;
-#endif
-
/*
** Characters that may appear in the second argument to matchinfo().
*/
@@ -210149,7 +212359,7 @@ static u32 jsonTranslateBlobToText(
jsonAppendChar(pOut, '\'');
break;
case 'v':
- jsonAppendRawNZ(pOut, "\\u0009", 6);
+ jsonAppendRawNZ(pOut, "\\u000b", 6);
break;
case 'x':
if( sz2<4 ){
@@ -210999,19 +213209,27 @@ static void jsonReturnTextJsonFromBlob(
**
** If the value is a primitive, return it as an SQL value.
** If the value is an array or object, return it as either
-** JSON text or the BLOB encoding, depending on the JSON_B flag
-** on the userdata.
+** JSON text or the BLOB encoding, depending on the eMode flag
+** as follows:
+**
+** eMode==0 JSONB if the JSON_B flag is set in userdata or
+** text if the JSON_B flag is omitted from userdata.
+**
+** eMode==1 Text
+**
+** eMode==2 JSONB
*/
static void jsonReturnFromBlob(
JsonParse *pParse, /* Complete JSON parse tree */
u32 i, /* Index of the node */
sqlite3_context *pCtx, /* Return value for this function */
- int textOnly /* return text JSON. Disregard user-data */
+ int eMode /* Format of return: text of JSONB */
){
u32 n, sz;
int rc;
sqlite3 *db = sqlite3_context_db_handle(pCtx);
+ assert( eMode>=0 && eMode<=2 );
n = jsonbPayloadSize(pParse, i, &sz);
if( n==0 ){
sqlite3_result_error(pCtx, "malformed JSON", -1);
@@ -211052,7 +213270,19 @@ static void jsonReturnFromBlob(
rc = sqlite3DecOrHexToI64(z, &iRes);
sqlite3DbFree(db, z);
if( rc==0 ){
- sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes);
+ if( iRes<0 ){
+ /* A hexadecimal literal with 16 significant digits and with the
+ ** high-order bit set is a negative integer in SQLite (and hence
+ ** iRes comes back as negative) but should be interpreted as a
+ ** positive value if it occurs within JSON. The value is too
+ ** large to appear as an SQLite integer so it must be converted
+ ** into floating point. */
+ double r;
+ r = (double)*(sqlite3_uint64*)&iRes;
+ sqlite3_result_double(pCtx, bNeg ? -r : r);
+ }else{
+ sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes);
+ }
}else if( rc==3 && bNeg ){
sqlite3_result_int64(pCtx, SMALLEST_INT64);
}else if( rc==1 ){
@@ -211130,8 +213360,14 @@ static void jsonReturnFromBlob(
}
case JSONB_ARRAY:
case JSONB_OBJECT: {
- int flags = textOnly ? 0 : SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx));
- if( flags & JSON_BLOB ){
+ if( eMode==0 ){
+ if( (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)) & JSON_BLOB)!=0 ){
+ eMode = 2;
+ }else{
+ eMode = 1;
+ }
+ }
+ if( eMode==2 ){
sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT);
}else{
jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n);
@@ -212778,6 +215014,7 @@ struct JsonEachCursor {
u32 nRoot; /* Size of the root path in bytes */
u8 eType; /* Type of the container for element i */
u8 bRecursive; /* True for json_tree(). False for json_each() */
+ u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */
u32 nParent; /* Current nesting depth */
u32 nParentAlloc; /* Space allocated for aParent[] */
JsonParent *aParent; /* Parent elements of i */
@@ -212789,6 +215026,8 @@ typedef struct JsonEachConnection JsonEachConnection;
struct JsonEachConnection {
sqlite3_vtab base; /* Base class - must be first */
sqlite3 *db; /* Database connection */
+ u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */
+ u8 bRecursive; /* True for json_tree(). False for json_each() */
};
@@ -212831,6 +215070,8 @@ static int jsonEachConnect(
if( pNew==0 ) return SQLITE_NOMEM;
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
pNew->db = db;
+ pNew->eMode = argv[0][4]=='b' ? 2 : 1;
+ pNew->bRecursive = argv[0][4+pNew->eMode]=='t';
}
return rc;
}
@@ -212842,8 +215083,8 @@ static int jsonEachDisconnect(sqlite3_vtab *pVtab){
return SQLITE_OK;
}
-/* constructor for a JsonEachCursor object for json_each(). */
-static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
+/* constructor for a JsonEachCursor object for json_each()/json_tree(). */
+static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
JsonEachConnection *pVtab = (JsonEachConnection*)p;
JsonEachCursor *pCur;
@@ -212851,21 +215092,13 @@ static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur));
if( pCur==0 ) return SQLITE_NOMEM;
pCur->db = pVtab->db;
+ pCur->eMode = pVtab->eMode;
+ pCur->bRecursive = pVtab->bRecursive;
jsonStringZero(&pCur->path);
*ppCursor = &pCur->base;
return SQLITE_OK;
}
-/* constructor for a JsonEachCursor object for json_tree(). */
-static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
- int rc = jsonEachOpenEach(p, ppCursor);
- if( rc==SQLITE_OK ){
- JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor;
- pCur->bRecursive = 1;
- }
- return rc;
-}
-
/* Reset a JsonEachCursor back to its original state. Free any memory
** held. */
static void jsonEachCursorReset(JsonEachCursor *p){
@@ -213070,7 +215303,7 @@ static int jsonEachColumn(
}
case JEACH_VALUE: {
u32 i = jsonSkipLabel(p);
- jsonReturnFromBlob(&p->sParse, i, ctx, 1);
+ jsonReturnFromBlob(&p->sParse, i, ctx, p->eMode);
if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
}
@@ -213314,36 +215547,7 @@ static sqlite3_module jsonEachModule = {
jsonEachBestIndex, /* xBestIndex */
jsonEachDisconnect, /* xDisconnect */
0, /* xDestroy */
- jsonEachOpenEach, /* xOpen - open a cursor */
- jsonEachClose, /* xClose - close a cursor */
- jsonEachFilter, /* xFilter - configure scan constraints */
- jsonEachNext, /* xNext - advance a cursor */
- jsonEachEof, /* xEof - check for end of scan */
- jsonEachColumn, /* xColumn - read data */
- jsonEachRowid, /* xRowid - read data */
- 0, /* xUpdate */
- 0, /* xBegin */
- 0, /* xSync */
- 0, /* xCommit */
- 0, /* xRollback */
- 0, /* xFindMethod */
- 0, /* xRename */
- 0, /* xSavepoint */
- 0, /* xRelease */
- 0, /* xRollbackTo */
- 0, /* xShadowName */
- 0 /* xIntegrity */
-};
-
-/* The methods of the json_tree virtual table. */
-static sqlite3_module jsonTreeModule = {
- 0, /* iVersion */
- 0, /* xCreate */
- jsonEachConnect, /* xConnect */
- jsonEachBestIndex, /* xBestIndex */
- jsonEachDisconnect, /* xDisconnect */
- 0, /* xDestroy */
- jsonEachOpenTree, /* xOpen - open a cursor */
+ jsonEachOpen, /* xOpen - open a cursor */
jsonEachClose, /* xClose - close a cursor */
jsonEachFilter, /* xFilter - configure scan constraints */
jsonEachNext, /* xNext - advance a cursor */
@@ -213432,22 +215636,21 @@ SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON)
/*
-** Register the JSON table-valued functions
+** Register the JSON table-valued function named zName and return a
+** pointer to its Module object. Return NULL if something goes wrong.
*/
-SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){
- int rc = SQLITE_OK;
- static const struct {
- const char *zName;
- sqlite3_module *pModule;
- } aMod[] = {
- { "json_each", &jsonEachModule },
- { "json_tree", &jsonTreeModule },
- };
+SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3 *db, const char *zName){
unsigned int i;
- for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){
- rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0);
+ static const char *azModule[] = {
+ "json_each", "json_tree", "jsonb_each", "jsonb_tree"
+ };
+ assert( sqlite3HashFind(&db->aModule, zName)==0 );
+ for(i=0; i<sizeof(azModule)/sizeof(azModule[0]); i++){
+ if( sqlite3StrICmp(azModule[i],zName)==0 ){
+ return sqlite3VtabCreateModule(db, azModule[i], &jsonEachModule, 0, 0);
+ }
}
- return rc;
+ return 0;
}
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */
@@ -213517,7 +215720,7 @@ SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){
#else
/* #include "sqlite3.h" */
#endif
-SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
+SQLITE_PRIVATE sqlite3_int64 sqlite3GetToken(const unsigned char*,int*); /* In SQLite core */
/* #include <stddef.h> */
@@ -213552,7 +215755,7 @@ typedef unsigned int u32;
# define NEVER(X) (X)
#endif
#ifndef offsetof
-#define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD))
+# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define FLEXARRAY
@@ -214590,6 +216793,12 @@ static void resetCursor(RtreeCursor *pCsr){
pCsr->base.pVtab = (sqlite3_vtab*)pRtree;
pCsr->pReadAux = pStmt;
+ /* The following will only fail if the previous sqlite3_step() call failed,
+ ** in which case the error has already been caught. This statement never
+ ** encounters an error within an sqlite3_column_xxx() function, as it
+ ** calls sqlite3_column_value(), which does not use malloc(). So it is safe
+ ** to ignore the error code here. */
+ sqlite3_reset(pStmt);
}
/*
@@ -227678,8 +229887,8 @@ typedef struct DbpageCursor DbpageCursor;
struct DbpageCursor {
sqlite3_vtab_cursor base; /* Base class. Must be first */
- int pgno; /* Current page number */
- int mxPgno; /* Last page to visit on this scan */
+ Pgno pgno; /* Current page number */
+ Pgno mxPgno; /* Last page to visit on this scan */
Pager *pPager; /* Pager being read/written */
DbPage *pPage1; /* Page 1 of the database */
int iDb; /* Index of database to analyze */
@@ -227816,7 +230025,7 @@ static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
}else{
memset(pCsr, 0, sizeof(DbpageCursor));
pCsr->base.pVtab = pVTab;
- pCsr->pgno = -1;
+ pCsr->pgno = 0;
}
*ppCursor = (sqlite3_vtab_cursor *)pCsr;
@@ -227869,7 +230078,8 @@ static int dbpageFilter(
sqlite3 *db = pTab->db;
Btree *pBt;
- (void)idxStr;
+ UNUSED_PARAMETER(idxStr);
+ UNUSED_PARAMETER(argc);
/* Default setting is no rows of result */
pCsr->pgno = 1;
@@ -227915,12 +230125,12 @@ static int dbpageColumn(
int rc = SQLITE_OK;
switch( i ){
case 0: { /* pgno */
- sqlite3_result_int(ctx, pCsr->pgno);
+ sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno);
break;
}
case 1: { /* data */
DbPage *pDbPage = 0;
- if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){
+ if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){
/* The pending byte page. Assume it is zeroed out. Attempting to
** request this page from the page is an SQLITE_CORRUPT error. */
sqlite3_result_zeroblob(ctx, pCsr->szPage);
@@ -227994,10 +230204,10 @@ static int dbpageUpdate(
goto update_fail;
}
if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
- pgno = (Pgno)sqlite3_value_int(argv[2]);
+ pgno = (Pgno)sqlite3_value_int64(argv[2]);
isInsert = 1;
}else{
- pgno = sqlite3_value_int(argv[0]);
+ pgno = (Pgno)sqlite3_value_int64(argv[0]);
if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){
zErr = "cannot insert";
goto update_fail;
@@ -228049,7 +230259,8 @@ static int dbpageUpdate(
memcpy(aPage, pData, szPage);
pTab->pgnoTrunc = 0;
}
- }else{
+ }
+ if( rc!=SQLITE_OK ){
pTab->pgnoTrunc = 0;
}
sqlite3PagerUnref(pDbPage);
@@ -228132,6 +230343,536 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; }
#endif /* SQLITE_ENABLE_DBSTAT_VTAB */
/************** End of dbpage.c **********************************************/
+/************** Begin file carray.c ******************************************/
+/*
+** 2016-06-29
+**
+** 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 table-valued-function that
+** returns the values in a C-language array.
+** Examples:
+**
+** SELECT * FROM carray($ptr,5)
+**
+** The query above returns 5 integers contained in a C-language array
+** at the address $ptr. $ptr is a pointer to the array of integers.
+** The pointer value must be assigned to $ptr using the
+** sqlite3_bind_pointer() interface with a pointer type of "carray".
+** For example:
+**
+** static int aX[] = { 53, 9, 17, 2231, 4, 99 };
+** int i = sqlite3_bind_parameter_index(pStmt, "$ptr");
+** sqlite3_bind_pointer(pStmt, i, aX, "carray", 0);
+**
+** There is an optional third parameter to determine the datatype of
+** the C-language array. Allowed values of the third parameter are
+** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example:
+**
+** SELECT * FROM carray($ptr,10,'char*');
+**
+** The default value of the third parameter is 'int32'.
+**
+** HOW IT WORKS
+**
+** The carray "function" is really a virtual table with the
+** following schema:
+**
+** CREATE TABLE carray(
+** value,
+** pointer HIDDEN,
+** count HIDDEN,
+** ctype TEXT HIDDEN
+** );
+**
+** If the hidden columns "pointer" and "count" are unconstrained, then
+** the virtual table has no rows. Otherwise, the virtual table interprets
+** the integer value of "pointer" as a pointer to the array and "count"
+** as the number of elements in the array. The virtual table steps through
+** the array, element by element.
+*/
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY)
+/* #include "sqliteInt.h" */
+#if defined(_WIN32) || defined(__RTP__) || defined(_WRS_KERNEL)
+ struct iovec {
+ void *iov_base;
+ size_t iov_len;
+ };
+#else
+# include <sys/uio.h>
+#endif
+
+/*
+** Names of allowed datatypes
+*/
+static const char *azCarrayType[] = {
+ "int32", "int64", "double", "char*", "struct iovec"
+};
+
+/*
+** Structure used to hold the sqlite3_carray_bind() information
+*/
+typedef struct carray_bind carray_bind;
+struct carray_bind {
+ void *aData; /* The data */
+ int nData; /* Number of elements */
+ int mFlags; /* Control flags */
+ void (*xDel)(void*); /* Destructor for aData */
+};
+
+
+/* carray_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct carray_cursor carray_cursor;
+struct carray_cursor {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ sqlite3_int64 iRowid; /* The rowid */
+ void *pPtr; /* Pointer to the array of values */
+ sqlite3_int64 iCnt; /* Number of integers in the array */
+ unsigned char eType; /* One of the CARRAY_type values */
+};
+
+/*
+** The carrayConnect() method is invoked to create a new
+** carray_vtab that describes the carray virtual table.
+**
+** Think of this routine as the constructor for carray_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the carray_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+** result set of queries against carray will look like.
+*/
+static int carrayConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ sqlite3_vtab *pNew;
+ int rc;
+
+/* Column numbers */
+#define CARRAY_COLUMN_VALUE 0
+#define CARRAY_COLUMN_POINTER 1
+#define CARRAY_COLUMN_COUNT 2
+#define CARRAY_COLUMN_CTYPE 3
+
+ rc = sqlite3_declare_vtab(db,
+ "CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)");
+ if( rc==SQLITE_OK ){
+ pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for carray_cursor objects.
+*/
+static int carrayDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new carray_cursor object.
+*/
+static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
+ carray_cursor *pCur;
+ pCur = sqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a carray_cursor.
+*/
+static int carrayClose(sqlite3_vtab_cursor *cur){
+ sqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a carray_cursor to its next row of output.
+*/
+static int carrayNext(sqlite3_vtab_cursor *cur){
+ carray_cursor *pCur = (carray_cursor*)cur;
+ pCur->iRowid++;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the carray_cursor
+** is currently pointing.
+*/
+static int carrayColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ carray_cursor *pCur = (carray_cursor*)cur;
+ sqlite3_int64 x = 0;
+ switch( i ){
+ case CARRAY_COLUMN_POINTER: return SQLITE_OK;
+ case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break;
+ case CARRAY_COLUMN_CTYPE: {
+ sqlite3_result_text(ctx, azCarrayType[pCur->eType], -1, SQLITE_STATIC);
+ return SQLITE_OK;
+ }
+ default: {
+ switch( pCur->eType ){
+ case CARRAY_INT32: {
+ int *p = (int*)pCur->pPtr;
+ sqlite3_result_int(ctx, p[pCur->iRowid-1]);
+ return SQLITE_OK;
+ }
+ case CARRAY_INT64: {
+ sqlite3_int64 *p = (sqlite3_int64*)pCur->pPtr;
+ sqlite3_result_int64(ctx, p[pCur->iRowid-1]);
+ return SQLITE_OK;
+ }
+ case CARRAY_DOUBLE: {
+ double *p = (double*)pCur->pPtr;
+ sqlite3_result_double(ctx, p[pCur->iRowid-1]);
+ return SQLITE_OK;
+ }
+ case CARRAY_TEXT: {
+ const char **p = (const char**)pCur->pPtr;
+ sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT);
+ return SQLITE_OK;
+ }
+ default: {
+ const struct iovec *p = (struct iovec*)pCur->pPtr;
+ assert( pCur->eType==CARRAY_BLOB );
+ sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base,
+ (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT);
+ return SQLITE_OK;
+ }
+ }
+ }
+ }
+ sqlite3_result_int64(ctx, x);
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** rowid is the same as the output value.
+*/
+static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ carray_cursor *pCur = (carray_cursor*)cur;
+ *pRowid = pCur->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int carrayEof(sqlite3_vtab_cursor *cur){
+ carray_cursor *pCur = (carray_cursor*)cur;
+ return pCur->iRowid>pCur->iCnt;
+}
+
+/*
+** This method is called to "rewind" the carray_cursor object back
+** to the first row of output.
+*/
+static int carrayFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ carray_cursor *pCur = (carray_cursor *)pVtabCursor;
+ pCur->pPtr = 0;
+ pCur->iCnt = 0;
+ switch( idxNum ){
+ case 1: {
+ carray_bind *pBind = sqlite3_value_pointer(argv[0], "carray-bind");
+ if( pBind==0 ) break;
+ pCur->pPtr = pBind->aData;
+ pCur->iCnt = pBind->nData;
+ pCur->eType = pBind->mFlags & 0x07;
+ break;
+ }
+ case 2:
+ case 3: {
+ pCur->pPtr = sqlite3_value_pointer(argv[0], "carray");
+ pCur->iCnt = pCur->pPtr ? sqlite3_value_int64(argv[1]) : 0;
+ if( idxNum<3 ){
+ pCur->eType = CARRAY_INT32;
+ }else{
+ unsigned char i;
+ const char *zType = (const char*)sqlite3_value_text(argv[2]);
+ for(i=0; i<sizeof(azCarrayType)/sizeof(azCarrayType[0]); i++){
+ if( sqlite3_stricmp(zType, azCarrayType[i])==0 ) break;
+ }
+ if( i>=sizeof(azCarrayType)/sizeof(azCarrayType[0]) ){
+ pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
+ "unknown datatype: %Q", zType);
+ return SQLITE_ERROR;
+ }else{
+ pCur->eType = i;
+ }
+ }
+ break;
+ }
+ }
+ pCur->iRowid = 1;
+ return SQLITE_OK;
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the carray virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+**
+** In this implementation idxNum is used to represent the
+** query plan. idxStr is unused.
+**
+** idxNum is:
+**
+** 1 If only the pointer= constraint exists. In this case, the
+** parameter must be bound using sqlite3_carray_bind().
+**
+** 2 if the pointer= and count= constraints exist.
+**
+** 3 if the ctype= constraint also exists.
+**
+** idxNum is 0 otherwise and carray becomes an empty table.
+*/
+static int carrayBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i; /* Loop over constraints */
+ int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */
+ int cntIdx = -1; /* Index of the count= constraint, or -1 if none */
+ int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */
+ unsigned seen = 0; /* Bitmask of == constrainted columns */
+
+ const struct sqlite3_index_constraint *pConstraint;
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ if( pConstraint->iColumn>=0 ) seen |= 1 << pConstraint->iColumn;
+ if( pConstraint->usable==0 ) continue;
+ switch( pConstraint->iColumn ){
+ case CARRAY_COLUMN_POINTER:
+ ptrIdx = i;
+ break;
+ case CARRAY_COLUMN_COUNT:
+ cntIdx = i;
+ break;
+ case CARRAY_COLUMN_CTYPE:
+ ctypeIdx = i;
+ break;
+ }
+ }
+ if( ptrIdx>=0 ){
+ pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[ptrIdx].omit = 1;
+ pIdxInfo->estimatedCost = (double)1;
+ pIdxInfo->estimatedRows = 100;
+ pIdxInfo->idxNum = 1;
+ if( cntIdx>=0 ){
+ pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2;
+ pIdxInfo->aConstraintUsage[cntIdx].omit = 1;
+ pIdxInfo->idxNum = 2;
+ if( ctypeIdx>=0 ){
+ pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3;
+ pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1;
+ pIdxInfo->idxNum = 3;
+ }else if( seen & (1<<CARRAY_COLUMN_CTYPE) ){
+ /* In a three-argument carray(), we need to know the value of all
+ ** three arguments */
+ return SQLITE_CONSTRAINT;
+ }
+ }else if( seen & (1<<CARRAY_COLUMN_COUNT) ){
+ /* In a two-argument carray(), we need to know the value of both
+ ** arguments */
+ return SQLITE_CONSTRAINT;
+ }
+ }else{
+ pIdxInfo->estimatedCost = (double)2147483647;
+ pIdxInfo->estimatedRows = 2147483647;
+ pIdxInfo->idxNum = 0;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** carray virtual table.
+*/
+static sqlite3_module carrayModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ carrayConnect, /* xConnect */
+ carrayBestIndex, /* xBestIndex */
+ carrayDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ carrayOpen, /* xOpen - open a cursor */
+ carrayClose, /* xClose - close a cursor */
+ carrayFilter, /* xFilter - configure scan constraints */
+ carrayNext, /* xNext - advance a cursor */
+ carrayEof, /* xEof - check for end of scan */
+ carrayColumn, /* xColumn - read data */
+ carrayRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadow */
+ 0 /* xIntegrity */
+};
+
+/*
+** Destructor for the carray_bind object
+*/
+static void carrayBindDel(void *pPtr){
+ carray_bind *p = (carray_bind*)pPtr;
+ if( p->xDel!=SQLITE_STATIC ){
+ p->xDel(p->aData);
+ }
+ sqlite3_free(p);
+}
+
+/*
+** Invoke this interface in order to bind to the single-argument
+** version of CARRAY().
+*/
+SQLITE_API int sqlite3_carray_bind(
+ sqlite3_stmt *pStmt,
+ int idx,
+ void *aData,
+ int nData,
+ int mFlags,
+ void (*xDestroy)(void*)
+){
+ carray_bind *pNew = 0;
+ int i;
+ int rc = SQLITE_OK;
+
+ /* Ensure that the mFlags value is acceptable. */
+ assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 );
+ assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 );
+ if( mFlags<CARRAY_INT32 || mFlags>CARRAY_BLOB ){
+ rc = SQLITE_ERROR;
+ goto carray_bind_error;
+ }
+
+ pNew = sqlite3_malloc64(sizeof(*pNew));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ goto carray_bind_error;
+ }
+
+ pNew->nData = nData;
+ pNew->mFlags = mFlags;
+ if( xDestroy==SQLITE_TRANSIENT ){
+ sqlite3_int64 sz = nData;
+ switch( mFlags ){
+ case CARRAY_INT32: sz *= 4; break;
+ case CARRAY_INT64: sz *= 8; break;
+ case CARRAY_DOUBLE: sz *= 8; break;
+ case CARRAY_TEXT: sz *= sizeof(char*); break;
+ default: sz *= sizeof(struct iovec); break;
+ }
+ if( mFlags==CARRAY_TEXT ){
+ for(i=0; i<nData; i++){
+ const char *z = ((char**)aData)[i];
+ if( z ) sz += strlen(z) + 1;
+ }
+ }else if( mFlags==CARRAY_BLOB ){
+ for(i=0; i<nData; i++){
+ sz += ((struct iovec*)aData)[i].iov_len;
+ }
+ }
+
+ pNew->aData = sqlite3_malloc64( sz );
+ if( pNew->aData==0 ){
+ rc = SQLITE_NOMEM;
+ goto carray_bind_error;
+ }
+
+ if( mFlags==CARRAY_TEXT ){
+ char **az = (char**)pNew->aData;
+ char *z = (char*)&az[nData];
+ for(i=0; i<nData; i++){
+ const char *zData = ((char**)aData)[i];
+ sqlite3_int64 n;
+ if( zData==0 ){
+ az[i] = 0;
+ continue;
+ }
+ az[i] = z;
+ n = strlen(zData);
+ memcpy(z, zData, n+1);
+ z += n+1;
+ }
+ }else if( mFlags==CARRAY_BLOB ){
+ struct iovec *p = (struct iovec*)pNew->aData;
+ unsigned char *z = (unsigned char*)&p[nData];
+ for(i=0; i<nData; i++){
+ size_t n = ((struct iovec*)aData)[i].iov_len;
+ p[i].iov_len = n;
+ p[i].iov_base = z;
+ z += n;
+ memcpy(p[i].iov_base, ((struct iovec*)aData)[i].iov_base, n);
+ }
+ }else{
+ memcpy(pNew->aData, aData, sz);
+ }
+ pNew->xDel = sqlite3_free;
+ }else{
+ pNew->aData = aData;
+ pNew->xDel = xDestroy;
+ }
+ return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel);
+
+ carray_bind_error:
+ if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){
+ xDestroy(aData);
+ }
+ sqlite3_free(pNew);
+ return rc;
+}
+
+/*
+** Invoke this routine to register the carray() function.
+*/
+SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3 *db){
+ return sqlite3VtabCreateModule(db, "carray", &carrayModule, 0, 0);
+}
+
+#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) */
+
+/************** End of carray.c **********************************************/
/************** Begin file sqlite3session.c **********************************/
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
@@ -230950,6 +233691,19 @@ static int sessionAppendDelete(
return rc;
}
+static int sessionPrepare(
+ sqlite3 *db,
+ sqlite3_stmt **pp,
+ char **pzErrmsg,
+ const char *zSql
+){
+ int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0);
+ if( pzErrmsg && rc!=SQLITE_OK ){
+ *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }
+ 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.
@@ -230971,12 +233725,12 @@ static int sessionSelectStmt(
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 */
+ sqlite3_stmt **ppStmt, /* OUT: Prepared SELECT statement */
+ char **pzErrmsg /* OUT: Error message */
){
int rc = SQLITE_OK;
char *zSql = 0;
const char *zSep = "";
- int nSql = -1;
int i;
SessionBuffer cols = {0, 0, 0};
@@ -231056,7 +233810,7 @@ static int sessionSelectStmt(
#endif
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
+ rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql);
}
sqlite3_free(zSql);
sqlite3_free(nooptest.aBuf);
@@ -231220,7 +233974,7 @@ static int sessionGenerateChangeset(
/* Build and compile a statement to execute: */
if( rc==SQLITE_OK ){
rc = sessionSelectStmt(db, 0, pSession->zDb,
- zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel
+ zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0
);
}
@@ -232429,6 +235183,7 @@ struct SessionApplyCtx {
u8 bRebase; /* True to collect rebase information */
u8 bIgnoreNoop; /* True to ignore no-op conflicts */
int bRowid;
+ char *zErr; /* Error message, if any */
};
/* Number of prepared UPDATE statements to cache. */
@@ -232654,7 +235409,7 @@ static int sessionDeleteRow(
}
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
+ rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf);
}
sqlite3_free(buf.aBuf);
@@ -232681,7 +235436,7 @@ static int sessionSelectRow(
){
/* TODO */
return sessionSelectStmt(db, p->bIgnoreNoop,
- "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect
+ "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr
);
}
@@ -232718,16 +235473,12 @@ static int sessionInsertRow(
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
+ rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf);
}
sqlite3_free(buf.aBuf);
return rc;
}
-static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
- return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
-}
-
/*
** Prepare statements for applying changes to the sqlite_stat1 table.
** These are similar to those created by sessionSelectRow(),
@@ -232737,14 +235488,14 @@ static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
int rc = sessionSelectRow(db, "sqlite_stat1", p);
if( rc==SQLITE_OK ){
- rc = sessionPrepare(db, &p->pInsert,
+ rc = sessionPrepare(db, &p->pInsert, 0,
"INSERT INTO main.sqlite_stat1 VALUES(?1, "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
"?3)"
);
}
if( rc==SQLITE_OK ){
- rc = sessionPrepare(db, &p->pDelete,
+ rc = sessionPrepare(db, &p->pDelete, 0,
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
"AND (?4 OR stat IS ?3)"
@@ -232968,7 +235719,7 @@ static int sessionConflictHandler(
void *pCtx, /* First argument for conflict handler */
int *pbReplace /* OUT: Set to true if PK row is found */
){
- int res = 0; /* Value returned by conflict handler */
+ int res = SQLITE_CHANGESET_OMIT;/* Value returned by conflict handler */
int rc;
int nCol;
int op;
@@ -232989,11 +235740,9 @@ static int sessionConflictHandler(
if( rc==SQLITE_ROW ){
/* There exists another row with the new.* primary key. */
- if( p->bIgnoreNoop
- && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1)
+ if( 0==p->bIgnoreNoop
+ || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1)
){
- res = SQLITE_CHANGESET_OMIT;
- }else{
pIter->pConflict = p->pSelect;
res = xConflict(pCtx, eType, pIter);
pIter->pConflict = 0;
@@ -233007,7 +235756,9 @@ static int sessionConflictHandler(
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
return SQLITE_OK;
- }else{
+ }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE
+ || eType==SQLITE_CHANGESET_CONFLICT
+ ){
/* No other row with the new.* primary key. */
res = xConflict(pCtx, eType+1, pIter);
if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
@@ -233105,7 +235856,7 @@ static int sessionApplyOneOp(
sqlite3_step(p->pDelete);
rc = sqlite3_reset(p->pDelete);
- if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){
+ if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
);
@@ -233317,6 +236068,10 @@ static int sessionChangesetApply(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
),
+ int(*xFilterIter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p
+ ),
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -233457,6 +236212,9 @@ static int sessionChangesetApply(
** next change. A log message has already been issued. */
if( schemaMismatch ) continue;
+ /* If this is a call to apply_v3(), invoke xFilterIter here. */
+ if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) continue;
+
rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
}
@@ -233503,6 +236261,7 @@ static int sessionChangesetApply(
assert( sApply.bRebase || sApply.rebase.nBuf==0 );
if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){
+ assert( ppRebase!=0 && pnRebase!=0 );
*ppRebase = (void*)sApply.rebase.aBuf;
*pnRebase = sApply.rebase.nBuf;
sApply.rebase.aBuf = 0;
@@ -233520,22 +236279,74 @@ static int sessionChangesetApply(
db->flags &= ~((u64)SQLITE_FkNoAction);
db->aDb[0].pSchema->schema_cookie -= 32;
}
+
+ assert( rc!=SQLITE_OK || sApply.zErr==0 );
+ sqlite3_set_errmsg(db, rc, sApply.zErr);
+ sqlite3_free(sApply.zErr);
+
sqlite3_mutex_leave(sqlite3_db_mutex(db));
return rc;
}
/*
-** Apply the changeset passed via pChangeset/nChangeset to the main
-** database attached to handle "db".
+** This function is called by all six sqlite3changeset_apply() variants:
+**
+** + sqlite3changeset_apply()
+** + sqlite3changeset_apply_v2()
+** + sqlite3changeset_apply_v3()
+** + sqlite3changeset_apply_strm()
+** + sqlite3changeset_apply_strm_v2()
+** + sqlite3changeset_apply_strm_v3()
+**
+** Arguments passed to this function are as follows:
+**
+** db:
+** Database handle to apply changeset to main database of.
+**
+** nChangeset/pChangeset:
+** These are both passed zero for the streaming variants. For the normal
+** apply() functions, these are passed the size of and the buffer containing
+** the changeset, respectively.
+**
+** xInput/pIn:
+** These are both passed zero for the normal variants. For the streaming
+** apply() functions, these are passed the input callback and context
+** pointer, respectively.
+**
+** xFilter:
+** The filter function as passed to apply() or apply_v2() (to filter by
+** table name), if any. This is always NULL for apply_v3() calls.
+**
+** xFilterIter:
+** The filter function as passed to apply_v3(), if any.
+**
+** xConflict:
+** The conflict handler callback (must not be NULL).
+**
+** pCtx:
+** The context pointer passed to the xFilter and xConflict handler callbacks.
+**
+** ppRebase, pnRebase:
+** Zero for apply(). The rebase changeset output pointers, if any, for
+** apply_v2() and apply_v3().
+**
+** flags:
+** Zero for apply(). The flags parameter for apply_v2() and apply_v3().
*/
-SQLITE_API int sqlite3changeset_apply_v2(
+static int sessionChangesetApplyV23(
sqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
+ 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(*xFilterIter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ sqlite3_changeset_iter *p /* Handle describing current change */
+ ),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -233546,19 +236357,75 @@ SQLITE_API int sqlite3changeset_apply_v2(
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
-
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(
+ &pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1
+ );
if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(
- db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
+ rc = sessionChangesetApply(db, pIter,
+ xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
-
return rc;
}
/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+SQLITE_API int sqlite3changeset_apply_v2(
+ 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 */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ xFilter, 0, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
+
+/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+SQLITE_API int sqlite3changeset_apply_v3(
+ 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() */
+ sqlite3_changeset_iter *p /* Handle describing current change */
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ 0, xFilter, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
+
+/*
** 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.
@@ -233578,8 +236445,10 @@ SQLITE_API int sqlite3changeset_apply(
),
void *pCtx /* First argument passed to xConflict */
){
- return sqlite3changeset_apply_v2(
- db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0
+ return sessionChangesetApplyV23(db,
+ nChangeset, pChangeset, 0, 0,
+ xFilter, 0, xConflict, pCtx,
+ 0, 0, 0
);
}
@@ -233588,6 +236457,29 @@ SQLITE_API int sqlite3changeset_apply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
+SQLITE_API int sqlite3changeset_apply_v3_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() */
+ sqlite3_changeset_iter *p
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ 0, xFilter, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
+}
SQLITE_API int sqlite3changeset_apply_v2_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
@@ -233605,15 +236497,11 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
){
- sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
- if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(
- db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
- );
- }
- return rc;
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ xFilter, 0, xConflict, pCtx,
+ ppRebase, pnRebase, flags
+ );
}
SQLITE_API int sqlite3changeset_apply_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
@@ -233630,8 +236518,10 @@ SQLITE_API int sqlite3changeset_apply_strm(
),
void *pCtx /* First argument passed to xConflict */
){
- return sqlite3changeset_apply_v2_strm(
- db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0
+ return sessionChangesetApplyV23(db,
+ 0, 0, xInput, pIn,
+ xFilter, 0, xConflict, pCtx,
+ 0, 0, 0
);
}
@@ -235603,27 +238493,20 @@ typedef sqlite3_uint64 u64;
# define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
# define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
-/* The uptr type is an unsigned integer large enough to hold a pointer
+/*
+** This macro is used in a single assert() within fts5 to check that an
+** allocation is aligned to an 8-byte boundary. But it is a complicated
+** macro to get right for multiple platforms without generating warnings.
+** So instead of reproducing the entire definition from sqliteInt.h, we
+** just do without this assert() for the rare non-amalgamation builds.
*/
-#if defined(HAVE_STDINT_H)
- typedef uintptr_t uptr;
-#elif SQLITE_PTRSIZE==4
- typedef u32 uptr;
-#else
- typedef u64 uptr;
-#endif
-
-#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC
-# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0)
-#else
-# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0)
-#endif
+#define EIGHT_BYTE_ALIGNMENT(x) 1
/*
** Macros needed to provide flexible arrays in a portable way
*/
#ifndef offsetof
-# define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD))
+# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define FLEXARRAY
@@ -236365,7 +239248,7 @@ static int sqlite3Fts5ExprPattern(
** i64 iRowid = sqlite3Fts5ExprRowid(pExpr);
** }
*/
-static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc);
+static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc);
static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax);
static int sqlite3Fts5ExprEof(Fts5Expr*);
static i64 sqlite3Fts5ExprRowid(Fts5Expr*);
@@ -241934,7 +244817,13 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
** is not considered an error if the query does not match any documents.
*/
-static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
+static int sqlite3Fts5ExprFirst(
+ Fts5Expr *p,
+ Fts5Index *pIdx,
+ i64 iFirst,
+ i64 iLast,
+ int bDesc
+){
Fts5ExprNode *pRoot = p->pRoot;
int rc; /* Return code */
@@ -241956,6 +244845,9 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD
assert( pRoot->bEof==0 );
rc = fts5ExprNodeNext(p, pRoot, 0, 0);
}
+ if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){
+ pRoot->bEof = 1;
+ }
return rc;
}
@@ -244808,6 +247700,36 @@ struct Fts5SegIter {
u8 bDel; /* True if the delete flag is set */
};
+static int fts5IndexCorruptRowid(Fts5Index *pIdx, i64 iRowid){
+ pIdx->rc = FTS5_CORRUPT;
+ sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
+ "fts5: corruption found reading blob %lld from table \"%s\"",
+ iRowid, pIdx->pConfig->zName
+ );
+ return SQLITE_CORRUPT_VTAB;
+}
+#define FTS5_CORRUPT_ROWID(pIdx, iRowid) fts5IndexCorruptRowid(pIdx, iRowid)
+
+static int fts5IndexCorruptIter(Fts5Index *pIdx, Fts5SegIter *pIter){
+ pIdx->rc = FTS5_CORRUPT;
+ sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
+ "fts5: corruption on page %d, segment %d, table \"%s\"",
+ pIter->iLeafPgno, pIter->pSeg->iSegid, pIdx->pConfig->zName
+ );
+ return SQLITE_CORRUPT_VTAB;
+}
+#define FTS5_CORRUPT_ITER(pIdx, pIter) fts5IndexCorruptIter(pIdx, pIter)
+
+static int fts5IndexCorruptIdx(Fts5Index *pIdx){
+ pIdx->rc = FTS5_CORRUPT;
+ sqlite3Fts5ConfigErrmsg(pIdx->pConfig,
+ "fts5: corruption in table \"%s\"", pIdx->pConfig->zName
+ );
+ return SQLITE_CORRUPT_VTAB;
+}
+#define FTS5_CORRUPT_IDX(pIdx) fts5IndexCorruptIdx(pIdx)
+
+
/*
** Array of tombstone pages. Reference counted.
*/
@@ -245097,13 +248019,13 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
** All the reasons those functions might return SQLITE_ERROR - missing
** table, missing row, non-blob/text in block column - indicate
** backing store corruption. */
- if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT;
+ if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid);
if( rc==SQLITE_OK ){
u8 *aOut = 0; /* Read blob data into this buffer */
- int nByte = sqlite3_blob_bytes(p->pReader);
- int szData = (sizeof(Fts5Data) + 7) & ~7;
- sqlite3_int64 nAlloc = szData + nByte + FTS5_DATA_PADDING;
+ i64 nByte = sqlite3_blob_bytes(p->pReader);
+ i64 szData = (sizeof(Fts5Data) + 7) & ~7;
+ i64 nAlloc = szData + nByte + FTS5_DATA_PADDING;
pRet = (Fts5Data*)sqlite3_malloc64(nAlloc);
if( pRet ){
pRet->nn = nByte;
@@ -245147,7 +248069,7 @@ static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
Fts5Data *pRet = fts5DataRead(p, iRowid);
if( pRet ){
if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
fts5DataRelease(pRet);
pRet = 0;
}
@@ -245506,8 +248428,14 @@ static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){
/* TODO: Do we need this if the leaf-index is appended? Probably... */
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
- if( p->rc==SQLITE_OK && (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){
- p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
+ if( p->rc==SQLITE_OK ){
+ if( (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){
+ p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
+ }
+ }else if( p->rc==SQLITE_CORRUPT_VTAB ){
+ sqlite3Fts5ConfigErrmsg(p->pConfig,
+ "fts5: corrupt structure record for table \"%s\"", p->pConfig->zName
+ );
}
fts5DataRelease(pData);
if( p->rc!=SQLITE_OK ){
@@ -246130,7 +249058,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
while( iOff>=pIter->pLeaf->szLeaf ){
fts5SegIterNextPage(p, pIter);
if( pIter->pLeaf==0 ){
- if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ if( p->rc==SQLITE_OK ) FTS5_CORRUPT_ITER(p, pIter);
return;
}
iOff = 4;
@@ -246162,7 +249090,7 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
iOff += fts5GetVarint32(&a[iOff], nNew);
if( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}
pIter->term.n = nKeep;
@@ -246357,7 +249285,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
iRowidOff = fts5LeafFirstRowidOff(pNew);
if( iRowidOff ){
if( iRowidOff>=pNew->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
}else{
pIter->pLeaf = pNew;
pIter->iLeafOffset = iRowidOff;
@@ -246591,7 +249519,7 @@ static void fts5SegIterNext(
}
assert_nc( iOff<pLeaf->szLeaf );
if( iOff>pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}
}
@@ -246699,18 +249627,20 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
fts5DataRelease(pIter->pLeaf);
pIter->pLeaf = pLast;
pIter->iLeafPgno = pgnoLast;
- iOff = fts5LeafFirstRowidOff(pLast);
- if( iOff>pLast->szLeaf ){
- p->rc = FTS5_CORRUPT;
- return;
- }
- iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
- pIter->iLeafOffset = iOff;
+ if( p->rc==SQLITE_OK ){
+ iOff = fts5LeafFirstRowidOff(pLast);
+ if( iOff>pLast->szLeaf ){
+ FTS5_CORRUPT_ITER(p, pIter);
+ return;
+ }
+ iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
- if( fts5LeafIsTermless(pLast) ){
- pIter->iEndofDoclist = pLast->nn+1;
- }else{
- pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
+ if( fts5LeafIsTermless(pLast) ){
+ pIter->iEndofDoclist = pLast->nn+1;
+ }else{
+ pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
+ }
}
}
@@ -246780,7 +249710,7 @@ static void fts5LeafSeek(
iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff);
iOff = iTermOff;
if( iOff>n ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}
@@ -246823,7 +249753,7 @@ static void fts5LeafSeek(
iOff = iTermOff;
if( iOff>=n ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}
@@ -246845,7 +249775,7 @@ static void fts5LeafSeek(
iPgidx = (u32)pIter->pLeaf->szLeaf;
iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff);
if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}else{
nKeep = 0;
@@ -246860,7 +249790,7 @@ static void fts5LeafSeek(
search_success:
if( (i64)iOff+nNew>n || nNew<1 ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ITER(p, pIter);
return;
}
pIter->iLeafOffset = iOff + nNew;
@@ -247325,7 +250255,7 @@ static void fts5SegIterGotoPage(
assert( iLeafPgno>pIter->iLeafPgno );
if( iLeafPgno>pIter->pSeg->pgnoLast ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
}else{
fts5DataRelease(pIter->pNextLeaf);
pIter->pNextLeaf = 0;
@@ -247340,7 +250270,7 @@ static void fts5SegIterGotoPage(
u8 *a = pIter->pLeaf->p;
int n = pIter->pLeaf->szLeaf;
if( iOff<4 || iOff>=n ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
}else{
iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
@@ -247819,7 +250749,7 @@ static void fts5ChunkIterate(
if( nRem<=0 ){
break;
}else if( pSeg->pSeg==0 ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
return;
}else{
pgno++;
@@ -248922,7 +251852,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){
** a single page has been assigned to more than one segment. In
** this case a prior iteration of this loop may have corrupted the
** segment currently being trimmed. */
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iLeafRowid);
}else{
fts5BufferZero(&buf);
fts5BufferGrow(&p->rc, &buf, pData->nn);
@@ -249389,7 +252319,7 @@ static void fts5SecureDeleteOverflow(
}else if( bDetailNone ){
break;
}else if( iNext>=pLeaf->szLeaf || pLeaf->nn<pLeaf->szLeaf || iNext<4 ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
break;
}else{
int nShift = iNext - 4;
@@ -249409,7 +252339,7 @@ static void fts5SecureDeleteOverflow(
i1 += fts5GetVarint32(&aPg[i1], iFirst);
if( iFirst<iNext ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
break;
}
aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2);
@@ -249632,14 +252562,14 @@ static void fts5DoSecureDelete(
nSuffix = (nPrefix2 + nSuffix2) - nPrefix;
if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
}else{
if( iKey!=1 ){
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
}
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
if( nPrefix2>pSeg->term.n ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
}else if( nPrefix2>nPrefix ){
memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
iOff += (nPrefix2-nPrefix);
@@ -250063,7 +252993,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
}
nByte += (((i64)pStruct->nLevel)+1) * sizeof(Fts5StructureLevel);
- assert( nByte==SZ_FTS5STRUCTURE(pStruct->nLevel+2) );
+ assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2) );
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pNew ){
@@ -250432,7 +253362,7 @@ static void fts5MergePrefixLists(
}
if( pHead==0 || pHead->pNext==0 ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_IDX(p);
break;
}
@@ -250469,7 +253399,7 @@ static void fts5MergePrefixLists(
assert_nc( tmp.n+nTail<=nTmp );
assert( tmp.n+nTail<=nTmp+nMerge*10 );
if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){
- if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ if( p->rc==SQLITE_OK ) FTS5_CORRUPT_IDX(p);
break;
}
fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2);
@@ -251038,11 +253968,14 @@ static int sqlite3Fts5IndexRollback(Fts5Index *p){
*/
static int sqlite3Fts5IndexReinit(Fts5Index *p){
Fts5Structure *pTmp;
- u8 tmpSpace[SZ_FTS5STRUCTURE(1)];
+ union {
+ Fts5Structure sFts;
+ u8 tmpSpace[SZ_FTS5STRUCTURE(1)];
+ } uFts;
fts5StructureInvalidate(p);
fts5IndexDiscardData(p);
- pTmp = (Fts5Structure*)tmpSpace;
- memset(pTmp, 0, SZ_FTS5STRUCTURE(1));
+ pTmp = &uFts.sFts;
+ memset(uFts.tmpSpace, 0, sizeof(uFts.tmpSpace));
if( p->pConfig->bContentlessDelete ){
pTmp->nOriginCntr = 1;
}
@@ -252502,19 +255435,27 @@ static int fts5TestUtf8(const char *z, int n){
/*
** This function is also purely an internal test. It does not contribute to
** FTS functionality, or even the integrity-check, in any way.
+**
+** This function sets output variable (*pbFail) to true if the test fails. Or
+** leaves it unchanged if the test succeeds.
*/
static void fts5TestTerm(
Fts5Index *p,
Fts5Buffer *pPrev, /* Previous term */
const char *z, int n, /* Possibly new term to test */
u64 expected,
- u64 *pCksum
+ u64 *pCksum,
+ int *pbFail
){
int rc = p->rc;
if( pPrev->n==0 ){
fts5BufferSet(&rc, pPrev, n, (const u8*)z);
}else
- if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){
+ if( *pbFail==0
+ && rc==SQLITE_OK
+ && (pPrev->n!=n || memcmp(pPrev->p, z, n))
+ && (p->pHash==0 || p->pHash->nEntry==0)
+ ){
u64 cksum3 = *pCksum;
const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */
int nTerm = pPrev->n-1; /* Size of zTerm in bytes */
@@ -252564,7 +255505,7 @@ static void fts5TestTerm(
fts5BufferSet(&rc, pPrev, n, (const u8*)z);
if( rc==SQLITE_OK && cksum3!=expected ){
- rc = FTS5_CORRUPT;
+ *pbFail = 1;
}
*pCksum = cksum3;
}
@@ -252573,7 +255514,7 @@ static void fts5TestTerm(
#else
# define fts5TestDlidxReverse(x,y,z)
-# define fts5TestTerm(u,v,w,x,y,z)
+# define fts5TestTerm(t,u,v,w,x,y,z)
#endif
/*
@@ -252598,14 +255539,17 @@ static void fts5IndexIntegrityCheckEmpty(
for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){
Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i));
if( pLeaf ){
- if( !fts5LeafIsTermless(pLeaf) ) p->rc = FTS5_CORRUPT;
- if( i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf) ) p->rc = FTS5_CORRUPT;
+ if( !fts5LeafIsTermless(pLeaf)
+ || (i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf))
+ ){
+ FTS5_CORRUPT_ROWID(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i));
+ }
}
fts5DataRelease(pLeaf);
}
}
-static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){
+static void fts5IntegrityCheckPgidx(Fts5Index *p, i64 iRowid, Fts5Data *pLeaf){
i64 iTermOff = 0;
int ii;
@@ -252623,12 +255567,12 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){
iOff = iTermOff;
if( iOff>=pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
}else if( iTermOff==nIncr ){
int nByte;
iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte);
if( (iOff+nByte)>pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
}else{
fts5BufferSet(&p->rc, &buf1, nByte, &pLeaf->p[iOff]);
}
@@ -252637,7 +255581,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){
iOff += fts5GetVarint32(&pLeaf->p[iOff], nKeep);
iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte);
if( nKeep>buf1.n || (iOff+nByte)>pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRowid);
}else{
buf1.n = nKeep;
fts5BufferAppendBlob(&p->rc, &buf1, nByte, &pLeaf->p[iOff]);
@@ -252645,7 +255589,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){
if( p->rc==SQLITE_OK ){
res = fts5BufferCompare(&buf1, &buf2);
- if( res<=0 ) p->rc = FTS5_CORRUPT;
+ if( res<=0 ) FTS5_CORRUPT_ROWID(p, iRowid);
}
}
fts5BufferSet(&p->rc, &buf2, buf1.n, buf1.p);
@@ -252706,7 +255650,7 @@ static void fts5IndexIntegrityCheckSegment(
** entry even if all the terms are removed from it by secure-delete
** operations. */
}else{
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRow);
}
}else{
@@ -252718,15 +255662,15 @@ static void fts5IndexIntegrityCheckSegment(
iOff = fts5LeafFirstTermOff(pLeaf);
iRowidOff = fts5LeafFirstRowidOff(pLeaf);
if( iRowidOff>=iOff || iOff>=pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iRow);
}else{
iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm);
res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm));
if( res==0 ) res = nTerm - nIdxTerm;
- if( res<0 ) p->rc = FTS5_CORRUPT;
+ if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow);
}
- fts5IntegrityCheckPgidx(p, pLeaf);
+ fts5IntegrityCheckPgidx(p, iRow, pLeaf);
}
fts5DataRelease(pLeaf);
if( p->rc ) break;
@@ -252756,7 +255700,7 @@ static void fts5IndexIntegrityCheckSegment(
iKey = FTS5_SEGMENT_ROWID(iSegid, iPg);
pLeaf = fts5DataRead(p, iKey);
if( pLeaf ){
- if( fts5LeafFirstRowidOff(pLeaf)!=0 ) p->rc = FTS5_CORRUPT;
+ if( fts5LeafFirstRowidOff(pLeaf)!=0 ) FTS5_CORRUPT_ROWID(p, iKey);
fts5DataRelease(pLeaf);
}
}
@@ -252771,12 +255715,12 @@ static void fts5IndexIntegrityCheckSegment(
int iRowidOff = fts5LeafFirstRowidOff(pLeaf);
ASSERT_SZLEAF_OK(pLeaf);
if( iRowidOff>=pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iKey);
}else if( bSecureDelete==0 || iRowidOff>0 ){
i64 iDlRowid = fts5DlidxIterRowid(pDlidx);
fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
if( iRowid<iDlRowid || (bSecureDelete==0 && iRowid!=iDlRowid) ){
- p->rc = FTS5_CORRUPT;
+ FTS5_CORRUPT_ROWID(p, iKey);
}
}
fts5DataRelease(pLeaf);
@@ -252828,6 +255772,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum
/* 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 */
+ int bTestFail = 0;
#endif
const int flags = FTS5INDEX_QUERY_NOOUTPUT;
@@ -252870,7 +255815,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum
char *z = (char*)fts5MultiIterTerm(pIter, &n);
/* If this is a new term, query for it. Update cksum3 with the results. */
- fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
+ fts5TestTerm(p, &term, z, n, cksum2, &cksum3, &bTestFail);
if( p->rc ) break;
if( eDetail==FTS5_DETAIL_NONE ){
@@ -252888,15 +255833,26 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum
}
}
}
- fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3);
+ fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3, &bTestFail);
fts5MultiIterFree(pIter);
- if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
-
- fts5StructureRelease(pStruct);
+ if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ){
+ p->rc = FTS5_CORRUPT;
+ sqlite3Fts5ConfigErrmsg(p->pConfig,
+ "fts5: checksum mismatch for table \"%s\"", p->pConfig->zName
+ );
+ }
#ifdef SQLITE_DEBUG
+ /* In SQLITE_DEBUG builds, expensive extra checks were run as part of
+ ** the integrity-check above. If no other errors were detected, but one
+ ** of these tests failed, set the result to SQLITE_CORRUPT_VTAB here. */
+ if( p->rc==SQLITE_OK && bTestFail ){
+ p->rc = FTS5_CORRUPT;
+ }
fts5BufferFree(&term);
#endif
+
+ fts5StructureRelease(pStruct);
fts5BufferFree(&poslist);
return fts5IndexReturn(p);
}
@@ -254240,6 +257196,17 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
#endif
}
+static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
+#if SQLITE_VERSION_NUMBER>=3008002
+#ifndef SQLITE_CORE
+ if( sqlite3_libversion_number()>=3008002 )
+#endif
+ {
+ pIdxInfo->estimatedRows = nRow;
+ }
+#endif
+}
+
static int fts5UsePatternMatch(
Fts5Config *pConfig,
struct sqlite3_index_constraint *p
@@ -254375,7 +257342,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
nSeenMatch++;
idxStr[iIdxStr++] = 'M';
sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
- idxStr += strlen(&idxStr[iIdxStr]);
+ iIdxStr += (int)strlen(&idxStr[iIdxStr]);
assert( idxStr[iIdxStr]=='\0' );
}
pInfo->aConstraintUsage[i].argvIndex = ++iCons;
@@ -254394,6 +257361,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
idxStr[iIdxStr++] = '=';
bSeenEq = 1;
pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ pInfo->aConstraintUsage[i].omit = 1;
}
}
}
@@ -254441,17 +257409,21 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
/* Calculate the estimated cost based on the flags set in idxFlags. */
if( bSeenEq ){
- pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0;
- if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo);
- }else if( bSeenLt && bSeenGt ){
- pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0;
- }else if( bSeenLt || bSeenGt ){
- pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0;
- }else{
- pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0;
- }
- for(i=1; i<nSeenMatch; i++){
- pInfo->estimatedCost *= 0.4;
+ pInfo->estimatedCost = nSeenMatch ? 1000.0 : 25.0;
+ fts5SetUniqueFlag(pInfo);
+ fts5SetEstimatedRows(pInfo, 1);
+ }else{
+ if( bSeenLt && bSeenGt ){
+ pInfo->estimatedCost = nSeenMatch ? 5000.0 : 750000.0;
+ }else if( bSeenLt || bSeenGt ){
+ pInfo->estimatedCost = nSeenMatch ? 7500.0 : 2250000.0;
+ }else{
+ pInfo->estimatedCost = nSeenMatch ? 10000.0 : 3000000.0;
+ }
+ for(i=1; i<nSeenMatch; i++){
+ pInfo->estimatedCost *= 0.4;
+ }
+ fts5SetEstimatedRows(pInfo, (i64)(pInfo->estimatedCost / 4.0));
}
pInfo->idxNum = idxFlags;
@@ -254650,7 +257622,9 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
int bDesc = pCsr->bDesc;
i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr);
- rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc);
+ rc = sqlite3Fts5ExprFirst(
+ pCsr->pExpr, pTab->p.pIndex, iRowid, pCsr->iLastRowid, bDesc
+ );
if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){
*pbSkip = 1;
}
@@ -254822,7 +257796,9 @@ static int fts5CursorFirstSorted(
static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){
int rc;
Fts5Expr *pExpr = pCsr->pExpr;
- rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc);
+ rc = sqlite3Fts5ExprFirst(
+ pExpr, pTab->p.pIndex, pCsr->iFirstRowid, pCsr->iLastRowid, bDesc
+ );
if( sqlite3Fts5ExprEof(pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
}
@@ -257307,7 +260283,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3", -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(pCtx, "fts5: 2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b", -1, SQLITE_TRANSIENT);
}
/*
@@ -257330,9 +260306,9 @@ static void fts5LocaleFunc(
sqlite3_value **apArg /* Function arguments */
){
const char *zLocale = 0;
- int nLocale = 0;
+ i64 nLocale = 0;
const char *zText = 0;
- int nText = 0;
+ i64 nText = 0;
assert( nArg==2 );
UNUSED_PARAM(nArg);
@@ -257349,10 +260325,10 @@ static void fts5LocaleFunc(
Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx);
u8 *pBlob = 0;
u8 *pCsr = 0;
- int nBlob = 0;
+ i64 nBlob = 0;
nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText;
- pBlob = (u8*)sqlite3_malloc(nBlob);
+ pBlob = (u8*)sqlite3_malloc64(nBlob);
if( pBlob==0 ){
sqlite3_result_error_nomem(pCtx);
return;
@@ -257430,8 +260406,9 @@ static int fts5IntegrityMethod(
" FTS5 table %s.%s: %s",
zSchema, zTabname, sqlite3_errstr(rc));
}
+ }else if( (rc&0xff)==SQLITE_CORRUPT ){
+ rc = SQLITE_OK;
}
-
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
pTab->p.pConfig->pzErrmsg = 0;
diff --git a/src/3rdparty/sqlite/sqlite3.h b/src/3rdparty/sqlite/sqlite3.h
index c2ed750305b..70a4a1b1a5e 100644
--- a/src/3rdparty/sqlite/sqlite3.h
+++ b/src/3rdparty/sqlite/sqlite3.h
@@ -146,9 +146,12 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.50.4"
-#define SQLITE_VERSION_NUMBER 3050004
-#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
+#define SQLITE_VERSION "3.51.0"
+#define SQLITE_VERSION_NUMBER 3051000
+#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
+#define SQLITE_SCM_BRANCH "trunk"
+#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
+#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -168,9 +171,9 @@ extern "C" {
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
-** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
-** macro. ^The sqlite3_libversion() function returns a pointer to the
-** to the sqlite3_version[] string constant. The sqlite3_libversion()
+** ^The sqlite3_version[] string constant contains the text of the
+** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a
+** pointer to the sqlite3_version[] string constant. The sqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL. ^The
** sqlite3_libversion_number() function returns an integer equal to
@@ -370,7 +373,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** without having to use a lot of C code.
**
** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
-** semicolon-separate SQL statements passed into its 2nd argument,
+** semicolon-separated SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument. ^If the callback function of the 3rd argument to
** sqlite3_exec() is not NULL, then it is invoked for each result row
@@ -403,7 +406,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** result row is NULL then the corresponding string pointer for the
** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
** sqlite3_exec() callback is an array of pointers to strings where each
-** entry represents the name of corresponding result column as obtained
+** entry represents the name of a corresponding result column as obtained
** from [sqlite3_column_name()].
**
** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
@@ -497,6 +500,9 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
+#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8))
+#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8))
+#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@@ -531,6 +537,8 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
+#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8))
+#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -589,7 +597,7 @@ SQLITE_API int sqlite3_exec(
** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into
** [sqlite3_open_v2()] does *not* cause the underlying database file
** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into
-** [sqlite3_open_v2()] has historically be a no-op and might become an
+** [sqlite3_open_v2()] has historically been a no-op and might become an
** error in future versions of SQLite.
*/
#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
@@ -683,7 +691,7 @@ SQLITE_API int sqlite3_exec(
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
** of an [sqlite3_io_methods] object. These values are ordered from
-** lest restrictive to most restrictive.
+** least restrictive to most restrictive.
**
** The argument to xLock() is always SHARED or higher. The argument to
** xUnlock is either SHARED or NONE.
@@ -924,7 +932,7 @@ struct sqlite3_io_methods {
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
** <li>[[SQLITE_FCNTL_SYNC_OMITTED]]
-** No longer in use.
+** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used.
**
** <li>[[SQLITE_FCNTL_SYNC]]
** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and
@@ -999,7 +1007,7 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_VFSNAME]]
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
-** all [VFSes] in the VFS stack. The names are of all VFS shims and the
+** all [VFSes] in the VFS stack. The names of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
** [sqlite3_malloc()] and the result is stored in the char* variable
** that the fourth parameter of [sqlite3_file_control()] points to.
@@ -1013,7 +1021,7 @@ struct sqlite3_io_methods {
** ^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
+** of type "[sqlite3_vfs] **". This opcode 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.
@@ -1203,7 +1211,7 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
** whether or not there is a database client in another process with a wal-mode
-** transaction open on the database or not. It is only available on unix.The
+** transaction open on the database or not. It is only available on unix. The
** (void*) argument passed with this file-control should be a pointer to a
** value of type (int). The integer value is set to 1 if the database is a wal
** mode database and there exists at least one client in another process that
@@ -1221,6 +1229,15 @@ struct sqlite3_io_methods {
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
** purges the contents of the in-memory page cache. If there is an open
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
+**
+** <li>[[SQLITE_FCNTL_FILESTAT]]
+** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information
+** about the [sqlite3_file] objects used access the database and journal files
+** for the given schema. The fourth parameter to [sqlite3_file_control()]
+** should be an initialized [sqlite3_str] pointer. JSON text describing
+** various aspects of the sqlite3_file object is appended to the sqlite3_str.
+** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time
+** options are used to enable it.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1266,6 +1283,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
+#define SQLITE_FCNTL_FILESTAT 45
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1628,7 +1646,7 @@ struct sqlite3_vfs {
** SQLite interfaces so that an application usually does not need to
** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
** calls sqlite3_initialize() so the SQLite library will be automatically
-** initialized when [sqlite3_open()] is called if it has not be initialized
+** initialized when [sqlite3_open()] is called if it has not been initialized
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
** compile-time option, then the automatic calls to sqlite3_initialize()
** are omitted and the application must call sqlite3_initialize() directly
@@ -1885,21 +1903,21 @@ struct sqlite3_mem_methods {
** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
-** routines with a wrapper that simulations memory allocation failure or
+** routines with a wrapper that simulates memory allocation failure or
** tracks memory usage, for example. </dd>
**
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
-** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
+** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of
** type int, interpreted as a boolean, which if true provides a hint to
** SQLite that it should avoid large memory allocations if possible.
** SQLite will run faster if it is free to make large memory allocations,
-** but some application might prefer to run slower in exchange for
+** but some applications might prefer to run slower in exchange for
** guarantees about memory fragmentation that are possible if large
** allocations are avoided. This hint is normally off.
** </dd>
**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
-** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
+** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
@@ -1944,7 +1962,7 @@ struct sqlite3_mem_methods {
** ^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
+** 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. </dd>
@@ -1973,7 +1991,7 @@ struct sqlite3_mem_methods {
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
** pointer to an instance of the [sqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
-** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
+** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of
** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
@@ -2015,7 +2033,7 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
-** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
@@ -2032,7 +2050,7 @@ struct sqlite3_mem_methods {
** the logger function is a copy of the first parameter to the corresponding
** [sqlite3_log()] call and is intended to be a [result code] or an
** [extended result code]. ^The third parameter passed to the logger is
-** log message after formatting via [sqlite3_snprintf()].
+** a log message after formatting via [sqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
@@ -2223,7 +2241,7 @@ struct sqlite3_mem_methods {
** These constants are the available integer configuration options that
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
**
-** The [sqlite3_db_config()] interface is a var-args functions. It takes a
+** The [sqlite3_db_config()] interface is a var-args function. It takes a
** variable number of parameters, though always at least two. The number of
** parameters passed into sqlite3_db_config() depends on which of these
** constants is given as the second parameter. This documentation page
@@ -2335,17 +2353,20 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
-** <dd> ^This option is used to enable or disable the
-** [fts3_tokenizer()] function which is part of the
-** [FTS3] full-text search engine extension.
-** There must 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. </dd>
+** <dd> ^This option is used to enable or disable using the
+** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine
+** extension - without using bound parameters as the parameters. Doing so
+** is disabled by default. There must be two additional arguments. The first
+** argument is an integer. If it is passed 0, then using fts3_tokenizer()
+** without bound parameters is disabled. If it is passed a positive value,
+** then calling fts3_tokenizer without bound parameters is enabled. If it
+** is passed a negative value, this setting is not modified - this can be
+** used to query for the current setting. The second parameter is a pointer
+** to an integer into which is written 0 or 1 to indicate the current value
+** of this setting (after it is modified, if applicable). The second
+** parameter may be a NULL pointer, in which case the value of the setting
+** is not reported back. Refer to [FTS3] documentation for further details.
+** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
@@ -2357,8 +2378,8 @@ struct sqlite3_mem_methods {
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
** 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.
+** If the first argument is -1, then no changes are made to the 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
@@ -2476,7 +2497,7 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
-** the legacy behavior of the [ALTER TABLE RENAME] command such it
+** the legacy behavior of the [ALTER TABLE RENAME] command such that it
** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
** additional information. This feature can also be turned on and off
@@ -2525,7 +2546,7 @@ struct sqlite3_mem_methods {
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag. When activated, this flag causes all newly
-** created database file to have a schema format version number (the 4-byte
+** created database files to have a schema format version number (the 4-byte
** integer found at offset 44 into the database header) of 1. This in turn
** means that the resulting database file will be readable and writable by
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
@@ -2552,7 +2573,7 @@ struct sqlite3_mem_methods {
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default. <p>This option takes two arguments: an integer and a pointer to
-** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
+** an integer. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
@@ -2595,8 +2616,8 @@ struct sqlite3_mem_methods {
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
** This capability is enabled by default. Applications can disable or
-** reenable this capability using the current DBCONFIG option. If the
-** the this capability is disabled, the [ATTACH] command will still work,
+** reenable this capability using the current DBCONFIG option. If
+** this capability is disabled, the [ATTACH] command will still work,
** but the database will be opened read-only. If this option is disabled,
** then the ability to create a new database using [ATTACH] is also disabled,
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
@@ -2630,7 +2651,7 @@ struct sqlite3_mem_methods {
**
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
** overall call to [sqlite3_db_config()] has a total of four parameters.
-** The first argument (the third parameter to sqlite3_db_config()) is a integer.
+** The first argument (the third parameter to sqlite3_db_config()) is an integer.
** The second argument is a pointer to an integer. If the first argument is 1,
** then the option becomes enabled. If the first integer argument is 0, then the
** option is disabled. If the first argument is -1, then the option setting
@@ -2920,7 +2941,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*);
** ^These routines return 0 if the statement is incomplete. ^If a
** memory allocation fails, then SQLITE_NOMEM is returned.
**
-** ^These routines do not parse the SQL statements thus
+** ^These routines do not parse the SQL statements and thus
** will not detect syntactically incorrect SQL.
**
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
@@ -3037,7 +3058,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
-** Internally, each SQLite database handle store two timeout values - the
+** Internally, each SQLite database handle stores two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
@@ -3067,7 +3088,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
-** Definition: A <b>result table</b> is memory data structure created by the
+** Definition: A <b>result table</b> is a memory data structure created by the
** [sqlite3_get_table()] interface. A result table records the
** complete query results from one or more queries.
**
@@ -3210,7 +3231,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** ^Calling sqlite3_free() with a pointer previously returned
** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
** that it might be reused. ^The sqlite3_free() routine is
-** a no-op if is called with a NULL pointer. Passing a NULL pointer
+** a no-op if it is called with a NULL pointer. Passing a NULL pointer
** to sqlite3_free() is harmless. After being freed, memory
** should neither be read nor written. Even reading previously freed
** memory might result in a segmentation fault or other severe error.
@@ -3228,13 +3249,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** sqlite3_free(X).
** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
-** ^If M is the size of the prior allocation, then min(N,M) bytes
-** of the prior allocation are copied into the beginning of buffer returned
+** ^If M is the size of the prior allocation, then min(N,M) bytes of the
+** prior allocation are copied into the beginning of the buffer returned
** by sqlite3_realloc(X,N) and the prior allocation is freed.
** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
-** ^The sqlite3_realloc64(X,N) interfaces works the same as
+** ^The sqlite3_realloc64(X,N) interface works the same as
** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
@@ -3284,7 +3305,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
** was last reset. ^The values returned by [sqlite3_memory_used()] and
** [sqlite3_memory_highwater()] include any overhead
** added by SQLite in its implementation of [sqlite3_malloc()],
-** but not overhead added by the any underlying system library
+** but not overhead added by any underlying system library
** routines that [sqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
@@ -3736,7 +3757,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** there is no harm in trying.)
**
** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
-** <dd>The database is opened [shared cache] enabled, overriding
+** <dd>The database is opened with [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
** The [use of shared cache mode is discouraged] and hence shared cache
@@ -3744,7 +3765,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
-** <dd>The database is opened [shared cache] disabled, overriding
+** <dd>The database is opened with [shared cache] disabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
**
@@ -4162,7 +4183,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** subsequent calls to other SQLite interface functions.)^
**
** ^The sqlite3_errstr(E) interface returns the English-language text
-** that describes the [result code] E, as UTF-8, or NULL if E is not an
+** that describes the [result code] E, as UTF-8, or NULL if E is not a
** result code for which a text error message is available.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
@@ -4170,7 +4191,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** ^If the most recent error references a specific token in the input
** SQL, the sqlite3_error_offset() interface returns the byte offset
** of the start of that token. ^The byte offset returned by
-** sqlite3_error_offset() assumes that the input SQL is UTF8.
+** sqlite3_error_offset() assumes that the input SQL is UTF-8.
** ^If the most recent error does not reference a specific token in the input
** SQL, then the sqlite3_error_offset() function returns -1.
**
@@ -4196,6 +4217,34 @@ SQLITE_API const char *sqlite3_errstr(int);
SQLITE_API int sqlite3_error_offset(sqlite3 *db);
/*
+** CAPI3REF: Set Error Codes And Message
+** METHOD: sqlite3
+**
+** Set the error code of the database handle passed as the first argument
+** to errcode, and the error message to a copy of nul-terminated string
+** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
+** the default message associated with the supplied error code. Subsequent
+** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will
+** return the values set by this routine in place of what was previously
+** set by SQLite itself.
+**
+** This function returns SQLITE_OK if the error code and error message are
+** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
+** the database handle is NULL or invalid.
+**
+** The error code and message set by this routine remains in effect until
+** they are changed, either by another call to this routine or until they are
+** changed to by SQLite itself to reflect the result of some subsquent
+** API call.
+**
+** This function is intended for use by SQLite extensions or wrappers. The
+** idea is that an extension or wrapper can use this routine to set error
+** messages and error codes and thus behave more like a core SQLite
+** feature from the point of view of an application.
+*/
+SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);
+
+/*
** CAPI3REF: Prepared Statement Object
** KEYWORDS: {prepared statement} {prepared statements}
**
@@ -4269,8 +4318,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
**
** These constants define various performance limits
** that can be lowered at run-time using [sqlite3_limit()].
-** The synopsis of the meanings of the various limits is shown below.
-** Additional information is available at [limits | Limits in SQLite].
+** A concise description of these limits follows, and additional information
+** is available at [limits | Limits in SQLite].
**
** <dl>
** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt>
@@ -4335,7 +4384,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
/*
** CAPI3REF: Prepare Flags
**
-** These constants define various flags that can be passed into
+** These constants define various flags that can be passed into the
** "prepFlags" parameter of the [sqlite3_prepare_v3()] and
** [sqlite3_prepare16_v3()] interfaces.
**
@@ -4422,7 +4471,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** there is a small performance advantage to passing an nByte parameter that
** is the number of bytes in the input string <i>including</i>
** the nul-terminator.
-** Note that nByte measure the length of the input in bytes, not
+** Note that nByte measures the length of the input in bytes, not
** characters, even for the UTF-16 interfaces.
**
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
@@ -4556,7 +4605,7 @@ SQLITE_API int sqlite3_prepare16_v3(
**
** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
-** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
+** maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
@@ -4744,7 +4793,7 @@ typedef struct sqlite3_value sqlite3_value;
**
** The context in which an SQL function executes is stored in an
** sqlite3_context object. ^A pointer to an sqlite3_context object
-** is always first parameter to [application-defined SQL functions].
+** is always the first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
** [sqlite3_aggregate_context()], [sqlite3_user_data()],
@@ -4868,9 +4917,11 @@ typedef struct sqlite3_context sqlite3_context;
** associated with the pointer P of type T. ^D is either a NULL pointer or
** a pointer to a destructor function for P. ^SQLite will invoke the
** destructor D with a single argument of P when it is finished using
-** P. The T parameter should be a static string, preferably a string
-** literal. The sqlite3_bind_pointer() routine is part of the
-** [pointer passing interface] added for SQLite 3.20.0.
+** P, even if the call to sqlite3_bind_pointer() fails. Due to a
+** historical design quirk, results are undefined if D is
+** SQLITE_TRANSIENT. The T parameter should be a static string,
+** preferably a string literal. The sqlite3_bind_pointer() routine is
+** part of the [pointer passing interface] added for SQLite 3.20.0.
**
** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
@@ -5481,7 +5532,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
**
** ^The sqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
-** or if the statement is never been evaluated, then sqlite3_finalize() returns
+** or if the statement has never been evaluated, then sqlite3_finalize() returns
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
** sqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
@@ -5713,7 +5764,7 @@ SQLITE_API int sqlite3_create_window_function(
/*
** CAPI3REF: Text Encodings
**
-** These constant define integer codes that represent the various
+** These constants define integer codes that represent the various
** text encodings supported by SQLite.
*/
#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
@@ -5805,7 +5856,7 @@ SQLITE_API int sqlite3_create_window_function(
** result.
** Every function that invokes [sqlite3_result_subtype()] should have this
** property. If it does not, then the call to [sqlite3_result_subtype()]
-** might become a no-op if the function is used as term in an
+** might become a no-op if the function is used as a term in an
** [expression index]. On the other hand, SQL functions that never invoke
** [sqlite3_result_subtype()] should avoid setting this property, as the
** purpose of this property is to disable certain optimizations that are
@@ -5932,7 +5983,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
** that the xUpdate method call was invoked to implement and if
-** and the prior [xColumn] method call that was invoked to extracted
+** the prior [xColumn] method call that was invoked to extract
** the value for that column returned without setting a result (probably
** because it queried [sqlite3_vtab_nochange()] and found that the column
** was unchanging). ^Within an [xUpdate] method, any value for which
@@ -6205,6 +6256,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
** or a NULL pointer if there were no prior calls to
** sqlite3_set_clientdata() with the same values of D and N.
** Names are compared using strcmp() and are thus case sensitive.
+** It returns 0 on success and SQLITE_NOMEM on allocation failure.
**
** If P and X are both non-NULL, then the destructor X is invoked with
** argument P on the first of the following occurrences:
@@ -8881,9 +8933,18 @@ SQLITE_API int sqlite3_status64(
** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
+** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same
+** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H
+** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead
+** of pointers to 32-bit integers, which allows larger status values
+** to be returned. If a status value exceeds 2,147,483,647 then
+** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64()
+** will return the full value.
+**
** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
*/
SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
+SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
/*
** CAPI3REF: Status Parameters for database connections
@@ -8980,6 +9041,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** If an IO or other error occurs while writing a page to disk, the effect
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
+** <p>
+** ^(There is overlap between the quantities measured by this parameter
+** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL.
+** Resetting one will reduce the other.)^
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
@@ -8995,6 +9060,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
** resolved.)^ ^The highwater mark is always 0.
+**
+** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt>
+** <dd>^(This parameter returns the number of bytes written to temporary
+** files on disk that could have been kept in memory had sufficient memory
+** been available. This value includes writes to intermediate tables that
+** are part of complex queries, external sorts that spill to disk, and
+** writes to TEMP tables.)^
+** ^The highwater mark is always 0.
+** <p>
+** ^(There is overlap between the quantities measured by this parameter
+** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE.
+** Resetting one will reduce the other.)^
** </dd>
** </dl>
*/
@@ -9011,7 +9088,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
#define SQLITE_DBSTATUS_CACHE_SPILL 12
-#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13
+#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */
/*
@@ -9776,7 +9854,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** is the number of pages currently in the write-ahead log file,
** including those that were just committed.
**
-** The callback function should normally return [SQLITE_OK]. ^If an error
+** ^The callback function should normally return [SQLITE_OK]. ^If an error
** code is returned, that error will propagate back up through the
** SQLite code base to cause the statement that provoked the callback
** to report an error, though the commit will have still occurred. If the
@@ -9784,13 +9862,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** that does not correspond to any valid SQLite error code, the results
** are undefined.
**
-** A single database handle may have at most a single write-ahead log callback
-** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
-** previously registered write-ahead log callback. ^The return value is
-** a copy of the third parameter from the previous call, if any, or 0.
-** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
-** overwrite any prior [sqlite3_wal_hook()] settings.
+** ^A single database handle may have at most a single write-ahead log
+** callback registered at one time. ^Calling [sqlite3_wal_hook()]
+** replaces the default behavior or previously registered write-ahead
+** log callback.
+**
+** ^The return value is a copy of the third parameter from the
+** previous call, if any, or 0.
+**
+** ^The [sqlite3_wal_autocheckpoint()] interface and the
+** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and
+** will overwrite any prior [sqlite3_wal_hook()] settings.
+**
+** ^If a write-ahead log callback is set using this function then
+** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint]
+** should be invoked periodically to keep the write-ahead log file
+** from growing without bound.
+**
+** ^Passing a NULL pointer for the callback disables automatic
+** checkpointing entirely. To re-enable the default behavior, call
+** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint].
*/
SQLITE_API void *sqlite3_wal_hook(
sqlite3*,
@@ -9807,7 +9898,7 @@ SQLITE_API void *sqlite3_wal_hook(
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file. ^Passing zero or
-** a negative value as the nFrame parameter disables automatic
+** a negative value as the N parameter disables automatic
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
@@ -9823,9 +9914,10 @@ SQLITE_API void *sqlite3_wal_hook(
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
-** pages. The use of this interface
-** is only necessary if the default setting is found to be suboptimal
-** for a particular application.
+** pages.
+**
+** ^The use of this interface is only necessary if the default setting
+** is found to be suboptimal for a particular application.
*/
SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
@@ -9890,6 +9982,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
** addition that it also truncates the log file to zero bytes just prior
** to a successful return.
+**
+** <dt>SQLITE_CHECKPOINT_NOOP<dd>
+** ^This mode always checkpoints zero frames. The only reason to invoke
+** a NOOP checkpoint is to access the values returned by
+** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt.
** </dl>
**
** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
@@ -9960,6 +10057,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
+#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
@@ -10787,7 +10885,7 @@ typedef struct sqlite3_snapshot {
** The [sqlite3_snapshot_get()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
+SQLITE_API int sqlite3_snapshot_get(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot **ppSnapshot
@@ -10836,7 +10934,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** The [sqlite3_snapshot_open()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
+SQLITE_API int sqlite3_snapshot_open(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot *pSnapshot
@@ -10853,7 +10951,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
** The [sqlite3_snapshot_free()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
+SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*);
/*
** CAPI3REF: Compare the ages of two snapshot handles.
@@ -10880,7 +10978,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
+SQLITE_API int sqlite3_snapshot_cmp(
sqlite3_snapshot *p1,
sqlite3_snapshot *p2
);
@@ -10908,7 +11006,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
+SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Serialize a database
@@ -10982,12 +11080,13 @@ SQLITE_API unsigned char *sqlite3_serialize(
**
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
** [database connection] D to disconnect from database S and then
-** reopen S as an in-memory database based on the serialization contained
-** in P. The serialized database P is N bytes in size. M is the size of
-** the buffer P, which might be larger than N. If M is larger than N, and
-** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
-** permitted to add content to the in-memory database as long as the total
-** size does not exceed M bytes.
+** reopen S as an in-memory database based on the serialization
+** contained in P. If S is a NULL pointer, the main database is
+** used. The serialized database P is N bytes in size. M is the size
+** of the buffer P, which might be larger than N. If M is larger than
+** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then
+** SQLite is permitted to add content to the in-memory database as
+** long as the total size does not exceed M bytes.
**
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
** invoke sqlite3_free() on the serialization buffer when the database
@@ -11055,6 +11154,54 @@ SQLITE_API int sqlite3_deserialize(
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
/*
+** CAPI3REF: Bind array values to the CARRAY table-valued function
+**
+** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to
+** one of the first argument of the [carray() table-valued function]. The
+** S parameter is a pointer to the [prepared statement] that uses the carray()
+** functions. I is the parameter index to be bound. P is a pointer to the
+** array to be bound, and N is the number of eements in the array. The
+** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64],
+** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to
+** indicate the datatype of the array being bound. The X argument is not a
+** NULL pointer, then SQLite will invoke the function X on the P parameter
+** after it has finished using P, even if the call to
+** sqlite3_carray_bind() fails. The special-case finalizer
+** SQLITE_TRANSIENT has no effect here.
+*/
+SQLITE_API int sqlite3_carray_bind(
+ sqlite3_stmt *pStmt, /* Statement to be bound */
+ int i, /* Parameter index */
+ void *aData, /* Pointer to array data */
+ int nData, /* Number of data elements */
+ int mFlags, /* CARRAY flags */
+ void (*xDel)(void*) /* Destructor for aData */
+);
+
+/*
+** CAPI3REF: Datatypes for the CARRAY table-valued function
+**
+** The fifth argument to the [sqlite3_carray_bind()] interface musts be
+** one of the following constants, to specify the datatype of the array
+** that is being bound into the [carray table-valued function].
+*/
+#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */
+#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */
+#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */
+#define SQLITE_CARRAY_TEXT 3 /* Data is char* */
+#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */
+
+/*
+** Versions of the above #defines that omit the initial SQLITE_, for
+** legacy compatibility.
+*/
+#define CARRAY_INT32 0 /* Data is 32-bit signed integers */
+#define CARRAY_INT64 1 /* Data is 64-bit signed integers */
+#define CARRAY_DOUBLE 2 /* Data is doubles */
+#define CARRAY_TEXT 3 /* Data is char* */
+#define CARRAY_BLOB 4 /* Data is struct iovec */
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
@@ -12313,14 +12460,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** update the "main" database attached to handle db with the changes found in
** the changeset passed via the second and third arguments.
**
+** All changes made by these functions are enclosed in a savepoint transaction.
+** If any other error (aside from a constraint failure when attempting to
+** write to the target database) occurs, then the savepoint transaction is
+** rolled back, restoring the target database to its original state, and an
+** SQLite error code returned. Additionally, starting with version 3.51.0,
+** an error code and error message that may be accessed using the
+** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database
+** handle.
+**
** The fourth argument (xFilter) passed to these functions is the "filter
-** callback". If it is not NULL, then for each table affected by at least one
-** change in the changeset, the filter callback is invoked with
-** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument as the first. If the "filter callback"
-** returns zero, then no attempt is made to apply any changes to the table.
-** Otherwise, if the return value is non-zero or the xFilter argument to
-** is NULL, all changes related to the table are attempted.
+** callback". This may be passed NULL, in which case all changes in the
+** changeset are applied to the database. For sqlite3changeset_apply() and
+** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
+** for each table affected by at least one change in the changeset. In this
+** case the table name is passed as the second argument, and a copy of
+** the context pointer passed as the sixth argument to apply() or apply_v2()
+** 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, all changes related to the table are attempted.
+**
+** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
+** per change. The second argument in this case is an sqlite3_changeset_iter
+** that may be queried using the usual APIs for the details of the current
+** change. If the "filter callback" returns zero in this case, then no attempt
+** is made to apply the current change. If it returns non-zero, the change
+** is applied.
**
** 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
@@ -12341,11 +12506,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** 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.
+** to modify the table contents according to each UPDATE, INSERT or DELETE
+** change that is not excluded by a filter callback. 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
@@ -12441,12 +12606,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** This can be used to further customize the application's conflict
** resolution strategy.
**
-** All changes made by these functions are enclosed in a savepoint transaction.
-** If any other error (aside from a constraint failure when attempting to
-** write to the target database) occurs, then the savepoint transaction is
-** rolled back, restoring the target database to its original state, and an
-** SQLite error code returned.
-**
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
** may set (*ppRebase) to point to a "rebase" that may be used with the
@@ -12496,6 +12655,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
+SQLITE_API int sqlite3changeset_apply_v3(
+ 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() */
+ sqlite3_changeset_iter *p /* Handle describing change */
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
/*
** CAPI3REF: Flags for sqlite3changeset_apply_v2
@@ -12915,6 +13091,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
);
+SQLITE_API int sqlite3changeset_apply_v3_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() */
+ sqlite3_changeset_iter *p
+ ),
+ 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 */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
SQLITE_API int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
diff --git a/src/3rdparty/sqlite/update_sqlite.sh b/src/3rdparty/sqlite/update_sqlite.sh
index b214cffc096..4b8e1869d8c 100755
--- a/src/3rdparty/sqlite/update_sqlite.sh
+++ b/src/3rdparty/sqlite/update_sqlite.sh
@@ -7,8 +7,8 @@
# sqlite.c and sqlite.h and updates qt_attribution.json
version_maj=3
-version_min=50
-version_patch=4
+version_min=51
+version_patch=0
year=2025
version=${version_maj}.${version_min}.${version_patch}
diff --git a/src/3rdparty/wayland/protocols/color-management/REUSE.toml b/src/3rdparty/wayland/protocols/color-management/REUSE.toml
index c7b978663b1..a2803d886f5 100644
--- a/src/3rdparty/wayland/protocols/color-management/REUSE.toml
+++ b/src/3rdparty/wayland/protocols/color-management/REUSE.toml
@@ -1,11 +1,12 @@
version = 1
[[annotations]]
-path = "xx-color-management-v4.xml"
+path = "color-management-v1.xml"
precedence = "closest"
SPDX-FileCopyrightText = ["Copyright 2019 Sebastian Wick",
"Copyright 2019 Erwin Burema",
"Copyright 2020 AMD",
"Copyright 2020-2024 Collabora, Ltd.",
- "Copyright 2024 Xaver Hugl"]
+ "Copyright 2024 Xaver Hugl",
+ "Copyright 2022-2025 Red Hat, Inc."]
SPDX-License-Identifier = "MIT"
diff --git a/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml b/src/3rdparty/wayland/protocols/color-management/color-management-v1.xml
index eab84dfd992..4c1bc759c39 100644
--- a/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml
+++ b/src/3rdparty/wayland/protocols/color-management/color-management-v1.xml
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="xx_color_management_v4">
+<protocol name="color_management_v1">
<copyright>
Copyright 2019 Sebastian Wick
Copyright 2019 Erwin Burema
Copyright 2020 AMD
Copyright 2020-2024 Collabora, Ltd.
Copyright 2024 Xaver Hugl
+ Copyright 2022-2025 Red Hat, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -63,20 +64,27 @@
color encoding terminology where possible. The glossary in the color-and-hdr
repository shall be the authority on the definition of terms in this
protocol.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
</description>
- <interface name="xx_color_manager_v4" version="1">
+ <interface name="wp_color_manager_v1" version="1">
<description summary="color manager singleton">
- A global interface used for getting color management extensions for
- wl_surface and wl_output objects, and for creating client defined image
- description objects. The extension interfaces allow
+ A singleton global interface used for getting color management extensions
+ for wl_surface and wl_output objects, and for creating client defined
+ image description objects. The extension interfaces allow
getting the image description of outputs and setting the image
description of surfaces.
+
+ Compositors should never remove this global.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the color manager">
- Destroy the xx_color_manager_v4 object. This does not affect any other
+ Destroy the wp_color_manager_v1 object. This does not affect any other
objects in any way.
</description>
</request>
@@ -116,9 +124,9 @@
<description summary="compositor supported features"/>
<entry name="icc_v2_v4" value="0"
- summary="new_icc_creator request"/>
+ summary="create_icc_creator request"/>
<entry name="parametric" value="1"
- summary="new_parametric_creator request"/>
+ summary="create_parametric_creator request"/>
<entry name="set_primaries" value="2"
summary="parametric set_primaries request"/>
<entry name="set_tf_power" value="3"
@@ -140,6 +148,8 @@
is supported as well.
</description>
</entry>
+ <entry name="windows_scrgb" value="7"
+ summary="create_windows_scrgb request"/>
</enum>
<enum name="primaries">
@@ -148,10 +158,12 @@
is the authority, when it comes to the exact values of primaries and
authoritative specifications, where an equivalent code point exists.
+ A value of 0 is invalid and will never be present in the list of enums.
+
Descriptions do list the specifications for convenience.
</description>
- <entry name="srgb" value="0">
+ <entry name="srgb" value="1">
<description summary="Color primaries for the sRGB color space as defined by the BT.709 standard">
Color primaries as defined by
- Rec. ITU-R BT.709-6
@@ -164,7 +176,7 @@
Equivalent to H.273 ColourPrimaries code point 1.
</description>
</entry>
- <entry name="pal_m" value="1">
+ <entry name="pal_m" value="2">
<description summary="Color primaries for PAL-M as defined by the BT.470 standard">
Color primaries as defined by
- Rec. ITU-R BT.470-6 System M (historical)
@@ -175,7 +187,7 @@
Equivalent to H.273 ColourPrimaries code point 4.
</description>
</entry>
- <entry name="pal" value="2">
+ <entry name="pal" value="3">
<description summary="Color primaries for PAL as defined by the BT.601 standard">
Color primaries as defined by
- Rec. ITU-R BT.470-6 System B, G (historical)
@@ -185,7 +197,7 @@
Equivalent to H.273 ColourPrimaries code point 5.
</description>
</entry>
- <entry name="ntsc" value="3">
+ <entry name="ntsc" value="4">
<description summary="Color primaries for NTSC as defined by the BT.601 standard">
Color primaries as defined by
- Rec. ITU-R BT.601-7 525
@@ -196,13 +208,13 @@
Equivalent to H.273 ColourPrimaries code point 6 and 7.
</description>
</entry>
- <entry name="generic_film" value="4">
+ <entry name="generic_film" value="5">
<description summary="Generic film with colour filters using Illuminant C">
Color primaries as defined by H.273 for generic film.
Equivalent to H.273 ColourPrimaries code point 8.
</description>
</entry>
- <entry name="bt2020" value="5">
+ <entry name="bt2020" value="6">
<description summary="Color primaries as defined by the BT.2020 and BT.2100 standard">
Color primaries as defined by
- Rec. ITU-R BT.2020-2
@@ -210,7 +222,7 @@
Equivalent to H.273 ColourPrimaries code point 9.
</description>
</entry>
- <entry name="cie1931_xyz" value="6">
+ <entry name="cie1931_xyz" value="7">
<description summary="Color primaries of the full CIE 1931 XYZ color space">
Color primaries as defined as the maximum of the CIE 1931 XYZ color
space by
@@ -219,21 +231,21 @@
Equivalent to H.273 ColourPrimaries code point 10.
</description>
</entry>
- <entry name="dci_p3" value="7">
+ <entry name="dci_p3" value="8">
<description summary="Color primaries of the DCI P3 color space as defined by the SMPTE RP 431 standard">
Color primaries as defined by Digital Cinema System and published in
SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point
11.
</description>
</entry>
- <entry name="display_p3" value="8">
+ <entry name="display_p3" value="9">
<description summary="Color primaries of Display P3 variant of the DCI-P3 color space as defined by the SMPTE EG 432 standard">
Color primaries as defined by Digital Cinema System and published in
SMPTE EG 432-1 (2010).
Equivalent to H.273 ColourPrimaries code point 12.
</description>
</entry>
- <entry name="adobe_rgb" value="9">
+ <entry name="adobe_rgb" value="10">
<description summary="Color primaries of the Adobe RGB color space as defined by the ISO 12640 standard">
Color primaries as defined by Adobe as "Adobe RGB" and later published
by ISO 12640-4 (2011).
@@ -243,23 +255,32 @@
<enum name="transfer_function">
<description summary="named transfer functions">
- Named transfer functions used to encode well-known transfer
+ Named transfer functions used to represent well-known transfer
characteristics. H.273 is the authority, when it comes to the exact
formulas and authoritative specifications, where an equivalent code
point exists.
+ A value of 0 is invalid and will never be present in the list of enums.
+
Descriptions do list the specifications for convenience.
</description>
- <entry name="bt709" value="0">
- <description summary="BT.709 transfer function">
- Transfer characteristics as defined by
+ <entry name="bt1886" value="1">
+ <description summary="BT.1886 display transfer characteristic">
+ Rec. ITU-R BT.1886 is the display transfer characteristic assumed by
+ - Rec. ITU-R BT.601-7 525 and 625
- Rec. ITU-R BT.709-6
- - Rec. ITU-R BT.1361-0 conventional colour gamut system (historical)
- Equivalent to H.273 TransferCharacteristics code point 1, 6, 14, 15.
+ - Rec. ITU-R BT.2020-2
+ These recommendations are referred to by H.273 TransferCharacteristics
+ code points 1, 6, 14, and 15, which are all equivalent.
+
+ This TF implies these default luminances from Rec. ITU-R BT.2035:
+ - primary color volume minimum: 0.01 cd/m²
+ - primary color volume maximum: 100 cd/m²
+ - reference white: 100 cd/m²
</description>
</entry>
- <entry name="gamma22" value="1">
+ <entry name="gamma22" value="2">
<description summary="Assumed display gamma 2.2 transfer function">
Transfer characteristics as defined by
- Rec. ITU-R BT.470-6 System M (historical)
@@ -269,60 +290,62 @@
of Federal Regulations 73.682 (a) (20)
- Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
Equivalent to H.273 TransferCharacteristics code point 4.
+
+ Note: an sRGB display (IEC 61966-2-1) uses this transfer function.
</description>
</entry>
- <entry name="gamma28" value="2">
+ <entry name="gamma28" value="3">
<description summary="Assumed display gamma 2.8 transfer function">
Transfer characteristics as defined by
- Rec. ITU-R BT.470-6 System B, G (historical)
Equivalent to H.273 TransferCharacteristics code point 5.
</description>
</entry>
- <entry name="st240" value="3">
+ <entry name="st240" value="4">
<description summary="SMPTE ST 240 transfer function">
Transfer characteristics as defined by
- SMPTE ST 240 (1999)
Equivalent to H.273 TransferCharacteristics code point 7.
</description>
</entry>
- <entry name="linear" value="4">
- <description summary="linear transfer function">
- Linear transfer characteristics.
- Equivalent to H.273 TransferCharacteristics code point 8.
+ <entry name="ext_linear" value="5">
+ <description summary="extended linear transfer function">
+ Linear transfer function defined over all real numbers.
+ Normalised electrical values are equal the normalised optical values.
+
+ The differences to H.273 TransferCharacteristics code point 8 are
+ the definition over all real numbers.
</description>
</entry>
- <entry name="log_100" value="5">
+ <entry name="log_100" value="6">
<description summary="logarithmic 100:1 transfer function">
Logarithmic transfer characteristic (100:1 range).
Equivalent to H.273 TransferCharacteristics code point 9.
</description>
</entry>
- <entry name="log_316" value="6">
+ <entry name="log_316" value="7">
<description summary="logarithmic (100*Sqrt(10) : 1) transfer function">
Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range).
Equivalent to H.273 TransferCharacteristics code point 10.
</description>
</entry>
- <entry name="xvycc" value="7">
+ <entry name="xvycc" value="8">
<description summary="IEC 61966-2-4 transfer function">
Transfer characteristics as defined by
- IEC 61966-2-4
Equivalent to H.273 TransferCharacteristics code point 11.
</description>
</entry>
- <entry name="bt1361" value="8">
- <description summary="BT.1361 extended transfer function">
- Transfer characteristics as defined by
- - Rec. ITU-R BT.1361-0 extended colour gamut system (historical)
- Equivalent to H.273 TransferCharacteristics code point 12.
- </description>
- </entry>
<entry name="srgb" value="9">
<description summary="sRGB piece-wise transfer function">
Transfer characteristics as defined by
- IEC 61966-2-1 sRGB
Equivalent to H.273 TransferCharacteristics code point 13 with
MatrixCoefficients set to 0.
+
+ Note: This is not appropriate for describing sRGB material.
+ sRGB material is intended to be viewed on an sRGB display, and
+ that is described by gamma22.
</description>
</entry>
<entry name="ext_srgb" value="10">
@@ -344,6 +367,12 @@
- primary color volume minimum: 0.005 cd/m²
- primary color volume maximum: 10000 cd/m²
- reference white: 203 cd/m²
+
+ The difference between the primary color volume minimum and maximum
+ must be approximately 10000 cd/m² as that is the swing of the EOTF
+ defined by ST 2084 and BT.2100. The default value for the
+ reference white is a protocol addition: it is suggested by
+ Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100.
</description>
</entry>
<entry name="st428" value="12">
@@ -364,87 +393,146 @@
- primary color volume minimum: 0.005 cd/m²
- primary color volume maximum: 1000 cd/m²
- reference white: 203 cd/m²
- Note: HLG is a scene referred signal. All absolute luminance values
- used here for HLG assume a 1000 cd/m² display.
+
+ HLG is a relative display-referred signal with a specified
+ non-linear mapping to the display peak luminance (the HLG OOTF).
+ All absolute luminance values used here for HLG assume a 1000 cd/m²
+ peak display.
+
+ The default value for the reference white is a protocol addition:
+ it is suggested by Report ITU-R BT.2408-7 and is not part of
+ ARIB STD-B67 or BT.2100.
</description>
</entry>
</enum>
<request name="get_output">
<description summary="create a color management interface for a wl_output">
- This creates a new xx_color_management_output_v4 object for the
+ This creates a new wp_color_management_output_v1 object for the
given wl_output.
- See the xx_color_management_output_v4 interface for more details.
+ See the wp_color_management_output_v1 interface for more details.
</description>
- <arg name="id" type="new_id" interface="xx_color_management_output_v4"/>
+ <arg name="id" type="new_id" interface="wp_color_management_output_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="get_surface">
<description summary="create a color management interface for a wl_surface">
- If a xx_color_management_surface_v4 object already exists for the given
+ If a wp_color_management_surface_v1 object already exists for the given
wl_surface, the protocol error surface_exists is raised.
- This creates a new color xx_color_management_surface_v4 object for the
+ This creates a new color wp_color_management_surface_v1 object for the
given wl_surface.
- See the xx_color_management_surface_v4 interface for more details.
+ See the wp_color_management_surface_v1 interface for more details.
</description>
- <arg name="id" type="new_id" interface="xx_color_management_surface_v4"/>
+ <arg name="id" type="new_id" interface="wp_color_management_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
- <request name="get_feedback_surface">
+ <request name="get_surface_feedback">
<description summary="create a color management feedback interface">
- This creates a new color xx_color_management_feedback_surface_v4 object
+ This creates a new color wp_color_management_surface_feedback_v1 object
for the given wl_surface.
- See the xx_color_management_feedback_surface_v4 interface for more
+ See the wp_color_management_surface_feedback_v1 interface for more
details.
</description>
<arg name="id" type="new_id"
- interface="xx_color_management_feedback_surface_v4"/>
+ interface="wp_color_management_surface_feedback_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
- <request name="new_icc_creator">
+ <request name="create_icc_creator">
<description summary="make a new ICC-based image description creator object">
Makes a new ICC-based image description creator object with all
properties initially unset. The client can then use the object's
interface to define all the required properties for an image description
- and finally create a xx_image_description_v4 object.
+ and finally create a wp_image_description_v1 object.
This request can be used when the compositor advertises
- xx_color_manager_v4.feature.icc_v2_v4.
+ wp_color_manager_v1.feature.icc_v2_v4.
Otherwise this request raises the protocol error unsupported_feature.
</description>
<arg name="obj"
- type="new_id" interface="xx_image_description_creator_icc_v4"
+ type="new_id" interface="wp_image_description_creator_icc_v1"
summary="the new creator object"/>
</request>
- <request name="new_parametric_creator">
+ <request name="create_parametric_creator">
<description summary="make a new parametric image description creator object">
Makes a new parametric image description creator object with all
properties initially unset. The client can then use the object's
interface to define all the required properties for an image description
- and finally create a xx_image_description_v4 object.
+ and finally create a wp_image_description_v1 object.
This request can be used when the compositor advertises
- xx_color_manager_v4.feature.parametric.
+ wp_color_manager_v1.feature.parametric.
Otherwise this request raises the protocol error unsupported_feature.
</description>
<arg name="obj"
- type="new_id" interface="xx_image_description_creator_params_v4"
+ type="new_id" interface="wp_image_description_creator_params_v1"
summary="the new creator object"/>
</request>
+ <request name="create_windows_scrgb">
+ <description summary="create Windows-scRGB image description object">
+ This creates a pre-defined image description for the so-called
+ Windows-scRGB stimulus encoding. This comes from the Windows 10 handling
+ of its own definition of an scRGB color space for an HDR screen
+ driven in BT.2100/PQ signalling mode.
+
+ Windows-scRGB uses sRGB (BT.709) color primaries and white point.
+ The transfer characteristic is extended linear.
+
+ The nominal color channel value range is extended, meaning it includes
+ negative and greater than 1.0 values. Negative values are used to
+ escape the sRGB color gamut boundaries. To make use of the extended
+ range, the client needs to use a pixel format that can represent those
+ values, e.g. floating-point 16 bits per channel.
+
+ Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system
+ 0 cd/m², and R=G=B=1.0 corresponds to BT.2100/PQ system 80 cd/m².
+ The maximum is R=G=B=125.0 corresponding to 10k cd/m².
+
+ Windows-scRGB is displayed by Windows 10 by converting it to
+ BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping the
+ luminance as above. No adjustment is made to the signal to account
+ for the viewing conditions.
+
+ The reference white level of Windows-scRGB is unknown. If a
+ reference white level must be assumed for compositor processing, it
+ should be R=G=B=2.5375 corresponding to 203 cd/m² of Report ITU-R
+ BT.2408-7.
+
+ The target color volume of Windows-scRGB is unknown. The color gamut
+ may be anything between sRGB and BT.2100.
+
+ Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from
+ Windows-scRGB by using R=G=B=1.0 as the reference white level, while
+ Windows-scRGB reference white level is unknown or varies. However,
+ it seems probable that Windows implements both
+ EGL_EXT_gl_colorspace_scrgb_linear and Vulkan
+ VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB.
+
+ This request can be used when the compositor advertises
+ wp_color_manager_v1.feature.windows_scrgb.
+ Otherwise this request raises the protocol error unsupported_feature.
+
+ The resulting image description object does not allow get_information
+ request. The wp_image_description_v1.ready event shall be sent.
+ </description>
+
+ <arg name="image_description"
+ type="new_id" interface="wp_image_description_v1"/>
+ </request>
+
<event name="supported_intent">
<description summary="supported rendering intent">
When this object is created, it shall immediately send this event once
@@ -486,22 +574,29 @@
<arg name="primaries" type="uint" enum="primaries"
summary="Named color primaries"/>
</event>
+
+ <event name="done">
+ <description summary="all features have been sent">
+ This event is sent when all supported rendering intents, features,
+ transfer functions and named primaries have been sent.
+ </description>
+ </event>
</interface>
- <interface name="xx_color_management_output_v4" version="1">
+ <interface name="wp_color_management_output_v1" version="1">
<description summary="output color properties">
- A xx_color_management_output_v4 describes the color properties of an
+ A wp_color_management_output_v1 describes the color properties of an
output.
- The xx_color_management_output_v4 is associated with the wl_output global
+ The wp_color_management_output_v1 is associated with the wl_output global
underlying the wl_output object. Therefore the client destroying the
wl_output object has no impact, but the compositor removing the output
- global makes the xx_color_management_output_v4 object inert.
+ global makes the wp_color_management_output_v1 object inert.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the color management output">
- Destroy the color xx_color_management_output_v4 object. This does not
+ Destroy the color wp_color_management_output_v1 object. This does not
affect any remaining protocol objects.
</description>
</request>
@@ -520,12 +615,12 @@
<request name="get_image_description">
<description summary="get the image description of the output">
- This creates a new xx_image_description_v4 object for the current image
+ This creates a new wp_image_description_v1 object for the current image
description of the output. There always is exactly one image description
active for an output so the client should destroy the image description
created by earlier invocations of this request. This request is usually
sent as a reaction to the image_description_changed event or when
- creating a xx_color_management_output_v4 object.
+ creating a wp_color_management_output_v1 object.
The image description of an output represents the color encoding the
output expects. There might be performance and power advantages, as well
@@ -535,41 +630,41 @@
of, then the color reproduction on those outputs might be considerably
worse.
- The created xx_image_description_v4 object preserves the image
+ The created wp_image_description_v1 object preserves the image
description of the output from the time the object was created.
The resulting image description object allows get_information request.
If this protocol object is inert, the resulting image description object
- shall immediately deliver the xx_image_description_v4.failed event with
+ shall immediately deliver the wp_image_description_v1.failed event with
the no_output cause.
If the interface version is inadequate for the output's image
description, meaning that the client does not support all the events
needed to deliver the crucial information, the resulting image
description object shall immediately deliver the
- xx_image_description_v4.failed event with the low_version cause.
+ wp_image_description_v1.failed event with the low_version cause.
Otherwise the object shall immediately deliver the ready event.
</description>
<arg name="image_description"
- type="new_id" interface="xx_image_description_v4"/>
+ type="new_id" interface="wp_image_description_v1"/>
</request>
</interface>
- <interface name="xx_color_management_surface_v4" version="1">
+ <interface name="wp_color_management_surface_v1" version="1">
<description summary="color management extension to a surface">
- A xx_color_management_surface_v4 allows the client to set the color
+ A wp_color_management_surface_v1 allows the client to set the color
space and HDR properties of a surface.
- If the wl_surface associated with the xx_color_management_surface_v4 is
- destroyed, the xx_color_management_surface_v4 object becomes inert.
+ If the wl_surface associated with the wp_color_management_surface_v1 is
+ destroyed, the wp_color_management_surface_v1 object becomes inert.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the color management interface for a surface">
- Destroy the xx_color_management_surface_v4 object and do the same as
+ Destroy the wp_color_management_surface_v1 object and do the same as
unset_image_description.
</description>
</request>
@@ -580,10 +675,14 @@
summary="unsupported rendering intent"/>
<entry name="image_description" value="1"
summary="invalid image description"/>
+ <entry name="inert" value="2"
+ summary="forbidden request on inert object"/>
</enum>
<request name="set_image_description">
<description summary="set the surface image description">
+ If this protocol object is inert, the protocol error inert is raised.
+
Set the image description of the underlying surface. The image
description and rendering intent are double-buffered state, see
wl_surface.commit.
@@ -593,36 +692,47 @@
description. Compositors might convert images to match their own or any
other image descriptions.
- Image description whose creation gracefully failed (received
- xx_image_description_v4.failed) are forbidden in this request, and in
- such case the protocol error image_description is raised.
+ Image descriptions which are not ready (see wp_image_description_v1)
+ are forbidden in this request, and in such case the protocol error
+ image_description is raised.
- All image descriptions whose creation succeeded (received
- xx_image_description_v4.ready) are allowed and must always be accepted
- by the compositor.
+ All image descriptions which are ready (see wp_image_description_v1)
+ are allowed and must always be accepted by the compositor.
A rendering intent provides the client's preference on how content
colors should be mapped to each output. The render_intent value must
be one advertised by the compositor with
- xx_color_manager_v4.render_intent event, otherwise the protocol error
+ wp_color_manager_v1.render_intent event, otherwise the protocol error
render_intent is raised.
+ When an image description is set on a surface, the Transfer
+ Characteristics of the image description defines the valid range of
+ the nominal (real-valued) color channel values. The processing of
+ out-of-range color channel values is undefined, but compositors are
+ recommended to clamp the values to the valid range when possible.
+
By default, a surface does not have an associated image description
nor a rendering intent. The handling of color on such surfaces is
compositor implementation defined. Compositors should handle such
- surfaces as sRGB but may handle them differently if they have specific
+ surfaces as sRGB, but may handle them differently if they have specific
requirements.
+
+ Setting the image description has copy semantics; after this request,
+ the image description can be immediately destroyed without affecting
+ the pending state of the surface.
</description>
<arg name="image_description"
- type="object" interface="xx_image_description_v4"/>
+ type="object" interface="wp_image_description_v1"/>
<arg name="render_intent"
- type="uint" enum="xx_color_manager_v4.render_intent"
+ type="uint" enum="wp_color_manager_v1.render_intent"
summary="rendering intent"/>
</request>
<request name="unset_image_description">
<description summary="remove the surface image description">
+ If this protocol object is inert, the protocol error inert is raised.
+
This request removes any image description from the surface. See
set_image_description for how a compositor handles a surface without
an image description. This is double-buffered state, see
@@ -631,18 +741,18 @@
</request>
</interface>
- <interface name="xx_color_management_feedback_surface_v4" version="1">
+ <interface name="wp_color_management_surface_feedback_v1" version="1">
<description summary="color management extension to a surface">
- A xx_color_management_feedback_surface_v4 allows the client to get the
- preferred color description of a surface.
+ A wp_color_management_surface_feedback_v1 allows the client to get the
+ preferred image description of a surface.
If the wl_surface associated with this object is destroyed, the
- xx_color_management_feedback_surface_v4 object becomes inert.
+ wp_color_management_surface_feedback_v1 object becomes inert.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the color management interface for a surface">
- Destroy the xx_color_management_feedback_surface_v4 object.
+ Destroy the wp_color_management_surface_feedback_v1 object.
</description>
</request>
@@ -650,6 +760,8 @@
<description summary="protocol errors"/>
<entry name="inert" value="0"
summary="forbidden request on inert object"/>
+ <entry name="unsupported_feature" value="1"
+ summary="attempted to use an unsupported feature"/>
</enum>
<event name="preferred_changed">
@@ -659,17 +771,20 @@
client for its wl_surface contents. This event is sent whenever the
compositor changes the wl_surface's preferred image description.
- This event is merely a notification. When the client wants to know
- what the preferred image description is, it shall use the get_preferred
- request.
+ This event sends the identity of the new preferred state as the argument,
+ so clients who are aware of the image description already can reuse it.
+ Otherwise, if the client client wants to know what the preferred image
+ description is, it shall use the get_preferred request.
The preferred image description is not automatically used for anything.
It is only a hint, and clients may set any valid image description with
- set_image_description but there might be performance and color accuracy
+ set_image_description, but there might be performance and color accuracy
improvements by providing the wl_surface contents in the preferred
image description. Therefore clients that can, should render according
to the preferred image description
</description>
+
+ <arg name="identity" type="uint" summary="image description id number"/>
</event>
<request name="get_preferred">
@@ -682,36 +797,56 @@
reproduction, if the image description of a content update matches the
preferred image description.
- This creates a new xx_image_description_v4 object for the currently
+ This creates a new wp_image_description_v1 object for the currently
preferred image description for the wl_surface. The client should
stop using and destroy the image descriptions created by earlier
invocations of this request for the associated wl_surface.
This request is usually sent as a reaction to the preferred_changed
- event or when creating a xx_color_management_feedback_surface_v4 object
+ event or when creating a wp_color_management_surface_feedback_v1 object
if the client is capable of adapting to image descriptions.
- The created xx_image_description_v4 object preserves the preferred image
+ The created wp_image_description_v1 object preserves the preferred image
description of the wl_surface from the time the object was created.
The resulting image description object allows get_information request.
+ If the image description is parametric, the client should set it on its
+ wl_surface only if the image description is an exact match with the
+ client content. Particularly if everything else matches, but the target
+ color volume is greater than what the client needs, the client should
+ create its own parameric image description with its exact parameters.
+
If the interface version is inadequate for the preferred image
description, meaning that the client does not support all the
events needed to deliver the crucial information, the resulting image
description object shall immediately deliver the
- xx_image_description_v4.failed event with the low_version cause,
+ wp_image_description_v1.failed event with the low_version cause,
otherwise the object shall immediately deliver the ready event.
</description>
<arg name="image_description"
- type="new_id" interface="xx_image_description_v4"/>
+ type="new_id" interface="wp_image_description_v1"/>
+ </request>
+
+ <request name="get_preferred_parametric">
+ <description summary="get the preferred image description">
+ The same description as for get_preferred applies, except the returned
+ image description is guaranteed to be parametric. This is meant for
+ clients that can only deal with parametric image descriptions.
+
+ If the compositor doesn't support parametric image descriptions, the
+ unsupported_feature error is emitted.
+ </description>
+
+ <arg name="image_description"
+ type="new_id" interface="wp_image_description_v1"/>
</request>
</interface>
- <interface name="xx_image_description_creator_icc_v4" version="1">
+ <interface name="wp_image_description_creator_icc_v1" version="1">
<description summary="holder of image description ICC information">
This type of object is used for collecting all the information required
- to create a xx_image_description_v4 object from an ICC file. A complete
+ to create a wp_image_description_v1 object from an ICC file. A complete
set of required parameters consists of these properties:
- ICC file
@@ -753,19 +888,19 @@
If the particular combination of the information is not supported
by the compositor, the resulting image description object shall
- immediately deliver the xx_image_description_v4.failed event with the
+ immediately deliver the wp_image_description_v1.failed event with the
'unsupported' cause. If a valid image description was created from the
- information, the xx_image_description_v4.ready event will eventually
+ information, the wp_image_description_v1.ready event will eventually
be sent instead.
- This request destroys the xx_image_description_creator_icc_v4 object.
+ This request destroys the wp_image_description_creator_icc_v1 object.
The resulting image description object does not allow get_information
request.
</description>
<arg name="image_description"
- type="new_id" interface="xx_image_description_v4"/>
+ type="new_id" interface="wp_image_description_v1"/>
</request>
<request name="set_icc_file">
@@ -774,23 +909,23 @@
description.
The data shall be found through the given fd at the given offset, having
- the given length. The fd must seekable and readable. Violating these
+ the given length. The fd must be seekable and readable. Violating these
requirements raises the bad_fd protocol error.
If reading the data fails due to an error independent of the client, the
- compositor shall send the xx_image_description_v4.failed event on the
- created xx_image_description_v4 with the 'operating_system' cause.
+ compositor shall send the wp_image_description_v1.failed event on the
+ created wp_image_description_v1 with the 'operating_system' cause.
- The maximum size of the ICC profile is 4 MB. If length is greater than
+ The maximum size of the ICC profile is 32 MB. If length is greater than
that or zero, the protocol error bad_size is raised. If offset + length
exceeds the file size, the protocol error out_of_file is raised.
A compositor may read the file at any time starting from this request
and only until whichever happens first:
- - If create request was issued, the xx_image_description_v4 object
+ - If create request was issued, the wp_image_description_v1 object
delivers either failed or ready event; or
- if create request was not issued, this
- xx_image_description_creator_icc_v4 object is destroyed.
+ wp_image_description_creator_icc_v1 object is destroyed.
A compositor shall not modify the contents of the file, and the fd may
be sealed for writes and size changes. The client must ensure to its
@@ -800,9 +935,9 @@
The data must represent a valid ICC profile. The ICC profile version
must be 2 or 4, it must be a 3 channel profile and the class must be
Display or ColorSpace. Violating these requirements will not result in a
- protocol error but will eventually send the
- xx_image_description_v4.failed event on the created
- xx_image_description_v4 with the 'unsupported' cause.
+ protocol error, but will eventually send the
+ wp_image_description_v1.failed event on the created
+ wp_image_description_v1 with the 'unsupported' cause.
See the International Color Consortium specification ICC.1:2022 for more
details about ICC profiles.
@@ -820,10 +955,10 @@
</request>
</interface>
- <interface name="xx_image_description_creator_params_v4" version="1">
+ <interface name="wp_image_description_creator_params_v1" version="1">
<description summary="holder of image description parameters">
This type of object is used for collecting all the parameters required
- to create a xx_image_description_v4 object. A complete set of required
+ to create a wp_image_description_v1 object. A complete set of required
parameters consists of these properties:
- transfer characteristic function (tf)
- chromaticities of primaries and white point (primary color volume)
@@ -834,6 +969,9 @@
- reference white luminance level
- mastering display primaries and white point (target color volume)
- mastering luminance range
+
+ The following properties are optional and will be ignored
+ if not explicitly set:
- maximum content light level
- maximum frame-average light level
@@ -853,20 +991,16 @@
<entry name="incomplete_set" value="0"
summary="incomplete parameter set"/>
- <entry name="inconsistent_set" value="1"
- summary="invalid combination of parameters"/>
- <entry name="already_set" value="2"
+ <entry name="already_set" value="1"
summary="property already set"/>
- <entry name="unsupported_feature" value="3"
+ <entry name="unsupported_feature" value="2"
summary="request not supported"/>
- <entry name="invalid_tf" value="4"
+ <entry name="invalid_tf" value="3"
summary="invalid transfer characteristic"/>
- <entry name="invalid_primaries" value="5"
- summary="invalid primaries or white point"/>
- <entry name="invalid_luminance" value="6"
+ <entry name="invalid_primaries_named" value="4"
+ summary="invalid primaries named"/>
+ <entry name="invalid_luminance" value="5"
summary="invalid luminance value or range"/>
- <entry name="invalid_mastering" value="7"
- summary="invalid mastering information"/>
</enum>
<request name="create" type="destructor">
@@ -878,17 +1012,23 @@
complete, the protocol error incomplete_set is raised. For the
definition of a complete set, see the description of this interface.
- Also, the combination of the parameter set is verified. If the set is
- not consistent, the protocol error inconsistent_set is raised.
+ The protocol error invalid_luminance is raised if any of the following
+ requirements is not met:
+ - When max_cll is set, it must be greater than min L and less or equal
+ to max L of the mastering luminance range.
+ - When max_fall is set, it must be greater than min L and less or equal
+ to max L of the mastering luminance range.
+ - When both max_cll and max_fall are set, max_fall must be less or equal
+ to max_cll.
If the particular combination of the parameter set is not supported
by the compositor, the resulting image description object shall
- immediately deliver the xx_image_description_v4.failed event with the
+ immediately deliver the wp_image_description_v1.failed event with the
'unsupported' cause. If a valid image description was created from the
- parameter set, the xx_image_description_v4.ready event will eventually
+ parameter set, the wp_image_description_v1.ready event will eventually
be sent instead.
- This request destroys the xx_image_description_creator_params_v4
+ This request destroys the wp_image_description_creator_params_v1
object.
The resulting image description object does not allow get_information
@@ -896,7 +1036,7 @@
</description>
<arg name="image_description"
- type="new_id" interface="xx_image_description_v4"/>
+ type="new_id" interface="wp_image_description_v1"/>
</request>
<request name="set_tf_named">
@@ -908,22 +1048,24 @@
content should be encoded and decoded according to the industry standard
practices for the transfer characteristic.
- Only names advertised with xx_color_manager_v4 event supported_tf_named
+ Only names advertised with wp_color_manager_v1 event supported_tf_named
are allowed. Other values shall raise the protocol error invalid_tf.
If transfer characteristic has already been set on this object, the
protocol error already_set is raised.
</description>
- <arg name="tf" type="uint" enum="xx_color_manager_v4.transfer_function"
+ <arg name="tf" type="uint" enum="wp_color_manager_v1.transfer_function"
summary="named transfer function"/>
</request>
<request name="set_tf_power">
<description summary="transfer characteristic as a power curve">
Sets the color component transfer characteristic to a power curve with
- the given exponent. This curve represents the conversion from electrical
- to optical pixel or color values.
+ the given exponent. Negative values are handled by mirroring the
+ positive half of the curve through the origin. The valid domain and
+ range of the curve are all finite real numbers. This curve represents
+ the conversion from electrical to optical color channel values.
When the resulting image description is attached to an image, the
content should be encoded with the inverse of the power curve.
@@ -938,7 +1080,7 @@
protocol error already_set is raised.
This request can be used when the compositor advertises
- xx_color_manager_v4.feature.set_tf_power. Otherwise this request raises
+ wp_color_manager_v1.feature.set_tf_power. Otherwise this request raises
the protocol error unsupported_feature.
</description>
@@ -951,15 +1093,15 @@
This describes the primary color volume which is the basis for color
value encoding.
- Only names advertised with xx_color_manager_v4 event
+ Only names advertised with wp_color_manager_v1 event
supported_primaries_named are allowed. Other values shall raise the
- protocol error invalid_primaries.
+ protocol error invalid_primaries_named.
If primaries have already been set on this object, the protocol error
already_set is raised.
</description>
- <arg name="primaries" type="uint" enum="xx_color_manager_v4.primaries"
+ <arg name="primaries" type="uint" enum="wp_color_manager_v1.primaries"
summary="named primaries"/>
</request>
@@ -969,33 +1111,36 @@
coordinates. This describes the primary color volume which is the basis
for color value encoding.
- Each coordinate value is multiplied by 10000 to get the argument value
- to carry precision of 4 decimals.
+ Each coordinate value is multiplied by 1 million to get the argument
+ value to carry precision of 6 decimals.
If primaries have already been set on this object, the protocol error
already_set is raised.
This request can be used if the compositor advertises
- xx_color_manager_v4.feature.set_primaries. Otherwise this request raises
+ wp_color_manager_v1.feature.set_primaries. Otherwise this request raises
the protocol error unsupported_feature.
</description>
- <arg name="r_x" type="int" summary="Red x * 10000"/>
- <arg name="r_y" type="int" summary="Red y * 10000"/>
- <arg name="g_x" type="int" summary="Green x * 10000"/>
- <arg name="g_y" type="int" summary="Green y * 10000"/>
- <arg name="b_x" type="int" summary="Blue x * 10000"/>
- <arg name="b_y" type="int" summary="Blue y * 10000"/>
- <arg name="w_x" type="int" summary="White x * 10000"/>
- <arg name="w_y" type="int" summary="White y * 10000"/>
+ <arg name="r_x" type="int" summary="Red x * 1M"/>
+ <arg name="r_y" type="int" summary="Red y * 1M"/>
+ <arg name="g_x" type="int" summary="Green x * 1M"/>
+ <arg name="g_y" type="int" summary="Green y * 1M"/>
+ <arg name="b_x" type="int" summary="Blue x * 1M"/>
+ <arg name="b_y" type="int" summary="Blue y * 1M"/>
+ <arg name="w_x" type="int" summary="White x * 1M"/>
+ <arg name="w_y" type="int" summary="White y * 1M"/>
</request>
<request name="set_luminances">
<description summary="primary color volume luminance range and reference white">
Sets the primary color volume luminance range and the reference white
- luminance level.
+ luminance level. These values include the minimum display emission
+ and ambient flare luminances, assumed to be optically additive and have
+ the chromaticity of the primary color volume white point.
- The default luminances are
+ The default luminances from
+ https://www.color.org/chardata/rgb/srgb.xalter are
- primary color volume minimum: 0.2 cd/m²
- primary color volume maximum: 80 cd/m²
- reference white: 80 cd/m²
@@ -1004,6 +1149,8 @@
luminances.
The default luminances get overwritten when this request is used.
+ With transfer_function.st2084_pq the given 'max_lum' value is ignored,
+ and 'max_lum' is taken as 'min_lum' + 10000 cd/m².
'min_lum' and 'max_lum' specify the minimum and maximum luminances of
the primary color volume as reproduced by the targeted display.
@@ -1018,9 +1165,12 @@
description should produce the same output level, even though the
'reference_lum' on both image representations can be different.
- If 'max_lum' is less than the 'reference_lum', or 'reference_lum' is
- less than or equal to 'min_lum', the protocol error invalid_luminance is
- raised.
+ 'reference_lum' may be higher than 'max_lum'. In that case reaching
+ the reference white output level in image content requires the
+ 'extended_target_volume' feature support.
+
+ If 'max_lum' or 'reference_lum' are less than or equal to 'min_lum',
+ the protocol error invalid_luminance is raised.
The minimum luminance is multiplied by 10000 to get the argument
'min_lum' value and carries precision of 4 decimals. The maximum
@@ -1031,7 +1181,7 @@
already_set is raised.
This request can be used if the compositor advertises
- xx_color_manager_v4.feature.set_luminances. Otherwise this request
+ wp_color_manager_v1.feature.set_luminances. Otherwise this request
raises the protocol error unsupported_feature.
</description>
@@ -1049,10 +1199,11 @@
using CIE 1931 xy chromaticity coordinates. This is compatible with the
SMPTE ST 2086 definition of HDR static metadata.
- The mastering display primaries define the target color volume.
+ The mastering display primaries and mastering display luminances define
+ the target color volume.
If mastering display primaries are not explicitly set, the target color
- volume is assumed to be equal to the primary color volume.
+ volume is assumed to have the same primaries as the primary color volume.
The target color volume is defined by all tristimulus values between 0.0
and 1.0 (inclusive) of the color space defined by the given mastering
@@ -1071,50 +1222,72 @@
has to be chosen (e.g. floating point to exceed the primary color
volume, or abusing limited quantization range as with xvYCC).
- Each coordinate value is multiplied by 10000 to get the argument value
- to carry precision of 4 decimals.
+ Each coordinate value is multiplied by 1 million to get the argument
+ value to carry precision of 6 decimals.
If mastering display primaries have already been set on this object, the
protocol error already_set is raised.
This request can be used if the compositor advertises
- xx_color_manager_v4.feature.set_mastering_display_primaries. Otherwise
+ wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise
this request raises the protocol error unsupported_feature. The
advertisement implies support only for target color volumes fully
contained within the primary color volume.
If a compositor additionally supports target color volume exceeding the
primary color volume, it must advertise
- xx_color_manager_v4.feature.extended_target_volume. If a client uses
+ wp_color_manager_v1.feature.extended_target_volume. If a client uses
target color volume exceeding the primary color volume and the
compositor does not support it, the result is implementation defined.
Compositors are recommended to detect this case and fail the image
description gracefully, but it may as well result in color artifacts.
</description>
- <arg name="r_x" type="int" summary="Red x * 10000"/>
- <arg name="r_y" type="int" summary="Red y * 10000"/>
- <arg name="g_x" type="int" summary="Green x * 10000"/>
- <arg name="g_y" type="int" summary="Green y * 10000"/>
- <arg name="b_x" type="int" summary="Blue x * 10000"/>
- <arg name="b_y" type="int" summary="Blue y * 10000"/>
- <arg name="w_x" type="int" summary="White x * 10000"/>
- <arg name="w_y" type="int" summary="White y * 10000"/>
+ <arg name="r_x" type="int" summary="Red x * 1M"/>
+ <arg name="r_y" type="int" summary="Red y * 1M"/>
+ <arg name="g_x" type="int" summary="Green x * 1M"/>
+ <arg name="g_y" type="int" summary="Green y * 1M"/>
+ <arg name="b_x" type="int" summary="Blue x * 1M"/>
+ <arg name="b_y" type="int" summary="Blue y * 1M"/>
+ <arg name="w_x" type="int" summary="White x * 1M"/>
+ <arg name="w_y" type="int" summary="White y * 1M"/>
</request>
<request name="set_mastering_luminance">
<description summary="display mastering luminance range">
Sets the luminance range that was used during the content mastering
- process as the minimum and maximum absolute luminance L. This is
+ process as the minimum and maximum absolute luminance L. These values
+ include the minimum display emission and ambient flare luminances,
+ assumed to be optically additive and have the chromaticity of the
+ primary color volume white point. This should be
compatible with the SMPTE ST 2086 definition of HDR static metadata.
- The mastering luminance range is undefined by default.
+ The mastering display primaries and mastering display luminances define
+ the target color volume.
+
+ If mastering luminances are not explicitly set, the target color volume
+ is assumed to have the same min and max luminances as the primary color
+ volume.
If max L is less than or equal to min L, the protocol error
invalid_luminance is raised.
Min L value is multiplied by 10000 to get the argument min_lum value
and carry precision of 4 decimals. Max L value is unscaled for max_lum.
+
+ This request can be used if the compositor advertises
+ wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise
+ this request raises the protocol error unsupported_feature. The
+ advertisement implies support only for target color volumes fully
+ contained within the primary color volume.
+
+ If a compositor additionally supports target color volume exceeding the
+ primary color volume, it must advertise
+ wp_color_manager_v1.feature.extended_target_volume. If a client uses
+ target color volume exceeding the primary color volume and the
+ compositor does not support it, the result is implementation defined.
+ Compositors are recommended to detect this case and fail the image
+ description gracefully, but it may as well result in color artifacts.
</description>
<arg name="min_lum" type="uint" summary="min L (cd/m²) * 10000"/>
@@ -1125,11 +1298,6 @@
<description summary="maximum content light level">
Sets the maximum content light level (max_cll) as defined by CTA-861-H.
- This can only be set when set_tf_cicp is used to set the transfer
- characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system.
- Otherwise, 'create' request shall raise inconsistent_set protocol
- error.
-
max_cll is undefined by default.
</description>
@@ -1141,10 +1309,6 @@
Sets the maximum frame-average light level (max_fall) as defined by
CTA-861-H.
- This can only be set when set_tf_cicp is used to set the transfer
- characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system.
- Otherwise, 'create' request shall raise inconsistent_set protocol error.
-
max_fall is undefined by default.
</description>
@@ -1152,15 +1316,15 @@
</request>
</interface>
- <interface name="xx_image_description_v4" version="1">
+ <interface name="wp_image_description_v1" version="1">
<description summary="Colorimetric image description">
An image description carries information about the color encoding used on
a surface when attached to a wl_surface via
- xx_color_management_surface_v4.set_image_description. A compositor can use
+ wp_color_management_surface_v1.set_image_description. A compositor can use
this information to decode pixel values into colorimetrically meaningful
quantities.
- Note, that the xx_image_description_v4 object is not ready to be used
+ Note, that the wp_image_description_v1 object is not ready to be used
immediately after creation. The object eventually delivers either the
'ready' or the 'failed' event, specified in all requests creating it. The
object is deemed "ready" after receiving the 'ready' event.
@@ -1171,7 +1335,7 @@
interfaces shall raise protocol errors defined there.
Once created and regardless of how it was created, a
- xx_image_description_v4 object always refers to one fixed image
+ wp_image_description_v1 object always refers to one fixed image
description. It cannot change after creation.
</description>
@@ -1179,8 +1343,8 @@
<description summary="destroy the image description">
Destroy this object. It is safe to destroy an object which is not ready.
- Destroying a xx_image_description_v4 object has no side-effects, not
- even if a xx_color_management_surface_v4.set_image_description has not
+ Destroying a wp_image_description_v1 object has no side-effects, not
+ even if a wp_color_management_surface_v1.set_image_description has not
yet been followed by a wl_surface.commit.
</description>
</request>
@@ -1209,7 +1373,7 @@
<event name="failed">
<description summary="graceful error on creating the image description">
- If creating a xx_image_description_v4 object fails for a reason that is
+ If creating a wp_image_description_v1 object fails for a reason that is
not defined as a protocol error, this event is sent.
The requests that create image description objects define whether and
@@ -1217,7 +1381,7 @@
This event cannot be triggered after the image description was
successfully formed.
- Once this event has been sent, the xx_image_description_v4 object will
+ Once this event has been sent, the wp_image_description_v1 object will
never become ready and it can only be destroyed.
</description>
@@ -1229,11 +1393,11 @@
<event name="ready">
<description summary="indication that the object is ready to be used">
- Once this event has been sent, the xx_image_description_v4 object is
+ Once this event has been sent, the wp_image_description_v1 object is
deemed "ready". Ready objects can be used to send requests and can be
used through other interfaces.
- Every ready xx_image_description_v4 protocol object refers to an
+ Every ready wp_image_description_v1 protocol object refers to an
underlying image description record in the compositor. Multiple protocol
objects may end up referring to the same record. Clients may identify
these "copies" by comparing their id numbers: if the numbers from two
@@ -1250,7 +1414,8 @@
Image description id number is not a protocol object id. Zero is
reserved as an invalid id number. It shall not be possible for a client
to refer to an image description by its id number in protocol. The id
- numbers might not be portable between Wayland connections.
+ numbers might not be portable between Wayland connections. A compositor
+ shall not send an invalid id number.
This identity allows clients to de-duplicate image description records
and avoid get_information request if they already have the image
@@ -1262,7 +1427,7 @@
<request name="get_information">
<description summary="get information about the image description">
- Creates a xx_image_description_info_v4 object which delivers the
+ Creates a wp_image_description_info_v1 object which delivers the
information that makes up the image description.
Not all image description protocol objects allow get_information
@@ -1272,20 +1437,34 @@
</description>
<arg name="information"
- type="new_id" interface="xx_image_description_info_v4"/>
+ type="new_id" interface="wp_image_description_info_v1"/>
</request>
</interface>
- <interface name="xx_image_description_info_v4" version="1">
+ <interface name="wp_image_description_info_v1" version="1">
<description summary="Colorimetric image description information">
Sends all matching events describing an image description object exactly
once and finally sends the 'done' event.
- Once a xx_image_description_info_v4 object has delivered a 'done' event it
+ This means
+ - if the image description is parametric, it must send
+ - primaries
+ - named_primaries, if applicable
+ - at least one of tf_power and tf_named, as applicable
+ - luminances
+ - target_primaries
+ - target_luminance
+ - if the image description is parametric, it may send, if applicable,
+ - target_max_cll
+ - target_max_fall
+ - if the image description contains an ICC profile, it must send the
+ icc_file event
+
+ Once a wp_image_description_info_v1 object has delivered a 'done' event it
is automatically destroyed.
- Every xx_image_description_info_v4 created from the same
- xx_image_description_v4 shall always return the exact same data.
+ Every wp_image_description_info_v1 created from the same
+ wp_image_description_v1 shall always return the exact same data.
</description>
<event name="done" type="destructor">
@@ -1316,18 +1495,18 @@
Delivers the primary color volume primaries and white point using CIE
1931 xy chromaticity coordinates.
- Each coordinate value is multiplied by 10000 to get the argument value
- to carry precision of 4 decimals.
+ Each coordinate value is multiplied by 1 million to get the argument
+ value to carry precision of 6 decimals.
</description>
- <arg name="r_x" type="int" summary="Red x * 10000"/>
- <arg name="r_y" type="int" summary="Red y * 10000"/>
- <arg name="g_x" type="int" summary="Green x * 10000"/>
- <arg name="g_y" type="int" summary="Green y * 10000"/>
- <arg name="b_x" type="int" summary="Blue x * 10000"/>
- <arg name="b_y" type="int" summary="Blue y * 10000"/>
- <arg name="w_x" type="int" summary="White x * 10000"/>
- <arg name="w_y" type="int" summary="White y * 10000"/>
+ <arg name="r_x" type="int" summary="Red x * 1M"/>
+ <arg name="r_y" type="int" summary="Red y * 1M"/>
+ <arg name="g_x" type="int" summary="Green x * 1M"/>
+ <arg name="g_y" type="int" summary="Green y * 1M"/>
+ <arg name="b_x" type="int" summary="Blue x * 1M"/>
+ <arg name="b_y" type="int" summary="Blue y * 1M"/>
+ <arg name="w_x" type="int" summary="White x * 1M"/>
+ <arg name="w_y" type="int" summary="White y * 1M"/>
</event>
<event name="primaries_named">
@@ -1336,7 +1515,7 @@
explicitly enumerated named set.
</description>
- <arg name="primaries" type="uint" enum="xx_color_manager_v4.primaries"
+ <arg name="primaries" type="uint" enum="wp_color_manager_v1.primaries"
summary="named primaries"/>
</event>
@@ -1360,14 +1539,16 @@
named function.
</description>
- <arg name="tf" type="uint" enum="xx_color_manager_v4.transfer_function"
+ <arg name="tf" type="uint" enum="wp_color_manager_v1.transfer_function"
summary="named transfer function"/>
</event>
<event name="luminances">
<description summary="primary color volume luminance range and reference white">
Delivers the primary color volume luminance range and the reference
- white luminance level.
+ white luminance level. These values include the minimum display emission
+ and ambient flare luminances, assumed to be optically additive and have
+ the chromaticity of the primary color volume white point.
The minimum luminance is multiplied by 10000 to get the argument
'min_lum' value and carries precision of 4 decimals. The maximum
@@ -1393,25 +1574,28 @@
volume is equal to the primary color volume, then this event is not
sent.
- Each coordinate value is multiplied by 10000 to get the argument value
- to carry precision of 4 decimals.
+ Each coordinate value is multiplied by 1 million to get the argument
+ value to carry precision of 6 decimals.
</description>
- <arg name="r_x" type="int" summary="Red x * 10000"/>
- <arg name="r_y" type="int" summary="Red y * 10000"/>
- <arg name="g_x" type="int" summary="Green x * 10000"/>
- <arg name="g_y" type="int" summary="Green y * 10000"/>
- <arg name="b_x" type="int" summary="Blue x * 10000"/>
- <arg name="b_y" type="int" summary="Blue y * 10000"/>
- <arg name="w_x" type="int" summary="White x * 10000"/>
- <arg name="w_y" type="int" summary="White y * 10000"/>
+ <arg name="r_x" type="int" summary="Red x * 1M"/>
+ <arg name="r_y" type="int" summary="Red y * 1M"/>
+ <arg name="g_x" type="int" summary="Green x * 1M"/>
+ <arg name="g_y" type="int" summary="Green y * 1M"/>
+ <arg name="b_x" type="int" summary="Blue x * 1M"/>
+ <arg name="b_y" type="int" summary="Blue y * 1M"/>
+ <arg name="w_x" type="int" summary="White x * 1M"/>
+ <arg name="w_y" type="int" summary="White y * 1M"/>
</event>
<event name="target_luminance">
<description summary="target luminance range">
Provides the luminance range that the image description is targeting as
- the minimum and maximum absolute luminance L. This is compatible with
- the SMPTE ST 2086 definition of HDR static metadata.
+ the minimum and maximum absolute luminance L. These values include the
+ minimum display emission and ambient flare luminances, assumed to be
+ optically additive and have the chromaticity of the primary color
+ volume white point. This should be compatible with the SMPTE ST 2086
+ definition of HDR static metadata.
This luminance range is only theoretical and may not correspond to the
luminance of light emitted on an actual display.
diff --git a/src/3rdparty/wayland/protocols/color-management/qt_attribution.json b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json
index 246e9df70fa..1001cc9b90d 100644
--- a/src/3rdparty/wayland/protocols/color-management/qt_attribution.json
+++ b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json
@@ -4,15 +4,15 @@
"Name": "Wayland Color Management Protocol",
"QDocModule": "qtwaylandcompositor",
"QtUsage": "Used in the Qt Wayland platform plugin.",
- "Files": "xx-color-management-v4.xml",
+ "Files": "color-management-v1.xml",
"Description": "An extension to use different colorspaces from sRGB",
"Homepage": "https://wayland.freedesktop.org",
- "Version": "experimental v4",
- "DownloadLocation": "https://gitlab.freedesktop.org/swick/wayland-protocols/-/blob/708a8b4119d4072820158a115166598733d378f4/staging/color-management/xx-color-management-v4.xml",
+ "Version": "1",
+ "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/1.45/staging/color-management/color-management-v1.xml",
"LicenseId": "MIT",
"License": "MIT License",
"LicenseFile": "../MIT_LICENSE.txt",
- "Copyright": "Copyright 2019 Sebastian Wick\nCopyright 2019 Erwin Burema\nCopyright 2020 AMD\nCopyright 2020-2024 Collabora, Ltd.\nCopyright 2024 Xaver Hugl"
+ "Copyright": "Copyright 2019 Sebastian Wick\nCopyright 2019 Erwin Burema\nCopyright 2020 AMD\nCopyright 2020-2024 Collabora, Ltd.\nCopyright 2024 Xaver Hugl\nCopyright 2022-2025 Red Hat, Inc."
}
]
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index c4a3480b2f9..380b571c3cf 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -638,6 +638,7 @@ qt_internal_extend_target(Core CONDITION WIN32
platform/windows/qcomptr_p.h
platform/windows/qbstr_p.h
platform/windows/qcomvariant_p.h
+ platform/windows/quniquehandle_types_windows.cpp platform/windows/quniquehandle_types_windows_p.h
LIBRARIES
advapi32
authz
diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp
index 05e3a39e589..b0c6c46b125 100644
--- a/src/corelib/itemmodels/qrangemodel.cpp
+++ b/src/corelib/itemmodels/qrangemodel.cpp
@@ -20,6 +20,8 @@ public:
private:
std::unique_ptr<QRangeModelImplBase, QRangeModelImplBase::Deleter> impl;
+ friend class QRangeModelImplBase;
+
mutable QHash<int, QByteArray> m_roleNames;
};
@@ -28,6 +30,16 @@ QRangeModel::QRangeModel(QRangeModelImplBase *impl, QObject *parent)
{
}
+QRangeModelImplBase *QRangeModelImplBase::getImplementation(QRangeModel *model)
+{
+ return model->d_func()->impl.get();
+}
+
+const QRangeModelImplBase *QRangeModelImplBase::getImplementation(const QRangeModel *model)
+{
+ return model->d_func()->impl.get();
+}
+
/*!
\class QRangeModel
\inmodule QtCore
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index f38dc88c0d9..c2f473542d7 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -842,6 +842,11 @@ namespace QRangeModelDetails
{
using protocol = wrapped_t<Protocol>;
using row = typename range_traits<wrapped_t<Range>>::value_type;
+ static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>,
+ protocol_childRows<protocol, row>>;
+ static constexpr bool is_list = static_size_v<row> == 0
+ && (!has_metaobject_v<row> || row_category<row>::isMultiRole);
+ static constexpr bool is_table = !is_list && !is_tree;
static constexpr bool has_newRow = protocol_newRow<protocol>();
static constexpr bool has_deleteRow = protocol_deleteRow<protocol, row>();
@@ -1046,6 +1051,9 @@ public:
typename C::MultiData
>;
+ static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model);
+ static Q_CORE_EXPORT const QRangeModelImplBase *getImplementation(const QRangeModel *model);
+
private:
friend class QRangeModelPrivate;
QRangeModel *m_rangeModel;
@@ -1147,13 +1155,6 @@ protected:
}
}
- static constexpr bool isMutable()
- {
- return range_features::is_mutable && row_features::is_mutable
- && std::is_reference_v<row_reference>
- && Structure::is_mutable_impl;
- }
-
static constexpr int static_row_count = QRangeModelDetails::static_size_v<range_type>;
static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>;
static constexpr bool rows_are_owning_or_raw_pointers =
@@ -1161,9 +1162,6 @@ protected:
static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>;
static constexpr bool one_dimensional_range = static_column_count == 0;
- static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; }
- static constexpr bool dynamicColumns() { return static_column_count < 0; }
-
// A row might be a value (or range of values), or a pointer.
// row_ptr is always a pointer, and const_row_ptr is a pointer to const.
using row_ptr = wrapped_row_type *;
@@ -1205,6 +1203,15 @@ protected:
"The range holding a move-only row-type must support insert(pos, start, end)");
public:
+ static constexpr bool isMutable()
+ {
+ return range_features::is_mutable && row_features::is_mutable
+ && std::is_reference_v<row_reference>
+ && Structure::is_mutable_impl;
+ }
+ static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; }
+ static constexpr bool dynamicColumns() { return static_column_count < 0; }
+
explicit QRangeModelImpl(Range &&model, Protocol&& protocol, QRangeModel *itemModel)
: Ancestor(itemModel)
, ProtocolStorage{std::forward<Protocol>(protocol)}
@@ -1236,7 +1243,7 @@ public:
if (row == index.row() && column == index.column())
return index;
- if (column < 0 || column >= this->itemModel().columnCount())
+ if (column < 0 || column >= this->columnCount({}))
return {};
if (row == index.row())
@@ -1974,8 +1981,8 @@ public:
}
if (sourceRow == destRow || sourceRow == destRow - 1 || count <= 0
- || sourceRow < 0 || sourceRow + count - 1 >= this->itemModel().rowCount(sourceParent)
- || destRow < 0 || destRow > this->itemModel().rowCount(destParent)) {
+ || sourceRow < 0 || sourceRow + count - 1 >= this->rowCount(sourceParent)
+ || destRow < 0 || destRow > this->rowCount(destParent)) {
return false;
}
@@ -1995,11 +2002,14 @@ public:
}
}
- QModelIndex parent(const QModelIndex &child) const { return that().parent(child); }
+ const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
+ protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
+
+ QModelIndex parent(const QModelIndex &child) const { return that().parentImpl(child); }
- int rowCount(const QModelIndex &parent) const { return that().rowCount(parent); }
+ int rowCount(const QModelIndex &parent) const { return that().rowCountImpl(parent); }
- int columnCount(const QModelIndex &parent) const { return that().columnCount(parent); }
+ int columnCount(const QModelIndex &parent) const { return that().columnCountImpl(parent); }
void destroy() { delete std::addressof(that()); }
@@ -2035,6 +2045,11 @@ public:
protected:
~QRangeModelImpl()
{
+ deleteOwnedRows();
+ }
+
+ void deleteOwnedRows()
+ {
// We delete row objects if we are not operating on a reference or pointer
// to a range, as in that case, the owner of the referenced/pointed to
// range also owns the row entries.
@@ -2335,9 +2350,6 @@ protected:
}
- const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
- protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
-
ModelData m_data;
};
@@ -2385,7 +2397,7 @@ protected:
return this->createIndex(row, column, QRangeModelDetails::pointerTo(*it));
}
- QModelIndex parent(const QModelIndex &child) const
+ QModelIndex parentImpl(const QModelIndex &child) const
{
if (!child.isValid())
return {};
@@ -2410,12 +2422,12 @@ protected:
return {};
}
- int rowCount(const QModelIndex &parent) const
+ int rowCountImpl(const QModelIndex &parent) const
{
return Base::size(this->childRange(parent));
}
- int columnCount(const QModelIndex &) const
+ int columnCountImpl(const QModelIndex &) const
{
// all levels of a tree have to have the same, static, column count
if constexpr (Base::one_dimensional_range)
@@ -2662,6 +2674,9 @@ class QGenericTableItemModelImpl
using Base = QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
friend class QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
+ static constexpr bool is_mutable_impl = true;
+
+public:
using range_type = typename Base::range_type;
using range_features = typename Base::range_features;
using row_type = typename Base::row_type;
@@ -2669,9 +2684,6 @@ class QGenericTableItemModelImpl
using row_traits = typename Base::row_traits;
using row_features = typename Base::row_features;
- static constexpr bool is_mutable_impl = true;
-
-public:
explicit QGenericTableItemModelImpl(Range &&model, QRangeModel *itemModel)
: Base(std::forward<Range>(model), {}, itemModel)
{}
@@ -2692,19 +2704,19 @@ protected:
}
}
- QModelIndex parent(const QModelIndex &) const
+ QModelIndex parentImpl(const QModelIndex &) const
{
return {};
}
- int rowCount(const QModelIndex &parent) const
+ int rowCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return int(Base::size(*this->m_data.model()));
}
- int columnCount(const QModelIndex &parent) const
+ int columnCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
@@ -2760,7 +2772,7 @@ protected:
// dynamically sized rows all have to have the same column count
if constexpr (Base::dynamicColumns() && row_features::has_resize) {
if (QRangeModelDetails::isValid(empty_row))
- QRangeModelDetails::refTo(empty_row).resize(this->itemModel().columnCount());
+ QRangeModelDetails::refTo(empty_row).resize(this->columnCount({}));
}
return empty_row;
diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm
index db81108baef..687fc7e85fa 100644
--- a/src/corelib/kernel/qcore_mac.mm
+++ b/src/corelib/kernel/qcore_mac.mm
@@ -50,6 +50,8 @@ extern char **environ;
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
// --------------------------------------------------------------------------
#if defined(Q_OS_MACOS)
@@ -396,6 +398,21 @@ std::optional<uint32_t> qt_mac_sipConfiguration()
return configuration;
}
+bool qt_mac_processHasEntitlement(const QString &entitlement)
+{
+ if (QCFType<SecTaskRef> task = SecTaskCreateFromSelf(kCFAllocatorDefault)) {
+ if (QCFType<CFTypeRef> value = SecTaskCopyValueForEntitlement(task,
+ entitlement.toCFString(), nullptr)) {
+
+ if (CFGetTypeID(value) != CFBooleanGetTypeID())
+ return false;
+
+ return CFBooleanGetValue(value.as<CFBooleanRef>());
+ }
+ }
+ return false;
+}
+
#define CHECK_SPAWN(expr) \
if ((expr) != 0) { \
posix_spawnattr_destroy(&attr); \
@@ -474,55 +491,11 @@ AppleApplication *qt_apple_sharedApplication()
#if !defined(QT_BOOTSTRAPPED)
-#if defined(Q_OS_MACOS)
-namespace {
-struct SandboxChecker
-{
- SandboxChecker() : m_thread([this]{
- m_isSandboxed = []{
- QCFType<SecStaticCodeRef> staticCode = nullptr;
- NSURL *executableUrl = NSBundle.mainBundle.executableURL;
- if (SecStaticCodeCreateWithPath((__bridge CFURLRef)executableUrl,
- kSecCSDefaultFlags, &staticCode) != errSecSuccess)
- return false;
-
- QCFType<SecRequirementRef> sandboxRequirement;
- if (SecRequirementCreateWithString(CFSTR("entitlement[\"com.apple.security.app-sandbox\"] exists"),
- kSecCSDefaultFlags, &sandboxRequirement) != errSecSuccess)
- return false;
-
- if (SecStaticCodeCheckValidityWithErrors(staticCode,
- kSecCSBasicValidateOnly, sandboxRequirement, nullptr) != errSecSuccess)
- return false;
-
- return true;
- }();
- })
- {}
- ~SandboxChecker() {
- std::scoped_lock lock(m_mutex);
- if (m_thread.joinable())
- m_thread.detach();
- }
- bool isSandboxed() const {
- std::scoped_lock lock(m_mutex);
- if (m_thread.joinable())
- m_thread.join();
- return m_isSandboxed;
- }
-private:
- bool m_isSandboxed;
- mutable std::thread m_thread;
- mutable std::mutex m_mutex;
-};
-} // namespace
-static SandboxChecker sandboxChecker;
-#endif // Q_OS_MACOS
-
bool qt_apple_isSandboxed()
{
#if defined(Q_OS_MACOS)
- return sandboxChecker.isSandboxed();
+ static bool isSandboxed = qt_mac_processHasEntitlement(u"com.apple.security.app-sandbox"_s);
+ return isSandboxed;
#else
return true; // All other Apple platforms
#endif
diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h
index 9d1cdcac68a..2c4b4c02c55 100644
--- a/src/corelib/kernel/qcore_mac_p.h
+++ b/src/corelib/kernel/qcore_mac_p.h
@@ -216,6 +216,7 @@ public:
#ifdef Q_OS_MACOS
Q_CORE_EXPORT bool qt_mac_runningUnderRosetta();
Q_CORE_EXPORT std::optional<uint32_t> qt_mac_sipConfiguration();
+Q_CORE_EXPORT bool qt_mac_processHasEntitlement(const QString &entitlement);
#ifdef QT_BUILD_INTERNAL
Q_AUTOTEST_EXPORT void qt_mac_ensureResponsible();
#endif
diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp
index de7c4df6d1d..afc85fe36fb 100644
--- a/src/corelib/kernel/qcoreapplication.cpp
+++ b/src/corelib/kernel/qcoreapplication.cpp
@@ -2917,7 +2917,7 @@ void QCoreApplication::requestPermissionImpl(const QPermission &requestedPermiss
}
private:
- QtPrivate::SlotObjSharedPtr slotObject;
+ QtPrivate::SlotObjUniquePtr slotObject;
QPointer<const QObject> context;
};
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp
index 8366787ea66..57089f164b2 100644
--- a/src/corelib/kernel/qvariant.cpp
+++ b/src/corelib/kernel/qvariant.cpp
@@ -2974,4 +2974,156 @@ const QVariant *QVariantConstPointer::operator->() const
implement operator->().
*/
+/*!
+ \class QVariant::ConstReference
+ \since 6.11
+ \inmodule QtCore
+ \brief The QVariant::ConstReference acts as a const reference to a QVariant.
+
+ As the generic iterators don't actually instantiate a QVariant on each
+ step, they cannot return a reference to one from operator*().
+ QVariant::ConstReference provides the same functionality as an actual
+ reference to a QVariant would, but is backed a referred-to value given as
+ template parameter. The template is implemented for
+ QMetaSequence::ConstIterator, QMetaSequence::Iterator,
+ QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(const Referred &referred)
+
+ Creates a QVariant::ConstReference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(Referred &&referred)
+
+ Creates a QVariant::ConstReference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::operator QVariant() const
+
+ Dereferences the reference to a QVariant.
+ This method needs to be specialized for each Referred type. It is
+ pre-defined for QMetaSequence::ConstIterator, QMetaSequence::Iterator,
+ QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator.
+ */
+
+
+/*!
+ \class QVariant::Reference
+ \since 6.11
+ \inmodule QtCore
+ \brief The QVariant::Reference acts as a non-const reference to a QVariant.
+
+ As the generic iterators don't actually instantiate a QVariant on each
+ step, they cannot return a reference to one from operator*().
+ QVariant::Reference provides the same functionality as an actual reference
+ to a QVariant would, but is backed a referred-to value given as template
+ parameter. The template is implemented for QMetaSequence::Iterator and
+ QMetaAssociation::Iterator.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::Reference(const Referred &referred)
+
+ Creates a QVariant::Reference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::Reference(Referred &&referred)
+
+ Creates a QVariant::Reference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const Reference<Referred> &value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(Reference<Referred> &&value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const QVariant &value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+ This method needs to be specialized for each Referred type. It is
+ pre-defined for QMetaSequence::Iterator and QMetaAssociation::Iterator.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::operator QVariant() const
+
+ Dereferences the reference to a QVariant. By default this instantiates a
+ temporary QVariant::ConstReference and calls dereferences that. In cases
+ where instantiating a temporary ConstReference is expensive, this method
+ should be specialized.
+ */
+
+/*!
+ \class QVariant::ConstPointer
+ \since 6.11
+ \inmodule QtCore
+ \brief QVariant::ConstPointer is a template class that emulates a const pointer to QVariant.
+
+ QVariant::ConstPointer wraps pointed-to value and returns a
+ QVariant::ConstReference to it from its operator*(). This makes it suitable
+ as replacement for an actual pointer. We cannot return an actual pointer
+ from generic iterators as the iterators don't hold an actual QVariant.
+*/
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(const Pointed &pointed)
+
+ Constructs a QVariant::ConstPointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(Pointed &&pointed)
+
+ Constructs a QVariant::ConstPointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstReference<Pointer> QVariant::ConstPointer<Pointed>::operator*() const
+
+ Dereferences the QVariant::ConstPointer to a QVariant::ConstReference.
+ */
+
+/*!
+ \class QVariant::Pointer
+ \since 6.11
+ \inmodule QtCore
+ \brief QVariant::Pointer is a template class that emulates a non-const pointer to QVariant.
+
+ QVariant::Pointer wraps pointed-to value and returns a QVariant::Reference
+ to it from its operator*(). This makes it suitable as replacement for an
+ actual pointer. We cannot return an actual pointer from generic iterators as
+ the iterators don't hold an actual QVariant.
+*/
+
+/*!
+ \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(const Pointed &pointed)
+
+ Constructs a QVariant::Pointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(Pointed &&pointed)
+
+ Constructs a QVariant::Pointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::Reference<Pointer> QVariant::Pointer<Pointed>::operator*() const
+
+ Dereferences the QVariant::Pointer to a QVariant::Reference.
+ */
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h
index 542b1d9b709..9b219d089b5 100644
--- a/src/corelib/kernel/qvariant.h
+++ b/src/corelib/kernel/qvariant.h
@@ -61,7 +61,20 @@ inline T qvariant_cast(const QVariant &);
namespace QtPrivate {
template<> constexpr inline bool qIsRelocatable<QVariant> = true;
-}
+
+template<typename Referred>
+class ConstReference;
+
+template<typename Referred>
+class Reference;
+
+template<typename Pointed>
+class ConstPointer;
+
+template<typename Pointed>
+class Pointer;
+} // namespace QtPrivate
+
class Q_CORE_EXPORT QVariant
{
template <typename T, typename... Args>
@@ -228,6 +241,123 @@ private:
>;
public:
+ template<typename Referred>
+ class ConstReference
+ {
+ private:
+ const Referred m_referred;
+
+ public:
+ // You can initialize a const reference from another one, but you can't assign to it.
+
+ explicit ConstReference(const Referred &referred)
+ noexcept(std::is_nothrow_copy_constructible_v<Referred>)
+ : m_referred(referred) {}
+ explicit ConstReference(Referred &&referred)
+ noexcept(std::is_nothrow_move_constructible_v<Referred>)
+ : m_referred(std::move(referred)) {}
+ ConstReference(const ConstReference &) = default;
+ ConstReference(ConstReference &&) = default;
+ ~ConstReference() = default;
+ ConstReference &operator=(const ConstReference &value) = delete;
+ ConstReference &operator=(ConstReference &&value) = delete;
+
+ // To be specialized for each Referred
+ operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant);
+ };
+
+ template<typename Referred>
+ class Reference
+ {
+ private:
+ Referred m_referred;
+
+ friend void swap(Reference a, Reference b) { return a.swap(std::move(b)); }
+
+ public:
+ // Assigning and initializing are different operations for references.
+
+ explicit Reference(const Referred &referred)
+ noexcept(std::is_nothrow_copy_constructible_v<Referred>)
+ : m_referred(referred) {}
+ explicit Reference(Referred &&referred)
+ noexcept(std::is_nothrow_move_constructible_v<Referred>)
+ : m_referred(std::move(referred)) {}
+ Reference(const Reference &) = default;
+ Reference(Reference &&) = default;
+ ~Reference() = default;
+
+ Reference &operator=(const Reference &value)
+ noexcept(Referred::canNoexceptAssignQVariant)
+ {
+ return operator=(QVariant(value));
+ }
+
+ Reference &operator=(Reference &&value)
+ noexcept(Referred::canNoexceptAssignQVariant)
+ {
+ return operator=(QVariant(value));
+ }
+
+ operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant)
+ {
+ return ConstReference(m_referred);
+ }
+
+ void swap(Reference b)
+ {
+ // swapping a reference is not swapping the referred item, but swapping its contents.
+ QVariant tmp = *this;
+ *this = std::move(b);
+ b = std::move(tmp);
+ }
+
+ // To be specialized for each Referred
+ Reference &operator=(const QVariant &value) noexcept(Referred::canNoexceptAssignQVariant);
+ };
+
+ template<typename Pointed>
+ class ConstPointer
+ {
+ private:
+ Pointed m_pointed;
+
+ public:
+ explicit ConstPointer(const Pointed &pointed)
+ noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ : m_pointed(pointed) {}
+ explicit ConstPointer(Pointed &&pointed)
+ noexcept(std::is_nothrow_move_constructible_v<Pointed>)
+ : m_pointed(std::move(pointed)) {}
+
+ ConstReference<Pointed> operator*()
+ const noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ {
+ return ConstReference<Pointed>(m_pointed);
+ }
+ };
+
+ template<typename Pointed>
+ class Pointer
+ {
+ private:
+ Pointed m_pointed;
+
+ public:
+ explicit Pointer(const Pointed &pointed)
+ noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ : m_pointed(pointed) {}
+ explicit Pointer(Pointed &&pointed)
+ noexcept(std::is_nothrow_move_constructible_v<Pointed>)
+ : m_pointed(std::move(pointed)) {}
+
+ Reference<Pointed> operator*()
+ const noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ {
+ return Reference<Pointed>(m_pointed);
+ }
+ };
+
template <typename T, typename... Args,
if_constructible<T, Args...> = true>
explicit QVariant(std::in_place_type_t<T>, Args&&... args)
diff --git a/src/corelib/platform/windows/quniquehandle_types_windows.cpp b/src/corelib/platform/windows/quniquehandle_types_windows.cpp
new file mode 100644
index 00000000000..801c9ab13d6
--- /dev/null
+++ b/src/corelib/platform/windows/quniquehandle_types_windows.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "quniquehandle_types_windows_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QtUniqueHandleTraits {
+
+bool HDCTraits::close(Type handle, HWND hwnd) noexcept
+{
+ return ::ReleaseDC(hwnd, handle);
+}
+
+} // namespace QtUniqueHandleTraits
+
+QT_END_NAMESPACE
diff --git a/src/corelib/platform/windows/quniquehandle_types_windows_p.h b/src/corelib/platform/windows/quniquehandle_types_windows_p.h
new file mode 100644
index 00000000000..638441a87ef
--- /dev/null
+++ b/src/corelib/platform/windows/quniquehandle_types_windows_p.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QUNIQUEHANDLE_TYPES_WINDOWS_P_H
+#define QUNIQUEHANDLE_TYPES_WINDOWS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/qt_windows.h>
+#include <QtCore/private/quniquehandle_p.h>
+
+#if defined(Q_OS_WIN) || defined(Q_QDOC)
+
+QT_BEGIN_NAMESPACE
+
+namespace QtUniqueHandleTraits {
+
+struct HDCTraits
+{
+ using Type = HDC;
+ static Type invalidValue() noexcept { return nullptr; }
+ Q_CORE_EXPORT static bool close(Type handle, HWND hwnd) noexcept;
+};
+
+struct HDCDeleter
+{
+ using Type = HDCTraits::Type;
+
+ constexpr HDCDeleter() noexcept = default;
+ explicit constexpr HDCDeleter(HWND hwnd) noexcept
+ : hwnd(hwnd)
+ {}
+
+ void operator()(Type handle) const noexcept
+ {
+ if (handle != HDCTraits::invalidValue()) {
+ const bool success = HDCTraits::close(handle, hwnd);
+ Q_ASSERT(success);
+ }
+ }
+
+ HWND hwnd{ nullptr };
+};
+
+} // namespace QtUniqueHandleTraits
+using QUniqueHDCHandle = QUniqueHandle<
+ QtUniqueHandleTraits::HDCTraits,
+ QtUniqueHandleTraits::HDCDeleter
+>;
+
+QT_END_NAMESPACE
+
+#endif // Q_OS_WIN
+
+#endif // QUNIQUEHANDLE_TYPES_WINDOWS_P_H
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index 974c486b915..deac396061d 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -3991,7 +3991,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
\c{'compatible'} option corresponds to \c RelativeToBefore (and Python's
\c{fold = True}).
- \sa {Timezone transitions}, QDateTime::TransitionResolution
+ \sa {Timezone transitions}
*/
/*!
diff --git a/src/corelib/time/qtimezonelocale.cpp b/src/corelib/time/qtimezonelocale.cpp
index a794682fe0b..6ad4aa1479c 100644
--- a/src/corelib/time/qtimezonelocale.cpp
+++ b/src/corelib/time/qtimezonelocale.cpp
@@ -611,6 +611,27 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc
// Custom zone with perverse m_id ?
return;
}
+ const auto isMixedCaseAbbrev = [tail](char ch) {
+ // cv-RU and en-GU abbreviate Chamorro as ChST
+ // scn-IT abbreviates Cuba as CuT/CuST/CuDT
+ // blo-BJ abbreviates GMT as Gk
+ switch (tail.size()) {
+ case 2: return tail == "Gk";
+ case 3: return tail == "CuT";
+ case 4:
+ if (tail[0] == 'C' && tail[1] == ch && tail[3] == 'T') {
+ switch (ch) {
+ case 'h': return tail[2] == 'S';
+ case 'u': return tail[2] == 'S' || tail[2] == 'D';
+ default: break;
+ }
+ }
+ return false;
+ default:
+ break;
+ }
+ return false;
+ };
// Even if it is abbr or city name, we don't care if we've found one before.
bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword = false;
@@ -632,7 +653,7 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc
maybeCityName = false;
inword = false;
} else if (QChar::isLower(ch)) {
- maybeAbbr = false;
+ maybeAbbr = isMixedCaseAbbrev(ch);
// Dar_es_Salaam shows both cases as word starts
inword = true;
} else if (QChar::isUpper(ch)) {
@@ -891,8 +912,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
if (best.ianaIdIndex != invalidIanaId)
return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
- // Now try for a region format:
- best = {};
+ // Now try for a region format.
+ // Since we may get the IANA ID directly from a zone, we may not need an
+ // ianaIdIndex from CLDR-derived tables: and the active backend may know
+ // some zones newer than our latest CLDR.
+ NamePrefixMatch found;
for (const qsizetype locInd : indices) {
const LocaleZoneData &locData = localeZoneData[locInd];
const LocaleZoneData &nextData = localeZoneData[locInd + 1];
@@ -928,11 +952,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
QStringView city = row.exemplarCity().viewData(exemplarCityTable);
if (textMatches(city)) {
qsizetype length = cut + city.size() + suffix.size();
- if (length > best.nameLength) {
- bool gotZone = row.ianaIdIndex == best.ianaIdIndex
+ if (length > found.nameLength) {
+ bool gotZone = row.ianaId() == found.ianaId // (cheap pre-test)
|| QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
if (gotZone)
- best = { length, timeType, row.ianaIdIndex };
+ found = { row.ianaId().toByteArray(), length, timeType };
}
}
}
@@ -945,38 +969,16 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
QString city = QString::fromLatin1(local.replace('_', ' '));
if (textMatches(city)) {
qsizetype length = cut + city.size() + suffix.size();
- if (length > best.nameLength) {
- // Have to find iana in ianaIdData. Although its entries
- // from locale-independent data are nicely sorted, the
- // rest are (sadly) not.
- QByteArrayView run(ianaIdData, qstrlen(ianaIdData));
- // std::size includes the trailing '\0', so subtract one:
- const char *stop = ianaIdData + std::size(ianaIdData) - 1;
- while (run != iana) {
- if (run.end() < stop) { // Step to the next:
- run = QByteArrayView(run.end() + 1);
- } else {
- run = QByteArrayView();
- break;
- }
- }
- if (!run.isEmpty()) {
- Q_ASSERT(run == iana);
- const auto ianaIdIndex = run.begin() - ianaIdData;
- Q_ASSERT(ianaIdIndex <= (std::numeric_limits<quint16>::max)());
- best = { length, timeType, quint16(ianaIdIndex) };
- }
- }
+ if (length > found.nameLength)
+ found = { iana, length, timeType };
}
}
// TODO: similar for territories, at least once localeName() does so.
}
}
- if (best.ianaIdIndex != invalidIanaId)
- return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
#undef localeRows
- return {}; // No match found.
+ return found;
}
QTimeZonePrivate::NamePrefixMatch
diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp
index 556f7e21402..3f0254b1c5f 100644
--- a/src/corelib/time/qtimezoneprivate.cpp
+++ b/src/corelib/time/qtimezoneprivate.cpp
@@ -1177,7 +1177,7 @@ QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
}
Q_ASSERT(!name.isEmpty());
} else { // Fall back to a UTC-offset name:
- name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
+ name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
id = name.toUtf8();
}
init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
@@ -1292,12 +1292,16 @@ QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
if (!(name.startsWith("GMT"_L1) || name.startsWith("UTC"_L1)) || name.size() < 5)
return false;
// Fallback drops trailing ":00" minute:
- QStringView tail{avoid};
+ QStringView tail{avoid}; // TODO: deal with sign earlier ! Also: invisible Unicode !
tail = tail.sliced(3);
- if (tail.endsWith(":00"_L1))
- tail = tail.chopped(3);
if (name.sliced(3) == tail)
return true;
+ while (tail.endsWith(":00"_L1))
+ tail = tail.chopped(3);
+ while (name.endsWith(":00"_L1))
+ name = name.chopped(3);
+ if (name == tail)
+ return true;
// Accept U+2212 as minus sign:
const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
// Fallback doesn't zero-pad hour:
diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h
index 0f19d1fd025..9a86ded6efb 100644
--- a/src/corelib/time/qtimezoneprivate_p.h
+++ b/src/corelib/time/qtimezoneprivate_p.h
@@ -50,7 +50,7 @@ class Q_AUTOTEST_EXPORT QTimeZonePrivate : public QSharedData
{
// Nothing should be copy-assigning instances of either this or its derived
// classes (only clone() should copy, using the copy-constructor):
- bool operator=(const QTimeZonePrivate &) const = delete;
+ QTimeZonePrivate &operator=(const QTimeZonePrivate &) const = delete;
protected:
QTimeZonePrivate(const QTimeZonePrivate &other) = default;
public:
@@ -210,7 +210,7 @@ Q_DECLARE_TYPEINFO(QTimeZonePrivate::Data, Q_RELOCATABLE_TYPE);
class Q_AUTOTEST_EXPORT QUtcTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QUtcTimeZonePrivate &) const = delete;
+ QUtcTimeZonePrivate &operator=(const QUtcTimeZonePrivate &) const = delete;
QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other);
public:
// Create default UTC time zone
@@ -273,7 +273,7 @@ private:
#if QT_CONFIG(timezone_tzdb)
class QChronoTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QChronoTimeZonePrivate &) const = delete;
+ QChronoTimeZonePrivate &operator=(const QChronoTimeZonePrivate &) const = delete;
QChronoTimeZonePrivate(const QChronoTimeZonePrivate &) = default;
public:
QChronoTimeZonePrivate();
@@ -307,7 +307,7 @@ private:
#elif defined(Q_OS_DARWIN)
class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QMacTimeZonePrivate &) const = delete;
+ QMacTimeZonePrivate &operator=(const QMacTimeZonePrivate &) const = delete;
QMacTimeZonePrivate(const QMacTimeZonePrivate &other);
public:
// Create default time zone
@@ -353,7 +353,7 @@ private:
#elif defined(Q_OS_ANDROID)
class QAndroidTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QAndroidTimeZonePrivate &) const = delete;
+ QAndroidTimeZonePrivate &operator=(const QAndroidTimeZonePrivate &) const = delete;
QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &) = default;
public:
// Create default time zone
@@ -421,7 +421,7 @@ struct QTzTimeZoneCacheEntry
class Q_AUTOTEST_EXPORT QTzTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QTzTimeZonePrivate &) const = delete;
+ QTzTimeZonePrivate &operator=(const QTzTimeZonePrivate &) const = delete;
QTzTimeZonePrivate(const QTzTimeZonePrivate &) = default;
public:
// Create default time zone
@@ -474,7 +474,7 @@ private:
#elif QT_CONFIG(icu)
class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QIcuTimeZonePrivate &) const = delete;
+ QIcuTimeZonePrivate &operator=(const QIcuTimeZonePrivate &) const = delete;
QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other);
public:
// Create default time zone
@@ -518,7 +518,7 @@ private:
#elif defined(Q_OS_WIN)
class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QWinTimeZonePrivate &) const = delete;
+ QWinTimeZonePrivate &operator=(const QWinTimeZonePrivate &) const = delete;
QWinTimeZonePrivate(const QWinTimeZonePrivate &) = default;
public:
struct QWinTransitionRule {
diff --git a/src/corelib/tools/quniquehandle_p.h b/src/corelib/tools/quniquehandle_p.h
index 3ba557e838d..fd6ab693912 100644
--- a/src/corelib/tools/quniquehandle_p.h
+++ b/src/corelib/tools/quniquehandle_p.h
@@ -18,6 +18,7 @@
#include <QtCore/qtconfigmacros.h>
#include <QtCore/qassert.h>
#include <QtCore/qcompare.h>
+#include <QtCore/qfunctionaltools_impl.h>
#include <QtCore/qswap.h>
#include <QtCore/qtclasshelpermacros.h>
@@ -99,6 +100,42 @@ QT_BEGIN_NAMESPACE
...
+ Example 3:
+
+ struct TempFileTraits {
+ using Type = FILE*;
+
+ static Type invalidValue() {
+ return nullptr;
+ }
+
+ static bool close(Type handle) {
+ return fclose(handle) == 0;
+ }
+ };
+
+ struct TempFileDeleter {
+ using Type = TempFileTraits::Type;
+
+ void operator()(Type handle) {
+ if (handle != TempFileTraits::invalidValue()) {
+ TempFileTraits::close(handle);
+ if (path)
+ remove(path);
+ }
+ }
+
+ const char* path{ nullptr };
+ };
+
+ using TempFileHandle = QUniqueHandle<TempFileTraits, TempFileDeleter>;
+
+ Usage:
+
+ TempFileHandle tempFile(fopen("temp.bin", "wb"), TempFileDeleter{ "my_temp.bin" });
+
+ ...
+
NOTE: The QUniqueHandle assumes that closing a resource is
guaranteed to succeed, and provides no support for handling failure
to close a resource. It is therefore only recommended for use cases
@@ -108,9 +145,32 @@ QT_BEGIN_NAMESPACE
// clang-format off
+namespace QtUniqueHandleTraits {
+
template <typename HandleTraits>
-class QUniqueHandle
+struct DefaultDeleter
{
+ using Type = typename HandleTraits::Type;
+
+ void operator()(Type handle) const noexcept
+ {
+ if (handle != HandleTraits::invalidValue()) {
+ const bool success = HandleTraits::close(handle);
+ Q_ASSERT(success);
+ }
+ }
+};
+
+} // namespace QtUniqueHandleTraits
+
+template <typename HandleTraits, typename Deleter = QtUniqueHandleTraits::DefaultDeleter<HandleTraits>>
+class QUniqueHandle : private QtPrivate::CompactStorage<Deleter>
+{
+ using Storage = QtPrivate::CompactStorage<Deleter>;
+
+ template <typename D>
+ using if_default_constructible = std::enable_if_t<std::is_default_constructible_v<D>, bool>;
+
public:
using Type = typename HandleTraits::Type;
static_assert(std::is_nothrow_default_constructible_v<Type>);
@@ -120,6 +180,11 @@ public:
static_assert(std::is_nothrow_copy_assignable_v<Type>);
static_assert(std::is_nothrow_move_assignable_v<Type>);
static_assert(std::is_nothrow_destructible_v<Type>);
+ static_assert(std::is_nothrow_copy_constructible_v<Deleter>);
+ static_assert(std::is_nothrow_move_constructible_v<Deleter>);
+ static_assert(std::is_nothrow_copy_assignable_v<Deleter>);
+ static_assert(std::is_nothrow_move_assignable_v<Deleter>);
+ static_assert(std::is_nothrow_destructible_v<Deleter>);
static_assert(noexcept(std::declval<Type>() == std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() != std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() < std::declval<Type>()));
@@ -127,16 +192,24 @@ public:
static_assert(noexcept(std::declval<Type>() > std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() >= std::declval<Type>()));
- QUniqueHandle() = default;
-
- explicit QUniqueHandle(const Type &handle) noexcept
+ template <if_default_constructible<Deleter> = true>
+ explicit QUniqueHandle(const Type& handle = HandleTraits::invalidValue()) noexcept
: m_handle{ handle }
{}
- QUniqueHandle(QUniqueHandle &&other) noexcept
- : m_handle{ other.release() }
+ QUniqueHandle(const Type &handle, const Deleter &deleter) noexcept
+ : Storage{ deleter }, m_handle{ handle }
+ {}
+
+ QUniqueHandle(const Type &handle, Deleter &&deleter) noexcept
+ : Storage{ std::move(deleter) }, m_handle{ handle }
{}
+ QUniqueHandle(QUniqueHandle &&other) noexcept
+ : Storage{ std::move(other.deleter()) }, m_handle{ other.release() }
+ {
+ }
+
~QUniqueHandle() noexcept
{
close();
@@ -145,6 +218,7 @@ public:
void swap(QUniqueHandle &other) noexcept
{
qSwap(m_handle, other.m_handle);
+ qSwap(deleter(), other.deleter());
}
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QUniqueHandle)
@@ -168,6 +242,16 @@ public:
return m_handle;
}
+ [[nodiscard]] Deleter& deleter() noexcept
+ {
+ return Storage::object();
+ }
+
+ [[nodiscard]] const Deleter& deleter() const noexcept
+ {
+ return Storage::object();
+ }
+
void reset(const Type& handle = HandleTraits::invalidValue()) noexcept
{
if (handle == m_handle)
@@ -193,8 +277,7 @@ public:
if (!isValid())
return;
- const bool success = HandleTraits::close(m_handle);
- Q_ASSERT(success);
+ deleter()(m_handle);
m_handle = HandleTraits::invalidValue();
}
@@ -222,8 +305,8 @@ private:
// clang-format on
-template <typename Trait>
-void swap(QUniqueHandle<Trait> &lhs, QUniqueHandle<Trait> &rhs) noexcept
+template <typename Trait, typename Deleter>
+void swap(QUniqueHandle<Trait, Deleter> &lhs, QUniqueHandle<Trait, Deleter> &rhs) noexcept
{
lhs.swap(rhs);
}
diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp
index ae730a9b3af..7b1e76cc78f 100644
--- a/src/gui/kernel/qevent.cpp
+++ b/src/gui/kernel/qevent.cpp
@@ -1146,6 +1146,33 @@ Q_IMPL_POINTER_EVENT(QHoverEvent)
*/
/*!
+ \property QWheelEvent::device
+ \brief the device from which the wheel event originated
+
+ \sa pointingDevice()
+*/
+
+/*!
+ \property QWheelEvent::inverted
+ \since 5.7
+ \brief whether the delta values delivered with the event are inverted
+
+ Normally, a vertical wheel will produce a QWheelEvent with positive delta
+ values if the top of the wheel is rotating away from the hand operating it.
+ Similarly, a horizontal wheel movement will produce a QWheelEvent with
+ positive delta values if the top of the wheel is moved to the left.
+
+ However, on some platforms this is configurable, so that the same
+ operations described above will produce negative delta values (but with the
+ same magnitude). With the inverted property a wheel event consumer can
+ choose to always follow the direction of the wheel, regardless of the
+ system settings, but only for specific widgets.
+
+ \note Many platforms provide no such information. On such platforms
+ \l inverted always returns false.
+*/
+
+/*!
\fn bool QWheelEvent::inverted() const
\since 5.7
@@ -1236,6 +1263,24 @@ bool QWheelEvent::isEndEvent() const
#endif // QT_CONFIG(wheelevent)
/*!
+ \property QWheelEvent::pixelDelta
+ \brief the scrolling distance in pixels on screen
+
+ This value is provided on platforms that support high-resolution
+ pixel-based delta values, such as \macos. The value should be used
+ directly to scroll content on screen.
+
+ \note On platforms that support scrolling \l{phase()}{phases}, the delta
+ may be null when scrolling is about to begin (Qt::ScrollBegin) or has
+ ended (Qt::ScrollEnd).
+
+ \note On X11 this value is driver-specific and unreliable, use
+ angleDelta() instead.
+
+ \sa angleDelta()
+*/
+
+/*!
\fn QPoint QWheelEvent::pixelDelta() const
Returns the scrolling distance in pixels on screen. This value is
@@ -1256,6 +1301,27 @@ bool QWheelEvent::isEndEvent() const
*/
/*!
+ \property QWheelEvent::angleDelta
+ \brief the relative amount that the wheel was rotated, in eighths of a degree
+
+ A positive value indicates that the wheel was rotated forwards away from the
+ user; a negative value indicates that the wheel was rotated backwards toward
+ the user. \c angleDelta().y() provides the angle through which the common
+ vertical mouse wheel was rotated since the previous event. \c angleDelta().x()
+ provides the angle through which the horizontal mouse wheel was rotated, if
+ the mouse has a horizontal wheel; otherwise it stays at zero.
+
+ Most mouse types work in steps of 15 degrees, in which case the delta value
+ is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
+
+ \note On platforms that support scrolling \l{phase()}{phases}, the delta
+ may be null when scrolling is about to begin (Qt::ScrollBegin) or has
+ ended (Qt::ScrollEnd).
+
+ \sa pixelDelta()
+*/
+
+/*!
\fn QPoint QWheelEvent::angleDelta() const
Returns the relative amount that the wheel was rotated, in eighths of a
@@ -1294,6 +1360,15 @@ bool QWheelEvent::isEndEvent() const
*/
/*!
+ \property QWheelEvent::phase
+ \since 5.2
+ \brief the scrolling phase of this wheel event
+
+ \note The Qt::ScrollBegin and Qt::ScrollEnd phases are currently
+ supported only on \macos.
+*/
+
+/*!
\fn Qt::ScrollPhase QWheelEvent::phase() const
\since 5.2
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
index 518843ffcbd..741b089306e 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
@@ -243,6 +243,7 @@ static void initThemeHints()
touchDoubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt();
}
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
static bool checkNeedPortalSupport()
{
#if QT_CONFIG(dbus)
@@ -251,6 +252,7 @@ static bool checkNeedPortalSupport()
return false;
#endif // QT_CONFIG(dbus)
}
+#endif
// Using aggregate initialization instead of ctor so we can have a POD global static
#define Q_WINDOW_GEOMETRY_SPECIFICATION_INITIALIZER { Qt::TopLeftCorner, -1, -1, -1, -1 }
@@ -1349,11 +1351,13 @@ static void init_platform(const QString &pluginNamesWithArguments, const QString
themeNames.append(platformThemeName);
}
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
// 2) Special case - check whether it's a flatpak or snap app to use xdg-desktop-portal platform theme for portals support
if (checkNeedPortalSupport()) {
qCDebug(lcQpaTheme) << "Adding xdgdesktopportal to list of theme names";
themeNames.append(QStringLiteral("xdgdesktopportal"));
}
+#endif
// 3) Ask the platform integration for a list of theme names
const auto platformIntegrationThemeNames = QGuiApplicationPrivate::platform_integration->themeNames();
diff --git a/src/gui/kernel/qinputdevice.cpp b/src/gui/kernel/qinputdevice.cpp
index 8e5c38922e0..caed0fc6135 100644
--- a/src/gui/kernel/qinputdevice.cpp
+++ b/src/gui/kernel/qinputdevice.cpp
@@ -187,6 +187,11 @@ QString QInputDevice::name() const
}
/*!
+ \property QInputDevice::type
+ \brief the device type
+*/
+
+/*!
Returns the device type.
*/
QInputDevice::DeviceType QInputDevice::type() const
diff --git a/src/gui/kernel/qpointingdevice.cpp b/src/gui/kernel/qpointingdevice.cpp
index dcce354688a..7062340b287 100644
--- a/src/gui/kernel/qpointingdevice.cpp
+++ b/src/gui/kernel/qpointingdevice.cpp
@@ -241,6 +241,11 @@ void QPointingDevice::setMaximumTouchPoints(int c)
#endif // QT_DEPRECATED_SINCE(6, 0)
/*!
+ \property QPointingDevice::pointerType
+ \brief the pointer type
+*/
+
+/*!
Returns the pointer type.
*/
QPointingDevice::PointerType QPointingDevice::pointerType() const
@@ -250,6 +255,12 @@ QPointingDevice::PointerType QPointingDevice::pointerType() const
}
/*!
+ \property QPointingDevice::maximumPoints
+ \brief the maximum number of simultaneous touch points (fingers) that
+ can be detected
+*/
+
+/*!
Returns the maximum number of simultaneous touch points (fingers) that
can be detected.
*/
@@ -260,6 +271,11 @@ int QPointingDevice::maximumPoints() const
}
/*!
+ \property QPointingDevice::buttonCount
+ \brief the maximum number of on-device buttons that can be detected
+*/
+
+/*!
Returns the maximum number of on-device buttons that can be detected.
*/
int QPointingDevice::buttonCount() const
@@ -269,6 +285,13 @@ int QPointingDevice::buttonCount() const
}
/*!
+ \property QPointingDevice::uniqueId
+ \brief a unique ID (of dubious utility) for the device
+
+ You probably should rather be concerned with QPointerEventPoint::uniqueId().
+*/
+
+/*!
Returns a unique ID (of dubious utility) for the device.
You probably should rather be concerned with QPointerEventPoint::uniqueId().
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 62b7ab6efb0..5590f2fb431 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -7186,6 +7186,26 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
*/
/*!
+ \fn bool QRhiGraphicsPipeline::hasDepthClamp() const
+ \return true if depth clamp is enabled.
+
+ \since 6.11
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthClamp(bool enable)
+
+ Enables depth clamping when \a enable is true. When depth clamping is
+ enabled, primitives that would otherwise be clipped by the near or far
+ clip plane are rasterized and their depth values are clamped to the
+ depth range. When disabled (the default), such primitives are clipped.
+
+ \note This setting is ignored on OpenGL ES.
+
+ \since 6.11
+ */
+
+/*!
\fn QRhiGraphicsPipeline::CompareOp QRhiGraphicsPipeline::depthOp() const
\return the depth comparison function.
*/
diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h
index b5a3f7b43be..5ee2a9acd13 100644
--- a/src/gui/rhi/qrhi.h
+++ b/src/gui/rhi/qrhi.h
@@ -1453,6 +1453,9 @@ public:
bool hasDepthWrite() const { return m_depthWrite; }
void setDepthWrite(bool enable) { m_depthWrite = enable; }
+ bool hasDepthClamp() const { return m_depthClamp; }
+ void setDepthClamp(bool enable) { m_depthClamp = enable; }
+
CompareOp depthOp() const { return m_depthOp; }
void setDepthOp(CompareOp op) { m_depthOp = op; }
@@ -1524,6 +1527,7 @@ protected:
QVarLengthArray<TargetBlend, 8> m_targetBlends;
bool m_depthTest = false;
bool m_depthWrite = false;
+ bool m_depthClamp = false;
CompareOp m_depthOp = Less;
bool m_stencilTest = false;
StencilOpState m_stencilFront;
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index a5f860e7724..1441be24043 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -4673,7 +4673,7 @@ bool QD3D11GraphicsPipeline::create()
rastDesc.FrontCounterClockwise = m_frontFace == CCW;
rastDesc.DepthBias = m_depthBias;
rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias;
- rastDesc.DepthClipEnable = true;
+ rastDesc.DepthClipEnable = m_depthClamp ? FALSE : TRUE;
rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
rastDesc.MultisampleEnable = rhiD->effectiveSampleDesc(m_sampleCount).Count > 1;
HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index 4f09b3c136b..b68b65b1063 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -6263,7 +6263,7 @@ bool QD3D12GraphicsPipeline::create()
stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW;
stream.rasterizerState.object.DepthBias = m_depthBias;
stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias;
- stream.rasterizerState.object.DepthClipEnable = TRUE;
+ stream.rasterizerState.object.DepthClipEnable = m_depthClamp ? FALSE : TRUE;
stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1;
stream.depthStencilState.object.DepthEnable = m_depthTest;
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 81d2719117e..1308d4362e5 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -585,6 +585,10 @@ QT_BEGIN_NAMESPACE
#define GL_PROGRAM 0x82E2
#endif
+#ifndef GL_DEPTH_CLAMP
+#define GL_DEPTH_CLAMP 0x864F
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -998,6 +1002,13 @@ bool QRhiGles2::create(QRhi::Flags flags)
}
if (caps.gles)
+ caps.depthClamp = false;
+ else
+ caps.depthClamp = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // Desktop 3.2
+ if (!caps.depthClamp)
+ caps.depthClamp = ctx->hasExtension("GL_EXT_depth_clamp") || ctx->hasExtension("GL_ARB_depth_clamp");
+
+ if (caps.gles)
caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0
else
caps.textureCompareMode = true;
@@ -4096,6 +4107,15 @@ void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2Grap
f->glDepthMask(depthWrite);
}
+ const bool depthClamp = psD->m_depthClamp;
+ if (caps.depthClamp && (forceUpdate || depthClamp != state.depthClamp)) {
+ state.depthClamp = depthClamp;
+ if (depthClamp)
+ f->glEnable(GL_DEPTH_CLAMP);
+ else
+ f->glDisable(GL_DEPTH_CLAMP);
+ }
+
const GLenum depthFunc = toGlCompareOp(psD->m_depthOp);
if (forceUpdate || depthFunc != state.depthFunc) {
state.depthFunc = depthFunc;
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
index ea061f9d218..70dd96a8dc7 100644
--- a/src/gui/rhi/qrhigles2_p.h
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -593,6 +593,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
} blend[16];
bool depthTest;
bool depthWrite;
+ bool depthClamp;
GLenum depthFunc;
bool stencilTest;
GLuint stencilReadMask;
@@ -1075,6 +1076,7 @@ public:
uint baseVertex : 1;
uint compute : 1;
uint textureCompareMode : 1;
+ uint depthClamp : 1;
uint properMapBuffer : 1;
uint nonBaseLevelFramebufferTexture : 1;
uint texelFetch : 1;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index c3f1031b44b..7fa05f69232 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -403,6 +403,7 @@ struct QMetalGraphicsPipelineData
MTLWinding winding;
MTLCullMode cullMode;
MTLTriangleFillMode triangleFillMode;
+ MTLDepthClipMode depthClipMode;
float depthBias;
float slopeScaledDepthBias;
QMetalShader vs;
@@ -1477,7 +1478,6 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand
[cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
cbD->d->currentDepthStencilState = d->ds;
}
-
if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
[cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
cbD->currentCullMode = int(d->cullMode);
@@ -1486,6 +1486,10 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand
[cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
cbD->currentTriangleFillMode = int(d->triangleFillMode);
}
+ if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) {
+ [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode];
+ cbD->currentDepthClipMode = int(d->depthClipMode);
+ }
if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
[cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
cbD->currentFrontFaceWinding = int(d->winding);
@@ -5035,6 +5039,7 @@ void QMetalGraphicsPipeline::mapStates()
d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
d->cullMode = toMetalCullMode(m_cullMode);
d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
+ d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
d->depthBias = float(m_depthBias);
d->slopeScaledDepthBias = m_slopeScaledDepthBias;
}
@@ -6257,6 +6262,7 @@ void QMetalCommandBuffer::resetPerPassCachedState()
currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
currentCullMode = -1;
currentTriangleFillMode = -1;
+ currentDepthClipMode = -1;
currentFrontFaceWinding = -1;
currentDepthBiasValues = { 0.0f, 0.0f };
diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h
index 7c19ae9e767..6649a6cd304 100644
--- a/src/gui/rhi/qrhimetal_p.h
+++ b/src/gui/rhi/qrhimetal_p.h
@@ -299,6 +299,7 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer
QRhiCommandBuffer::IndexFormat currentIndexFormat;
int currentCullMode;
int currentTriangleFillMode;
+ int currentDepthClipMode;
int currentFrontFaceWinding;
std::pair<float, float> currentDepthBiasValues;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index c5167a6e7de..481ffd57b5d 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -949,6 +949,8 @@ bool QRhiVulkan::create(QRhi::Flags flags)
// elsewhere states that the minimum bufferOffset is 4...
texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment);
+ caps.depthClamp = physDevFeatures.depthClamp;
+
caps.wideLines = physDevFeatures.wideLines;
caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1);
@@ -8402,6 +8404,8 @@ bool QVkGraphicsPipeline::create()
VkPipelineRasterizationStateCreateInfo rastInfo = {};
rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ if (m_depthClamp && rhiD->caps.depthClamp)
+ rastInfo.depthClampEnable = m_depthClamp;
rastInfo.cullMode = toVkCullMode(m_cullMode);
rastInfo.frontFace = toVkFrontFace(m_frontFace);
if (m_depthBias != 0 || !qFuzzyIsNull(m_slopeScaledDepthBias)) {
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index d141a84c5fb..1e9318513fd 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -936,6 +936,7 @@ public:
struct {
bool compute = false;
+ bool depthClamp = false;
bool wideLines = false;
bool debugUtils = false;
bool vertexAttribDivisor = false;
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index b50dc4a43bf..7bbc9cf63db 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -257,6 +257,19 @@ QFontEngine *QFontPrivate::engineForScript(int script) const
return QT_FONT_ENGINE_FROM_DATA(engineData, script);
}
+QFontEngine *QFontPrivate::engineForCharacter(char32_t c, EngineQueryOptions opt) const
+{
+ const bool smallCaps = !(opt & EngineQueryOption::IgnoreSmallCapsEngine);
+ const auto script = QChar::script(c);
+ QFontEngine *engine;
+ if (smallCaps && capital == QFont::SmallCaps && QChar::isLower(c))
+ engine = smallCapsFontPrivate()->engineForScript(script);
+ else
+ engine = engineForScript(script);
+ Q_ASSERT(engine != nullptr);
+ return engine;
+}
+
void QFontPrivate::alterCharForCapitalization(QChar &c) const {
switch (capital) {
case QFont::AllUppercase:
diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h
index 75550439521..27bc2a6a7cc 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -163,6 +163,11 @@ private:
class Q_GUI_EXPORT QFontPrivate
{
public:
+ enum class EngineQueryOption {
+ Default = 0,
+ IgnoreSmallCapsEngine = 0x1,
+ };
+ Q_DECLARE_FLAGS(EngineQueryOptions, EngineQueryOption)
QFontPrivate();
QFontPrivate(const QFontPrivate &other);
@@ -170,6 +175,7 @@ public:
~QFontPrivate();
QFontEngine *engineForScript(int script) const;
+ QFontEngine *engineForCharacter(char32_t c, EngineQueryOptions opt = {}) const;
void alterCharForCapitalization(QChar &c) const;
QAtomicInt ref;
@@ -208,6 +214,7 @@ public:
void unsetVariableAxis(QFont::Tag tag);
bool hasVariableAxis(QFont::Tag tag, float value) const;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QFontPrivate::EngineQueryOptions)
class Q_GUI_EXPORT QFontCache : public QObject
diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp
index 4ea21c9f0f3..c4403a16c6d 100644
--- a/src/gui/text/qfontmetrics.cpp
+++ b/src/gui/text/qfontmetrics.cpp
@@ -410,9 +410,8 @@ bool QFontMetrics::inFont(QChar ch) const
*/
bool QFontMetrics::inFontUcs4(uint ucs4) const
{
- const int script = QChar::script(ucs4);
- QFontEngine *engine = d->engineForScript(script);
- Q_ASSERT(engine != nullptr);
+ constexpr auto Ignore = QFontPrivate::EngineQueryOption::IgnoreSmallCapsEngine;
+ QFontEngine *engine = d->engineForCharacter(ucs4, Ignore);
if (engine->type() == QFontEngine::Box)
return false;
return engine->canRender(ucs4);
@@ -432,13 +431,7 @@ bool QFontMetrics::inFontUcs4(uint ucs4) const
*/
int QFontMetrics::leftBearing(QChar ch) const
{
- const int script = ch.script();
- QFontEngine *engine;
- if (d->capital == QFont::SmallCaps && ch.isLower())
- engine = d->smallCapsFontPrivate()->engineForScript(script);
- else
- engine = d->engineForScript(script);
- Q_ASSERT(engine != nullptr);
+ QFontEngine *engine = d->engineForCharacter(ch.unicode());
if (engine->type() == QFontEngine::Box)
return 0;
@@ -465,12 +458,7 @@ int QFontMetrics::leftBearing(QChar ch) const
*/
int QFontMetrics::rightBearing(QChar ch) const
{
- const int script = ch.script();
- QFontEngine *engine;
- if (d->capital == QFont::SmallCaps && ch.isLower())
- engine = d->smallCapsFontPrivate()->engineForScript(script);
- else
- engine = d->engineForScript(script);
+ QFontEngine *engine = d->engineForCharacter(ch.unicode());
Q_ASSERT(engine != nullptr);
if (engine->type() == QFontEngine::Box)
return 0;
@@ -574,13 +562,7 @@ int QFontMetrics::horizontalAdvance(QChar ch) const
if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing)
return 0;
- const int script = ch.script();
- QFontEngine *engine;
- if (d->capital == QFont::SmallCaps && ch.isLower())
- engine = d->smallCapsFontPrivate()->engineForScript(script);
- else
- engine = d->engineForScript(script);
- Q_ASSERT(engine != nullptr);
+ QFontEngine *engine = d->engineForCharacter(ch.unicode());
d->alterCharForCapitalization(ch);
@@ -684,13 +666,7 @@ QRect QFontMetrics::boundingRect(const QString &text, const QTextOption &option)
*/
QRect QFontMetrics::boundingRect(QChar ch) const
{
- const int script = ch.script();
- QFontEngine *engine;
- if (d->capital == QFont::SmallCaps && ch.isLower())
- engine = d->smallCapsFontPrivate()->engineForScript(script);
- else
- engine = d->engineForScript(script);
- Q_ASSERT(engine != nullptr);
+ QFontEngine *engine = d->engineForCharacter(ch.unicode());
d->alterCharForCapitalization(ch);
@@ -1345,13 +1321,7 @@ bool QFontMetricsF::inFontUcs4(uint ucs4) const
*/
qreal QFontMetricsF::leftBearing(QChar ch) const
{
- const int script = ch.script();
- QFontEngine *engine;
- if (d->capital == QFont::SmallCaps && ch.isLower())
- engine = d->smallCapsFontPrivate()->engineForScript(script);
- else
- engine = d->engineForScript(script);
- Q_ASSERT(engine != nullptr);
+ QFontEngine *engine = d->engineForCharacter(ch.unicode());
if (engine->type() == QFontEngine::Box)
return 0;
diff --git a/src/gui/text/qfontvariableaxis.cpp b/src/gui/text/qfontvariableaxis.cpp
index be83a3e02ce..bbc14f4cd11 100644
--- a/src/gui/text/qfontvariableaxis.cpp
+++ b/src/gui/text/qfontvariableaxis.cpp
@@ -60,6 +60,18 @@ QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QFontVariableAxisPrivate)
QFontVariableAxis::QFontVariableAxis(const QFontVariableAxis &axis) = default;
/*!
+ \property QFontVariableAxis::tag
+ \brief the tag of the axis
+
+ This is a four-character sequence which identifies the axis. Certain tags
+ have standardized meanings, such as "wght" (weight) and "wdth" (width),
+ but any sequence of four latin-1 characters is a valid tag. By convention,
+ non-standard/custom axes are denoted by tags in all uppercase.
+
+ \sa QFont::setVariableAxis(), name()
+*/
+
+/*!
Returns the tag of the axis. This is a four-character sequence which identifies the axis.
Certain tags have standardized meanings, such as "wght" (weight) and "wdth" (width), but any
sequence of four latin-1 characters is a valid tag. By convention, non-standard/custom axes
@@ -91,6 +103,13 @@ void QFontVariableAxis::setTag(QFont::Tag tag)
}
/*!
+ \property QFontVariableAxis::name
+ \brief the name of the axis, if provided by the font
+
+ \sa tag()
+*/
+
+/*!
Returns the name of the axis, if provided by the font.
\sa tag()
@@ -153,6 +172,15 @@ void QFontVariableAxis::setMinimumValue(qreal minimumValue)
}
/*!
+ \property QFontVariableAxis::maximumValue
+ \brief the maximum value of the axis
+
+ Setting the axis to a value which is higher than this is not supported.
+
+ \sa minimumValue(), defaultValue()
+*/
+
+/*!
Returns the maximum value of the axis. Setting the axis to a value which is higher than this
is not supported.
@@ -182,6 +210,16 @@ void QFontVariableAxis::setMaximumValue(qreal maximumValue)
}
/*!
+ \property QFontVariableAxis::defaultValue
+ \brief the default value of the axis
+
+ This is the value the axis will have if none has been provided in the
+ QFont query.
+
+ \sa minimumValue(), maximumValue()
+*/
+
+/*!
Returns the default value of the axis. This is the value the axis will have if none has been
provided in the QFont query.
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index 3452d0a448e..dbcccd8e943 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -333,10 +333,16 @@ void QHttp2ProtocolHandler::handleHeadersReceived(const HPack::HttpHeader &heade
// of parsing and related errors/bugs, but it would be nice to have
// more detailed validation of headers.
if (name == ":status") {
- statusCode = value.left(3).toInt();
- httpReply->setStatusCode(statusCode);
- m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
- httpReply->setReasonPhrase(QString::fromLatin1(value.mid(4)));
+ bool ok = false;
+ if (int status = value.toInt(&ok); ok && status >= 0 && status <= 999) {
+ statusCode = status;
+ httpReply->setStatusCode(statusCode);
+ m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
+ } else {
+ finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
+ "invalid :status value"_L1);
+ return;
+ }
} else if (name == "content-length") {
bool ok = false;
const qlonglong length = value.toLongLong(&ok);
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index f680950701c..f76d79571c3 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -272,6 +272,11 @@ void QNetworkReplyHttpImpl::close()
void QNetworkReplyHttpImpl::abort()
{
+ abortImpl(QNetworkReply::OperationCanceledError);
+}
+
+void QNetworkReplyHttpImpl::abortImpl(QNetworkReply::NetworkError error)
+{
Q_D(QNetworkReplyHttpImpl);
// FIXME
if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
@@ -282,7 +287,8 @@ void QNetworkReplyHttpImpl::abort()
if (d->state != QNetworkReplyPrivate::Finished) {
// call finished which will emit signals
// FIXME shouldn't this be emitted Queued?
- d->error(OperationCanceledError, tr("Operation canceled"));
+ d->error(error,
+ error == TimeoutError ? tr("Operation timed out") : tr("Operation canceled"));
d->finished();
}
@@ -2120,7 +2126,7 @@ void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData()
void QNetworkReplyHttpImplPrivate::_q_transferTimedOut()
{
Q_Q(QNetworkReplyHttpImpl);
- q->abort();
+ q->abortImpl(QNetworkReply::TimeoutError);
}
void QNetworkReplyHttpImplPrivate::setupTransferTimeout()
@@ -2242,8 +2248,10 @@ void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, c
// Can't set and emit multiple errors.
if (errorCode != QNetworkReply::NoError) {
// But somewhat unavoidable if we have cancelled the request:
- if (errorCode != QNetworkReply::OperationCanceledError)
+ if (errorCode != QNetworkReply::OperationCanceledError
+ && errorCode != QNetworkReply::TimeoutError) {
qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
+ }
return;
}
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
index 0d16d02ff53..a354b388ad6 100644
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -59,6 +59,7 @@ public:
void close() override;
void abort() override;
+ void abortImpl(QNetworkReply::NetworkError error);
qint64 bytesAvailable() const override;
bool isSequential () const override;
qint64 size() const override;
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index e0ef6cec794..4c4e5fac962 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -491,7 +491,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
return;
if (m_panel.visible) {
- const QString selection = QString::fromNSString(m_panel.URL.path);
+ const QString selection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
if (selection != m_currentSelection) {
m_currentSelection = selection;
emit m_helper->currentChanged(QUrl::fromLocalFile(selection));
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
index dab348beaa4..7a6f010ba8f 100644
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
@@ -88,6 +88,11 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
return false;
}
+ // Tahoe has issues with window-modal alert buttons not responding to mouse
+ if (windowModality == Qt::WindowModal
+ && QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe)
+ return false;
+
// And without options we don't know what to show
if (!options())
return false;
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
index 5807d157636..eb36f7351d0 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
@@ -322,6 +322,16 @@ void QWasmAccessibility::setProperty(emscripten::val element, const std::string
element.set(property, val);
}
+void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text)
+{
+ const emscripten::val element = getHtmlElement(iface);
+ setAttribute(element, attribute, iface->text(text).toStdString());
+}
+void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text)
+{
+ const emscripten::val element = getHtmlElement(iface);
+ setProperty(element, property, iface->text(text).toStdString());
+}
void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element, const char *eventType)
{
@@ -331,6 +341,17 @@ void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripte
true);
}
+void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType)
+{
+ if (iface->object()) {
+ QAccessibleEvent event(iface->object(), eventType);
+ handleUpdateByInterfaceRole(&event);
+ } else {
+ QAccessibleEvent event(iface, eventType);
+ handleUpdateByInterfaceRole(&event);
+ }
+}
+
emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
{
// Get the html container element for the interface; this depends on which
@@ -484,11 +505,11 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
m_elements[iface] = element;
setHtmlElementGeometry(iface);
- setHtmlElementTextName(iface);
setHtmlElementDisabled(iface);
setHtmlElementVisibility(iface, !iface->state().invisible);
handleIdentifierUpdate(iface);
handleDescriptionChanged(iface);
+ sendEvent(iface, QAccessible::NameChanged);
linkToParent(iface);
// Link in child elements
@@ -624,28 +645,6 @@ void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect g
style.set("height", std::to_string(geometry.height()) + "px");
}
-void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface)
-{
- const emscripten::val element = getHtmlElement(iface);
- const QString name = iface->text(QAccessible::Name);
- const QString value = iface->text(QAccessible::Value);
-
- // A <div> cannot contain aria-label
- if (iface->role() == QAccessible::StaticText)
- setProperty(element, "innerText", name.toStdString());
- else if (iface->role() == QAccessible::EditableText)
- setProperty(element, "value", value.toStdString());
- else
- setAttribute(element, "aria-label", name.toStdString());
-}
-
-void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface)
-{
- const emscripten::val element = getHtmlElement(iface);
- QString value = iface->text(QAccessible::Value);
- setProperty(element, "value", value.toStdString());
-}
-
void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface)
{
const auto element = getHtmlElement(iface);
@@ -677,7 +676,8 @@ void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ // StaticText is a div
+ setNamedProperty(event->accessibleInterface(), "innerText", QAccessible::Name);
} break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
@@ -698,7 +698,7 @@ void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
setProperty(element, "type", "text");
} break;
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
} break;
case QAccessible::ObjectShow:
case QAccessible::Focus: {
@@ -711,12 +711,12 @@ void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event)
else
setProperty(element, "type", "text");
}
- setHtmlElementTextNameLE(iface);
+ setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
} break;
case QAccessible::TextRemoved:
case QAccessible::TextInserted:
case QAccessible::TextCaretMoved: {
- setHtmlElementTextNameLE(event->accessibleInterface());
+ setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value);
} break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
@@ -751,7 +751,15 @@ void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
{
- qCDebug(lcQpaAccessibility) << "TODO: implement handleButtonUpdate for event" << event->type();
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
+ break;
+ }
}
void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
@@ -759,7 +767,7 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
switch (event->type()) {
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::StateChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -778,7 +786,8 @@ void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event)
switch (event->type()) {
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ /* A switch is like a button in this regard */
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::StateChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -841,7 +850,7 @@ void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
case QAccessible::Focus:
case QAccessible::DialogStart:
case QAccessible::StateChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
default:
qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
@@ -869,10 +878,10 @@ void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
linkToParent(iface);
setHtmlElementVisibility(iface, !iface->state().invisible);
setHtmlElementGeometry(iface);
- setHtmlElementTextName(iface);
setHtmlElementDisabled(iface);
handleIdentifierUpdate(iface);
handleDescriptionChanged(iface);
+ sendEvent(iface, QAccessible::NameChanged);
}
}
for (int i = 0; i < iface->childCount(); ++i)
@@ -884,7 +893,7 @@ void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
switch (event->type()) {
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::StateChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -905,7 +914,7 @@ void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
} break;
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::ValueChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -927,7 +936,7 @@ void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
} break;
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::ValueChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -946,7 +955,7 @@ void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
switch (event->type()) {
case QAccessible::Focus:
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::ValueChanged: {
QAccessibleInterface *accessible = event->accessibleInterface();
@@ -965,10 +974,10 @@ void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::Focus: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
default:
qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
@@ -980,10 +989,10 @@ void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
case QAccessible::NameChanged: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
case QAccessible::Focus: {
- setHtmlElementTextName(event->accessibleInterface());
+ setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name);
} break;
default:
qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
@@ -1106,13 +1115,19 @@ void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface)
void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
{
+ if (handleUpdateByEventType(event))
+ handleUpdateByInterfaceRole(event);
+}
+
+bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event)
+{
if (!m_accessibilityEnabled)
- return;
+ return false;
QAccessibleInterface *iface = event->accessibleInterface();
if (!iface) {
- qWarning() << "notifyAccessibilityUpdate with null a11y interface" << event->type() << event->object();
- return;
+ qWarning() << "handleUpdateByEventType with null a11y interface" << event->type() << event->object();
+ return false;
}
// Handle event types that creates/removes objects.
@@ -1120,13 +1135,13 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
case QAccessible::ObjectCreated:
// Do nothing, there are too many changes to the interface
// before ObjectShow is called
- return;
+ return false;
case QAccessible::ObjectDestroyed:
// The object might be under destruction, and the interface is not valid
// but we can look at the pointer,
removeObject(iface);
- return;
+ return false;
case QAccessible::ObjectShow: // We do not get ObjectCreated from widgets, we get ObjectShow
createObject(iface);
@@ -1142,7 +1157,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
};
if (getHtmlElement(iface).isUndefined())
- return;
+ return false;
// Handle some common event types. See
// https://doc.qt.io/qt-5/qaccessible.html#Event-enum
@@ -1155,7 +1170,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
case QAccessible::DescriptionChanged:
handleDescriptionChanged(iface);
- return;
+ return false;
case QAccessible::Focus:
// We do not get all callbacks for the geometry
@@ -1166,7 +1181,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
case QAccessible::IdentifierChanged:
handleIdentifierUpdate(iface);
- return;
+ return false;
case QAccessible::ObjectShow:
linkToParent(iface);
@@ -1174,23 +1189,37 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
// Sync up properties on show;
setHtmlElementGeometry(iface);
- setHtmlElementTextName(iface);
+ sendEvent(iface, QAccessible::NameChanged);
break;
case QAccessible::ObjectHide:
linkToParent(iface);
setHtmlElementVisibility(iface, false);
- return;
+ return false;
case QAccessible::LocationChanged:
setHtmlElementGeometry(iface);
- return;
+ return false;
// TODO: maybe handle more types here
default:
break;
};
+ return true;
+}
+
+void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event)
+{
+ if (!m_accessibilityEnabled)
+ return;
+
+ QAccessibleInterface *iface = event->accessibleInterface();
+ if (!iface) {
+ qWarning() << "handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object();
+ return;
+ }
+
// Switch on interface role, see
// https://doc.qt.io/qt-5/qaccessibleinterface.html#role
switch (iface->role()) {
@@ -1198,7 +1227,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
handleStaticTextUpdate(event);
break;
case QAccessible::Button:
- handleStaticTextUpdate(event);
+ handleButtonUpdate(event);
break;
case QAccessible::CheckBox:
handleCheckBoxUpdate(event);
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.h b/src/plugins/platforms/wasm/qwasmaccessibility.h
index ddbfec918d6..f20c7db5ac3 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.h
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.h
@@ -73,8 +73,6 @@ private:
void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible);
void setHtmlElementGeometry(QAccessibleInterface *iface);
void setHtmlElementGeometry(emscripten::val element, QRect geometry);
- void setHtmlElementTextName(QAccessibleInterface *iface);
- void setHtmlElementTextNameLE(QAccessibleInterface *iface);
void setHtmlElementFocus(QAccessibleInterface *iface);
void setHtmlElementDisabled(QAccessibleInterface *iface);
void setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface);
@@ -105,6 +103,9 @@ private:
void relinkParentForChildren(QAccessibleInterface *iface);
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
+ bool handleUpdateByEventType(QAccessibleEvent *event);
+ void handleUpdateByInterfaceRole(QAccessibleEvent *event);
+
void setRootObject(QObject *o) override;
void initialize() override;
void cleanup() override;
@@ -117,7 +118,11 @@ private:
void setProperty(emscripten::val element, const std::string &attr, const char *val);
void setProperty(emscripten::val element, const std::string &attr, bool val);
+ void setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text);
+ void setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text);
+
void addEventListener(QAccessibleInterface *, emscripten::val element, const char *eventType);
+ void sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType);
private:
static QWasmAccessibility *s_instance;
diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt
index 0ce9a4b091c..d9415f0a011 100644
--- a/src/plugins/platforms/wayland/CMakeLists.txt
+++ b/src/plugins/platforms/wayland/CMakeLists.txt
@@ -157,7 +157,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient
${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/qt-windowmanager.xml
${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/hardware-integration.xml
${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/server-buffer-extension.xml
- ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/color-management/color-management-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/pointer-warp/pointer-warp-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/session-management/xx-session-management-v1.xml
)
diff --git a/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp
index 92c746d3541..edbeb1f72ea 100644
--- a/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp
+++ b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp
@@ -40,6 +40,11 @@ void QWaylandClientExtensionPrivate::globalRemoved(const RegistryGlobal &global)
}
}
+/*!
+ \class QWaylandClientExtension
+ \internal
+*/
+
void QWaylandClientExtension::initialize()
{
Q_D(QWaylandClientExtension);
diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp
index 2114e59328b..2a0c8a4c854 100644
--- a/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp
+++ b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
ColorManager::ColorManager(struct ::wl_registry *registry, uint32_t id, int version)
- : QtWayland::xx_color_manager_v4(registry, id, version)
+ : QtWayland::wp_color_manager_v1(registry, id, version)
{
}
@@ -22,7 +22,7 @@ ColorManager::~ColorManager()
destroy();
}
-void ColorManager::xx_color_manager_v4_supported_feature(uint32_t feature)
+void ColorManager::wp_color_manager_v1_supported_feature(uint32_t feature)
{
switch (feature) {
case feature_icc_v2_v4:
@@ -49,14 +49,14 @@ void ColorManager::xx_color_manager_v4_supported_feature(uint32_t feature)
}
}
-void ColorManager::xx_color_manager_v4_supported_primaries_named(uint32_t primaries)
+void ColorManager::wp_color_manager_v1_supported_primaries_named(uint32_t primaries)
{
- mPrimaries.push_back(QtWayland::xx_color_manager_v4::primaries(primaries));
+ mPrimaries.push_back(QtWayland::wp_color_manager_v1::primaries(primaries));
}
-void ColorManager::xx_color_manager_v4_supported_tf_named(uint32_t transferFunction)
+void ColorManager::wp_color_manager_v1_supported_tf_named(uint32_t transferFunction)
{
- mTransferFunctions.push_back(QtWayland::xx_color_manager_v4::transfer_function(transferFunction));
+ mTransferFunctions.push_back(QtWayland::wp_color_manager_v1::transfer_function(transferFunction));
}
ColorManager::Features ColorManager::supportedFeatures() const
@@ -64,12 +64,12 @@ ColorManager::Features ColorManager::supportedFeatures() const
return mFeatures;
}
-bool ColorManager::supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const
+bool ColorManager::supportsNamedPrimary(QtWayland::wp_color_manager_v1::primaries primaries) const
{
return mPrimaries.contains(primaries);
}
-bool ColorManager::supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const
+bool ColorManager::supportsTransferFunction(QtWayland::wp_color_manager_v1::transfer_function transferFunction) const
{
return mTransferFunctions.contains(transferFunction);
}
@@ -92,8 +92,8 @@ std::unique_ptr<ImageDescription> ColorManager::createImageDescription(const QCo
return nullptr;
constexpr std::array tfMapping = {
- std::make_pair(QColorSpace::TransferFunction::Linear, transfer_function_linear),
- std::make_pair(QColorSpace::TransferFunction::SRgb, transfer_function_srgb),
+ std::make_pair(QColorSpace::TransferFunction::Linear, transfer_function_ext_linear),
+ std::make_pair(QColorSpace::TransferFunction::SRgb, transfer_function_gamma22),
std::make_pair(QColorSpace::TransferFunction::St2084, transfer_function_st2084_pq),
std::make_pair(QColorSpace::TransferFunction::Hlg, transfer_function_hlg),
};
@@ -106,99 +106,105 @@ std::unique_ptr<ImageDescription> ColorManager::createImageDescription(const QCo
transferFunction = transfer_function_gamma22;
else if (qFuzzyCompare(colorspace.gamma(), 2.8f) && supportsTransferFunction(transfer_function_gamma28))
transferFunction = transfer_function_gamma28;
- if (!transferFunction && !(mFeatures & Feature::PowerTransferFunction))
- return nullptr;
- } else if (!transferFunction) {
+ if (!transferFunction && !(mFeatures & Feature::PowerTransferFunction)) {
+ if (qFuzzyCompare(colorspace.gamma(), 563.0f / 256.0f) && supportsTransferFunction(transfer_function_gamma22)) {
+ // If power tf is not supported, we can use Adobe RGB gamma approximation
+ transferFunction = transfer_function_gamma22;
+ } else {
+ return nullptr;
+ }
+ }
+ } else if (!transferFunction || !supportsTransferFunction(*transferFunction)) {
return nullptr;
}
- auto creator = new_parametric_creator();
+ auto creator = create_parametric_creator();
if (primary != primaryMapping.end()) {
- xx_image_description_creator_params_v4_set_primaries_named(creator, primary->second);
+ wp_image_description_creator_params_v1_set_primaries_named(creator, primary->second);
} else {
const auto primaries = colorspace.primaryPoints();
- xx_image_description_creator_params_v4_set_primaries(creator,
- std::round(10'000 * primaries.redPoint.x()), std::round(10'000 * primaries.redPoint.y()),
- std::round(10'000 * primaries.greenPoint.x()), std::round(10'000 * primaries.greenPoint.y()),
- std::round(10'000 * primaries.bluePoint.x()), std::round(10'000 * primaries.bluePoint.y()),
- std::round(10'000 * primaries.whitePoint.x()), std::round(10'000 * primaries.whitePoint.y())
+ wp_image_description_creator_params_v1_set_primaries(creator,
+ std::round(1'000'000 * primaries.redPoint.x()), std::round(1'000'000 * primaries.redPoint.y()),
+ std::round(1'000'000 * primaries.greenPoint.x()), std::round(1'000'000 * primaries.greenPoint.y()),
+ std::round(1'000'000 * primaries.bluePoint.x()), std::round(1'000'000 * primaries.bluePoint.y()),
+ std::round(1'000'000 * primaries.whitePoint.x()), std::round(1'000'000 * primaries.whitePoint.y())
);
}
if (transferFunction) {
- xx_image_description_creator_params_v4_set_tf_named(creator, *transferFunction);
+ wp_image_description_creator_params_v1_set_tf_named(creator, *transferFunction);
} else {
Q_ASSERT(colorspace.transferFunction() == QColorSpace::TransferFunction::Gamma);
- xx_image_description_creator_params_v4_set_tf_power(creator, std::round(colorspace.gamma() * 10'000));
+ wp_image_description_creator_params_v1_set_tf_power(creator, std::round(colorspace.gamma() * 10'000));
}
- return std::make_unique<ImageDescription>(xx_image_description_creator_params_v4_create(creator));
+ return std::make_unique<ImageDescription>(wp_image_description_creator_params_v1_create(creator));
}
ImageDescriptionInfo::ImageDescriptionInfo(ImageDescription *descr)
- : QtWayland::xx_image_description_info_v4(descr->get_information())
+ : QtWayland::wp_image_description_info_v1(descr->get_information())
{
}
ImageDescriptionInfo::~ImageDescriptionInfo()
{
- xx_image_description_info_v4_destroy(object());
+ wp_image_description_info_v1_destroy(object());
}
-void ImageDescriptionInfo::xx_image_description_info_v4_done()
+void ImageDescriptionInfo::wp_image_description_info_v1_done()
{
Q_EMIT done();
}
-void ImageDescriptionInfo::xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size)
+void ImageDescriptionInfo::wp_image_description_info_v1_icc_file(int32_t icc, uint32_t icc_size)
{
Q_UNUSED(icc_size)
close(icc);
}
-void ImageDescriptionInfo::xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y)
+void ImageDescriptionInfo::wp_image_description_info_v1_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y)
{
- mContainerRed = QPointF(r_x, r_y) / 10'000.0;
- mContainerGreen = QPointF(g_x, g_y) / 10'000.0;
- mContainerBlue = QPointF(b_x, b_y) / 10'000.0;
- mContainerWhite = QPointF(w_x, w_y) / 10'000.0;
+ mContainerRed = QPointF(r_x, r_y) / 1'000'000.0;
+ mContainerGreen = QPointF(g_x, g_y) / 1'000'000.0;
+ mContainerBlue = QPointF(b_x, b_y) / 1'000'000.0;
+ mContainerWhite = QPointF(w_x, w_y) / 1'000'000.0;
}
-void ImageDescriptionInfo::xx_image_description_info_v4_tf_named(uint32_t transferFunction)
+void ImageDescriptionInfo::wp_image_description_info_v1_tf_named(uint32_t transferFunction)
{
mTransferFunction = transferFunction;
}
-void ImageDescriptionInfo::xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum)
+void ImageDescriptionInfo::wp_image_description_info_v1_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum)
{
mMinLuminance = min_lum / 10'000.0;
mMaxLuminance = max_lum;
mReferenceLuminance = reference_lum;
}
-void ImageDescriptionInfo::xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y)
+void ImageDescriptionInfo::wp_image_description_info_v1_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y)
{
- mTargetRed = QPointF(r_x, r_y) / 10'000.0;
- mTargetGreen = QPointF(g_x, g_y) / 10'000.0;
- mTargetBlue = QPointF(b_x, b_y) / 10'000.0;
- mTargetWhite = QPointF(w_x, w_y) / 10'000.0;
+ mTargetRed = QPointF(r_x, r_y) / 1'000'000.0;
+ mTargetGreen = QPointF(g_x, g_y) / 1'000'000.0;
+ mTargetBlue = QPointF(b_x, b_y) / 1'000'000.0;
+ mTargetWhite = QPointF(w_x, w_y) / 1'000'000.0;
}
-void ImageDescriptionInfo::xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum)
+void ImageDescriptionInfo::wp_image_description_info_v1_target_luminance(uint32_t min_lum, uint32_t max_lum)
{
mTargetMinLuminance = min_lum / 10'000.0;
mTargetMaxLuminance = max_lum;
}
-ImageDescription::ImageDescription(::xx_image_description_v4 *descr)
- : QtWayland::xx_image_description_v4(descr)
+ImageDescription::ImageDescription(::wp_image_description_v1 *descr)
+ : QtWayland::wp_image_description_v1(descr)
{
}
ImageDescription::~ImageDescription()
{
- xx_image_description_v4_destroy(object());
+ wp_image_description_v1_destroy(object());
}
-void ImageDescription::xx_image_description_v4_failed(uint32_t cause, const QString &msg)
+void ImageDescription::wp_image_description_v1_failed(uint32_t cause, const QString &msg)
{
Q_UNUSED(cause);
qCWarning(lcQpaWayland) << "image description failed!" << msg;
@@ -206,25 +212,26 @@ void ImageDescription::xx_image_description_v4_failed(uint32_t cause, const QStr
// maybe fall back to the previous or preferred image description
}
-void ImageDescription::xx_image_description_v4_ready(uint32_t identity)
+void ImageDescription::wp_image_description_v1_ready(uint32_t identity)
{
Q_UNUSED(identity);
Q_EMIT ready();
}
-ColorManagementFeedback::ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj)
- : QtWayland::xx_color_management_feedback_surface_v4(obj)
+ColorManagementFeedback::ColorManagementFeedback(::wp_color_management_surface_feedback_v1 *obj)
+ : QtWayland::wp_color_management_surface_feedback_v1(obj)
, mPreferred(std::make_unique<ImageDescription>(get_preferred()))
{
}
ColorManagementFeedback::~ColorManagementFeedback()
{
- xx_color_management_feedback_surface_v4_destroy(object());
+ wp_color_management_surface_feedback_v1_destroy(object());
}
-void ColorManagementFeedback::xx_color_management_feedback_surface_v4_preferred_changed()
+void ColorManagementFeedback::wp_color_management_surface_feedback_v1_preferred_changed(uint32_t identity)
{
+ Q_UNUSED(identity);
mPreferred = std::make_unique<ImageDescription>(get_preferred());
mPendingPreferredInfo = std::make_unique<ImageDescriptionInfo>(mPreferred.get());
connect(mPendingPreferredInfo.get(), &ImageDescriptionInfo::done, this, &ColorManagementFeedback::preferredChanged);
@@ -235,22 +242,22 @@ void ColorManagementFeedback::handlePreferredDone()
mPreferredInfo = std::move(mPendingPreferredInfo);
}
-ColorManagementSurface::ColorManagementSurface(::xx_color_management_surface_v4 *obj)
- : QtWayland::xx_color_management_surface_v4(obj)
+ColorManagementSurface::ColorManagementSurface(::wp_color_management_surface_v1 *obj)
+ : QtWayland::wp_color_management_surface_v1(obj)
{
}
ColorManagementSurface::~ColorManagementSurface()
{
- xx_color_management_surface_v4_destroy(object());
+ wp_color_management_surface_v1_destroy(object());
}
void ColorManagementSurface::setImageDescription(ImageDescription *descr)
{
if (descr)
- xx_color_management_surface_v4_set_image_description(object(), descr->object(), QtWayland::xx_color_manager_v4::render_intent::render_intent_perceptual);
+ wp_color_management_surface_v1_set_image_description(object(), descr->object(), QtWayland::wp_color_manager_v1::render_intent::render_intent_perceptual);
else
- xx_color_management_surface_v4_unset_image_description(object());
+ wp_color_management_surface_v1_unset_image_description(object());
}
}
diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h
index 8e44bd66b7b..04c3962ff8d 100644
--- a/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h
+++ b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h
@@ -20,7 +20,7 @@
#include <QColorSpace>
#include <QList>
-#include "qwayland-xx-color-management-v4.h"
+#include "qwayland-color-management-v1.h"
QT_BEGIN_NAMESPACE
@@ -28,7 +28,7 @@ namespace QtWaylandClient {
class ImageDescription;
-class ColorManager : public QObject, public QtWayland::xx_color_manager_v4
+class ColorManager : public QObject, public QtWayland::wp_color_manager_v1
{
Q_OBJECT
public:
@@ -48,22 +48,22 @@ public:
~ColorManager() override;
Features supportedFeatures() const;
- bool supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const;
- bool supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const;
+ bool supportsNamedPrimary(QtWayland::wp_color_manager_v1::primaries primaries) const;
+ bool supportsTransferFunction(QtWayland::wp_color_manager_v1::transfer_function transferFunction) const;
std::unique_ptr<ImageDescription> createImageDescription(const QColorSpace &colorspace);
private:
- void xx_color_manager_v4_supported_feature(uint32_t feature) override;
- void xx_color_manager_v4_supported_primaries_named(uint32_t primaries) override;
- void xx_color_manager_v4_supported_tf_named(uint32_t transferFunction) override;
+ void wp_color_manager_v1_supported_feature(uint32_t feature) override;
+ void wp_color_manager_v1_supported_primaries_named(uint32_t primaries) override;
+ void wp_color_manager_v1_supported_tf_named(uint32_t transferFunction) override;
Features mFeatures;
- QList<QtWayland::xx_color_manager_v4::primaries> mPrimaries;
- QList<QtWayland::xx_color_manager_v4::transfer_function> mTransferFunctions;
+ QList<QtWayland::wp_color_manager_v1::primaries> mPrimaries;
+ QList<QtWayland::wp_color_manager_v1::transfer_function> mTransferFunctions;
};
-class ImageDescriptionInfo : public QObject, public QtWayland::xx_image_description_info_v4
+class ImageDescriptionInfo : public QObject, public QtWayland::wp_image_description_info_v1
{
Q_OBJECT
public:
@@ -88,34 +88,34 @@ public:
double mTargetMaxLuminance;
private:
- void xx_image_description_info_v4_done() override;
- void xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size) override;
- void xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override;
- void xx_image_description_info_v4_tf_named(uint32_t transferFunction) override;
- void xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override;
- void xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override;
- void xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum) override;
+ void wp_image_description_info_v1_done() override;
+ void wp_image_description_info_v1_icc_file(int32_t icc, uint32_t icc_size) override;
+ void wp_image_description_info_v1_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override;
+ void wp_image_description_info_v1_tf_named(uint32_t transferFunction) override;
+ void wp_image_description_info_v1_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override;
+ void wp_image_description_info_v1_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override;
+ void wp_image_description_info_v1_target_luminance(uint32_t min_lum, uint32_t max_lum) override;
};
-class ImageDescription : public QObject, public QtWayland::xx_image_description_v4
+class ImageDescription : public QObject, public QtWayland::wp_image_description_v1
{
Q_OBJECT
public:
- explicit ImageDescription(::xx_image_description_v4 *descr);
+ explicit ImageDescription(::wp_image_description_v1 *descr);
~ImageDescription();
Q_SIGNAL void ready();
private:
- void xx_image_description_v4_failed(uint32_t cause, const QString &msg) override;
- void xx_image_description_v4_ready(uint32_t identity) override;
+ void wp_image_description_v1_failed(uint32_t cause, const QString &msg) override;
+ void wp_image_description_v1_ready(uint32_t identity) override;
};
-class ColorManagementFeedback : public QObject, public QtWayland::xx_color_management_feedback_surface_v4
+class ColorManagementFeedback : public QObject, public QtWayland::wp_color_management_surface_feedback_v1
{
Q_OBJECT
public:
- explicit ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj);
+ explicit ColorManagementFeedback(::wp_color_management_surface_feedback_v1 *obj);
~ColorManagementFeedback();
Q_SIGNAL void preferredChanged();
@@ -123,7 +123,7 @@ public:
std::unique_ptr<ImageDescriptionInfo> mPreferredInfo;
private:
- void xx_color_management_feedback_surface_v4_preferred_changed() override;
+ void wp_color_management_surface_feedback_v1_preferred_changed(uint32_t identity) override;
void handlePreferredDone();
std::unique_ptr<ImageDescription> mPreferred;
@@ -131,11 +131,11 @@ private:
};
-class ColorManagementSurface : public QObject, public QtWayland::xx_color_management_surface_v4
+class ColorManagementSurface : public QObject, public QtWayland::wp_color_management_surface_v1
{
Q_OBJECT
public:
- explicit ColorManagementSurface(::xx_color_management_surface_v4 *obj);
+ explicit ColorManagementSurface(::wp_color_management_surface_v1 *obj);
~ColorManagementSurface();
void setImageDescription(ImageDescription *descr);
diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
index 10bc2d5bfa2..1fc5e5c30a0 100644
--- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp
+++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
@@ -485,9 +485,13 @@ void QWaylandDisplay::reconnect()
connect(
this, &QWaylandDisplay::connected, this,
- [&allPlatformWindows] {
+ [this, &allPlatformWindows] {
for (auto &window : std::as_const(allPlatformWindows)) {
- window->initializeWlSurface();
+ window->initializeWlSurface(false);
+ }
+ forceRoundTrip(); // we need a roundtrip to receive the color space features the compositor supports
+ for (auto &window : std::as_const(allPlatformWindows)) {
+ window->initializeColorSpace();
}
},
Qt::SingleShotConnection);
@@ -797,10 +801,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
inputDevice->setDataControlDevice(mGlobals.dataControlManager->createDevice(inputDevice));
}
#endif
- } else if (interface == QLatin1String(QtWayland::xx_color_manager_v4::interface()->name)) {
+ } else if (interface == QLatin1String(QtWayland::wp_color_manager_v1::interface()->name)) {
mGlobals.colorManager = std::make_unique<ColorManager>(registry, id, 1);
- // we need a roundtrip to receive the features the compositor supports
- forceRoundTrip();
} else if (interface == QLatin1String(QtWayland::wp_pointer_warp_v1::interface()->name)) {
mGlobals.pointerWarp.reset(new WithDestructor<QtWayland::wp_pointer_warp_v1, wp_pointer_warp_v1_destroy>(
registry, id, 1));
diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
index 0ccc4dba57a..5ab285ad97d 100644
--- a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
+++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
@@ -192,10 +192,12 @@ void QWaylandInputContext::setFocusObject(QObject *object)
if (window && window->handle()) {
if (mCurrentWindow.data() != window) {
if (!inputMethodAccepted()) {
- auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
- if (surface)
- inputInterface->disableSurface(surface);
- mCurrentWindow.clear();
+ if (mCurrentWindow) {
+ auto *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->wlSurface();
+ if (surface)
+ inputInterface->disableSurface(surface);
+ mCurrentWindow.clear();
+ }
} else {
auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
if (surface) {
diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp
index b853db21529..fa70b53cbd0 100644
--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp
+++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp
@@ -229,7 +229,7 @@ void QWaylandShmBackingStore::endPaint()
// Inspired by QCALayerBackingStore.
bool QWaylandShmBackingStore::scroll(const QRegion &region, int dx, int dy)
{
- if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer))
+ if (Q_UNLIKELY(!mBackBuffer))
return false;
const qreal devicePixelRatio = waylandWindow()->scale();
@@ -241,6 +241,8 @@ bool QWaylandShmBackingStore::scroll(const QRegion &region, int dx, int dy)
return false;
recreateBackBufferIfNeeded();
+ if (!mFrontBuffer)
+ return false;
const QPoint scrollDelta(dx, dy);
const QMargins margins = windowDecorationMargins();
diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp
index be527b08f4d..0be22ff80e7 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow.cpp
+++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp
@@ -62,6 +62,7 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display)
mFrameCallbackTimeout = frameCallbackTimeout;
}
+ mSurfaceFormat.setColorSpace(QColorSpace{});
initializeWlSurface();
mFlags = window->flags();
@@ -214,7 +215,7 @@ void QWaylandWindow::setPendingImageDescription()
mColorManagementSurface->setImageDescription(mPendingImageDescription.get());
}
-void QWaylandWindow::initializeWlSurface()
+void QWaylandWindow::initializeWlSurface(bool colorSpace)
{
Q_ASSERT(!mSurface);
{
@@ -242,6 +243,13 @@ void QWaylandWindow::initializeWlSurface()
mViewport.reset(new QWaylandViewport(display()->createViewport(this)));
}
+ if (colorSpace) {
+ initializeColorSpace();
+ }
+}
+
+void QWaylandWindow::initializeColorSpace()
+{
QColorSpace requestedColorSpace = window()->requestedFormat().colorSpace();
if (requestedColorSpace != QColorSpace{} && mDisplay->colorManager()) {
// TODO try a similar (same primaries + supported transfer function) color space if this fails?
diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h
index d6b24d0569f..9e1bd92af30 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow_p.h
+++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h
@@ -247,7 +247,9 @@ public:
virtual void reinit();
void reset();
- void initializeWlSurface();
+ void initializeWlSurface(bool colorSpace = true);
+
+ void initializeColorSpace();
bool windowEvent(QEvent *event) override;
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index 1c3a3909bc2..15ab167c83f 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -179,9 +179,6 @@ QWindowsContextPrivate::QWindowsContextPrivate()
QWindowsContext::QWindowsContext() :
d(new QWindowsContextPrivate)
{
-#ifdef Q_CC_MSVC
-# pragma warning( disable : 4996 )
-#endif
m_instance = this;
}
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index 8e3ab67ced5..72daffb56b1 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -466,15 +466,17 @@ static bool applyBlurBehindWindow(HWND hwnd)
return result;
}
+static bool shouldShowTitlebarButton(Qt::WindowFlags flags, Qt::WindowFlags button)
+{
+ return !flags.testFlag(Qt::CustomizeWindowHint) || flags.testFlags(Qt::CustomizeWindowHint | button);
+}
+
// from qwidget_win.cpp, pass flags separately in case they have been "autofixed".
static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags)
{
- if ((flags & Qt::MSWindowsFixedSizeDialogHint) || !(flags & Qt::WindowMaximizeButtonHint))
- return false;
- // if the user explicitly asked for the maximize button, we try to add
- // it even if the window has fixed size.
- return (flags & Qt::CustomizeWindowHint) ||
- w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX);
+ return !flags.testFlag(Qt::MSWindowsFixedSizeDialogHint) &&
+ (shouldShowTitlebarButton(flags, Qt::WindowMaximizeButtonHint) ||
+ w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX));
}
bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags)
@@ -805,6 +807,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
if (topLevel) {
if ((type == Qt::Window || dialog || tool)) {
+ const bool defaultTitlebar = !flags.testFlag(Qt::CustomizeWindowHint);
if (!(flags & Qt::FramelessWindowHint)) {
style |= WS_POPUP;
if (flags & Qt::MSWindowsFixedSizeDialogHint) {
@@ -812,16 +815,16 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
} else {
style |= WS_THICKFRAME;
}
- if (flags & Qt::WindowTitleHint)
+ if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint))
style |= WS_CAPTION; // Contains WS_DLGFRAME
}
- if (flags & Qt::WindowSystemMenuHint)
+ if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowSystemMenuHint))
style |= WS_SYSMENU;
- else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) {
+ else if (dialog && (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint)) && !(flags & Qt::FramelessWindowHint)) {
style |= WS_SYSMENU | WS_BORDER; // QTBUG-2027, dialogs without system menu.
exStyle |= WS_EX_DLGMODALFRAME;
}
- const bool showMinimizeButton = flags & Qt::WindowMinimizeButtonHint;
+ const bool showMinimizeButton = shouldShowTitlebarButton(flags, Qt::WindowMinimizeButtonHint);
if (showMinimizeButton)
style |= WS_MINIMIZEBOX;
const bool showMaximizeButton = shouldShowMaximizeButton(w, flags);
diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
index 6ca4b120ccf..827cae9530b 100644
--- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt
+++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
@@ -74,12 +74,12 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_largefile
SQLITE_DISABLE_LFS
)
-qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_r
+qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_r AND NOT QT_FEATURE_system_sqlite
DEFINES
HAVE_LOCALTIME_R=1
)
-qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_s
+qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_s AND NOT QT_FEATURE_system_sqlite
DEFINES
HAVE_LOCALTIME_S=1
)
diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp
index 2944c02fd79..c8cd7c26f61 100644
--- a/src/plugins/styles/modernwindows/qwindows11style.cpp
+++ b/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -109,6 +109,7 @@ inline ControlState calcControlState(const QStyleOption *option)
#define More u"\uE712"_s
#define Help u"\uE897"_s
+#define Clear u"\uE894"_s
template <typename R, typename P, typename B>
static inline void drawRoundedRect(QPainter *p, R &&rect, P &&pen, B &&brush)
@@ -1720,9 +1721,10 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
}
case CE_ItemViewItem: {
if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
- QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
- QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
- QRect textRect = proxy()->subElementRect(SE_ItemViewItemText, vopt, widget);
+ const auto p = proxy();
+ QRect checkRect = p->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
+ QRect iconRect = p->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
+ QRect textRect = p->subElementRect(SE_ItemViewItemText, vopt, widget);
// draw the background
proxy()->drawPrimitive(PE_PanelItemViewItem, option, painter, widget);
@@ -1823,16 +1825,17 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
d->viewItemDrawText(painter, vopt, textRect);
// paint a vertical marker for QListView
- if (vopt->state & State_Selected) {
+ if (vopt->state & State_Selected && !highContrastTheme) {
if (const QListView *lv = qobject_cast<const QListView *>(widget);
- lv && lv->viewMode() != QListView::IconMode && !highContrastTheme) {
- painter->setPen(vopt->palette.accent().color());
- const auto xPos = isRtl ? rect.right() - 1 : rect.left();
- const QLineF lines[2] = {
- QLineF(xPos, rect.y() + 2, xPos, rect.y() + rect.height() - 2),
- QLineF(xPos + 1, rect.y() + 2, xPos + 1, rect.y() + rect.height() - 2),
- };
- painter->drawLines(lines, 2);
+ lv && lv->viewMode() != QListView::IconMode) {
+ const auto col = vopt->palette.accent().color();
+ painter->setBrush(col);
+ painter->setPen(col);
+ const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f;
+ const auto yOfs = rect.height() / 4.;
+ QRectF r(QPointF(xPos, rect.y() + yOfs),
+ QPointF(xPos + 1, rect.y() + rect.height() - yOfs));
+ painter->drawRoundedRect(r, 1, 1);
}
}
}
@@ -1872,7 +1875,7 @@ QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOp
case QStyle::SE_RadioButtonIndicator:
case QStyle::SE_CheckBoxIndicator:
ret = QWindowsVistaStyle::subElementRect(element, option, widget);
- ret.moveLeft(contentItemHMargin);
+ ret.moveLeft(ret.left() + contentItemHMargin);
break;
case QStyle::SE_ComboBoxFocusRect:
case QStyle::SE_CheckBoxFocusRect:
@@ -1883,22 +1886,29 @@ QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOp
case QStyle::SE_LineEditContents:
ret = option->rect.adjusted(4,0,-4,0);
break;
- case QStyle::SE_ItemViewItemText:
- if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
- const int decorationOffset = item->features.testFlag(QStyleOptionViewItem::HasDecoration) ? item->decorationSize.width() : 0;
- const int checkboxOffset = item->features.testFlag(QStyleOptionViewItem::HasCheckIndicator) ? 16 : 0;
- if (widget && qobject_cast<QComboBoxPrivateContainer *>(widget->parentWidget())) {
- if (option->direction == Qt::LeftToRight)
- ret = option->rect.adjusted(decorationOffset + checkboxOffset + 5, 0, -5, 0);
- else
- ret = option->rect.adjusted(5, 0, decorationOffset - checkboxOffset - 5, 0);
+ case SE_ItemViewItemCheckIndicator:
+ case SE_ItemViewItemDecoration:
+ case SE_ItemViewItemText: {
+ ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ if (!ret.isValid() || highContrastTheme)
+ return ret;
+
+ if (const QListView *lv = qobject_cast<const QListView *>(widget);
+ lv && lv->viewMode() != QListView::IconMode) {
+ const int xOfs = contentHMargin;
+ const bool isRtl = option->direction == Qt::RightToLeft;
+ if (isRtl) {
+ ret.moveRight(ret.right() - xOfs);
+ if (ret.left() < option->rect.left())
+ ret.setLeft(option->rect.left());
} else {
- ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ ret.moveLeft(ret.left() + xOfs);
+ if (ret.right() > option->rect.right())
+ ret.setRight(option->rect.right());
}
- } else {
- ret = QWindowsVistaStyle::subElementRect(element, option, widget);
}
break;
+ }
#if QT_CONFIG(progressbar)
case SE_ProgressBarGroove:
case SE_ProgressBarContents:
@@ -2091,6 +2101,19 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption
}
break;
}
+#if QT_CONFIG(groupbox)
+ case CC_GroupBox: {
+ ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
+ switch (subControl) {
+ case SC_GroupBoxCheckBox:
+ ret.moveTop(1);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+#endif // QT_CONFIG(groupbox)
default:
ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
}
@@ -2234,6 +2257,25 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
contentSize.rwidth() += 2 * contentHMargin - oldMargin;
break;
}
+ case CT_ItemViewItem: {
+ if (const auto *viewItemOpt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ if (const QListView *lv = qobject_cast<const QListView *>(widget);
+ lv && lv->viewMode() != QListView::IconMode) {
+ QStyleOptionViewItem vOpt(*viewItemOpt);
+ // viewItemSize only takes PM_FocusFrameHMargin into account but no additional
+ // margin, therefore adjust it here for a correct width during layouting when
+ // WrapText is enabled
+ vOpt.rect.setRight(vOpt.rect.right() - contentHMargin);
+ contentSize = QWindowsVistaStyle::sizeFromContents(type, &vOpt, size, widget);
+ contentSize.rwidth() += contentHMargin;
+ contentSize.rheight() += 2 * contentHMargin;
+
+ } else {
+ contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
+ }
+ }
+ break;
+ }
default:
contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
break;
@@ -2544,6 +2586,7 @@ void QWindows11Style::polish(QPalette& result)
d->m_titleBarCloseIcon = QIcon();
d->m_titleBarNormalIcon = QIcon();
d->m_toolbarExtensionButton = QIcon();
+ d->m_lineEditClearButton = QIcon();
}
QPixmap QWindows11Style::standardPixmap(StandardPixmap standardPixmap,
@@ -2568,10 +2611,17 @@ QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon,
{
auto *d = const_cast<QWindows11StylePrivate*>(d_func());
switch (standardIcon) {
+ case SP_LineEditClearButton: {
+ if (d->m_lineEditClearButton.isNull()) {
+ auto e = new WinFontIconEngine(Clear.at(0), d->assetFont);
+ d->m_lineEditClearButton = QIcon(e);
+ }
+ return d->m_lineEditClearButton;
+ }
case SP_ToolBarHorizontalExtensionButton:
case SP_ToolBarVerticalExtensionButton: {
if (d->m_toolbarExtensionButton.isNull()) {
- auto e = new WinFontIconEngine(More.at(0), d->assetFont);
+ auto e = new WinFontIconEngine(More, d->assetFont);
e->setScale(1.0);
d->m_toolbarExtensionButton = QIcon(e);
}
diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h
index 736caae956c..a51a93ddd9b 100644
--- a/src/plugins/styles/modernwindows/qwindows11style_p.h
+++ b/src/plugins/styles/modernwindows/qwindows11style_p.h
@@ -123,6 +123,7 @@ class QWindows11StylePrivate : public QWindowsVistaStylePrivate {
protected:
QIcon m_toolbarExtensionButton;
+ QIcon m_lineEditClearButton;
};
QT_END_NAMESPACE
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
index 22ca18b10bf..7389635108b 100644
--- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
@@ -1716,7 +1716,9 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt
QWindowsThemeData popupbackgroundTheme(widget, painter, QWindowsVistaStylePrivate::MenuTheme,
MENU_POPUPBACKGROUND, stateId, option->rect);
d->drawBackground(popupbackgroundTheme);
+ return;
}
+ break;
case PE_PanelMenuBar:
break;
@@ -4959,8 +4961,7 @@ QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon,
return QWindowsStyle::standardIcon(standardIcon, option, widget);
}
-
-WinFontIconEngine::WinFontIconEngine(const QChar &glyph, const QFont &font)
+WinFontIconEngine::WinFontIconEngine(const QString &glyph, const QFont &font)
: QFontIconEngine({}, font)
, m_font(font)
, m_glyph(glyph)
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
index cf982ceb133..23b34547faa 100644
--- a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
@@ -184,7 +184,7 @@ private:
class WinFontIconEngine : public QFontIconEngine
{
public:
- WinFontIconEngine(const QChar &glyph, const QFont &font);
+ WinFontIconEngine(const QString &glyph, const QFont &font);
QString key() const override;
QIconEngine *clone() const override;
@@ -194,7 +194,7 @@ public:
protected:
QFont m_font;
- QChar m_glyph;
+ QString m_glyph;
double m_scale = 0.7;
};
diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp
index 784e69d2486..a98f5da2389 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -410,7 +410,7 @@ public:
static QMetaMethod findMethod(const QObject *obj, const char *signature);
private:
- bool invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const;
+ void invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const;
void invokeTestOnData(int index) const;
QMetaMethod m_initTestCaseMethod; // might not exist, check isValid().
@@ -1315,11 +1315,8 @@ static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView t
Call slot_data(), init(), slot(), cleanup(), init(), slot(), cleanup(), ...
If data is set then it is the only test that is performed
-
- If the function was successfully called, true is returned, otherwise
- false.
*/
-bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const
+void TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const
{
QBenchmarkTestMethodData benchmarkData;
QBenchmarkTestMethodData::current = &benchmarkData;
@@ -1422,8 +1419,6 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<Wat
QTestResult::finishedCurrentTestFunction();
QTestResult::setSkipCurrentTest(false);
QTestResult::setBlacklistCurrentTest(false);
-
- return true;
}
void *fetchData(QTestData *data, const char *tagName, int typeId)
@@ -1743,10 +1738,8 @@ void TestMethods::invokeTests(QObject *testObject) const
const char *data = nullptr;
if (i < QTest::testTags.size() && !QTest::testTags.at(i).isEmpty())
data = qstrdup(QTest::testTags.at(i).toLatin1().constData());
- const bool ok = invokeTest(i, QLatin1StringView(data), watchDog);
+ invokeTest(i, QLatin1StringView(data), watchDog);
delete [] data;
- if (!ok)
- break;
}
}
diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp
index fc969e17380..7e3dfbd68db 100644
--- a/src/widgets/accessible/itemviews.cpp
+++ b/src/widgets/accessible/itemviews.cpp
@@ -60,7 +60,7 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const
}
QAccessibleTable::QAccessibleTable(QWidget *w)
- : QAccessibleObject(w)
+ : QAccessibleWidgetV2(w)
{
Q_ASSERT(view());
@@ -589,13 +589,6 @@ int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const
return -1;
}
-QString QAccessibleTable::text(QAccessible::Text t) const
-{
- if (t == QAccessible::Description)
- return view()->accessibleDescription();
- return view()->accessibleName();
-}
-
QRect QAccessibleTable::rect() const
{
if (!view()->isVisible())
@@ -677,7 +670,7 @@ void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t)
return static_cast<QAccessibleSelectionInterface*>(this);
if (t == QAccessible::TableInterface)
return static_cast<QAccessibleTableInterface*>(this);
- return nullptr;
+ return QAccessibleWidgetV2::interface_cast(t);
}
void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event)
diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h
index 9b30f36ced3..e74be151115 100644
--- a/src/widgets/accessible/itemviews_p.h
+++ b/src/widgets/accessible/itemviews_p.h
@@ -31,7 +31,9 @@ QT_BEGIN_NAMESPACE
class QAccessibleTableCell;
class QAccessibleTableHeaderCell;
-class QAccessibleTable :public QAccessibleTableInterface, public QAccessibleSelectionInterface, public QAccessibleObject
+class QAccessibleTable : public QAccessibleTableInterface,
+ public QAccessibleSelectionInterface,
+ public QAccessibleWidgetV2
{
public:
explicit QAccessibleTable(QWidget *w);
@@ -39,7 +41,6 @@ public:
QAccessible::Role role() const override;
QAccessible::State state() const override;
- QString text(QAccessible::Text t) const override;
QRect rect() const override;
QAccessibleInterface *childAt(int x, int y) const override;
diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp
index 1ed9dd06e3c..11cbbabe25a 100644
--- a/src/widgets/dialogs/qfiledialog.cpp
+++ b/src/widgets/dialogs/qfiledialog.cpp
@@ -3764,6 +3764,9 @@ void QFileDialogPrivate::enterDirectory(const QModelIndex &index)
QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index;
QString path = sourceIndex.data(QFileSystemModel::FilePathRole).toString();
if (path.isEmpty() || model->isDir(sourceIndex)) {
+ if (q->directory().path() == path)
+ return;
+
const QFileDialog::FileMode fileMode = q->fileMode();
q->setDirectory(path);
emit q->directoryEntered(path);
diff --git a/src/widgets/dialogs/qfontdialog.cpp b/src/widgets/dialogs/qfontdialog.cpp
index 870b0c40faf..c4f8af1a639 100644
--- a/src/widgets/dialogs/qfontdialog.cpp
+++ b/src/widgets/dialogs/qfontdialog.cpp
@@ -172,7 +172,7 @@ void QFontDialogPrivate::init()
sizeAccel = new QLabel(q);
#ifndef QT_NO_SHORTCUT
- sizeAccel->setBuddy(sizeEdit);
+ sizeAccel->setBuddy(sizeList);
#endif
sizeAccel->setIndent(2);
diff --git a/src/widgets/dialogs/qsidebar.cpp b/src/widgets/dialogs/qsidebar.cpp
index c8498bc56bf..a357d34f327 100644
--- a/src/widgets/dialogs/qsidebar.cpp
+++ b/src/widgets/dialogs/qsidebar.cpp
@@ -413,7 +413,9 @@ void QSidebar::selectUrl(const QUrl &url)
selectionModel()->clear();
for (int i = 0; i < model()->rowCount(); ++i) {
if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) {
- selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
+ emit goToUrl(url);
+ selectionModel()->setCurrentIndex(model()->index(i, 0),
+ QItemSelectionModel::SelectCurrent);
break;
}
}
@@ -468,7 +470,6 @@ void QSidebar::removeEntry()
void QSidebar::clicked(const QModelIndex &index)
{
QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl();
- emit goToUrl(url);
selectUrl(url);
}
diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp
index 00f9766af29..5ba92714f6c 100644
--- a/src/widgets/kernel/qlayout.cpp
+++ b/src/widgets/kernel/qlayout.cpp
@@ -345,6 +345,17 @@ void QLayout::getContentsMargins(int *left, int *top, int *right, int *bottom) c
}
/*!
+ \property QLayout::contentsMargins
+ \since 4.6
+ \brief the margins used around the layout
+
+ By default, QLayout uses the values provided by the style. On
+ most platforms, the margin is 11 pixels in all directions.
+
+ \sa setContentsMargins(), getContentsMargins()
+*/
+
+/*!
\since 4.6
Returns the margins used around the layout.
diff --git a/src/widgets/styles/qfusionstyle.cpp b/src/widgets/styles/qfusionstyle.cpp
index 33777933495..ae5894661a2 100644
--- a/src/widgets/styles/qfusionstyle.cpp
+++ b/src/widgets/styles/qfusionstyle.cpp
@@ -2984,8 +2984,6 @@ void QFusionStyle::polish(QWidget *widget)
#if QT_CONFIG(spinbox)
|| qobject_cast<QAbstractSpinBox *>(widget)
#endif
- || (widget->inherits("QDockSeparator"))
- || (widget->inherits("QDockWidgetSeparator"))
) {
widget->setAttribute(Qt::WA_Hover, true);
widget->setAttribute(Qt::WA_OpaquePaintEvent, false);
@@ -3028,8 +3026,6 @@ void QFusionStyle::unpolish(QWidget *widget)
#if QT_CONFIG(spinbox)
|| qobject_cast<QAbstractSpinBox *>(widget)
#endif
- || (widget->inherits("QDockSeparator"))
- || (widget->inherits("QDockWidgetSeparator"))
) {
widget->setAttribute(Qt::WA_Hover, false);
}
diff --git a/src/widgets/styles/qstylesheetstyle_default.cpp b/src/widgets/styles/qstylesheetstyle_default.cpp
index 73e7c9524d6..ae6b10560fe 100644
--- a/src/widgets/styles/qstylesheetstyle_default.cpp
+++ b/src/widgets/styles/qstylesheetstyle_default.cpp
@@ -306,7 +306,7 @@ StyleSheet QStyleSheetStyle::getDefaultStyleSheet() const
{
-qt-background-role: button;
}*/
- if (baseStyle()->inherits("QPlastiqueStyle") || baseStyle()->inherits("QCleanlooksStyle") || baseStyle()->inherits("QFusionStyle"))
+ if (baseStyle()->inherits("QFusionStyle"))
{
SET_ELEMENT_NAME("QComboBox"_L1);
ADD_ATTRIBUTE_SELECTOR("readOnly"_L1, "true"_L1, AttributeSelector::MatchEqual);
diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp
index b9143a59ee7..bea7593ef77 100644
--- a/src/widgets/styles/qwindowsstyle.cpp
+++ b/src/widgets/styles/qwindowsstyle.cpp
@@ -857,6 +857,7 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt,
proxy()->drawPrimitive(PE_PanelMenu, &copy, p, w);
break;
}
+ Q_FALLTHROUGH();
case PE_FrameMenu:
if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
if (frame->lineWidth == 2 || pe == PE_Frame) {
@@ -911,7 +912,10 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt,
const QBrush menuBackground = opt->palette.base().color();
QColor borderColor = opt->palette.window().color();
qDrawPlainRect(p, opt->rect, borderColor, 1, &menuBackground);
+ } else {
+ QCommonStyle::drawPrimitive(pe, opt, p, w);
}
+ break;
case PE_FrameWindow: {
QPalette popupPal = opt->palette;
popupPal.setColor(QPalette::Light, opt->palette.window().color());
diff --git a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
index d0ef37d817f..e9578d7246a 100644
--- a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
+++ b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
@@ -1,3 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(${RunCMake_TEST} LANGUAGES CXX)
-include(${RunCMake_TEST}.cmake)
+
+# To allow including the gen files in other subdirectory projects.
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+include(${SBOM_INCLUDE_FILE}.cmake)
diff --git a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
index 3103e651093..81e83ccead3 100644
--- a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
@@ -1,27 +1,68 @@
include(QtRunCMake)
-function(run_cmake_and_build case)
+function(run_cmake_and_build case format_case)
+ set(include_file "${case}")
+ set(case "${format_case}-${case}")
+
# Set common build directory for configure and build
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
set(options
"-DQt6_DIR=${Qt6_DIR}"
"-DCMAKE_INSTALL_PREFIX=${RunCMake_TEST_BINARY_DIR}/installed"
+ "-DSBOM_INCLUDE_FILE=${include_file}"
+ "-DFORMAT_CASE=${format_case}"
)
+ if(format_case STREQUAL "spdx23")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=ON
+ -DQT_SBOM_GENERATE_CYDX_V1_6=OFF
+ )
+ elseif(format_case STREQUAL "cydx16")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=OFF
+ -DQT_SBOM_GENERATE_CYDX_V1_6=ON
+ )
+ elseif(format_case STREQUAL "all")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=ON
+ -DQT_SBOM_GENERATE_CYDX_V1_6=ON
+ )
+ elseif(format_case STREQUAL "none")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=OFF
+ )
+ endif()
+
+ # Check CI environment variables for SBOM options to ensure we only enabled checks that
+ # require additional dependencies on machines that actually have them.
+ # Also allow force enabling all checks via QT_SBOM_FORCE_ALL_CHECKS env var.
set(maybe_sbom_env_args "$ENV{SBOM_COMMON_ARGS}")
+ set(force_all_checks "$ENV{QT_SBOM_FORCE_ALL_CHECKS}")
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_DEFAULT_CHECKS=ON")
endif()
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT=ON")
endif()
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON")
endif()
+ if(maybe_sbom_env_args MATCHES "QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON"
+ OR force_all_checks)
+ list(APPEND options "-DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON")
+ endif()
+
# Need to pass the python interpreter paths, to avoid sbom2doc not found errors.
# This mirrors what coin/instructions/prepare_building_env.yaml does.
set(maybe_python3_path "$ENV{PYTHON3_PATH}")
@@ -42,9 +83,17 @@ function(run_cmake_and_build case)
# fine.
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
+
+ # Check the sbom files are present after installation.
+ set(RunCMake-check-file "check.cmake")
run_cmake_command(${case}-install ${CMAKE_COMMAND} --install .)
+ unset(RunCMake-check-file)
endfunction()
-run_cmake_and_build(minimal)
-run_cmake_and_build(full)
-run_cmake_and_build(versions)
+set(format_cases spdx23 cydx16 all none)
+foreach(format_case IN LISTS format_cases)
+ run_cmake_and_build(minimal "${format_case}")
+ run_cmake_and_build(full "${format_case}")
+ run_cmake_and_build(versions "${format_case}")
+endforeach()
+
diff --git a/tests/auto/cmake/RunCMake/Sbom/check.cmake b/tests/auto/cmake/RunCMake/Sbom/check.cmake
new file mode 100644
index 00000000000..9e143e35ab2
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/check.cmake
@@ -0,0 +1,63 @@
+function(check_exists file)
+ if(NOT EXISTS "${file}")
+ get_filename_component(file_dir "${file}" DIRECTORY)
+ file(GLOB dir_contents "${file_dir}/*")
+ string(APPEND RunCMake_TEST_FAILED "${file} does not exist\n. "
+ "Contents of directory ${file_dir}:\n ${dir_contents}\n")
+ endif()
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+function(check_not_exists file)
+ if(EXISTS "${file}")
+ get_filename_component(file_dir "${file}" DIRECTORY)
+ file(GLOB dir_contents "${file_dir}/*")
+ string(APPEND RunCMake_TEST_FAILED "${file} exists\n. "
+ "Contents of directory ${file_dir}:\n ${dir_contents}\n")
+ endif()
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+# Check that the correct option values are used for the project root sbom.
+set(root_result_file "${RunCMake_TEST_BINARY_DIR}/result.cmake")
+if(EXISTS "${root_result_file}")
+ include("${root_result_file}")
+
+ if(NOT "${ORIGINAL_QT_GENERATE_SBOM}" STREQUAL "${RESULT_QT_GENERATE_SBOM}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_GENERATE_SBOM is ${RESULT_QT_GENERATE_SBOM}, expected ${ORIGINAL_QT_GENERATE_SBOM} \n")
+ endif()
+
+ if(NOT "${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2}" STREQUAL "${RESULT_QT_SBOM_GENERATE_SPDX_V2}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_SBOM_GENERATE_SPDX_V2 is ${RESULT_QT_SBOM_GENERATE_SPDX_V2}, "
+ "expected ${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2} \n")
+ endif()
+
+ if(NOT "${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6}" STREQUAL "${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_SBOM_GENERATE_CYDX_V1_6 is ${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}, "
+ "expected ${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6} \n")
+ endif()
+endif()
+
+
+# Glob for all result.cmake files recursively in the root of the test binary dir, and run checks
+# for each of them.
+file(GLOB_RECURSE result_files
+ "${RunCMake_TEST_BINARY_DIR}/**/result.cmake"
+)
+
+# Confirm that the all subproject sbom files are installed, including the root one.
+foreach(result_file IN LISTS result_files)
+ include("${result_file}")
+
+ foreach(sbom_doc IN LISTS SBOM_DOCUMENTS)
+ check_exists("${sbom_doc}")
+ endforeach()
+
+ foreach(sbom_doc IN LISTS NO_SBOM_DOCUMENTS)
+ check_not_exists("${sbom_doc}")
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake
new file mode 100644
index 00000000000..a892fee2082
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake
@@ -0,0 +1,67 @@
+if(NOT SBOM_PROJECT_NAME)
+ set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+endif()
+# Convert to lower case, otherwise on case-sensitive filesystems the generated
+# filenames may not match the expected ones.
+string(TOLOWER "${SBOM_PROJECT_NAME}" SBOM_PROJECT_NAME)
+
+if(NOT SBOM_VERSION)
+ set(SBOM_VERSION "1.0.0")
+endif()
+
+if(NOT SBOM_INSTALL_DIR)
+ set(SBOM_INSTALL_DIR "sbom")
+endif()
+
+set(sbom_document_base_name "${SBOM_PROJECT_NAME}-${SBOM_VERSION}")
+set(sbom_install_dir "${CMAKE_BINARY_DIR}/installed/${SBOM_INSTALL_DIR}")
+
+set(spdx_file "${sbom_install_dir}/${sbom_document_base_name}.spdx")
+set(spdx_json_file "${sbom_install_dir}/${sbom_document_base_name}.spdx.json")
+set(cydx_file "${sbom_install_dir}/${sbom_document_base_name}.cdx.json")
+
+set(sbom_documents "")
+set(no_sbom_documents "")
+
+if(FORMAT_CASE STREQUAL "spdx23" OR FORMAT_CASE STREQUAL "all")
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ list(APPEND sbom_documents "${spdx_file}")
+ else()
+ list(APPEND no_sbom_documents "${spdx_file}")
+ endif()
+
+ if(QT_SBOM_GENERATE_SPDX_V2_JSON)
+ list(APPEND sbom_documents "${spdx_json_file}")
+ else()
+ list(APPEND no_sbom_documents "${spdx_json_file}")
+ endif()
+endif()
+
+if(FORMAT_CASE STREQUAL "cydx16" OR FORMAT_CASE STREQUAL "all")
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ list(APPEND sbom_documents "${cydx_file}")
+ else()
+ list(APPEND no_sbom_documents "${cydx_file}")
+ endif()
+endif()
+
+if(FORMAT_CASE STREQUAL "none")
+ set(no_sbom_documents ${spdx_file} ${spdx_json_file} ${cydx_file})
+ set(sbom_documents "")
+endif()
+
+# These values will be used by the check.cmake script after installation.
+file(GENERATE
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/result.cmake"
+ CONTENT
+ "
+set(SBOM_DOCUMENTS \"${sbom_documents}\")
+set(NO_SBOM_DOCUMENTS \"${no_sbom_documents}\")
+set(ORIGINAL_QT_GENERATE_SBOM \"${original_QT_GENERATE_SBOM}\")
+set(ORIGINAL_QT_SBOM_GENERATE_SPDX_V2 \"${original_QT_SBOM_GENERATE_SPDX_V2}\")
+set(ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6 \"${original_QT_SBOM_GENERATE_CYDX_V1_6}\")
+set(RESULT_QT_GENERATE_SBOM \"${QT_GENERATE_SBOM}\")
+set(RESULT_QT_SBOM_GENERATE_SPDX_V2 \"${QT_SBOM_GENERATE_SPDX_V2}\")
+set(RESULT_QT_SBOM_GENERATE_CYDX_V1_6 \"${QT_SBOM_GENERATE_CYDX_V1_6}\")
+"
+)
diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake
new file mode 100644
index 00000000000..11ec50d76d6
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake
@@ -0,0 +1,12 @@
+# Record the sbom option values before they might be modified by an sbom_setup call, due to
+# missing python dependencies.
+set(original_QT_GENERATE_SBOM "${QT_GENERATE_SBOM}")
+set(original_QT_SBOM_GENERATE_SPDX_V2 "${QT_SBOM_GENERATE_SPDX_V2}")
+set(original_QT_SBOM_GENERATE_CYDX_V1_6 "${QT_SBOM_GENERATE_CYDX_V1_6}")
+
+# Explicitly set these because none case only has QT_GENERATE_SBOM passed as OFF.
+# In this case, the defaults for the formats is to remain ON.
+if(NOT QT_GENERATE_SBOM)
+ set(original_QT_SBOM_GENERATE_SPDX_V2 ON)
+ set(original_QT_SBOM_GENERATE_CYDX_V1_6 ON)
+endif()
diff --git a/tests/auto/cmake/RunCMake/Sbom/full.cmake b/tests/auto/cmake/RunCMake/Sbom/full.cmake
index 7241b8e77a8..b5ac77c3ccc 100644
--- a/tests/auto/cmake/RunCMake/Sbom/full.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/full.cmake
@@ -1,23 +1,30 @@
# Needed to make the sbom functions available.
find_package(Qt6 REQUIRED Core)
+include(CommonResultGenIntro)
+
_qt_internal_setup_sbom(
GENERATE_SBOM_DEFAULT "TRUE"
)
set(IS_FULL_BUILD "TRUE")
+# These are used by common_result_gen.cmake.
+set(SBOM_VERSION "2.0.0")
+set(SBOM_INSTALL_DIR "sbom_full")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}ProjectFull")
+
_qt_internal_sbom_begin_project(
- SBOM_PROJECT_NAME "${PROJECT_NAME}Project"
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
SUPPLIER "QtProjectTest"
SUPPLIER_URL "https://qt-project.org/SbomTest"
LICENSE_EXPRESSION "LGPL-3.0-only"
COPYRIGHTS "2025 The Qt Company Ltd."
INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
- INSTALL_SBOM_DIR "sbom"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
DOWNLOAD_LOCATION "https://download.qt.io/sbom"
CPE "cpe:2.3:a:qt:qtprojecttest:1.0.0:*:*:*:*:*:*:*"
- VERSION "1.0.0"
+ VERSION "${SBOM_VERSION}"
DOCUMENT_CREATOR_TOOL "Test Build System Tool"
LICENSE_DIR_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/custom_licenses"
)
@@ -26,3 +33,7 @@ include(common_targets.cmake)
_qt_internal_sbom_end_project()
+include(CommonResultGen)
+
+# Also create separate sboms for some sibling projects under this subdir.
+add_subdirectory(subprojects)
diff --git a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
index 4422314b2d5..27dfc0a3b12 100644
--- a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
@@ -1,17 +1,26 @@
# Needed to make the sbom functions available.
find_package(Qt6 REQUIRED Core)
+include(CommonResultGenIntro)
+
_qt_internal_setup_sbom(
GENERATE_SBOM_DEFAULT "TRUE"
)
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.0")
+
_qt_internal_sbom_begin_project(
SUPPLIER "QtProjectTest"
SUPPLIER_URL "https://qt-project.org/SbomTest"
- VERSION "1.0.0"
+ VERSION "${SBOM_VERSION}"
)
include(common_targets.cmake)
_qt_internal_sbom_end_project()
+include(CommonResultGen)
+
+# Also create separate sboms for some sibling projects under this subdir.
+add_subdirectory(subprojects)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt
new file mode 100644
index 00000000000..0d9e12534df
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt
@@ -0,0 +1,5 @@
+# These subprojects look up the same system library dependency separately in each subdirectory
+# scope to ensure that we simulate the same scenario as in a top-level qt5.git build, so that
+# we try to reuse the same system library entity in multiple sboms.
+add_subdirectory(subproj1)
+add_subdirectory(subproj2)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt
new file mode 100644
index 00000000000..a59908d8889
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt
@@ -0,0 +1,51 @@
+project(subproj1)
+
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.1")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+
+_qt_internal_sbom_begin_project(
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
+ SUPPLIER "QtProjectTest"
+ SUPPLIER_URL "https://qt-project.org/SbomTest"
+ VERSION "${SBOM_VERSION}"
+)
+
+add_library(subproj1_helper STATIC)
+target_sources(subproj1_helper PRIVATE subproj1_helper.cpp)
+target_link_libraries(subproj1_helper)
+install(TARGETS subproj1_helper
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+)
+_qt_internal_add_sbom(subproj1_helper
+ TYPE "LIBRARY"
+ RUNTIME_PATH bin
+ ARCHIVE_PATH lib
+ LIBRARY_PATH lib
+)
+
+
+# This will actually refer to the root project Threads, that gets brought in via Qt6::Core
+# dependency.
+find_package(Threads)
+if(TARGET Threads::Threads)
+ _qt_internal_add_sbom(Threads::Threads
+ TYPE SYSTEM_LIBRARY
+ )
+ target_link_libraries(subproj1_helper PRIVATE Threads::Threads)
+endif()
+
+# Create another IMPORTED target that is meant to simulate a system library, so we don't refer to
+# a target that exists in the root scope.
+add_library(FancySystemLib IMPORTED INTERFACE)
+_qt_internal_add_sbom(FancySystemLib
+ TYPE SYSTEM_LIBRARY
+)
+target_link_libraries(subproj1_helper PRIVATE FancySystemLib)
+
+_qt_internal_sbom_end_project()
+
+include(CommonResultGen)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp
new file mode 100644
index 00000000000..f6b17d4b9f8
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp
@@ -0,0 +1,4 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+int func() { return 0; };
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt
new file mode 100644
index 00000000000..0a408ddf47f
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt
@@ -0,0 +1,46 @@
+project(subproj2)
+
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.2")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+
+_qt_internal_sbom_begin_project(
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
+ SUPPLIER "QtProjectTest"
+ SUPPLIER_URL "https://qt-project.org/SbomTest"
+ VERSION "${SBOM_VERSION}"
+)
+
+add_library(subproj2_helper STATIC)
+target_sources(subproj2_helper PRIVATE subproj2_helper.cpp)
+target_link_libraries(subproj2_helper)
+install(TARGETS subproj2_helper
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+)
+_qt_internal_add_sbom(subproj2_helper
+ TYPE "LIBRARY"
+ RUNTIME_PATH bin
+ ARCHIVE_PATH lib
+ LIBRARY_PATH lib
+)
+
+find_package(Threads)
+if(TARGET Threads::Threads)
+ _qt_internal_add_sbom(Threads::Threads
+ TYPE SYSTEM_LIBRARY
+ )
+ target_link_libraries(subproj2_helper PRIVATE Threads::Threads)
+endif()
+
+add_library(FancySystemLib IMPORTED INTERFACE)
+_qt_internal_add_sbom(FancySystemLib
+ TYPE SYSTEM_LIBRARY
+)
+target_link_libraries(subproj2_helper PRIVATE FancySystemLib)
+
+_qt_internal_sbom_end_project()
+
+include(CommonResultGen)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp
new file mode 100644
index 00000000000..f6b17d4b9f8
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp
@@ -0,0 +1,4 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+int func() { return 0; };
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
index e713901101c..721d3d3af98 100644
--- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
+++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
@@ -432,6 +432,9 @@ private slots:
void get_QTransform() { get_impl(QTransform{1, 2, 3, 4, 5, 6, 7, 8, 9}); } // too large
void get_NonDefaultConstructible();
+ void reference();
+ void pointer();
+
private:
using StdVariant = std::variant<std::monostate,
// list here all the types with which we instantiate getIf_impl:
@@ -6213,6 +6216,73 @@ void tst_QVariant::get_NonDefaultConstructible()
get_impl(NonDefaultConstructible{42});
}
+struct QVariantWrapper
+{
+public:
+ static constexpr bool canNoexceptConvertToQVariant
+ = std::is_nothrow_copy_constructible_v<QVariant>;
+ static constexpr bool canNoexceptAssignQVariant
+ = std::is_nothrow_copy_assignable_v<QVariant>;
+
+ QVariantWrapper(QVariant *content = nullptr) noexcept : m_content(content) {}
+
+ QVariant content() const noexcept(canNoexceptConvertToQVariant) { return *m_content; }
+ void setContent(const QVariant &content) noexcept(canNoexceptAssignQVariant)
+ {
+ *m_content = content;
+ }
+
+private:
+ QVariant *m_content = nullptr;
+};
+
+QT_BEGIN_NAMESPACE
+template<>
+QVariant::ConstReference<QVariantWrapper>::operator QVariant() const
+ noexcept(QVariantWrapper::canNoexceptConvertToQVariant)
+{
+ return m_referred.content();
+}
+
+template<>
+QVariant::Reference<QVariantWrapper> &QVariant::Reference<QVariantWrapper>::operator=(
+ const QVariant &content) noexcept(QVariantWrapper::canNoexceptAssignQVariant)
+{
+ m_referred.setContent(content);
+ return *this;
+}
+QT_END_NAMESPACE
+
+void tst_QVariant::reference()
+{
+ QVariant content(5);
+
+ QVariant::ConstReference<QVariantWrapper> constRef(&content);
+ QCOMPARE(QVariant(constRef), QVariant(5));
+
+ QVariant::Reference<QVariantWrapper> ref(&content);
+ QCOMPARE(QVariant(ref), QVariant(5));
+
+ ref = QVariant(12);
+ QCOMPARE(QVariant(ref), QVariant(12));
+ QCOMPARE(content, QVariant(12));
+}
+
+void tst_QVariant::pointer()
+{
+ QVariant content(5);
+
+ QVariant::ConstPointer<QVariantWrapper> constPtr(&content);
+ QCOMPARE(*constPtr, QVariant(5));
+
+ QVariant::Pointer<QVariantWrapper> ptr(&content);
+ QCOMPARE(*ptr, QVariant(5));
+
+ *ptr = QVariant(12);
+ QCOMPARE(*ptr, QVariant(12));
+ QCOMPARE(content, QVariant(12));
+}
+
template <typename T>
T mutate(const T &t) { return t + t; }
template <>
diff --git a/tests/auto/corelib/time/CMakeLists.txt b/tests/auto/corelib/time/CMakeLists.txt
index 1fb95e9245c..29e882429f4 100644
--- a/tests/auto/corelib/time/CMakeLists.txt
+++ b/tests/auto/corelib/time/CMakeLists.txt
@@ -8,6 +8,7 @@ if(QT_FEATURE_datetimeparser)
add_subdirectory(qdatetimeparser)
endif()
add_subdirectory(qtime)
-if(QT_FEATURE_timezone)
- add_subdirectory(qtimezone)
+add_subdirectory(qtimezone)
+if(QT_FEATURE_timezone AND QT_FEATURE_developer_build)
+ add_subdirectory(qtimezonebackend)
endif()
diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
index 3f479943188..d1d86a4802f 100644
--- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
+++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
@@ -3,7 +3,9 @@
#include <QTest>
#include <qtimezone.h>
-#include <private/qtimezoneprivate_p.h>
+#if QT_CONFIG(timezone)
+# include <private/qtimezoneprivate_p.h>
+#endif
#include <private/qcomparisontesthelper_p.h>
#include <qlocale.h>
@@ -51,33 +53,21 @@ private Q_SLOTS:
void checkOffset();
void stressTest();
void windowsId();
- void isValidId_data();
- void isValidId();
void serialize();
void malformed();
- // Backend tests
+ // Backend tests (see also ../qtimezonebackend/)
void utcTest();
- void icuTest();
- void tzTest();
- void macTest();
void darwinTypes();
- void winTest();
void localeSpecificDisplayName_data();
void localeSpecificDisplayName();
- void roundtripDisplayNames_data();
- void roundtripDisplayNames();
+ // Compatibility with std::chrono::tzdb:
void stdCompatibility_data();
void stdCompatibility();
-#endif // timezone backends
+#endif // timezone backends exist
private:
- void printTimeZone(const QTimeZone &tz);
#if QT_CONFIG(timezone)
-# if defined(QT_BUILD_INTERNAL)
- // Generic tests of privates, called by implementation-specific private tests:
- void testCetPrivate(const QTimeZonePrivate &tzp);
- void testEpochTranPrivate(const QTimeZonePrivate &tzp);
-# endif // QT_BUILD_INTERNAL
+ void printTimeZone(const QTimeZone &tz);
// Where tzdb contains a link between zones in different territories, CLDR
// doesn't treat those as aliases for one another. For details see "Links in
// the tz database" at:
@@ -102,6 +92,7 @@ private:
static constexpr bool debug = false;
};
+#if QT_CONFIG(timezone)
void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
{
QDateTime now = QDateTime::currentDateTime();
@@ -167,9 +158,11 @@ void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
qDebug() << "Transition before 1 Jun = " << tz.previousTransition(jun).atUtc;
qDebug() << "";
}
+#endif // feature timezone
void tst_QTimeZone::createTest()
{
+#if QT_CONFIG(timezone)
const QTimeZone tz("Pacific/Auckland");
if constexpr (debug)
@@ -265,13 +258,16 @@ void tst_QTimeZone::createTest()
QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
}
}
+#else
+ QSKIP("Test depends on backends, enabled by feature timezone");
+#endif // feature timezone
}
void tst_QTimeZone::nullTest()
{
QTimeZone nullTz1;
QTimeZone nullTz2;
- QTimeZone utc("UTC");
+ QTimeZone utc(QTimeZone::UTC);
// Validity tests
QCOMPARE(nullTz1.isValid(), false);
@@ -290,6 +286,7 @@ void tst_QTimeZone::nullTest()
utc = nullTz1;
QCOMPARE(utc.isValid(), false);
+#if QT_CONFIG(timezone)
QCOMPARE(nullTz1.id(), QByteArray());
QCOMPARE(nullTz1.territory(), QLocale::AnyTerritory);
QCOMPARE(nullTz1.comment(), QString());
@@ -335,6 +332,7 @@ void tst_QTimeZone::nullTest()
QCOMPARE(data.offsetFromUtc, invalidOffset);
QCOMPARE(data.standardTimeOffset, invalidOffset);
QCOMPARE(data.daylightTimeOffset, invalidOffset);
+#endif // feature timezone
}
void tst_QTimeZone::assign()
@@ -487,7 +485,7 @@ void tst_QTimeZone::offset()
void tst_QTimeZone::dataStreamTest()
{
-#ifndef QT_NO_DATASTREAM
+#if QT_CONFIG(timezone) && !defined(QT_NO_DATASTREAM)
// Test the OffsetFromUtc backend serialization. First with a custom timezone:
QTimeZone tz1("QST"_ba, 23456,
u"Qt Standard Time"_s, u"QST"_s, QLocale::Norway, u"Qt Testing"_s);
@@ -546,7 +544,7 @@ void tst_QTimeZone::dataStreamTest()
QCOMPARE(ds.status(), QDataStream::Ok);
}
QCOMPARE(tz2.id(), tz1.id());
-#endif
+#endif // feature timezone and enabled datastream
}
#if QT_CONFIG(timezone)
@@ -791,7 +789,7 @@ void tst_QTimeZone::hasAlternativeName()
void tst_QTimeZone::specificTransition_data()
{
-#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__)
+#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__)
QSKIP("libstdc++'s C++20 misreads the IANA DB for Moscow's transitions (among others).");
#endif
#if defined Q_OS_ANDROID && !QT_CONFIG(timezone_tzdb)
@@ -1159,120 +1157,6 @@ void tst_QTimeZone::windowsId()
}
}
-void tst_QTimeZone::isValidId_data()
-{
-#ifdef QT_BUILD_INTERNAL
- QTest::addColumn<QByteArray>("input");
- QTest::addColumn<bool>("valid");
-
- // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
- // Can't start with '-'
- // Parts separated by '/', each part min 1 and max of 14 chars
- // (Android has parts with lengths up to 17, so tolerates this as a special case.)
-#define TESTSET(name, section, valid) \
- QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \
- QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \
- QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid
-
- // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
- // Can't start with '-'
- // Parts separated by '/', each part min 1 and max of 14 chars
- TESTSET("empty", "", false);
- TESTSET("minimal", "m", true);
-#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
- TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this
- TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this.
-#else
- TESTSET("maximal", "12345678901234", true);
- TESTSET("maximal twice", "12345678901234/12345678901234", true);
- TESTSET("too long", "123456789012345", false);
- TESTSET("too-long/maximal", "123456789012345/12345678901234", false);
- TESTSET("maximal/too-long", "12345678901234/123456789012345", false);
-#endif
-
- TESTSET("bad hyphen", "-hyphen", false);
- TESTSET("good hyphen", "hy-phen", true);
-
- TESTSET("valid char _", "_", true);
- TESTSET("valid char .", ".", true);
- TESTSET("valid char :", ":", true);
- TESTSET("valid char +", "+", true);
- TESTSET("valid char A", "A", true);
- TESTSET("valid char Z", "Z", true);
- TESTSET("valid char a", "a", true);
- TESTSET("valid char z", "z", true);
- TESTSET("valid char 0", "0", true);
- TESTSET("valid char 9", "9", true);
-
- TESTSET("valid pair az", "az", true);
- TESTSET("valid pair AZ", "AZ", true);
- TESTSET("valid pair 09", "09", true);
- TESTSET("valid pair .z", ".z", true);
- TESTSET("valid pair _z", "_z", true);
- TESTSET("invalid pair -z", "-z", false);
-
- TESTSET("valid triple a/z", "a/z", true);
- TESTSET("valid triple a.z", "a.z", true);
- TESTSET("valid triple a-z", "a-z", true);
- TESTSET("valid triple a_z", "a_z", true);
- TESTSET("invalid triple a z", "a z", false);
- TESTSET("invalid triple a\\z", "a\\z", false);
- TESTSET("invalid triple a,z", "a,z", false);
-
- TESTSET("invalid space", " ", false);
- TESTSET("invalid char ^", "^", false);
- TESTSET("invalid char \"", "\"", false);
- TESTSET("invalid char $", "$", false);
- TESTSET("invalid char %", "%", false);
- TESTSET("invalid char &", "&", false);
- TESTSET("invalid char (", "(", false);
- TESTSET("invalid char )", ")", false);
- TESTSET("invalid char =", "=", false);
- TESTSET("invalid char -", "-", false);
- TESTSET("invalid char ?", "?", false);
- TESTSET("invalid char ß", "ß", false);
- TESTSET("invalid char \\x01", "\x01", false);
- TESTSET("invalid char ' '", " ", false);
-
-#undef TESTSET
-
- QTest::newRow("az alone") << QByteArray("az") << true;
- QTest::newRow("AZ alone") << QByteArray("AZ") << true;
- QTest::newRow("09 alone") << QByteArray("09") << true;
- QTest::newRow("a/z alone") << QByteArray("a/z") << true;
- QTest::newRow("a.z alone") << QByteArray("a.z") << true;
- QTest::newRow("a-z alone") << QByteArray("a-z") << true;
- QTest::newRow("a_z alone") << QByteArray("a_z") << true;
- QTest::newRow(".z alone") << QByteArray(".z") << true;
- QTest::newRow("_z alone") << QByteArray("_z") << true;
- QTest::newRow("a z alone") << QByteArray("a z") << false;
- QTest::newRow("a\\z alone") << QByteArray("a\\z") << false;
- QTest::newRow("a,z alone") << QByteArray("a,z") << false;
- QTest::newRow("/z alone") << QByteArray("/z") << false;
- QTest::newRow("-z alone") << QByteArray("-z") << false;
-#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
- QTest::newRow("long alone") << QByteArray("12345678901234567") << true;
- QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false;
-#else
- QTest::newRow("long alone") << QByteArray("12345678901234") << true;
- QTest::newRow("over-long alone") << QByteArray("123456789012345") << false;
-#endif
-
-#else
- QSKIP("This test requires a Qt -developer-build.");
-#endif // QT_BUILD_INTERNAL
-}
-
-void tst_QTimeZone::isValidId()
-{
-#ifdef QT_BUILD_INTERNAL
- QFETCH(QByteArray, input);
- QFETCH(bool, valid);
-
- QCOMPARE(QTimeZonePrivate::isValidId(input), valid);
-#endif
-}
-
void tst_QTimeZone::serialize()
{
int parts = 0;
@@ -1365,7 +1249,7 @@ void tst_QTimeZone::utcTest()
tz = QTimeZone(15 * 3600); // no IANA ID, so uses minimal id, skipping :00 minutes
QVERIFY(tz.isValid());
- QCOMPARE(tz.id(), "UTC+15");
+ QCOMPARE(tz.id(), "UTC+15:00");
QCOMPARE(tz.offsetFromUtc(now), 15 * 3600);
QCOMPARE(tz.standardTimeOffset(now), 15 * 3600);
QCOMPARE(tz.daylightTimeOffset(now), 0);
@@ -1402,316 +1286,6 @@ void tst_QTimeZone::utcTest()
QCOMPARE(tz.daylightTimeOffset(now), 0);
}
-// Relies on local variable names: zone tzp and locale enUS.
-#define ZONE_DNAME_CHECK(type, name, val) \
- QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val);
-
-void tst_QTimeZone::icuTest()
-{
-#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) \
- && (defined(Q_OS_VXWORKS) || !defined(Q_OS_UNIX))
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QIcuTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid is not available:
- QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon"));
- // and construction gives an invalid result:
- QIcuTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QIcuTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by ICU version installed
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS("en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto"));
-#endif // ICU not on Unix, without tzdb
-}
-
-void tst_QTimeZone::tzTest()
-{
-#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX \
- && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \
- || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS))
- const auto UTC = QTimeZone::UTC;
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QTzTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QTzTimeZonePrivate tzpi("Gondwana/Erewhon");
- QVERIFY(!tzpi.isValid());
-
- // Test named constructor
- QTzTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Test POSIX-format value for $TZ:
- QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00");
- QVERIFY(tzposix.isValid());
- QVERIFY(tzposix.hasDaylightTime());
-
- // Cope with stray space at start of value (QTBUG-135109):
- QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3");
- QVERIFY(syd.isValid());
- QVERIFY(syd.hasDaylightTime());
-
- // RHEL has been seen with this as Africa/Casablanca's POSIX rule:
- QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25");
- const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst
- QVERIFY(permaDst.isValid());
- QVERIFY(permaDst.hasDaylightTime());
- QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch()));
- QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch()));
- // Note that the final /25 could be misunderstood as putting a fall-back at
- // 1am on the next year's Jan 1st; check we don't do that:
- QVERIFY(permaDst.isDaylightTime(
- QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch()));
- // It shouldn't have any transitions. QTimeZone::hasTransitions() only says
- // whether the backend supports them, so ask for transitions in a wide
- // enough interval that one would show up, if there are any:
- QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(),
- QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch()
- ).isEmpty());
-
- QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule
- QVERIFY(tzBrazil.isValid());
- QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800);
-
- // Test display names by type, either ICU or abbreviation only
- QLocale enUS(u"en_US");
- // Only test names in debug mode, names used can vary by ICU version installed
- if constexpr (debug) {
-#if QT_CONFIG(icu)
- ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-#else
- ZONE_DNAME_CHECK(StandardTime, LongName, "CET");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "CET");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST");
- ZONE_DNAME_CHECK(GenericTime, LongName, "CET");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "CET");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET");
-#endif // icu
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto"));
- if (QTest::currentTestFailed())
- return;
-
- // Test first and last transition rule
- // Warning: This could vary depending on age of TZ file!
-
- // Test low date uses first rule found
- constexpr qint64 ancient = -Q_INT64_C(9999999999999);
- // Note: Depending on the OS in question, the database may be carrying the
- // Local Mean Time. which for Berlin is 0:53:28
- QTimeZonePrivate::Data dat = tzp.data(ancient);
- QCOMPARE(dat.atMSecsSinceEpoch, ancient);
- QCOMPARE(dat.daylightTimeOffset, 0);
- if (dat.abbreviation == u"LMT") {
- QCOMPARE(dat.standardTimeOffset, 3208);
- } else {
- QCOMPARE(dat.standardTimeOffset, 3600);
-
- constexpr qint64 invalidTime = std::numeric_limits<qint64>::min();
- constexpr int invalidOffset = std::numeric_limits<int>::min();
- // Test previous to low value is invalid
- dat = tzp.previousTransition(ancient);
- QCOMPARE(dat.atMSecsSinceEpoch, invalidTime);
- QCOMPARE(dat.standardTimeOffset, invalidOffset);
- QCOMPARE(dat.daylightTimeOffset, invalidOffset);
- }
-
- dat = tzp.nextTransition(ancient);
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
- QTimeZone::fromSecondsAheadOfUtc(3600)),
- QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32),
- QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- // Date-times late enough to exercise POSIX rules:
- qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch();
- qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch();
- // Relevant last Sundays in October and March:
- QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday);
- QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday);
- QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday);
-
- dat = tzp.data(stdHi);
- QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- dat = tzp.data(dstHi);
- QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.previousTransition(stdHi);
- QCOMPARE(dat.abbreviation, u"CET");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- dat = tzp.previousTransition(dstHi);
- QCOMPARE(dat.abbreviation, u"CEST");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.nextTransition(stdHi);
- QCOMPARE(dat.abbreviation, u"CEST");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.nextTransition(dstHi);
- QCOMPARE(dat.abbreviation, u"CET");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
- QTimeZone::fromSecondsAheadOfUtc(3600)),
- QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- // Test TZ timezone vs UTC timezone for non-whole-hour negative offset:
- QTzTimeZonePrivate tztz1("America/Caracas");
- QUtcTimeZonePrivate tzutc1("UTC-04:30");
- QVERIFY(tztz1.isValid());
- QVERIFY(tzutc1.isValid());
- QTzTimeZonePrivate::Data datatz1 = tztz1.data(std);
- QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std);
- QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc);
-
- // Test TZ timezone vs UTC timezone for non-whole-hour positive offset:
- QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name
- QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name
- // Can't assign QtzTZP, so use a reference; prefer new name.
- QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c;
- QUtcTimeZonePrivate tzutc2("UTC+05:30");
- QVERIFY2(tztz2.isValid(), tztz2.id().constData());
- QVERIFY(tzutc2.isValid());
- QTzTimeZonePrivate::Data datatz2 = tztz2.data(std);
- QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std);
- QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc);
-
- // Test a timezone with an abbreviation that isn't all letters:
- QTzTimeZonePrivate tzBarnaul("Asia/Barnaul");
- if (tzBarnaul.isValid()) {
- QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07");
-
- // first full day of the new rule (tzdata2016b)
- QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC);
- QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07");
- }
-#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !timezone_tzdb && !Q_OS_DARWIN && !Q_OS_ANDROID
-}
-
-void tst_QTimeZone::macTest()
-{
-#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb)
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QMacTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QMacTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QMacTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by version
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS(u"en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto"));
-#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN without tzdb
-}
-
void tst_QTimeZone::darwinTypes()
{
#ifndef Q_OS_DARWIN
@@ -1722,59 +1296,6 @@ void tst_QTimeZone::darwinTypes()
#endif
}
-void tst_QTimeZone::winTest()
-{
-#if defined(QT_BUILD_INTERNAL) && defined(USING_WIN_TZ)
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QWinTimeZonePrivate tzpd;
- if constexpr (debug)
- qDebug() << "System ID = " << tzpd.id()
- << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale())
- << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale());
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QWinTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QWinTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by version
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS(u"en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- ZONE_DNAME_CHECK(GenericTime, LongName,
- "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
- ZONE_DNAME_CHECK(GenericTime, ShortName,
- "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto"));
-#endif // QT_BUILD_INTERNAL && USING_WIN_TZ
-}
-
-#undef ZONE_DNAME_CHECK
-
void tst_QTimeZone::localeSpecificDisplayName_data()
{
QTest::addColumn<QByteArray>("zoneName");
@@ -1851,285 +1372,6 @@ void tst_QTimeZone::localeSpecificDisplayName()
#endif
}
-void tst_QTimeZone::roundtripDisplayNames_data()
-{
-#ifdef QT_BUILD_INTERNAL
- QTest::addColumn<QTimeZone>("zone");
- QTest::addColumn<QLocale>("locale");
- QTest::addColumn<QTimeZone::TimeType>("type");
-
- constexpr QTimeZone::TimeType types[] = {
- QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
- };
- const auto typeName = [](QTimeZone::TimeType type) {
- switch (type) {
- case QTimeZone::GenericTime: return "Gen";
- case QTimeZone::StandardTime: return "Std";
- case QTimeZone::DaylightTime: return "DST";
- }
- Q_UNREACHABLE_RETURN("Unrecognised");
- };
- const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba);
-#ifdef EXHAUSTIVE_ZONE_DISPLAY
- const QList<QByteArray> idList = allList;
-#else
- const QList<QByteArray> idList = {
- "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba,
- "America/Caracas"_ba, "America/Indiana/Tell_City"_ba, "America/Managua"_ba,
- "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba,
- "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba,
- "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba,
- "Brazil/Acre"_ba, "CST6CDT"_ba, "Canada/Atlantic"_ba,
- "Chile/EasterIsland"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba,
- "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba,
- "Europe/Prague"_ba, "Europe/Vatican"_ba,
- "Indian/Comoro"_ba, "Mexico/BajaSur"_ba,
- "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba,
- "US/Aleutian"_ba,
- "UTC"_ba,
- // Those named overtly in tst_QDateTime - special cases first:
- "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba,
- "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba,
- // ... then ordinary names in alphabetic order:
- "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba,
- "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba,
- "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba,
- "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba,
- "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba,
- "Europe/Rome"_ba,
- "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba,
- "Vulcan/ShiKahr"_ba // Invalid: also worth testing.
- };
- // Some valid zones in that list may be absent from the platform's
- // availableTimeZoneIds(), yet in fact work when used as it's asked to
- // instantiate them (e.g. Etc/Universal on macOS). This can give them a
- // displayName() that we fail to decode, without timezone_locale, due to
- // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we
- // have to filter on membership of allList when creating rows.
-#endif // Exhaustive
- const QLocale fr(QLocale::French, QLocale::France);
- const QLocale hi(QLocale::Hindi, QLocale::India);
- for (const QByteArray &id : idList) {
- if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id))
- continue;
- QTimeZone zone = QTimeZone(id);
- if (!zone.isValid())
- continue;
- for (const auto type : types) {
- QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type))
- << zone << fr << type;
- QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type))
- << zone << hi << type;
- }
- }
-#else
- QSKIP("Test needs access to internal APIs");
-#endif
-}
-
-void tst_QTimeZone::roundtripDisplayNames()
-{
-#ifdef QT_BUILD_INTERNAL
- QFETCH(const QTimeZone, zone);
- QFETCH(const QLocale, locale);
- QFETCH(const QTimeZone::TimeType, type);
- static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC);
- static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC);
- const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan;
-
- // Some zones exercise region format.
- const QString name = zone.displayName(type, QTimeZone::LongName, locale);
- if (!name.isEmpty()) {
- const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type);
- const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs()
- ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch;
- const QString extended = name + "some spurious cruft"_L1;
- auto match =
- QTimeZonePrivate::findLongNamePrefix(extended, locale, when);
- if (!match)
- match = QTimeZonePrivate::findLongNamePrefix(extended, locale);
- if (!match)
- match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale);
- if (!match)
- match = QTimeZonePrivate::findLongUtcPrefix(extended);
- auto report = qScopeGuard([=]() {
- qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
- << "via" << name;
- });
- QCOMPARE(match.nameLength, name.size());
- report.dismiss();
-#if 0
- if (match.ianaId != zone.id()) {
- const QTimeZone found = QTimeZone(match.ianaId);
- if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when)
- != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) {
- // For DST, some zones haven't done it in ages, so tran may be ancient.
- // Meanwhile, match.ianaId is typically the canonical zone for a metazone.
- // That, in turn, may not have been doing DST when zone was.
- // So we can't rely on a match, but can report the mismatches.
- qDebug() << "Long name" << name << "on"
- << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)
- << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
- << "got" << match.ianaId << "on"
- << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when);
- // There are also some absurdly over-generic names, that lead to
- // ambiguities, e.g. "heure : West"
- }
- }
-#endif // Debug code
- } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */
- qDebug("Empty display name");
- }
-#else
- Q_ASSERT(!"Should be skipped when building data table");
-#endif
-}
-
-#ifdef QT_BUILD_INTERNAL
-// Test each private produces the same basic results for CET
-void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp)
-{
- // Known datetimes
- const auto UTC = QTimeZone::UTC;
- const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600);
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
-
- QCOMPARE(tzp.offsetFromUtc(std), 3600);
- QCOMPARE(tzp.offsetFromUtc(dst), 7200);
-
- QCOMPARE(tzp.standardTimeOffset(std), 3600);
- QCOMPARE(tzp.standardTimeOffset(dst), 3600);
-
- QCOMPARE(tzp.daylightTimeOffset(std), 0);
- QCOMPARE(tzp.daylightTimeOffset(dst), 3600);
-
- QCOMPARE(tzp.hasDaylightTime(), true);
- QCOMPARE(tzp.isDaylightTime(std), false);
- QCOMPARE(tzp.isDaylightTime(dst), true);
-
- QTimeZonePrivate::Data dat = tzp.data(std);
- QCOMPARE(dat.atMSecsSinceEpoch, std);
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
- QCOMPARE(dat.abbreviation, tzp.abbreviation(std));
-
- dat = tzp.data(dst);
- QCOMPARE(dat.atMSecsSinceEpoch, dst);
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
- QCOMPARE(dat.abbreviation, tzp.abbreviation(dst));
-
- // Only test transitions if host system supports them
- if (tzp.hasTransitions()) {
- QTimeZonePrivate::Data tran = tzp.nextTransition(std);
- // 2012-03-25 02:00 CET, +1 -> +2
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
- QCOMPARE(tran.offsetFromUtc, 7200);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- tran = tzp.nextTransition(dst);
- // 2012-10-28 03:00 CEST, +2 -> +1
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 10, 28), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
- QCOMPARE(tran.offsetFromUtc, 3600);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
-
- tran = tzp.previousTransition(std);
- // 2011-10-30 03:00 CEST, +2 -> +1
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2011, 10, 30), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
- QCOMPARE(tran.offsetFromUtc, 3600);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
-
- tran = tzp.previousTransition(dst);
- // 2012-03-25 02:00 CET, +1 -> +2 (again)
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
- QCOMPARE(tran.offsetFromUtc, 7200);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- QTimeZonePrivate::DataList expected;
- // 2011-03-27 02:00 CET, +1 -> +2
- tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0),
- eastOneHour).toMSecsSinceEpoch();
- tran.offsetFromUtc = 7200;
- tran.standardTimeOffset = 3600;
- tran.daylightTimeOffset = 3600;
- expected << tran;
- // 2011-10-30 03:00 CEST, +2 -> +1
- tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)
- ).toMSecsSinceEpoch();
- tran.offsetFromUtc = 3600;
- tran.standardTimeOffset = 3600;
- tran.daylightTimeOffset = 0;
- expected << tran;
- QTimeZonePrivate::DataList result = tzp.transitions(prev, std);
- QCOMPARE(result.size(), expected.size());
- for (int i = 0; i < expected.size(); ++i) {
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour),
- QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour));
- QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
- QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
- QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
- }
- }
-}
-
-// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT)
-void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp)
-{
- if (!tzp.hasTransitions())
- return; // test only viable for transitions
-
- const auto UTC = QTimeZone::UTC;
- const auto hour = std::chrono::hours{1};
- QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch
- // 1970-04-26 02:00 EST, -5 -> -4
- const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0),
- QTimeZone::fromDurationAheadOfUtc(-5 * hour));
- const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC);
-#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th.
- QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time());
-#else
- QCOMPARE(found, after);
-#endif
- QCOMPARE(tran.offsetFromUtc, -4 * 3600);
- QCOMPARE(tran.standardTimeOffset, -5 * 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- // Pre-epoch time-zones might not be supported at all:
- tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch());
- if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()
- // Toronto *did* have a transition before 1970 (DST since 1918):
- && tran.atMSecsSinceEpoch < 0) {
- // ... but, if they are, we should be able to search back to them:
- tran = tzp.previousTransition(0); // i.e. last before epoch
- // 1969-10-26 02:00 EDT, -4 -> -5
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(1969, 10, 26), QTime(2, 0),
- QTimeZone::fromDurationAheadOfUtc(-4 * hour)));
- QCOMPARE(tran.offsetFromUtc, -5 * 3600);
- QCOMPARE(tran.standardTimeOffset, -5 * 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
- } else {
- // Do not use QSKIP(): that would discard the rest of this sub-test's caller.
- qDebug() << "No support for pre-epoch time-zone transitions";
- }
-}
-#endif // QT_BUILD_INTERNAL
-
#if __cpp_lib_chrono >= 201907L
Q_DECLARE_METATYPE(const std::chrono::time_zone *);
#endif
diff --git a/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt
new file mode 100644
index 00000000000..6bead8b6549
--- /dev/null
+++ b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qtimezonebackend Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qtimezonebackend LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qtimezonebackend
+ SOURCES
+ tst_qtimezonebackend.cpp
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_FOREACH
+ QT_NO_KEYWORDS
+ LIBRARIES
+ Qt::CorePrivate
+)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qtimezonebackend CONDITION QT_FEATURE_icu
+ LIBRARIES
+ ICU::i18n ICU::uc ICU::data
+)
diff --git a/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp
new file mode 100644
index 00000000000..71519ccd9fc
--- /dev/null
+++ b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp
@@ -0,0 +1,784 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
+#include <qtimezone.h>
+#include <private/qtimezoneprivate_p.h>
+
+#include <qlocale.h>
+#include <qscopeguard.h>
+
+#if defined(Q_OS_WIN) && !QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb)
+# define USING_WIN_TZ
+#endif
+
+// Enable to test exhaustively - this is slow and expensive.
+// It is also quite likely to trip over problems that we can't necessarily fix.
+// #define EXHAUSTIVE_ZONE_DISPLAY
+
+using namespace Qt::StringLiterals;
+
+class tst_QTimeZoneBackend : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ // Tests of QTZP functionality:
+ void isValidId_data();
+ void isValidId();
+ void roundtripDisplayNames_data();
+ void roundtripDisplayNames();
+ // Tests of specific backends (see also ../qtimezone/):
+ void icuTest();
+ void tzTest();
+ void macTest();
+ void winTest();
+
+private:
+ // Generic tests of privates, called by implementation-specific private tests:
+ void testCetPrivate(const QTimeZonePrivate &tzp);
+ void testEpochTranPrivate(const QTimeZonePrivate &tzp);
+ // Set to true to print debug output, test Display Names and run long stress tests
+ static constexpr bool debug = false;
+};
+
+void tst_QTimeZoneBackend::isValidId_data()
+{
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<bool>("valid");
+
+ // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
+ // Can't start with '-'
+ // Parts separated by '/', each part min 1 and max of 14 chars
+ // (Android has parts with lengths up to 17, so tolerates this as a special case.)
+#define TESTSET(name, section, valid) \
+ QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \
+ QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \
+ QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid
+
+ // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
+ // Can't start with '-'
+ // Parts separated by '/', each part min 1 and max of 14 chars
+ TESTSET("empty", "", false);
+ TESTSET("minimal", "m", true);
+#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
+ TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this
+ TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this.
+#else
+ TESTSET("maximal", "12345678901234", true);
+ TESTSET("maximal twice", "12345678901234/12345678901234", true);
+ TESTSET("too long", "123456789012345", false);
+ TESTSET("too-long/maximal", "123456789012345/12345678901234", false);
+ TESTSET("maximal/too-long", "12345678901234/123456789012345", false);
+#endif
+
+ TESTSET("bad hyphen", "-hyphen", false);
+ TESTSET("good hyphen", "hy-phen", true);
+
+ TESTSET("valid char _", "_", true);
+ TESTSET("valid char .", ".", true);
+ TESTSET("valid char :", ":", true);
+ TESTSET("valid char +", "+", true);
+ TESTSET("valid char A", "A", true);
+ TESTSET("valid char Z", "Z", true);
+ TESTSET("valid char a", "a", true);
+ TESTSET("valid char z", "z", true);
+ TESTSET("valid char 0", "0", true);
+ TESTSET("valid char 9", "9", true);
+
+ TESTSET("valid pair az", "az", true);
+ TESTSET("valid pair AZ", "AZ", true);
+ TESTSET("valid pair 09", "09", true);
+ TESTSET("valid pair .z", ".z", true);
+ TESTSET("valid pair _z", "_z", true);
+ TESTSET("invalid pair -z", "-z", false);
+
+ TESTSET("valid triple a/z", "a/z", true);
+ TESTSET("valid triple a.z", "a.z", true);
+ TESTSET("valid triple a-z", "a-z", true);
+ TESTSET("valid triple a_z", "a_z", true);
+ TESTSET("invalid triple a z", "a z", false);
+ TESTSET("invalid triple a\\z", "a\\z", false);
+ TESTSET("invalid triple a,z", "a,z", false);
+
+ TESTSET("invalid space", " ", false);
+ TESTSET("invalid char ^", "^", false);
+ TESTSET("invalid char \"", "\"", false);
+ TESTSET("invalid char $", "$", false);
+ TESTSET("invalid char %", "%", false);
+ TESTSET("invalid char &", "&", false);
+ TESTSET("invalid char (", "(", false);
+ TESTSET("invalid char )", ")", false);
+ TESTSET("invalid char =", "=", false);
+ TESTSET("invalid char -", "-", false);
+ TESTSET("invalid char ?", "?", false);
+ TESTSET("invalid char ß", "ß", false);
+ TESTSET("invalid char \\x01", "\x01", false);
+ TESTSET("invalid char ' '", " ", false);
+
+#undef TESTSET
+
+ QTest::newRow("az alone") << QByteArray("az") << true;
+ QTest::newRow("AZ alone") << QByteArray("AZ") << true;
+ QTest::newRow("09 alone") << QByteArray("09") << true;
+ QTest::newRow("a/z alone") << QByteArray("a/z") << true;
+ QTest::newRow("a.z alone") << QByteArray("a.z") << true;
+ QTest::newRow("a-z alone") << QByteArray("a-z") << true;
+ QTest::newRow("a_z alone") << QByteArray("a_z") << true;
+ QTest::newRow(".z alone") << QByteArray(".z") << true;
+ QTest::newRow("_z alone") << QByteArray("_z") << true;
+ QTest::newRow("a z alone") << QByteArray("a z") << false;
+ QTest::newRow("a\\z alone") << QByteArray("a\\z") << false;
+ QTest::newRow("a,z alone") << QByteArray("a,z") << false;
+ QTest::newRow("/z alone") << QByteArray("/z") << false;
+ QTest::newRow("-z alone") << QByteArray("-z") << false;
+#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
+ QTest::newRow("long alone") << QByteArray("12345678901234567") << true;
+ QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false;
+#else
+ QTest::newRow("long alone") << QByteArray("12345678901234") << true;
+ QTest::newRow("over-long alone") << QByteArray("123456789012345") << false;
+#endif
+}
+
+void tst_QTimeZoneBackend::isValidId()
+{
+ QFETCH(QByteArray, input);
+ QFETCH(bool, valid);
+
+ QCOMPARE(QTimeZonePrivate::isValidId(input), valid);
+}
+
+void tst_QTimeZoneBackend::roundtripDisplayNames_data()
+{
+ QTest::addColumn<QTimeZone>("zone");
+ QTest::addColumn<QLocale>("locale");
+ QTest::addColumn<QTimeZone::TimeType>("type");
+
+ constexpr QTimeZone::TimeType types[] = {
+ QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
+ };
+ const auto typeName = [](QTimeZone::TimeType type) {
+ switch (type) {
+ case QTimeZone::GenericTime: return "Gen";
+ case QTimeZone::StandardTime: return "Std";
+ case QTimeZone::DaylightTime: return "DST";
+ }
+ Q_UNREACHABLE_RETURN("Unrecognised");
+ };
+ const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba);
+#ifdef EXHAUSTIVE_ZONE_DISPLAY
+ const QList<QByteArray> idList = allList;
+#else
+ const QList<QByteArray> idList = {
+ "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba,
+ "America/Caracas"_ba, "America/Coyhaique"_ba,
+ "America/Indiana/Tell_City"_ba, "America/Managua"_ba,
+ "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba,
+ "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba,
+ "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba,
+ "Brazil/Acre"_ba, "Canada/Atlantic"_ba, "Chile/EasterIsland"_ba,
+ "CST6CDT"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba,
+ "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba,
+ "Europe/Prague"_ba, "Europe/Vatican"_ba,
+ "Indian/Comoro"_ba, "Mexico/BajaSur"_ba,
+ "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba,
+ "US/Aleutian"_ba,
+ "UTC"_ba,
+ // Those named overtly in tst_QDateTime - special cases first:
+ "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba,
+ "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba,
+ // ... then ordinary names in alphabetic order:
+ "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba,
+ "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba,
+ "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba,
+ "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba,
+ "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba,
+ "Europe/Rome"_ba,
+ "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba,
+ "Vulcan/ShiKahr"_ba // Invalid: also worth testing.
+ };
+ // Some valid zones in that list may be absent from the platform's
+ // availableTimeZoneIds(), yet in fact work when used as it's asked to
+ // instantiate them (e.g. Etc/Universal on macOS). This can give them a
+ // displayName() that we fail to decode, without timezone_locale, due to
+ // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we
+ // have to filter on membership of allList when creating rows.
+#endif // Exhaustive
+ const QLocale fr(QLocale::French, QLocale::France);
+ const QLocale hi(QLocale::Hindi, QLocale::India);
+ for (const QByteArray &id : idList) {
+ if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id))
+ continue;
+ QTimeZone zone = QTimeZone(id);
+ if (!zone.isValid())
+ continue;
+ for (const auto type : types) {
+ QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type))
+ << zone << fr << type;
+ QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type))
+ << zone << hi << type;
+ }
+ }
+}
+
+void tst_QTimeZoneBackend::roundtripDisplayNames()
+{
+ QFETCH(const QTimeZone, zone);
+ QFETCH(const QLocale, locale);
+ QFETCH(const QTimeZone::TimeType, type);
+ static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC);
+ static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC);
+ const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan;
+
+ // Some zones exercise region format.
+ const QString name = zone.displayName(type, QTimeZone::LongName, locale);
+ if (!name.isEmpty()) {
+ const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type);
+ const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs()
+ ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch;
+ const QString extended = name + "some spurious cruft"_L1;
+ auto match =
+ QTimeZonePrivate::findLongNamePrefix(extended, locale, when);
+ if (!match)
+ match = QTimeZonePrivate::findLongNamePrefix(extended, locale);
+ if (!match)
+ match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale);
+ if (!match)
+ match = QTimeZonePrivate::findLongUtcPrefix(extended);
+ auto report = qScopeGuard([=]() {
+ qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
+ << "via" << name;
+ });
+ QCOMPARE(match.nameLength, name.size());
+ report.dismiss();
+#if 0
+ if (match.ianaId != zone.id()) {
+ const QTimeZone found = QTimeZone(match.ianaId);
+ if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when)
+ != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) {
+ // For DST, some zones haven't done it in ages, so tran may be ancient.
+ // Meanwhile, match.ianaId is typically the canonical zone for a metazone.
+ // That, in turn, may not have been doing DST when zone was.
+ // So we can't rely on a match, but can report the mismatches.
+ qDebug() << "Long name" << name << "on"
+ << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)
+ << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
+ << "got" << match.ianaId << "on"
+ << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when);
+ // There are also some absurdly over-generic names, that lead to
+ // ambiguities, e.g. "heure : West"
+ }
+ }
+#endif // Debug code
+ } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */
+ qDebug("Empty display name");
+ }
+}
+
+// Relies on local variable names: zone tzp and locale enUS.
+#define ZONE_DNAME_CHECK(type, name, val) \
+ QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val);
+
+void tst_QTimeZoneBackend::icuTest()
+{
+#if QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && (defined(Q_OS_VXWORKS) || !defined(Q_OS_UNIX))
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QIcuTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid is not available:
+ QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon"));
+ // and construction gives an invalid result:
+ QIcuTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QIcuTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by ICU version installed
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS("en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto"));
+#endif // ICU without tzdb, on VxWorks or not on Unix
+}
+
+void tst_QTimeZoneBackend::tzTest()
+{
+#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \
+ || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS))
+ const auto UTC = QTimeZone::UTC;
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QTzTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QTzTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QVERIFY(!tzpi.isValid());
+
+ // Test named constructor
+ QTzTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Test POSIX-format value for $TZ:
+ QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00");
+ QVERIFY(tzposix.isValid());
+ QVERIFY(tzposix.hasDaylightTime());
+
+ // Cope with stray space at start of value (QTBUG-135109):
+ QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3");
+ QVERIFY(syd.isValid());
+ QVERIFY(syd.hasDaylightTime());
+
+ // RHEL has been seen with this as Africa/Casablanca's POSIX rule:
+ QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25");
+ const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst
+ QVERIFY(permaDst.isValid());
+ QVERIFY(permaDst.hasDaylightTime());
+ QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch()));
+ QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch()));
+ // Note that the final /25 could be misunderstood as putting a fall-back at
+ // 1am on the next year's Jan 1st; check we don't do that:
+ QVERIFY(permaDst.isDaylightTime(
+ QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch()));
+ // It shouldn't have any transitions. QTimeZone::hasTransitions() only says
+ // whether the backend supports them, so ask for transitions in a wide
+ // enough interval that one would show up, if there are any:
+ QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(),
+ QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch()
+ ).isEmpty());
+
+ QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule
+ QVERIFY(tzBrazil.isValid());
+ QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800);
+
+ // Test display names by type, either ICU or abbreviation only
+ QLocale enUS(u"en_US");
+ // Only test names in debug mode, names used can vary by ICU version installed
+ if constexpr (debug) {
+#if QT_CONFIG(icu)
+ ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+#else
+ ZONE_DNAME_CHECK(StandardTime, LongName, "CET");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "CET");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST");
+ ZONE_DNAME_CHECK(GenericTime, LongName, "CET");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "CET");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET");
+#endif // icu
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto"));
+ if (QTest::currentTestFailed())
+ return;
+
+ // Test first and last transition rule
+ // Warning: This could vary depending on age of TZ file!
+
+ // Test low date uses first rule found
+ constexpr qint64 ancient = -Q_INT64_C(9999999999999);
+ // Note: Depending on the OS in question, the database may be carrying the
+ // Local Mean Time. which for Berlin is 0:53:28
+ QTimeZonePrivate::Data dat = tzp.data(ancient);
+ QCOMPARE(dat.atMSecsSinceEpoch, ancient);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+ if (dat.abbreviation == u"LMT") {
+ QCOMPARE(dat.standardTimeOffset, 3208);
+ } else {
+ QCOMPARE(dat.standardTimeOffset, 3600);
+
+ constexpr qint64 invalidTime = std::numeric_limits<qint64>::min();
+ constexpr int invalidOffset = std::numeric_limits<int>::min();
+ // Test previous to low value is invalid
+ dat = tzp.previousTransition(ancient);
+ QCOMPARE(dat.atMSecsSinceEpoch, invalidTime);
+ QCOMPARE(dat.standardTimeOffset, invalidOffset);
+ QCOMPARE(dat.daylightTimeOffset, invalidOffset);
+ }
+
+ dat = tzp.nextTransition(ancient);
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
+ QTimeZone::fromSecondsAheadOfUtc(3600)),
+ QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32),
+ QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ // Date-times late enough to exercise POSIX rules:
+ qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch();
+ qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch();
+ // Relevant last Sundays in October and March:
+ QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday);
+ QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday);
+ QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday);
+
+ dat = tzp.data(stdHi);
+ QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ dat = tzp.data(dstHi);
+ QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.previousTransition(stdHi);
+ QCOMPARE(dat.abbreviation, u"CET");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ dat = tzp.previousTransition(dstHi);
+ QCOMPARE(dat.abbreviation, u"CEST");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.nextTransition(stdHi);
+ QCOMPARE(dat.abbreviation, u"CEST");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.nextTransition(dstHi);
+ QCOMPARE(dat.abbreviation, u"CET");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
+ QTimeZone::fromSecondsAheadOfUtc(3600)),
+ QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ // Test TZ timezone vs UTC timezone for non-whole-hour negative offset:
+ QTzTimeZonePrivate tztz1("America/Caracas");
+ QUtcTimeZonePrivate tzutc1("UTC-04:30");
+ QVERIFY(tztz1.isValid());
+ QVERIFY(tzutc1.isValid());
+ QTzTimeZonePrivate::Data datatz1 = tztz1.data(std);
+ QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std);
+ QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc);
+
+ // Test TZ timezone vs UTC timezone for non-whole-hour positive offset:
+ QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name
+ QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name
+ // Can't assign QtzTZP, so use a reference; prefer new name.
+ QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c;
+ QUtcTimeZonePrivate tzutc2("UTC+05:30");
+ QVERIFY2(tztz2.isValid(), tztz2.id().constData());
+ QVERIFY(tzutc2.isValid());
+ QTzTimeZonePrivate::Data datatz2 = tztz2.data(std);
+ QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std);
+ QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc);
+
+ // Test a timezone with an abbreviation that isn't all letters:
+ QTzTimeZonePrivate tzBarnaul("Asia/Barnaul");
+ if (tzBarnaul.isValid()) {
+ QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07");
+
+ // first full day of the new rule (tzdata2016b)
+ QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC);
+ QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07");
+ }
+#endif // Unix && !(timezone_tzdb || Darwin || Android || VxWorks)
+}
+
+void tst_QTimeZoneBackend::macTest()
+{
+#if defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb)
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QMacTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QMacTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QMacTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by version
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS(u"en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto"));
+#endif // Q_OS_DARWIN without tzdb
+}
+
+void tst_QTimeZoneBackend::winTest()
+{
+#if defined(USING_WIN_TZ)
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QWinTimeZonePrivate tzpd;
+ if constexpr (debug)
+ qDebug() << "System ID = " << tzpd.id()
+ << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale())
+ << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale());
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QWinTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QWinTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by version
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS(u"en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ ZONE_DNAME_CHECK(GenericTime, LongName,
+ "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
+ ZONE_DNAME_CHECK(GenericTime, ShortName,
+ "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto"));
+#endif // TZ backend
+}
+
+#undef ZONE_DNAME_CHECK
+
+// Test each private produces the same basic results for CET
+void tst_QTimeZoneBackend::testCetPrivate(const QTimeZonePrivate &tzp)
+{
+ // Known datetimes
+ const auto UTC = QTimeZone::UTC;
+ const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600);
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+
+ QCOMPARE(tzp.offsetFromUtc(std), 3600);
+ QCOMPARE(tzp.offsetFromUtc(dst), 7200);
+
+ QCOMPARE(tzp.standardTimeOffset(std), 3600);
+ QCOMPARE(tzp.standardTimeOffset(dst), 3600);
+
+ QCOMPARE(tzp.daylightTimeOffset(std), 0);
+ QCOMPARE(tzp.daylightTimeOffset(dst), 3600);
+
+ QCOMPARE(tzp.hasDaylightTime(), true);
+ QCOMPARE(tzp.isDaylightTime(std), false);
+ QCOMPARE(tzp.isDaylightTime(dst), true);
+
+ QTimeZonePrivate::Data dat = tzp.data(std);
+ QCOMPARE(dat.atMSecsSinceEpoch, std);
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+ QCOMPARE(dat.abbreviation, tzp.abbreviation(std));
+
+ dat = tzp.data(dst);
+ QCOMPARE(dat.atMSecsSinceEpoch, dst);
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+ QCOMPARE(dat.abbreviation, tzp.abbreviation(dst));
+
+ // Only test transitions if host system supports them
+ if (tzp.hasTransitions()) {
+ QTimeZonePrivate::Data tran = tzp.nextTransition(std);
+ // 2012-03-25 02:00 CET, +1 -> +2
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
+ QCOMPARE(tran.offsetFromUtc, 7200);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ tran = tzp.nextTransition(dst);
+ // 2012-10-28 03:00 CEST, +2 -> +1
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 10, 28), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
+ QCOMPARE(tran.offsetFromUtc, 3600);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+
+ tran = tzp.previousTransition(std);
+ // 2011-10-30 03:00 CEST, +2 -> +1
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2011, 10, 30), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
+ QCOMPARE(tran.offsetFromUtc, 3600);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+
+ tran = tzp.previousTransition(dst);
+ // 2012-03-25 02:00 CET, +1 -> +2 (again)
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
+ QCOMPARE(tran.offsetFromUtc, 7200);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ QTimeZonePrivate::DataList expected;
+ // 2011-03-27 02:00 CET, +1 -> +2
+ tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0),
+ eastOneHour).toMSecsSinceEpoch();
+ tran.offsetFromUtc = 7200;
+ tran.standardTimeOffset = 3600;
+ tran.daylightTimeOffset = 3600;
+ expected << tran;
+ // 2011-10-30 03:00 CEST, +2 -> +1
+ tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)
+ ).toMSecsSinceEpoch();
+ tran.offsetFromUtc = 3600;
+ tran.standardTimeOffset = 3600;
+ tran.daylightTimeOffset = 0;
+ expected << tran;
+ QTimeZonePrivate::DataList result = tzp.transitions(prev, std);
+ QCOMPARE(result.size(), expected.size());
+ for (int i = 0; i < expected.size(); ++i) {
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour),
+ QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour));
+ QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
+ QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
+ QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
+ }
+ }
+}
+
+// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT)
+void tst_QTimeZoneBackend::testEpochTranPrivate(const QTimeZonePrivate &tzp)
+{
+ if (!tzp.hasTransitions())
+ return; // test only viable for transitions
+
+ const auto UTC = QTimeZone::UTC;
+ const auto hour = std::chrono::hours{1};
+ QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch
+ // 1970-04-26 02:00 EST, -5 -> -4
+ const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0),
+ QTimeZone::fromDurationAheadOfUtc(-5 * hour));
+ const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC);
+#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th.
+ QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time());
+#else
+ QCOMPARE(found, after);
+#endif
+ QCOMPARE(tran.offsetFromUtc, -4 * 3600);
+ QCOMPARE(tran.standardTimeOffset, -5 * 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ // Pre-epoch time-zones might not be supported at all:
+ tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch());
+ if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()
+ // Toronto *did* have a transition before 1970 (DST since 1918):
+ && tran.atMSecsSinceEpoch < 0) {
+ // ... but, if they are, we should be able to search back to them:
+ tran = tzp.previousTransition(0); // i.e. last before epoch
+ // 1969-10-26 02:00 EDT, -4 -> -5
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(1969, 10, 26), QTime(2, 0),
+ QTimeZone::fromDurationAheadOfUtc(-4 * hour)));
+ QCOMPARE(tran.offsetFromUtc, -5 * 3600);
+ QCOMPARE(tran.standardTimeOffset, -5 * 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+ } else {
+ // Do not use QSKIP(): that would discard the rest of this sub-test's caller.
+ qDebug() << "No support for pre-epoch time-zone transitions";
+ }
+}
+
+QTEST_APPLESS_MAIN(tst_QTimeZoneBackend)
+#include "tst_qtimezonebackend.moc"
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 6ff905cb1cb..452b7dcef88 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -729,11 +729,12 @@ void tst_Http2::earlyError()
: QHttpNetworkConnection::ConnectionTypeHTTP2;
QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr,
connectionType);
+#if QT_CONFIG(ssl)
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setAllowedNextProtocols({"h2"});
connection.setSslConfiguration(config);
connection.ignoreSslErrors();
-
+#endif
// SETUP manually setup the QHttpNetworkRequest
QHttpNetworkRequest req;
req.setSsl(true);
@@ -809,11 +810,12 @@ void tst_Http2::abortReply()
: QHttpNetworkConnection::ConnectionTypeHTTP2;
QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr,
connectionType);
+#if QT_CONFIG(ssl)
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setAllowedNextProtocols({"h2"});
connection.setSslConfiguration(config);
connection.ignoreSslErrors();
-
+#endif
// SETUP manually setup the QHttpNetworkRequest
QHttpNetworkRequest req;
req.setSsl(true);
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
index fdc2dde7921..c2b03b70d9b 100644
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -10290,7 +10290,7 @@ void tst_QNetworkReply::requestWithTimeout()
QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred);
QCOMPARE(waitForFinish(reply), int(Failure));
QCOMPARE(spy.size(), 1);
- QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError);
+ QCOMPARE(reply->error(), QNetworkReply::TimeoutError);
}
#endif
diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
index 764644bb192..305f48c95ee 100644
--- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
+++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
@@ -2994,6 +2994,9 @@ void tst_QAccessibility::listTest()
listView->setModelColumn(1);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->resize(400,400);
+ listView->setAccessibleName(QLatin1String("list view's accessible name"));
+ listView->setToolTip(QLatin1String("This list view will be used to test accessibility"));
+ listView->setWhatsThis(QLatin1String("What's this list"));
listView->show();
QVERIFY(QTest::qWaitForWindowExposed(listView));
@@ -3001,6 +3004,10 @@ void tst_QAccessibility::listTest()
QCOMPARE(verifyHierarchy(iface), 0);
QCOMPARE((int)iface->role(), (int)QAccessible::List);
+ QCOMPARE(iface->text(QAccessible::Name), QLatin1String("list view's accessible name"));
+ QCOMPARE(iface->text(QAccessible::Description), QLatin1String("This list view will be used to test accessibility"));
+ QCOMPARE(iface->text(QAccessible::Help), QLatin1String("What's this list"));
+ QCOMPARE(iface->text(QAccessible::Value), QString());
QCOMPARE(iface->childCount(), 3);
{
diff --git a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
index df3bf7472d0..50971e27964 100644
--- a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
+++ b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
@@ -233,6 +233,10 @@ void tst_QFiledialog::directoryEnteredSignal()
sidebar->setCurrentIndex(secondItem);
QTest::keyPress(sidebar->viewport(), Qt::Key_Return);
QCOMPARE(spyDirectoryEntered.size(), 1);
+ // ensure signal isn't emitted again when clicking on the already active item
+ QTest::mouseClick(sidebar->viewport(), Qt::LeftButton, {},
+ sidebar->visualRect(secondItem).center());
+ QCOMPARE(spyDirectoryEntered.size(), 1);
spyDirectoryEntered.clear();
// lookInCombo
diff --git a/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp b/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp
index cdbf2011a4a..4f37e2490fd 100644
--- a/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp
+++ b/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp
@@ -53,9 +53,9 @@ void tst_QSidebar::selectUrls()
QSidebar qsidebar;
qsidebar.setModelAndUrls(&fsmodel, urls);
- QSignalSpy spy(&qsidebar, SIGNAL(goToUrl(QUrl)));
+ QSignalSpy spy(&qsidebar, &QSidebar::goToUrl);
qsidebar.selectUrl(urls.at(0));
- QCOMPARE(spy.size(), 0);
+ QCOMPARE(spy.size(), 1);
}
void tst_QSidebar::addUrls()
@@ -160,15 +160,19 @@ void tst_QSidebar::addUrls()
void tst_QSidebar::goToUrl()
{
QList<QUrl> urls;
+ const QUrl tempUrl = QUrl::fromLocalFile(QDir::temp().absolutePath());
urls << QUrl::fromLocalFile(QDir::rootPath())
- << QUrl::fromLocalFile(QDir::temp().absolutePath());
+ << tempUrl;
QFileSystemModel fsmodel;
fsmodel.setIconProvider(&defaultIconProvider);
QSidebar qsidebar;
qsidebar.setModelAndUrls(&fsmodel, urls);
qsidebar.show();
- QSignalSpy spy(&qsidebar, SIGNAL(goToUrl(QUrl)));
+ // switch to a different entry first
+ qsidebar.selectUrl(tempUrl);
+
+ QSignalSpy spy(&qsidebar, &QSidebar::goToUrl);
QTest::mousePress(qsidebar.viewport(), Qt::LeftButton, {},
qsidebar.visualRect(qsidebar.model()->index(0, 0)).center());
QCOMPARE(spy.size(), 1);
diff --git a/util/sbom/cyclonedx/.gitignore b/util/sbom/cyclonedx/.gitignore
new file mode 100644
index 00000000000..140f5f55f12
--- /dev/null
+++ b/util/sbom/cyclonedx/.gitignore
@@ -0,0 +1,6 @@
+__pycache__
+*.egg-info
+.coverage
+.idea
+*.pyc
+build/
diff --git a/util/sbom/cyclonedx/Makefile b/util/sbom/cyclonedx/Makefile
new file mode 100644
index 00000000000..711daf5da22
--- /dev/null
+++ b/util/sbom/cyclonedx/Makefile
@@ -0,0 +1,14 @@
+lint: check format_check
+
+env:
+ uv sync
+
+check:
+ uvx ruff check
+ uvx pyright
+
+format:
+ uvx ruff format
+
+format_check:
+ uvx ruff format --check
diff --git a/util/sbom/cyclonedx/README.md b/util/sbom/cyclonedx/README.md
new file mode 100644
index 00000000000..3a6394f4e74
--- /dev/null
+++ b/util/sbom/cyclonedx/README.md
@@ -0,0 +1,54 @@
+# Qt CycloneDX SBOM helper tool
+
+This repository contains a Python script to create a CycloneDX Software
+Bill of Materials (SBOM) document for Qt-based projects.
+
+## Requirements
+
+- [Python 3.9](https://www.python.org/downloads/),
+- `pip` to manage Python packages.
+- [cyclonedx-python-lib package](https://pypi.org/project/cyclonedx-python-lib/) can be installed via pip
+- optional [tomli package](https://pypi.org/project/tomli/) if using Python 3.9 and the vendored
+ tomli package is causing issues
+
+```sh
+pip install 'cyclonedx-python-lib[json-validation]' tomli
+```
+or
+```
+pip install . # in the current directory to parse the `pyproject.toml` dependencies
+```
+
+## Description
+
+The tool is not meant to be run standalone. Instead, the Qt CMake build system generates an
+intermediate TOML file during the build process, which is then processed by the provided Python
+script to create the final CycloneDX SBOM in JSON format.
+
+## Development
+
+The script is using [uv](https://docs.astral.sh/uv/getting-started/installation/) to manage
+a virtual environment for development. Install it before running the commands below.
+
+Run
+
+```sh
+# Create a virtual environment with with the project dependencies using `uv` in the current dir
+make env
+
+# Optionally install the package in editable mode, for IDE support if needed.
+uv pip install -e .
+
+# Run the script
+python ./qt_cyclonedx_generator/qt_cyclonedx_generator.py
+```
+
+To format the source code run:
+```sh
+make format # uses uvx ruff format
+```
+
+To run the lints:
+```sh
+make lint # uses uvx ruff check and pyright
+```
diff --git a/util/sbom/cyclonedx/pyproject.toml b/util/sbom/cyclonedx/pyproject.toml
new file mode 100644
index 00000000000..19500012555
--- /dev/null
+++ b/util/sbom/cyclonedx/pyproject.toml
@@ -0,0 +1,54 @@
+[project]
+name = "qt-cyclonedx-generator"
+version = "0.1.0"
+description = "Tool to generate a CycloneDX v1.6 SBOM for a Qt project."
+readme = "README.md"
+
+requires-python = ">=3.9"
+
+dependencies = [
+ "cyclonedx-python-lib[json-validation]>=10.0.0",
+ 'tomli ; python_version < "3.11"'
+]
+
+# Development dependencies.
+[dependency-groups]
+dev = [
+ "setuptools>=65",
+ "ruff>=0.11.2",
+ "tomli>=2.3.0",
+ "pyright>=1.1.406",
+]
+
+[project.scripts]
+qt_cyclonedx_generator = "qt_cyclonedx_generator.qt_cyclonedx_generator:main"
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[tool.ruff.lint]
+ignore = ["E402"]
+
+[tool.pyright]
+# To find the imports created by uv
+venvPath = "."
+venv = ".venv"
+
+# Exclude the default folders, plus the build one.
+exclude = [
+ "**/node_modules",
+ "**/__pycache__",
+ "**/.*",
+ "build/*",
+]
+
+# Because some of the toml libraries might not be found
+reportMissingImports = "none"
+
+# Strict is too strict.
+typeCheckingMode = "basic"
+
+pythonVersion = "3.9"
+pythonPlatform = "All"
+
diff --git a/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py b/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py
new file mode 100644
index 00000000000..50aa992f8ba
--- /dev/null
+++ b/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
diff --git a/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py b/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py
new file mode 100644
index 00000000000..3b04a47d714
--- /dev/null
+++ b/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py
@@ -0,0 +1,881 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import importlib.util
+import logging
+import sys
+
+
+def setup_log() -> logging.Logger:
+ logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)
+ log_object = logging.getLogger("qt-cyclonedx-generator")
+ log_object.setLevel(logging.INFO)
+ return log_object
+
+
+log = setup_log()
+
+
+def import_tomllib():
+ """Import the TOML library with fallback options for different Python versions.
+
+ Stock toml lib is available on Python 3.11+,
+ if not found try the tomli library, and if that isn't found
+ try to use the pip vendored tomli library.
+
+ Returns:
+ Tuple of (tomllib_module, success_flag)
+ """
+ if sys.version_info >= (3, 11):
+ import tomllib as tomllib_local
+
+ return tomllib_local, True
+ else:
+ try:
+ import tomli as tomllib_local
+
+ return tomllib_local, True
+ except ModuleNotFoundError:
+ try: # noinspection PyProtectedMember
+ import pip._vendor.tomli as tomllib_local
+
+ return tomllib_local, True
+ except ModuleNotFoundError:
+ return None, False
+
+
+tomllib, tomllib_available = import_tomllib()
+
+
+def check_package_dependencies() -> None:
+ """Check if required package dependencies are available."""
+ missing_packages = []
+
+ if not tomllib_available:
+ missing_packages.append("tomli")
+
+ if importlib.util.find_spec("cyclonedx") is None:
+ missing_packages.append("cyclonedx")
+
+ if importlib.util.find_spec("packageurl") is None:
+ missing_packages.append("packageurl")
+
+ if missing_packages:
+ log.error(f"Missing required dependencies: {', '.join(missing_packages)}")
+ log.error(f"Please install with: `pip install {' '.join(missing_packages)}`")
+ sys.exit(1)
+
+
+check_package_dependencies()
+
+import argparse
+from datetime import datetime, timezone
+from license_expression import get_spdx_licensing
+from pathlib import Path
+from packageurl import PackageURL
+from typing import (
+ TYPE_CHECKING,
+ Optional,
+ Any,
+ TypedDict,
+ Union,
+ cast,
+ Callable,
+)
+from uuid import UUID
+
+from cyclonedx.factory.license import LicenseFactory
+from cyclonedx.exception import MissingOptionalDependencyException
+from cyclonedx.model import (
+ XsUri,
+ ExternalReference,
+ ExternalReferenceType,
+ Property,
+ AttachedText,
+)
+from cyclonedx.model.bom import Bom
+from cyclonedx.model.component import Component, ComponentType
+from cyclonedx.model.contact import OrganizationalEntity, OrganizationalContact
+from cyclonedx.model.license import LicenseAcknowledgement, LicenseExpression
+from cyclonedx.model.lifecycle import PredefinedLifecycle, LifecyclePhase
+from cyclonedx.output.json import JsonV1Dot6
+from cyclonedx.schema import SchemaVersion
+from cyclonedx.validation.json import JsonStrictValidator
+
+if TYPE_CHECKING:
+ from cyclonedx.output.json import Json as JsonOutputter
+
+
+class TomlRequiredFieldError(Exception):
+ pass
+
+
+class MissingTomlFileError(Exception):
+ pass
+
+
+class SBOMWriteFailedError(Exception):
+ pass
+
+
+class ValidationFailedError(Exception):
+ pass
+
+
+class ValidationDependencyMissingError(Exception):
+ pass
+
+
+# TypedDict definitions for TOML data structures
+class CyclonePropertyDict(TypedDict):
+ name: str
+ value: str
+
+
+class LicenseDict(TypedDict):
+ license_id: str
+ text: str
+
+
+class RootComponentDict(TypedDict):
+ name: str
+ spdx_id: str
+ # Optional fields (since TypedDict doesn't support NotRequired in Python 3.9)
+ # We'll use .get() to access these safely
+ version: str
+ description: str
+ download_location: str
+ build_date: str
+ supplier: str
+ supplier_url: str
+ serial_number_uuid: str
+
+
+class ComponentDict(TypedDict):
+ name: str
+ spdx_id: str
+ # Optional fields
+ version: str
+ component_type: str
+ copyright: str
+ external_bom_link: str
+ supplier: str
+ purl_list: list[str]
+ cpe_list: list[str]
+ properties: list[CyclonePropertyDict]
+ dependencies: list[str]
+
+
+class BuildToolDict(TypedDict):
+ # Optional fields
+ name: str
+ component_type: str
+ version: str
+ description: str
+
+
+class TomlDataDict(TypedDict):
+ root_component: RootComponentDict
+ # Optional fields
+ project_build_tools: list[BuildToolDict]
+ components: list[ComponentDict]
+ licenses: list[LicenseDict]
+
+
+# Type alias for licenses dictionary
+LicensesDict = dict[str, LicenseDict]
+
+
+def validate_required_field(
+ data: Union[dict[str, Any], TomlDataDict, ComponentDict, RootComponentDict],
+ field: str,
+ context: str,
+) -> Any:
+ """Validate that a required field exists in the data dictionary.
+
+ Args:
+ data: The dictionary to check
+ field: The required field name
+ context: Description of where this validation is happening (for error messages)
+
+ Returns:
+ The value of the required field
+
+ Raises:
+ TomlRequiredFieldError: If the required field is missing
+ """
+ if field not in data:
+ raise TomlRequiredFieldError(f"Missing required field '{field}' in {context}")
+ return data[field]
+
+
+def _parse_build_date(date_str: str) -> datetime:
+ """Parse build date string, handling Python 3.9+ compatibility for Z suffix."""
+ override_to_utc = False
+ if sys.version_info < (3, 11) and date_str.endswith("Z"):
+ # Remove the Z at the end to support Python 3.9+, because
+ # datetime.fromisoformat learned to parse the Z in 3.11 only.
+ # In that case we need to override the timezone to be UTC manually.
+ date_str = date_str[:-1]
+ override_to_utc = True
+
+ build_date = datetime.fromisoformat(date_str)
+ if override_to_utc:
+ build_date = build_date.replace(tzinfo=timezone.utc)
+ return build_date
+
+
+def assign_optional_field(
+ source_data: Union[
+ dict[str, Any], TomlDataDict, ComponentDict, RootComponentDict, BuildToolDict
+ ],
+ target_args: dict[str, Any],
+ source_field: str,
+ target_field: Optional[str] = None,
+ transform: Optional[Callable[[Any], Any]] = None,
+) -> None:
+ """Assign an optional field from source data to target args if it exists.
+
+ Args:
+ source_data: The source dictionary to read from
+ target_args: The target dictionary to write to
+ source_field: The field name in the source data
+ target_field: The field name in the target args (defaults to source_field if None)
+ transform: Optional function to transform the value before assignment
+ """
+ if target_field is None:
+ target_field = source_field
+
+ if value := source_data.get(source_field):
+ if transform:
+ value = transform(value)
+ # Only assign if the transformed value is not None
+ if value is not None:
+ target_args[target_field] = value
+
+
+def main() -> None:
+ parser = get_cli_parser()
+ args = get_cli_args(parser)
+
+ handle_verbose_logging(args.verbose)
+
+ try:
+ create_cyclone_dx_sbom(
+ args.input_path,
+ args.output_path,
+ args.validate_json,
+ args.validate_json_required,
+ )
+ except (
+ TomlRequiredFieldError,
+ MissingTomlFileError,
+ SBOMWriteFailedError,
+ ValidationFailedError,
+ ValidationDependencyMissingError,
+ ) as e:
+ log.error(str(e))
+ sys.exit(1)
+
+
+def get_cli_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter,
+ description="""
+Generates a CycloneDX v1.6 SBOM using intermediate information from a toml file created by Qt's
+SBOM generator.
+
+Example usage:
+python qt_cyclonedx_generator.py --input-path /qt/qtbase.cdx.toml --output-path /qt/qtbase.cdx.json
+""",
+ )
+ parser.add_argument(
+ "-i",
+ "--input-path",
+ type=Path,
+ help="Path to the intermediate toml file.",
+ required=True,
+ )
+ parser.add_argument(
+ "-o",
+ "--output-path",
+ type=Path,
+ help="Path to the CycloneDX output file.",
+ required=True,
+ )
+ parser.add_argument(
+ "--validate-json",
+ action="store_true",
+ help="Validate the generated CycloneDX JSON output against the CycloneDX schema.",
+ )
+ parser.add_argument(
+ "--validate-json-required",
+ action="store_true",
+ help="Error out if validation dependencies are missing.",
+ )
+ parser.add_argument(
+ "--verbose",
+ action="store_true",
+ help="Enable verbose logging output.",
+ )
+
+ return parser
+
+
+def get_cli_args(parser: argparse.ArgumentParser) -> argparse.Namespace:
+ args = parser.parse_args(args=None if sys.argv[1:] else ["--help"])
+ return args
+
+
+def handle_verbose_logging(verbose: bool) -> None:
+ if verbose:
+ log.setLevel(logging.DEBUG)
+
+
+# Reads a toml file and generates a CycloneDX SBOM file.
+def create_cyclone_dx_sbom(
+ input_path: Path,
+ output_path: Path,
+ validate_json=False,
+ validate_json_required=False,
+) -> None:
+ if not input_path or not input_path.exists():
+ raise MissingTomlFileError(
+ f"Failed to read toml file. Input file '{input_path}' does not exist."
+ )
+
+ cydx_bom = get_cydx_bom()
+ toml_data = read_toml_file(input_path)
+ process_toml(toml_data, cydx_bom)
+ json = write_cydx_sbom(cydx_bom, output_path)
+ if validate_json:
+ validate_cydx_bom(json, validate_json_required)
+
+
+def read_toml_file(input_path: Path) -> TomlDataDict:
+ log.debug(f"Reading toml file from '{input_path}'")
+ with open(input_path, "rb") as f:
+ # Manually read and decode the file contents, because older
+ # tomli versions vendored in pip don't handle this properly.
+ buffer = f.read()
+ toml_str = buffer.decode()
+ return cast(TomlDataDict, cast(Any, tomllib).loads(toml_str))
+
+
+# Serializes the CycloneDX BOM to JSON and writes it to the output path.
+def write_cydx_sbom(cydx_bom: Bom, output_path: Path) -> str:
+ json_outputter: JsonOutputter = JsonV1Dot6(cydx_bom)
+ log.debug(f"Serializing CycloneDX BOM to JSON at '{output_path}'")
+ serialized_json = json_outputter.output_as_string(indent=2)
+
+ try:
+ with open(output_path, "w") as f:
+ f.write(serialized_json)
+ except (OSError, IOError) as e:
+ raise SBOMWriteFailedError(f"Failed to write SBOM to '{output_path}': {e}")
+
+ return serialized_json
+
+
+def validate_cydx_bom(serialized_json: str, validate_json_required: bool) -> None:
+ log.debug("Validating CycloneDX JSON output against schema v1.6")
+ json_validator = JsonStrictValidator(SchemaVersion.V1_6)
+ try:
+ validation_errors = json_validator.validate_str(serialized_json)
+ if validation_errors:
+ raise ValidationFailedError(f"JSON validation failed: {validation_errors}")
+ except MissingOptionalDependencyException as error:
+ missing_dep_message = (
+ f"JSON-validation was failed due to missing Python dependency: {error}"
+ )
+ if validate_json_required:
+ raise ValidationDependencyMissingError(missing_dep_message)
+ else:
+ log.warning(missing_dep_message)
+
+
+def get_cydx_bom() -> Bom:
+ bom = Bom()
+ return bom
+
+
+# Reads toml data created by Qt's SBOM generator and populates the CycloneDX BOM.
+# At minimum, expects a root component dict, and a list of components.
+def process_toml(toml_data: TomlDataDict, cydx_bom: Bom) -> None:
+ components: dict[str, Component] = {}
+
+ log.debug("Processing TOML data.")
+
+ root_component_object, main_supplier, project_spdx_id = handle_root_component(
+ cydx_bom, toml_data
+ )
+ components[project_spdx_id] = root_component_object
+
+ if "project_build_tools" in toml_data:
+ project_build_tools = toml_data["project_build_tools"]
+ handle_project_build_tools(cydx_bom, project_build_tools)
+
+ external_components_spdx_ids = set()
+ components_toml = []
+ license_factory = LicenseFactory()
+
+ # Create one component per CMake target (in spdx it would be a spdx package).
+ if "components" in toml_data:
+ components_toml = toml_data["components"]
+
+ components_len = len(components_toml)
+ log.debug(f"Processing {components_len} components from TOML data.")
+
+ licenses_dict: LicensesDict = {}
+ if "licenses" in toml_data:
+ licenses_toml = toml_data["licenses"]
+ for license_entry in licenses_toml:
+ license_id = license_entry["license_id"]
+ licenses_dict[license_id] = license_entry
+
+ for component_toml in components_toml:
+ process_component(
+ cydx_bom,
+ component_toml,
+ licenses_dict,
+ components,
+ external_components_spdx_ids,
+ main_supplier,
+ license_factory,
+ )
+
+ # Register dependencies for each component.
+ log.debug("Processing component dependencies.")
+ for component_toml in components_toml:
+ handle_component_dependencies(
+ cydx_bom,
+ component_toml,
+ components,
+ external_components_spdx_ids,
+ root_component_object,
+ )
+
+
+# Creates the root component of the BOM.
+# The CycloneDX root component is mapped to the Qt spdx project package e.g. qtbase.
+def handle_root_component(
+ cydx_bom: Bom, toml_data: TomlDataDict
+) -> tuple[Component, Optional[OrganizationalEntity], str]:
+ root_components_args: dict[str, Any] = {
+ "type": ComponentType.FRAMEWORK,
+ }
+
+ root_component = validate_required_field(toml_data, "root_component", "TOML data")
+ error_context = "root_component"
+ project_name = validate_required_field(root_component, "name", error_context)
+ root_components_args["name"] = project_name
+
+ project_spdx_id = validate_required_field(root_component, "spdx_id", error_context)
+ root_components_args["bom_ref"] = project_spdx_id
+
+ # Process optional fields
+ assign_optional_field(root_component, root_components_args, "version")
+ assign_optional_field(root_component, root_components_args, "description")
+
+ # Process optional fields with transformations
+ assign_optional_field(
+ root_component,
+ root_components_args,
+ source_field="download_location",
+ target_field="external_references",
+ transform=lambda url: [
+ ExternalReference(
+ type=ExternalReferenceType.DISTRIBUTION, # pyright: ignore [reportCallIssue]
+ url=XsUri(url), # pyright: ignore [reportCallIssue]
+ )
+ ],
+ )
+
+ if project_build_date_str := root_component.get("build_date"):
+ project_build_date = _parse_build_date(project_build_date_str)
+ cydx_bom.metadata.timestamp = project_build_date # pyright: ignore [reportAttributeAccessIssue]
+
+ if serial_number_uuid := root_component.get("serial_number_uuid"):
+ cydx_bom.serial_number = UUID(serial_number_uuid) # pyright: ignore [reportAttributeAccessIssue]
+
+ project_supplier = root_component.get("supplier")
+ project_supplier_url = root_component.get("supplier_url")
+
+ # TODO: This is currently not supplied by the build system API.
+ project_supplier_email = root_component.get("supplier_email")
+
+ main_supplier = create_organization_entity(project_supplier, project_supplier_url)
+ main_author = create_organization_contact(project_supplier, project_supplier_email)
+
+ if main_supplier:
+ cydx_bom.metadata.manufacturer = main_supplier # pyright: ignore [reportAttributeAccessIssue]
+ cydx_bom.metadata.supplier = main_supplier # pyright: ignore [reportAttributeAccessIssue]
+
+ # FOSSA complains if author is not set.
+ cydx_bom.metadata.authors = [main_author] # pyright: ignore [reportAttributeAccessIssue]
+
+ root_components_args["supplier"] = main_supplier
+ root_components_args["manufacturer"] = main_supplier
+
+ root_component_object = Component(**root_components_args)
+ cydx_bom.metadata.component = root_component_object # pyright: ignore [reportAttributeAccessIssue]
+
+ cydx_bom.metadata.lifecycles = [PredefinedLifecycle(phase=LifecyclePhase.BUILD)] # pyright: ignore [reportAttributeAccessIssue, reportCallIssue]
+
+ root_component_name_str = f"{root_component_object.name}" # pyright: ignore [reportAttributeAccessIssue]
+ root_component_version_str = f"(version {root_component_object.version})" # pyright: ignore [reportAttributeAccessIssue]
+ log.debug(
+ f"Created root component of the BOM. "
+ f"{root_component_name_str} ${root_component_version_str}"
+ )
+
+ return root_component_object, main_supplier, project_spdx_id
+
+
+# Handles project build tools and adds them as components to the BOM metadata tools section.
+# This is for tools like CMake, Ninja, etc.
+# Unclear yet if compilers and linkers should also go here.
+def handle_project_build_tools(
+ cydx_bom: Bom, project_build_tools_toml: list[BuildToolDict]
+) -> None:
+ log.debug("Processing project build tools.")
+
+ for build_tool_toml in project_build_tools_toml:
+ component_args = {}
+
+ assign_optional_field(build_tool_toml, component_args, source_field="name")
+ assign_optional_field(
+ build_tool_toml,
+ component_args,
+ source_field="component_type",
+ target_field="type",
+ transform=get_component_type_for_str,
+ )
+ assign_optional_field(build_tool_toml, component_args, "version")
+ assign_optional_field(build_tool_toml, component_args, "description")
+
+ component = Component(**component_args)
+ log.debug(
+ f"Created build tool component '{component.name} (version {component.version})'." # pyright: ignore [reportAttributeAccessIssue]
+ )
+ cydx_bom.metadata.tools.components.add(component) # pyright: ignore [reportAttributeAccessIssue]
+
+
+# Creates a CycloneDX component from the toml component data and adds it to the BOM.
+# The SPDX equivalent would be a SPDX package backed by a CMake target.
+# We mostly reuse spdx ids as bom-refs, rather than generating new ones. Makes it
+# easier to cross-check with the original SPDX data. And it's not like CycloneDX forbids it.
+def process_component(
+ cydx_bom: Bom,
+ component_toml: ComponentDict,
+ licenses_dict: LicensesDict,
+ components: dict[str, Component],
+ external_components_spdx_ids: set[str],
+ main_supplier: Optional[OrganizationalEntity],
+ license_factory: LicenseFactory,
+) -> None:
+ component_args = {}
+ properties = []
+
+ error_context = "component"
+ component_name = validate_required_field(component_toml, "name", error_context)
+ component_args["name"] = component_name
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ "version",
+ transform=lambda v: v if v != "unknown" else None,
+ )
+
+ # A spdx id maps to a bom-ref in CycloneDX.
+ spdx_id = validate_required_field(component_toml, "spdx_id", error_context)
+ component_args["bom_ref"] = spdx_id
+
+ # Some components might be external to the current BOM.
+ # For example for the QtSvg module, QtCore is external, because it's built in qtbase,
+ # not qtsvg. To be able to declare a dependency on QtCore, we still need to add it as a
+ # component. But we add a BOM external reference link to it, signalling that the full
+ # component information is in a different BOM.
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="external_bom_link",
+ target_field="external_references",
+ transform=lambda link: [
+ ExternalReference(
+ type=ExternalReferenceType.BOM, # pyright: ignore [reportCallIssue]
+ url=XsUri(link), # pyright: ignore [reportCallIssue]
+ )
+ ],
+ )
+ if component_toml.get("external_bom_link"):
+ external_components_spdx_ids.add(spdx_id)
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="component_type",
+ target_field="type",
+ transform=get_component_type_for_str,
+ )
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="copyright",
+ target_field="copyright",
+ )
+
+ handle_component_licenses(
+ component_toml, licenses_dict, license_factory, component_args
+ )
+
+ purl_list = component_toml.get("purl_list", [])
+ cpe_list = component_toml.get("cpe_list", [])
+ cpe_purl_args, extra_cpe_purl_properties = get_purl_and_cpe_component_args(
+ purl_list, cpe_list
+ )
+ if extra_cpe_purl_properties:
+ properties.extend(extra_cpe_purl_properties)
+
+ if supplier_name := component_toml.get("supplier"):
+ # If the specified supplier is the same as the main supplier, reuse it, because it has a
+ # url.
+ if main_supplier and supplier_name == main_supplier.name: # pyright: ignore [reportAttributeAccessIssue]
+ component_args["supplier"] = main_supplier
+ else:
+ component_supplier = create_organization_entity(supplier_name)
+ component_args["supplier"] = component_supplier
+
+ # Properties are extra vendor-specific information that are not part of the official spec.
+ if properties_list := component_toml.get("properties"):
+ for prop in properties_list:
+ properties.append(
+ Property(
+ name=prop["name"], # pyright: ignore [reportCallIssue]
+ value=prop["value"], # pyright: ignore [reportCallIssue]
+ )
+ )
+ if properties:
+ component_args["properties"] = properties
+
+ component = Component(**component_args, **cpe_purl_args)
+
+ log.debug(f"Created component '{component.name} (version {component.version})'.") # pyright: ignore [reportAttributeAccessIssue]
+
+ cydx_bom.components.add(component) # pyright: ignore [reportAttributeAccessIssue]
+ components[spdx_id] = component
+
+
+# Creates dependencies between components in the BOM, as well as between the root component
+# and non-external components that are part of the current BOM.
+def handle_component_dependencies(
+ cydx_bom: Bom,
+ component_toml: ComponentDict,
+ components: dict[str, Component],
+ external_components_spdx_ids: set[str],
+ root_component_object: Component,
+) -> None:
+ component_id = component_toml["spdx_id"]
+ component_object = components.get(component_id)
+
+ # If the component is not external, register it as a dependency of the root component,
+ # aka the root component "contains" the non-external component.
+ if component_id not in external_components_spdx_ids:
+ cydx_bom.register_dependency(root_component_object, [component_object]) # pyright: ignore [reportAttributeAccessIssue, reportArgumentType]
+
+ # Register dependencies declared in the toml.
+ # CycloneDX does not support different dependency types, like SPDX does.
+ if dependencies := component_toml.get("dependencies"):
+ for dependency_spdx_id in dependencies:
+ if dependency_spdx_id not in components:
+ log.warning(
+ f"Component {component_id} has a dependency on unknown component {dependency_spdx_id}, skipping."
+ )
+ continue
+ dep_component = components[dependency_spdx_id]
+ cydx_bom.register_dependency(component_object, [dep_component]) # pyright: ignore [reportAttributeAccessIssue, reportArgumentType]
+ log.debug(
+ f"Created dependency from '{component_id}' to '{dependency_spdx_id}'."
+ )
+
+
+# Handles PURL and CPE entries for a component.
+# The spec says that a component can have exactly one PURL. Qt generates several, because it's
+# unclear what will end up being useful for tooling. The other PURLs are added as extra
+# component properties. Same for CPEs.
+# The field names were picked to match the names generated by the python spdx to cyclonedx converter.
+def get_purl_and_cpe_component_args(
+ purl_list: list[str],
+ cpe_list: list[str],
+) -> tuple[dict[str, Any], list[Property]]:
+ component_args = {}
+ properties = []
+
+ purl_objects = [PackageURL.from_string(purl) for purl in purl_list if purl]
+
+ if len(purl_objects) > 0:
+ # First PURL will be the main one.
+ purl = purl_objects.pop(0)
+ component_args["purl"] = purl
+
+ # Rest will be properties.
+ for purl_entry in purl_objects:
+ properties.append(
+ Property(
+ name="spdx:external-reference:package-manager:purl", # pyright: ignore [reportCallIssue]
+ value=str(purl_entry), # pyright: ignore [reportCallIssue]
+ )
+ )
+
+ if len(cpe_list) > 0:
+ # First CPE will be the main one.
+ cpe = cpe_list.pop(0)
+ component_args["cpe"] = cpe
+
+ # Rest will be properties.
+ for cpe_entry in cpe_list:
+ properties.append(
+ Property(
+ name="spdx:external-reference:security:cpe23", # pyright: ignore [reportCallIssue]
+ value=str(cpe_entry), # pyright: ignore [reportCallIssue]
+ )
+ )
+
+ return component_args, properties
+
+
+# Handles licenses for a component.
+def handle_component_licenses(
+ component_toml: ComponentDict,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ component_args: dict[str, Any],
+) -> None:
+ def transformer(license_expression: str) -> Optional[list[LicenseExpression]]:
+ acknowledgement = LicenseAcknowledgement.CONCLUDED
+ return convert_license_expression_license_objects(
+ license_expression, licenses_dict, license_factory, acknowledgement
+ )
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="license_concluded_expression",
+ target_field="licenses",
+ transform=transformer,
+ )
+
+
+# Transforms a spdx license expression into CycloneDX license objects.
+def convert_license_expression_license_objects(
+ license_expression: str,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> Optional[list[LicenseExpression]]:
+ if "LicenseRef-" in license_expression:
+ return handle_custom_license_ref_expression(
+ license_expression, licenses_dict, license_factory, acknowledgement
+ )
+ else:
+ return handle_standard_license_ref_expression(
+ license_expression, license_factory, acknowledgement
+ )
+
+
+# CycloneDX v1.6 doesn't support an expression that contains LicenseRef- in it.
+# CycloneDX v1.7 seemingly does.
+# https://github.com/CycloneDX/specification/issues/454
+# https://github.com/CycloneDX/specification/issues/554
+# https://github.com/CycloneDX/specification/pull/582
+#
+# Split LicenseRef-* expressions into separate licenses, by which we lose the expression structure,
+# but at least preserve the license information.
+#
+# TODO: Improve this when CycloneDX v1.7 generation is added.
+def handle_custom_license_ref_expression(
+ license_expression: str,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> list[LicenseExpression]:
+ license_objects = []
+ licensing = get_spdx_licensing()
+ symbols = licensing.license_symbols(license_expression)
+
+ for symbol in symbols:
+ license_text = None
+ # This can be either a known public spdx id, in which case we won't have text for.
+ # Or it can be a LicenseRef-* for which we have license text in the licenses_dict.
+ maybe_license_id = symbol.key # pyright: ignore [reportAttributeAccessIssue]
+
+ if maybe_license_id in licenses_dict:
+ # Get the text for the LicenseRef-* entry.
+ maybe_license_text = licenses_dict[maybe_license_id].get("text")
+ license_text = AttachedText(content=maybe_license_text) # pyright: ignore [reportCallIssue]
+
+ license_obj = license_factory.make_from_string(
+ maybe_license_id,
+ license_text=license_text,
+ license_acknowledgement=acknowledgement,
+ )
+ license_objects.append(license_obj)
+
+ return license_objects
+
+
+# Transforms a spdx license expression without LicenseRef-* into a CycloneDX license object.
+def handle_standard_license_ref_expression(
+ license_expression: str,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> list[LicenseExpression]:
+ license_obj = license_factory.make_with_expression(
+ expression=license_expression, acknowledgement=acknowledgement
+ )
+ return [license_obj]
+
+
+# Transform from enum string value to actual enum.
+def get_component_type_for_str(component_type_str: str) -> ComponentType:
+ return ComponentType(component_type_str)
+
+
+# Creates an OrganizationalEntity for a supplier/manufacturer.
+def create_organization_entity(
+ supplier_name: Optional[str] = None, supplier_url: Optional[str] = None
+) -> Optional[OrganizationalEntity]:
+ args = {}
+ if supplier_name:
+ args["name"] = supplier_name
+ if supplier_url:
+ args["urls"] = [XsUri(supplier_url)] # pyright: ignore [reportCallIssue]
+
+ # Check if args has at least one entry.
+ if len(args) > 0:
+ return OrganizationalEntity(**args)
+
+ return None
+
+
+# Creates an OrganizationalContact for a supplier/manufacturer.
+def create_organization_contact(
+ contact_name: Optional[str] = None, contact_email: Optional[str] = None
+) -> Optional[OrganizationalContact]:
+ args = {}
+ if contact_name:
+ args["name"] = contact_name
+ if contact_email:
+ args["email"] = contact_email # pyright: ignore [reportCallIssue]
+
+ # Check if args has at least one entry.
+ if len(args) > 0:
+ return OrganizationalContact(**args)
+
+ return None
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/sbom/cyclonedx/uv.lock b/util/sbom/cyclonedx/uv.lock
new file mode 100644
index 00000000000..0b16a99e481
--- /dev/null
+++ b/util/sbom/cyclonedx/uv.lock
@@ -0,0 +1,625 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.10'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "arrow"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "boolean-py"
+version = "5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" },
+]
+
+[[package]]
+name = "cyclonedx-python-lib"
+version = "11.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "license-expression" },
+ { name = "packageurl-python" },
+ { name = "py-serializable" },
+ { name = "sortedcontainers" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/3b/c130406ae35bd264be7f816afbb01f95ccc4a63a5eeadfea5249d110efb2/cyclonedx_python_lib-11.3.0.tar.gz", hash = "sha256:ee3135a59ead90397339ea585b341a7ea9c53e734ad13f48a9865cfcf0f1139a", size = 1046415, upload-time = "2025-10-22T11:03:04.883Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/28/3bebcfe7f87e485164fba1dca08b6307bdbee6cc45d6bf57343552346f85/cyclonedx_python_lib-11.3.0-py3-none-any.whl", hash = "sha256:08094495d0cbbaddbe605f60f6e48ae98d29263386d7926df83ffe9bdea55f78", size = 383140, upload-time = "2025-10-22T11:03:03.102Z" },
+]
+
+[package.optional-dependencies]
+json-validation = [
+ { name = "jsonschema", extra = ["format-nongpl"] },
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "fqdn"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "isoduration"
+version = "20.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "arrow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
+]
+
+[[package]]
+name = "jsonpointer"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.25.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
+]
+
+[package.optional-dependencies]
+format-nongpl = [
+ { name = "fqdn" },
+ { name = "idna" },
+ { name = "isoduration" },
+ { name = "jsonpointer" },
+ { name = "rfc3339-validator" },
+ { name = "rfc3986-validator" },
+ { name = "rfc3987-syntax" },
+ { name = "uri-template" },
+ { name = "webcolors" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "lark"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/37/a13baf0135f348af608c667633cbe5d13aa2c5c15a56ae9ad3e6cba45ae3/lark-1.3.0.tar.gz", hash = "sha256:9a3839d0ca5e1faf7cfa3460e420e859b66bcbde05b634e73c369c8244c5fa48", size = 259551, upload-time = "2025-09-22T13:45:05.072Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl", hash = "sha256:80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831", size = 113002, upload-time = "2025-09-22T13:45:03.747Z" },
+]
+
+[[package]]
+name = "license-expression"
+version = "30.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "boolean-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+]
+
+[[package]]
+name = "packageurl-python"
+version = "0.17.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/f0/de0ac00a4484c0d87b71e3d9985518278d89797fa725e90abd3453bccb42/packageurl_python-0.17.5.tar.gz", hash = "sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61", size = 43832, upload-time = "2025-08-06T14:08:20.235Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/be/78/9dbb7d2ef240d20caf6f79c0f66866737c9d0959601fd783ff635d1d019d/packageurl_python-0.17.5-py3-none-any.whl", hash = "sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321", size = 30544, upload-time = "2025-08-06T14:08:19.055Z" },
+]
+
+[[package]]
+name = "py-serializable"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "defusedxml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" },
+]
+
+[[package]]
+name = "pyright"
+version = "1.1.406"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodeenv" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "qt-cyclonedx-generator"
+version = "0.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "cyclonedx-python-lib", extra = ["json-validation"] },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pyright" },
+ { name = "ruff" },
+ { name = "setuptools" },
+ { name = "tomli" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "cyclonedx-python-lib", extras = ["json-validation"], specifier = ">=10.0.0" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pyright", specifier = ">=1.1.406" },
+ { name = "ruff", specifier = ">=0.11.2" },
+ { name = "setuptools", specifier = ">=65" },
+ { name = "tomli", specifier = ">=2.3.0" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version < '3.10'" },
+ { name = "rpds-py", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
+]
+
+[[package]]
+name = "rfc3986-validator"
+version = "0.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
+]
+
+[[package]]
+name = "rfc3987-syntax"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "lark" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.27.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" },
+ { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" },
+ { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" },
+ { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" },
+ { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" },
+ { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" },
+ { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" },
+ { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" },
+ { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" },
+ { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" },
+ { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" },
+ { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" },
+ { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" },
+ { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" },
+ { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" },
+ { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" },
+ { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" },
+ { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" },
+ { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" },
+ { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" },
+ { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" },
+ { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" },
+ { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" },
+ { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" },
+ { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" },
+ { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" },
+ { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" },
+ { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" },
+ { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" },
+ { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" },
+ { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140, upload-time = "2025-08-27T12:15:05.441Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086, upload-time = "2025-08-27T12:15:07.404Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117, upload-time = "2025-08-27T12:15:09.275Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520, upload-time = "2025-08-27T12:15:10.727Z" },
+ { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657, upload-time = "2025-08-27T12:15:12.613Z" },
+ { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967, upload-time = "2025-08-27T12:15:14.113Z" },
+ { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372, upload-time = "2025-08-27T12:15:15.842Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264, upload-time = "2025-08-27T12:15:17.388Z" },
+ { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691, upload-time = "2025-08-27T12:15:19.144Z" },
+ { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989, upload-time = "2025-08-27T12:15:21.087Z" },
+ { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835, upload-time = "2025-08-27T12:15:22.696Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227, upload-time = "2025-08-27T12:15:24.278Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899, upload-time = "2025-08-27T12:15:25.926Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725, upload-time = "2025-08-27T12:15:27.398Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" },
+ { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" },
+ { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" },
+ { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" },
+ { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" },
+ { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" },
+ { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778, upload-time = "2025-08-27T12:16:13.851Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394, upload-time = "2025-08-27T12:16:15.609Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348, upload-time = "2025-08-27T12:16:17.251Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159, upload-time = "2025-08-27T12:16:19.251Z" },
+ { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775, upload-time = "2025-08-27T12:16:20.929Z" },
+ { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633, upload-time = "2025-08-27T12:16:22.548Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867, upload-time = "2025-08-27T12:16:24.29Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791, upload-time = "2025-08-27T12:16:25.954Z" },
+ { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525, upload-time = "2025-08-27T12:16:27.659Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255, upload-time = "2025-08-27T12:16:29.343Z" },
+ { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384, upload-time = "2025-08-27T12:16:31.005Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959, upload-time = "2025-08-27T12:16:32.73Z" },
+ { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784, upload-time = "2025-08-27T12:16:34.428Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.14.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" },
+ { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" },
+ { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" },
+ { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" },
+ { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" },
+ { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" },
+ { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" },
+ { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
+]
+
+[[package]]
+name = "uri-template"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
+]
+
+[[package]]
+name = "webcolors"
+version = "24.11.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" },
+]