diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 41b143f6743..e9f5903c5cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -121,8 +121,7 @@ jobs: uploadFiles: gen/Firebird-*-source.tar.xz build-alpine-x64: - # Disabled - if: ${{ false && github.ref_type != 'tag' }} + if: ${{ github.ref_type != 'tag' }} runs-on: ubuntu-24.04 container: alpine:3.22.1 @@ -133,7 +132,7 @@ jobs: fetch-depth: 10 - name: Prepare - run: apk update && apk --no-cache --update add build-base libtool git autoconf automake cmake zlib-dev icu-dev ncurses-dev libedit-dev linux-headers tar sed + run: apk update && apk --no-cache --update add build-base libtool git autoconf automake cmake zlib-dev icu-dev icu-data-full ncurses-dev libedit-dev linux-headers tar sed - name: Adjust snapshot build number uses: ./.github/actions/adjust-snapshot-buildno @@ -215,7 +214,8 @@ jobs: #if: github.ref_type != 'tag' runs-on: windows-11-arm env: - VS_VERSION: 2022 + VS_VERSION: 2026 + VS_VERSION_NUMBER: 18 steps: - name: Checkout @@ -235,7 +235,7 @@ jobs: - name: Build shell: cmd env: - VS_SCRIPT: C:\Program Files\Microsoft Visual Studio\%VS_VERSION%\Enterprise\Common7\Tools\VsDevCmd.bat + VS_SCRIPT: C:\Program Files\Microsoft Visual Studio\%VS_VERSION_NUMBER%\Enterprise\Common7\Tools\VsDevCmd.bat run: | set SEVENZIP=C:\Program Files\7-Zip set INNO6_SETUP_PATH=C:\Program Files (x86)\Inno Setup 6 diff --git a/README.md b/README.md index f7ebf9e5535..fd5cd2e142c 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,35 @@ The Firebird Project is a commercially independent project of C/C++ programmers, ## Basic information -* https://www.firebirdsql.org/ -* https://www.firebirdsql.org/en/historical-reference/ -* https://www.firebirdsql.org/en/features/ +* https://firebirdsql.org/ +* https://firebirdsql.org/en/historical-reference/ +* https://firebirdsql.org/en/features/ + +## Downloads + +* [Firebird Server Packages](https://firebirdsql.org/en/server-packages) + * [Firebird 5.0](https://firebirdsql.org/en/firebird-5-0) + * [Firebird 4.0](https://firebirdsql.org/en/firebird-4-0) + * [Firebird 3.0](https://firebirdsql.org/en/firebird-3-0) +* [Docker images](https://hub.docker.com/r/firebirdsql/firebird) +* [Connectivity](https://firebirdsql.org/en/connectivity) (drivers) ## Documentation -* https://www.firebirdsql.org/en/documentation/ -* https://www.firebirdfaq.org/ +* https://firebirdsql.org/en/documentation/ + * [Firebird 5 Quick Start Guide](https://firebirdsql.org/file/documentation/html/en/firebirddocs/qsg5/firebird-5-quickstartguide.html) + * [Firebird 5.0 Language Reference](https://firebirdsql.org/file/documentation/html/en/refdocs/fblangref50/firebird-50-language-reference.html) + * [Firebird 5.0.x Release Notes](https://firebirdsql.org/file/documentation/release_notes/html/en/5_0/rlsnotes50.html) + * [Firebird Configuration Reference](https://firebirdsql.org/file/documentation/html/en/refdocs/fbconf/firebird-configuration-reference.html) + * ... [and more](https://firebirdsql.org/en/documentation/) +* https://firebirdfaq.org/ ## Licensing The source code is released under variants of the Mozilla Public Licence 1.1 (MPL): -* https://www.firebirdsql.org/en/initial-developer-s-public-license-version-1-0/ -* https://www.firebirdsql.org/en/interbase-public-license/ +* https://firebirdsql.org/en/initial-developer-s-public-license-version-1-0/ +* https://firebirdsql.org/en/interbase-public-license/ ## Development @@ -32,10 +46,10 @@ The source code can be found at: https://github.com/FirebirdSQL/firebird Build instructions are here: -https://www.firebirdsql.org/en/building-the-code/ +https://firebirdsql.org/en/building-the-code/ Bugs and feature requests should be submitted at: https://github.com/FirebirdSQL/firebird/issues You may find more details at: -https://www.firebirdsql.org/en/development/ +https://firebirdsql.org/en/development/ diff --git a/builds/install/misc/replication.conf b/builds/install/misc/replication.conf index 571b2609f39..a5422bea876 100644 --- a/builds/install/misc/replication.conf +++ b/builds/install/misc/replication.conf @@ -220,6 +220,11 @@ database # Used only with asynchronous replication. # # schema_search_path = + + # If disabled, tablespaces-related DDL statements and clauses in + # CREATE/ALTER TABLE/INDEX will not be applied to the replica. + # + # apply_tablespaces_ddl = true } # diff --git a/builds/posix/Makefile.in b/builds/posix/Makefile.in index ab416b93ce5..07cbab28e71 100644 --- a/builds/posix/Makefile.in +++ b/builds/posix/Makefile.in @@ -861,13 +861,16 @@ $(INCLUDE_DEST)/ib_util.h : $(SRC_OtherDistribHeaders) $(CP) $^ $(INCLUDE_DEST)/ NEW_INCLUDES_SRC = $(shell find $(SRC_ROOT)/include/firebird -type f) +ifeq ($(SYSTEM_BOOST_FLG),Y) +NEW_INCLUDES_SRC := $(filter-out $(SRC_ROOT)/include/firebird/impl/boost/%,$(NEW_INCLUDES_SRC)) +endif NEW_INCLUDES_DEST = $(patsubst $(SRC_ROOT)/include/firebird/%, $(INCLUDE_DEST)/firebird/%, $(NEW_INCLUDES_SRC)) include_generic: $(NEW_INCLUDES_DEST) $(INCLUDE_DEST)/ib_util.h ifeq ($(SYSTEM_BOOST_FLG),Y) $(INCLUDE_DEST)/firebird/Message.h : $(SRC_ROOT)/include/firebird/Message.h - sed 's,^#include "\./impl/boost/\(.\+\)",#include ,; s/\bFB_BOOST_PP_/BOOST_PP_/g' $< > $@ + sed 's,^#include "\./impl/boost/\(.*\)",#include ,; s/FB_BOOST_PP_/BOOST_PP_/g' $< > $@ endif $(INCLUDE_DEST)/firebird/%: $(SRC_ROOT)/include/firebird/% diff --git a/builds/posix/make.shared.targets b/builds/posix/make.shared.targets index 47c58cb3612..6fac819d13f 100644 --- a/builds/posix/make.shared.targets +++ b/builds/posix/make.shared.targets @@ -80,6 +80,9 @@ $(OBJ)/dsql/DdlNodes.cpp: $(SRC_ROOT)/dsql/DdlNodes.epp $(OBJ)/dsql/PackageNodes.cpp: $(SRC_ROOT)/dsql/PackageNodes.epp $(GPRE_CURRENT) $(JRD_GPRE_FLAGS) $< $@ +$(OBJ)/dsql/TablespaceNodes.cpp: $(SRC_ROOT)/dsql/TablespaceNodes.epp + $(GPRE_CURRENT) $(JRD_GPRE_FLAGS) $< $@ + # Adding resources as prerequisite for some files $(FilesToAddVersionInfo): $(GEN_ROOT)/jrd/version.res diff --git a/builds/win32/make_all.bat b/builds/win32/make_all.bat index 9e9e3280dcc..1e7aae2602f 100644 --- a/builds/win32/make_all.bat +++ b/builds/win32/make_all.bat @@ -118,6 +118,13 @@ copy %FB_GEN_DIR%\iberror_c.h %FB_OUTPUT_DIR%\include\firebird\impl > nul :: New API headers xcopy /y %FB_ROOT_PATH%\src\include\firebird %FB_OUTPUT_DIR%\include\firebird /e > nul +:: With system boost, do not distribute the embedded copy and make Message.h use and BOOST_PP_ +@findstr /C:"" "%FB_TEMP_DIR%\FirebirdSystemLibs.props" >nul 2>&1 +@if not errorlevel 1 ( + rmdir /S /Q %FB_OUTPUT_DIR%\include\firebird\impl\boost 2>nul + powershell -NoProfile -Command "(Get-Content '%FB_ROOT_PATH%\src\include\firebird\Message.h') -replace '^#include \x22\./impl/boost/(.+)\x22', '#include ' -replace '\bFB_BOOST_PP_', 'BOOST_PP_' | Set-Content -Encoding ascii '%FB_OUTPUT_DIR%\include\firebird\Message.h'" +) + @if "%FB_CLIENT_ONLY%"=="" ( :: UDR copy %FB_ROOT_PATH%\src\extlib\*.sql %FB_OUTPUT_DIR%\plugins\udr > nul diff --git a/builds/win32/make_boot.bat b/builds/win32/make_boot.bat index 5ad8fe89d18..f3c3ef37e6e 100644 --- a/builds/win32/make_boot.bat +++ b/builds/win32/make_boot.bat @@ -29,6 +29,8 @@ for %%v in ( alice auth burp dsql gpre isql jrd misc msgs examples yvalve utilit @mkdir %FB_BIN_DIR%\tzdata 2>nul +call :system_libs_config + @if not defined FBBUILD_WITHOUT_CLOOP_GENERATION call :interfaces if "!ERRLEV!"=="1" goto :END @@ -41,8 +43,10 @@ if "!ERRLEV!"=="1" goto :END if "%FB_TARGET_PLATFORM%"=="x64" call :ttmath if "!ERRLEV!"=="1" goto :END -call :zlib -if "!ERRLEV!"=="1" goto :END +@if not defined FBBUILD_WITH_SYSTEM_ZLIB ( + call :zlib + if "!ERRLEV!"=="1" goto :END +) @if "%FB_CLIENT_ONLY%"=="" ( call :re2 @@ -101,10 +105,14 @@ for %%v in (firebird plugins) do ( ) :: Copy ICU and zlib to the output directory -@copy %FB_ROOT_PATH%\extern\icu\icudt???.dat %FB_BIN_DIR% >nul 2>&1 -@copy %FB_ICU_SOURCE_BIN%\*.dll %FB_BIN_DIR% >nul 2>&1 +@if not defined FBBUILD_WITH_SYSTEM_ICU ( + @copy %FB_ROOT_PATH%\extern\icu\icudt???.dat %FB_BIN_DIR% >nul 2>&1 + @copy %FB_ICU_SOURCE_BIN%\*.dll %FB_BIN_DIR% >nul 2>&1 +) @copy %FB_ROOT_PATH%\extern\icu\tzdata-extract\* %FB_BIN_DIR%\tzdata >nul 2>&1 -@copy %FB_ROOT_PATH%\extern\zlib\%FB_TARGET_PLATFORM%\*.dll %FB_BIN_DIR% >nul 2>&1 +@if not defined FBBUILD_WITH_SYSTEM_ZLIB ( + @copy %FB_ROOT_PATH%\extern\zlib\%FB_TARGET_PLATFORM%\*.dll %FB_BIN_DIR% >nul 2>&1 +) @if "%FB_CLIENT_ONLY%"=="" ( ::======= @@ -129,6 +137,53 @@ for %%v in (firebird plugins) do ( @goto :END +::=================== +:: Record the system libraries choice in a props file used by all later builds (including the IDE) +:system_libs_config +@mkdir %FB_TEMP_DIR% 2>nul +@set SYSLIBS_PROPS=%FB_TEMP_DIR%\FirebirdSystemLibs.props +@del "%SYSLIBS_PROPS%" 2>nul +@set SYSLIBS_ANY= +@if defined FBBUILD_WITH_SYSTEM_BOOST set SYSLIBS_ANY=1 +@if defined FBBUILD_WITH_SYSTEM_TOMMATH set SYSLIBS_ANY=1 +@if defined FBBUILD_WITH_SYSTEM_TOMCRYPT set SYSLIBS_ANY=1 +@if defined FBBUILD_WITH_SYSTEM_ZLIB set SYSLIBS_ANY=1 +@if defined FBBUILD_WITH_SYSTEM_ICU set SYSLIBS_ANY=1 +@if not defined SYSLIBS_ANY goto :EOF +:: Optional extra include directory for system ICU/zlib headers (e.g. vcpkg), since MSBuild ignores %INCLUDE% +@set SYSLIBS_INCDIR= +@if defined FBBUILD_SYSTEM_INCLUDE_DIR set SYSLIBS_INCDIR=%FBBUILD_SYSTEM_INCLUDE_DIR%; +@echo ^> "%SYSLIBS_PROPS%" +@echo ^>> "%SYSLIBS_PROPS%" +@if defined FBBUILD_WITH_SYSTEM_BOOST ( + @echo Using system boost headers + @echo ^^>> "%SYSLIBS_PROPS%" +) +@if defined FBBUILD_WITH_SYSTEM_TOMMATH ( + @echo Using system libtommath + @echo ^^>> "%SYSLIBS_PROPS%" + @echo ^^>> "%SYSLIBS_PROPS%" + @echo ^tommath.lib^>> "%SYSLIBS_PROPS%" + call :tommath_probe +) +@if defined FBBUILD_WITH_SYSTEM_TOMCRYPT ( + @echo Using system libtomcrypt + @echo ^^>> "%SYSLIBS_PROPS%" + @echo ^^>> "%SYSLIBS_PROPS%" + @echo ^tomcrypt.lib^>> "%SYSLIBS_PROPS%" +) +@if defined FBBUILD_WITH_SYSTEM_ZLIB ( + @echo Using system zlib + @echo ^%SYSLIBS_INCDIR%^>> "%SYSLIBS_PROPS%" +) +@if defined FBBUILD_WITH_SYSTEM_ICU ( + @echo Using system ICU + @echo ^%SYSLIBS_INCDIR%^>> "%SYSLIBS_PROPS%" +) +@echo ^>> "%SYSLIBS_PROPS%" +@echo ^>> "%SYSLIBS_PROPS%" +@goto :EOF + ::=================== :: BUILD btyacc :btyacc @@ -142,10 +197,17 @@ goto :EOF :: BUILD LibTom :LibTom @echo. +@if defined FBBUILD_WITH_SYSTEM_TOMMATH goto :LibTomCrypt @echo Building LibTomMath (%FB_OBJ_DIR%)... @call compile.bat extern\libtommath\libtommath_MSVC%MSVC_VERSION% libtommath_%FB_CONFIG%_%FB_TARGET_PLATFORM%.log libtommath if errorlevel 1 call :boot2 libtommath_%FB_OBJ_DIR% & goto :EOF +:LibTomCrypt @if defined FBBUILD_WITHOUT_TOMCRYPT goto :LibTomDone +@if defined FBBUILD_WITH_SYSTEM_TOMCRYPT goto :LibTomDone +@echo Building LibTomCrypt (%FB_OBJ_DIR%)... +@call compile.bat extern\libtomcrypt\libtomcrypt_MSVC%MSVC_VERSION% libtomcrypt_%FB_CONFIG%_%FB_TARGET_PLATFORM%.log libtomcrypt +if errorlevel 1 call :boot2 libtomcrypt_%FB_OBJ_DIR% +:LibTomDone @echo Building LibTomCrypt (%FB_OBJ_DIR%)... @call compile.bat extern\libtomcrypt\libtomcrypt_MSVC%MSVC_VERSION% libtomcrypt_%FB_CONFIG%_%FB_TARGET_PLATFORM%.log libtomcrypt if errorlevel 1 call :boot2 libtomcrypt_%FB_OBJ_DIR% diff --git a/builds/win32/make_icu.bat b/builds/win32/make_icu.bat index dd82ee1ebe7..176034b8baa 100644 --- a/builds/win32/make_icu.bat +++ b/builds/win32/make_icu.bat @@ -22,7 +22,14 @@ if not defined FB_UNZIP ( if not exist "%FB_UNZIP%" set FB_UNZIP= -if not defined FB_UNZIP ( +if defined FBBUILD_WITH_SYSTEM_ICU ( + @echo Using system ICU, extracting tzdata only + if not defined FB_UNZIP ( + cscript /nologo unzip.vbs %FB_ROOT_PATH%\extern\icu\tzdata\le.zip %FB_ROOT_PATH%\extern\icu\tzdata-extract + ) else ( + "%FB_UNZIP%" -o %FB_ROOT_PATH%\extern\icu\tzdata\le.zip -d %FB_ROOT_PATH%\extern\icu\tzdata-extract + ) +) else if not defined FB_UNZIP ( cscript /nologo unzip.vbs %FB_ROOT_PATH%\extern\icu\icu_windows.zip %FB_ROOT_PATH%\extern\icu cscript /nologo unzip.vbs %FB_ROOT_PATH%\extern\icu\icudt.zip %FB_ROOT_PATH%\extern\icu cscript /nologo unzip.vbs %FB_ROOT_PATH%\extern\icu\tzdata\le.zip %FB_ROOT_PATH%\extern\icu\tzdata-extract diff --git a/builds/win32/msvc15/FirebirdCommon.props b/builds/win32/msvc15/FirebirdCommon.props index 545ac6ff443..ffdcbd41969 100644 --- a/builds/win32/msvc15/FirebirdCommon.props +++ b/builds/win32/msvc15/FirebirdCommon.props @@ -5,12 +5,26 @@ ..\..\..\temp\$(PlatformName)\$(Configuration)\$(ProjectName)\ ..\..\..\temp\$(PlatformName)\$(Configuration)\$(ProjectName)\ false + $(Configuration) + Release + Debug + ../../../extern/boost; + ../../../extern/libtommath; + ../../../extern/libtommath/lib/$(PlatformName)\$(FbExternConfiguration)\tommath.lib + ../../../extern/libtomcrypt/src/headers; + ../../../extern/libtomcrypt/lib/$(PlatformName)\$(FbExternConfiguration)\tomcrypt.lib + + + ../../../extern/zlib; + ../../../extern/icu/include; + + /EHsc- /Zc:__cplusplus %(AdditionalOptions) true - ../../../src/include;../../../src/include/gen;../../../extern/libtomcrypt/src/headers;../../../extern/libtommath;../../../extern/icu/include;../../../extern/zlib;../../../extern/re2;../../../extern/boost;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbTomCryptIncludeDir)$(FbTomMathIncludeDir)$(FbIcuIncludeDir)$(FbZlibIncludeDir)../../../extern/re2;$(FbBoostIncludeDir)%(AdditionalIncludeDirectories) false @@ -35,7 +49,7 @@ true $(IntDir)$(TargetName).pdb $(IntDir)$(TargetName).lib - bcrypt.lib;psapi.lib;%(AdditionalDependencies) + bcrypt.lib;psapi.lib;$(FbTomMathSysLib);$(FbTomCryptSysLib);%(AdditionalDependencies) ../../../extern/re2/builds/$(PlatformName)/$(Configuration) diff --git a/builds/win32/msvc15/common.vcxproj b/builds/win32/msvc15/common.vcxproj index 65f08080be3..f71dd410954 100644 --- a/builds/win32/msvc15/common.vcxproj +++ b/builds/win32/msvc15/common.vcxproj @@ -495,7 +495,7 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) @@ -507,7 +507,7 @@ EditAndContinue - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) @@ -518,7 +518,7 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;../../../extern/ttmath\$(Configuration)\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;../../../extern/ttmath\$(Configuration)\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) @@ -532,7 +532,7 @@ - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;../../../extern/ttmath\$(Configuration)\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;../../../extern/ttmath\$(Configuration)\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) @@ -540,8 +540,8 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Release\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) @@ -553,8 +553,8 @@ EditAndContinue - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Debug\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) @@ -565,7 +565,7 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) @@ -579,7 +579,7 @@ - ws2_32.lib;../../../extern/libtommath/lib/$(PlatformName)\$(Configuration)\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\$(Configuration)\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) + ws2_32.lib;$(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\$(Configuration)\decnumber.lib;%(AdditionalDependencies) @@ -590,8 +590,8 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Release\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;../../../extern/ttmath\Release\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;../../../extern/ttmath\Release\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;../../../extern/ttmath\Release\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;../../../extern/ttmath\Release\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) @@ -605,8 +605,8 @@ - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Debug\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;../../../extern/ttmath\Debug\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;../../../extern/ttmath\Debug\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;../../../extern/ttmath\Debug\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;../../../extern/ttmath\Debug\ttmathuint_x86_64_msvc.obj;%(AdditionalDependencies) @@ -617,8 +617,8 @@ WIN32;NDEBUG;_LIB;SUPERSERVER;%(PreprocessorDefinitions) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Release\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Release\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Release\decnumber.lib;%(AdditionalDependencies) @@ -632,8 +632,8 @@ - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/libtomcrypt/lib/$(PlatformName)\Debug\tomcrypt.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) - ../../../extern/libtommath/lib/$(PlatformName)\Debug\tommath.lib;../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);$(FbTomCryptLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) + $(FbTomMathLib);../../../extern/decNumber/lib/$(PlatformName)\Debug\decnumber.lib;%(AdditionalDependencies) diff --git a/builds/win32/msvc15/engine.vcxproj b/builds/win32/msvc15/engine.vcxproj index 65dd4554e1f..a2721c675a5 100644 --- a/builds/win32/msvc15/engine.vcxproj +++ b/builds/win32/msvc15/engine.vcxproj @@ -229,7 +229,7 @@ Disabled - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_LIB;SUPERSERVER;DEV_BUILD;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -245,7 +245,7 @@ Disabled - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_LIB;SUPERSERVER;DEV_BUILD;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -281,7 +281,7 @@ OnlyExplicitInline true Speed - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_LIB;SUPERSERVER;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -302,7 +302,7 @@ OnlyExplicitInline true Speed - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_LIB;SUPERSERVER;NAMESPACE=Vulcan;%(PreprocessorDefinitions) diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index cc9f09abc13..e561ec88c72 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -30,6 +30,7 @@ + @@ -177,6 +178,7 @@ + @@ -234,6 +236,7 @@ + @@ -381,6 +384,7 @@ + @@ -410,6 +414,7 @@ + Document @@ -562,7 +567,7 @@ Disabled - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_LIB;SUPERSERVER;DEV_BUILD;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -572,7 +577,7 @@ Disabled - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_LIB;SUPERSERVER;DEV_BUILD;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -588,7 +593,7 @@ X64 - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_LIB;SUPERSERVER;NAMESPACE=Vulcan;%(PreprocessorDefinitions) @@ -599,7 +604,7 @@ ARM64 - ../../../src/include;../../../src/include/gen;../../../extern/icu/include;../../../src/vulcan;%(AdditionalIncludeDirectories) + ../../../src/include;../../../src/include/gen;$(FbIcuIncludeDir)../../../src/vulcan;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_LIB;SUPERSERVER;NAMESPACE=Vulcan;%(PreprocessorDefinitions) diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index cc260a038df..f4ebd410f1d 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -480,6 +480,9 @@ DSQL\preprocesed + + DSQL\preprocesed + Services @@ -498,6 +501,9 @@ JRD files + + JRD files + JRD files @@ -1130,6 +1136,12 @@ Header files + + Header files + + + Header files + Header files @@ -1184,6 +1196,9 @@ DSQL\GPRE files + + DSQL\GPRE files + JRD files\GPRE files diff --git a/builds/win32/preprocess.bat b/builds/win32/preprocess.bat index 8d223f78d02..4c07d081c45 100644 --- a/builds/win32/preprocess.bat +++ b/builds/win32/preprocess.bat @@ -63,7 +63,7 @@ goto :EOF @set GPRE=%FB_BIN_DIR%\gpre_boot @for %%i in (alice_meta) do @call :PREPROCESS alice %%i -@for %%i in (metd, DdlNodes, PackageNodes) do @call :PREPROCESS dsql %%i -gds_cxx +@for %%i in (metd, DdlNodes, PackageNodes, TablespaceNodes) do @call :PREPROCESS dsql %%i -gds_cxx @for %%i in (gpre_meta) do @call :PREPROCESS gpre/std %%i @for %%i in (dfw, dpm, dyn_util, fun, grant, ini, met, scl, Function, SystemTriggers, Package) do @call :PREPROCESS jrd %%i -gds_cxx @for %%i in (stats) do @call :PREPROCESS utilities %%i @@ -76,7 +76,7 @@ goto :EOF @for %%i in (LegacyManagement) do @call :PREPROCESS auth/SecurityDatabase %%i @for %%i in (backup, restore, OdsDetection) do @call :PREPROCESS burp %%i -ocxx -m @for %%i in (metd) do @call :PREPROCESS dsql %%i -gds_cxx -@for %%i in (DdlNodes, PackageNodes) do @call :PREPROCESS dsql %%i -gds_cxx +@for %%i in (DdlNodes, PackageNodes, TablespaceNodes) do @call :PREPROCESS dsql %%i -gds_cxx @for %%i in (gpre_meta) do @call :PREPROCESS gpre/std %%i @for %%i in (dfw, dpm, dyn_util, fun, grant, ini, met, scl, Function, SystemTriggers, Package) do @call :PREPROCESS jrd %%i -gds_cxx @for %%i in (extract, isql, show) do @call :PREPROCESS isql %%i -ocxx diff --git a/builds/win32/run_all.bat b/builds/win32/run_all.bat index 5805fc020a9..c6d1b6ed426 100644 --- a/builds/win32/run_all.bat +++ b/builds/win32/run_all.bat @@ -13,6 +13,11 @@ set FBBUILD_KITS=ISX ZIP set FBBUILD_TEST_ONLY= set FBBUILD_WITHOUT_TOMCRYPT= set FBBUILD_WITHOUT_CLOOP_GENERATION= +set FBBUILD_WITH_SYSTEM_BOOST= +set FBBUILD_WITH_SYSTEM_TOMMATH= +set FBBUILD_WITH_SYSTEM_TOMCRYPT= +set FBBUILD_WITH_SYSTEM_ZLIB= +set FBBUILD_WITH_SYSTEM_ICU= set FB2_SNAPSHOT= ::Check if on-line help is required @@ -87,6 +92,21 @@ goto :END @echo. @echo WITHOUT_CLOOP_GENERATION - omit Cloop build and interface generation @echo. +@echo WITH_SYSTEM_BOOST - use boost headers from the system include path +@echo instead of extern\boost +@echo. +@echo WITH_SYSTEM_TOMMATH - use libtommath headers and tommath.lib from the system +@echo include/lib paths instead of extern\libtommath +@echo. +@echo WITH_SYSTEM_TOMCRYPT - use libtomcrypt headers and tomcrypt.lib from the system +@echo include/lib paths instead of extern\libtomcrypt +@echo. +@echo WITH_SYSTEM_ZLIB - use zlib headers from the system include path and do not +@echo distribute zlib1.dll (instead of extern\zlib) +@echo. +@echo WITH_SYSTEM_ICU - use ICU headers from the system include path and do not +@echo distribute ICU libraries and icudt (instead of extern\icu) +@echo. @echo REALCLEAN - Run CLEAN_ALL.BAT REALCLEAN @echo This will do a deeper clean. @echo Recommended for multi-platform builds diff --git a/builds/win32/setenvvar.bat b/builds/win32/setenvvar.bat index 0f81c358b66..bffd4f5506e 100644 --- a/builds/win32/setenvvar.bat +++ b/builds/win32/setenvvar.bat @@ -36,6 +36,31 @@ if /I "%~1"=="WITHOUT_CLOOP_GENERATION" ( shift /1 goto :parse_args ) +if /I "%~1"=="WITH_SYSTEM_BOOST" ( + set FBBUILD_WITH_SYSTEM_BOOST=1 + shift /1 + goto :parse_args +) +if /I "%~1"=="WITH_SYSTEM_TOMMATH" ( + set FBBUILD_WITH_SYSTEM_TOMMATH=1 + shift /1 + goto :parse_args +) +if /I "%~1"=="WITH_SYSTEM_TOMCRYPT" ( + set FBBUILD_WITH_SYSTEM_TOMCRYPT=1 + shift /1 + goto :parse_args +) +if /I "%~1"=="WITH_SYSTEM_ZLIB" ( + set FBBUILD_WITH_SYSTEM_ZLIB=1 + shift /1 + goto :parse_args +) +if /I "%~1"=="WITH_SYSTEM_ICU" ( + set FBBUILD_WITH_SYSTEM_ICU=1 + shift /1 + goto :parse_args +) if /I "%~1"=="CLEAN" ( set FB_CLEAN=:rebuild shift /1 @@ -294,6 +319,11 @@ goto :END @echo FB_VSCOMNTOOLS=%FB_VSCOMNTOOLS% @echo platform=%FB_TARGET_PLATFORM% @if defined FBBUILD_WITHOUT_CLOOP_GENERATION echo without_cloop_generation=yes +@if defined FBBUILD_WITH_SYSTEM_BOOST echo with_system_boost=yes +@if defined FBBUILD_WITH_SYSTEM_TOMMATH echo with_system_tommath=yes +@if defined FBBUILD_WITH_SYSTEM_TOMCRYPT echo with_system_tomcrypt=yes +@if defined FBBUILD_WITH_SYSTEM_ZLIB echo with_system_zlib=yes +@if defined FBBUILD_WITH_SYSTEM_ICU echo with_system_icu=yes @if /I "%FB_CLIENT_ONLY%"=="STATIC" ( @echo fbclient_static_config=%FB_STATIC_CONFIG% @if defined FBBUILD_WITHOUT_TOMCRYPT echo without_tomcrypt=yes diff --git a/configure.ac b/configure.ac index b01795fa93f..2c9f1b4ee25 100644 --- a/configure.ac +++ b/configure.ac @@ -958,7 +958,7 @@ else AC_MSG_ERROR(Include file for tomcrypt not found - please install development tomcrypt package or use --with-builtin-tomcrypt)) XE_RESTORE_ENV() XE_SAVE_ENV() - LIBS= + LIBS=$MATHLIB AC_CHECK_LIB(tomcrypt, sha1_init, CRYPTLIB=-ltomcrypt, AC_MSG_ERROR(Library tomcrypt not found - please install development tomcrypt package or use --with-builtin-tomcrypt)) XE_RESTORE_ENV() diff --git a/doc/README.build.msvc.html b/doc/README.build.msvc.html index 478421a0516..27ea3d09388 100644 --- a/doc/README.build.msvc.html +++ b/doc/README.build.msvc.html @@ -109,6 +109,8 @@

4.1.-Standard build

  • make_all.bat [DEBUG] [CLEAN]
  • To skip the Cloop build and generated interface updates, pass WITHOUT_CLOOP_GENERATION to run_all.bat.
  • +
  • To use boost headers from the system include path (e.g. the INCLUDE environment variable) instead of extern\boost, pass WITH_SYSTEM_BOOST to make_boot.bat (or run_all.bat). The choice is recorded in temp\<Platform>\FirebirdSystemLibs.props and used by later builds, including from the IDE; a make_boot.bat run without the option removes it, and so does clean_all.bat. The installed Message.h then uses <boost/...> and the embedded boost copy is not distributed.
  • +
  • Likewise, WITH_SYSTEM_TOMMATH, WITH_SYSTEM_TOMCRYPT, WITH_SYSTEM_ZLIB and WITH_SYSTEM_ICU make the build use the headers (and, for libtommath and libtomcrypt, tommath.lib and tomcrypt.lib) from the system INCLUDE and LIB paths instead of extern\libtommath, extern\libtomcrypt, extern\zlib and extern\icu. The embedded copy is not built or extracted, and the corresponding runtime files (zlib1.dll, ICU libraries and icudt; tzdata is still extracted and copied) are not copied to the output directory, so they must be provided by you. These options are also recorded in FirebirdSystemLibs.props.
    • After the build finish, an output directory with the binaries is created below your firebird directory.

      diff --git a/doc/README.monitoring_tables b/doc/README.monitoring_tables index b0a476d05d7..76bb89220f9 100644 --- a/doc/README.monitoring_tables +++ b/doc/README.monitoring_tables @@ -5,7 +5,7 @@ Monitoring tables Function: Allow to monitor server-side activity happening inside a particular database. - Concept: + Concept: The engine offers a set of so called "virtual" tables that provides the user with a snapshot of the current activity within the given database. The word "virtual" means that the table data doesn't exist until explicitly asked for. @@ -25,7 +25,7 @@ Monitoring tables To refresh the snapshot, the current transaction should be finished and the monitoring tables should be queried in the new transaction context. Creation of a snapshot is usually quite fast operation, but some delay should be - expected under high load (especially in the Classic Server). + expected under high load (especially in the Classic Server). A valid database connection is required in order to retrieve the monitoring data. The monitoring tables return information about the attached database @@ -249,12 +249,18 @@ Monitoring tables MON$COMPILED_STATEMENTS (compiled statements) - MON$COMPILED_STATEMENT_ID (compiled statement ID) - - MON$SQL_TEXT (statement text, if appropriate) - - MON$EXPLAINED_PLAN (explained query plan) - - MON$OBJECT_NAME (PSQL object name) - - MON$OBJECT_TYPE (PSQL object type) - - MON$PACKAGE_NAME (PSQL object package name) - - MON$STAT_ID (statistics ID) + - MON$SQL_TEXT (statement text, if appropriate) + - MON$EXPLAINED_PLAN (explained query plan) + - MON$OBJECT_NAME (PSQL object name) + - MON$OBJECT_TYPE (PSQL object type) + - MON$PACKAGE_NAME (PSQL object package name) + - MON$STAT_ID (statistics ID) + + MON$TABLESPACE_STATS (per tablespace page-level statistics) + - MON$STAT_ID (statistics ID) + - MON$STAT_GROUP (statistics group) + - MON$TABLESPACE_NAME (tablespace name) + - MON$IO_STAT_ID (page-level statistics ID, refers to MON$IO_STATS) Notes: 1) Textual descriptions of all "state" and "mode" values can be found @@ -370,7 +376,7 @@ Modifications of the monitoring tables receives the isc_att_shutdown error code. Subsequent attempts to use this connection handle will cause network read/write errors. - 3) System attachment can not be cancelled, so engine silently skip system attachments + 3) System attachment can not be cancelled, so engine silently skip system attachments affected by DELETE FROM MON$ATTACHMENTS statements Example(s): @@ -435,7 +441,7 @@ Limitations and known issues Prior to FB v2.5, every blob occupied bytes of memory even if its contents is in fact smaller. So, with a huge number of prepared statements in the system, it becomes possible to get this failure. - + Another possible reason could be the temporary (very short in practice) growth of the transaction pool which caches the monitoring data while merging the clusters into a single fragment. diff --git a/doc/README.tablespaces b/doc/README.tablespaces new file mode 100644 index 00000000000..9ad1660e475 --- /dev/null +++ b/doc/README.tablespaces @@ -0,0 +1,184 @@ +----------------- +TABLESPACES +----------------- + + Goals: + Tablespaces allow you to organize the logic of placing database object files in the file system. It allows: + 1) Extend the current limits on database size + 2) Keep non active parts of a database on slow disks (having big volume) + 3) Split indices from the database + +----------------- +SYNTAX +----------------- + + 1. TABLESPACE + CREATE TABLESPACE [IF NOT EXISTS] FILE '/path/to/file' + CREATE OR ALTER TABLESPACE FILE '/path/to/file' + ALTER TABLESPACE SET FILE [TO] '/path/to/file' + + You can specify either an absolute path or a relative path (relative to the database file). + While creating, the specified file must not exist and will be created. While altering, the specified file must exist. + The ALTER command replaces the data/indices contained inside the original tablespace with the data/indices contained in the new one. + After altering, the old file remains intact but is no longer associated with the database. + + DROP TABLESPACE [IF EXISTS] + + It's currently impossible to drop a tabespace containing metadata objects inside, error is returned. + + It's possible to add a comment for the existing tablespace: + COMMENT ON TABLESPACE IS {'text' | NULL} + + 2. TABLE + "PRIMARY" keyword + The PRIMARY keyword can be used as a tablespace name if you want to reference the main database file. + + CREATE TABLE ... + [[IN] TABLESPACE { | PRIMARY}] + + It is also possible to specify a tablespace when creating a column or table constraint (unique, primary key, references): + + ::= ... UNIQUE ... [[IN] TABLESPACE { | PRIMARY}] | PRIMARY ... [[IN] TABLESPACE { | PRIMARY}] | REFERENCES ... [[IN] TABLESPACE { | PRIMARY}] ... + + ALTER TABLE SET TABLESPACE [TO] { | PRIMARY} + + The table data will be moved to the specified tablespace or to the main database. + + 3. INDEX + CREATE INDEX ... + [[IN] TABLESPACE { | PRIMARY}] + + By default, table indexes are created in the same tablespace as the table itself. + + ALTER INDEX ... + [SET TABLESPACE [TO] { | PRIMARY}] + + The index data will be moved to the specified tablespace or to the main database. + +----------------- +SECURITY +----------------- + + Only administrators and users with the CREATE TABLESPACE privilege can create tablespaces. + Only administrators, tablespace owners and users with the ALTER ANY TABLESPACE privilege can alter tablespaces (change tablespaces file paths)). + Only administrators, tablespace owners and users with the ALTER ANY TABLESPACE privilege can comment on tablespaces. + Only administrators, tablespace owners and users with the DROP ANY TABLESPACE privilege can drop tablespaces. + +----------------- +ODS CHANGES +----------------- + + A new table RDB$TABLESPACES: + RDB$TABLESPACE_ID - INTEGER # internally it will be pagespace ID + RDB$TABLESPACE_NAME - CHAR (63) # name of a tablespace + RDB$SECURITY_CLASS - CHAR (63) # security class for tablespace + RDB$SYSTEM_FLAG - SMALLINT # reserved for future + RDB$DESCRIPTION - BLOB TEXT # description of a tablespace + RDB$OWNER_NAME - CHAR (63) # owner of a tablespace + RDB$FILE_NAME - VARCHAR (255) # file where a tablespace data are located + RDB$OFFLINE - BOOLEAN # reserved for future + RDB$READ_ONLY - BOOLEAN # reserved for future + + A new table MON$TABLESPACE_STATS: + MON$STAT_ID INTEGER - INTEGER # statistics ID + MON$STAT_GROUP - SMALLINT # statistics group + MON$TABLESPACE_NAME - CHAR(63) # tablespace name + MON$IO_STAT_ID - INTEGER # page-level statistics ID, refers to MON$IO_STATS + + New field in RDB$INDICES: + RDB$TABLESPACE_NAME - CHAR (63) + + New field in RDB$RELATION_FIELDS: + RDB$TABLESPACE_NAME - CHAR (63) + + New fields in RDB$RELATIONS: + RDB$TABLESPACE_NAME - CHAR (63) + + Initially, RDB$TABLESPACES contains a record about the PRIMARY tablespace (defined as system). + +----------------- +UTILITIES +----------------- + + 1. GBAK backup + gbak -b works as usual for now. It gets data from a database transparently working with tablespaces. + + 2. GBAK restore + gbak -c / -r + + -ts_map[ping] + option is required for correct database restore if its backup contains tables or indexes saved in tablespaces. + To do this, specify the path to file, which consists of lines with two values: the first column is the name of the tablespace, + the second column is the new location of the tablespace. You can specify either an absolute path or a relative path. + TS1 /path/to/tablespace1.dat + TS2 /path/to/tablespace2.dat + + -ts + allows you to specify the path for the tablespace. You can specify either an absolute path or a relative path. + The option can be used as many times as required. It can also be used together with -ts_map. + + If you specify “PRIMARY” instead of the path for the new tablespace, the contents of the tablespace will be moved to the PRIMARY tablespace. + The original tablespace will not be created. + + -ts_orig[inal_paths] + To restore tablespaces to the original paths they were on when the backup was created. + It is still possible to override paths for some tablespaces using the -ts and -ts_map options. + This is an explicit option, not a default action. + The option does not overwrite existing files. + + If you do not specify the above options, when restoring a database that has tablespaces, + an error about the inability to determine the path to restore tablespaces will occur. + + 3. ISQL + SHOW {TABLESPACES | TABLESPACE } + Displays a list of all tablespaces names in alphabetical order or information about the specified tablespace. + + 4. Replication + There is an apply_tablespaces_ddl parameter for replication, it may be configured for the replica side. + If this parameter is disabled, CREATE/ALTER/DROP TSABLESPACE statements will be ignored on the replica and + tablespace-related clauses in CREATE/ALTER TABLE/INDEX statements will also be ignored. + This parameter is useful when the replica has its own set of tablespaces or none at all. + +----------------- +DETAILS +----------------- + + pag_header in every tablespace is reserved and may be replaced by a + new page type. + pag_scns and pag_pip are located in every tablespace. + pag_root is located in the tablespace where a table is located. + + Algorithm of moving the table data to another tablespace: + - allocate necessary number of pointer pages by extents + - allocate the rest of pointer pages by pages + - walking through PPs allocate DPs by pages or extents + - fix every PP by correcting DP numbers and ppg_next pointer and build a map + - walking through the map and copy every DP to the new one by fixing b_page and f_page numbers + - insert newly allocated page numbers into RDB$PAGES and mark original page numbers for deletion after commit + - if rolled back, delete the newly allocated pages and mark the original pages as active again + + Algorithm of moving the index to another tablespace: + - new index tree is created, containing all the keys from the original index + - new index tree becomes active, but contains a backpointer to the original index tree + - original index tree is marked for deletion after commit + - if rolled back, mark the new index tree for deletion and restore the original index tree as active + +----------------- +CONSTRAINTS +----------------- + + Currently it's possible to create up to 253 tablespaces, this limit may be shifted in the future. + Altering or dropping a tablespace requires an exclusive tablespace lock. + Moving an index or table to another tablespace requires a PR (protected read) table lock. + +----------------- +PLANS +----------------- + + 1. TEMPORARY predefined tablespaces + 2. New header page for tablespaces + 3. NBACKUP support for tablespaces + 4. Moving blobs to separate tablespaces (RDB$TABLESPACE_NAME column in RDB$RELATION_FIELDS is reserved for this purpose) + 5. Possibility to introduce UNDO tablespace in future versions + 6. The ability to set default tablespaces for tables, indexes and BLOBs at the database or schema level + diff --git a/doc/sql.extensions/README.schemas.md b/doc/sql.extensions/README.schemas.md index c32f2e4e096..55797349fdf 100644 --- a/doc/sql.extensions/README.schemas.md +++ b/doc/sql.extensions/README.schemas.md @@ -45,6 +45,7 @@ These objects exist outside schemas and function as before: - Blob filters - Schemas - Mappings and global mappings +- Tablespaces ### Schema-bound objects diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 899fc183bac..1d1d2d8f1ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,6 +47,7 @@ set(epp_boot_gds_files dsql/metd.epp dsql/DdlNodes.epp dsql/PackageNodes.epp + dsql/TablespaceNodes.epp jrd/dfw.epp jrd/dpm.epp jrd/dyn_util.epp @@ -471,6 +472,7 @@ set(engine_generated_src dsql/DdlNodes.epp dsql/metd.epp dsql/PackageNodes.epp + dsql/TablespaceNodes.epp jrd/dfw.epp jrd/dpm.epp jrd/dyn_util.epp diff --git a/src/burp/backup.epp b/src/burp/backup.epp index c1a8cc0ebac..d14f3436ffb 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -152,6 +152,7 @@ void write_relations(); void write_schemas(); void write_secclasses(); void write_shadow_files(); +void write_tablespaces(); void write_triggers(); void write_trigger_messages(); void write_types(); @@ -426,6 +427,13 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name) write_packages(); } + if (tdgbl->runtimeODS >= DB_VERSION_DDL14) + { + // Write tablespaces + BURP_verbose(411); // msg 411 writing tablespaces + write_tablespaces(); + } + // Now go back and write all data { @@ -819,6 +827,10 @@ burp_fld* get_fields( burp_rel* relation) field->fld_identity_type = X.RDB$IDENTITY_TYPE; } + // ODS 14 + if (!X.RDB$TABLESPACE_NAME.NULL) // For older ODS NULL is expected here + COPY(X.RDB$TABLESPACE_NAME, field->fld_tablespace); + field_list.add(field); } END_FOR @@ -1763,6 +1775,10 @@ void put_index( burp_rel* relation) if (!X.RDB$FOREIGN_KEY.NULL) PUT_TEXT (att_index_foreign_key, X.RDB$FOREIGN_KEY); + // ODS 14 + if (!X.RDB$TABLESPACE_NAME.NULL) + PUT_TEXT (att_index_tablespace_name, X.RDB$TABLESPACE_NAME); + if (!X.RDB$CONDITION_SOURCE.NULL) { put_source_blob(att_index_condition_source, att_index_condition_source, @@ -1839,6 +1855,10 @@ void put_index( burp_rel* relation) if (!X.RDB$FOREIGN_KEY.NULL) PUT_TEXT (att_index_foreign_key, X.RDB$FOREIGN_KEY); + // ODS 14 + if (!X.RDB$TABLESPACE_NAME.NULL) // For old versions I expect it will be NULL here. + PUT_TEXT (att_index_tablespace_name, X.RDB$TABLESPACE_NAME); + put(tdgbl, att_end); END_FOR; @@ -2141,6 +2161,9 @@ void put_relation( burp_rel* relation) put_int32(att_field_identity_type, field->fld_identity_type); } + if (field->fld_tablespace[0]) + PUT_TEXT(att_field_tablespace_name, field->fld_tablespace); + put(tdgbl, att_end); } @@ -4220,6 +4243,10 @@ void write_relations() if (!X.RDB$SQL_SECURITY.NULL) put_boolean(att_relation_sql_security, X.RDB$SQL_SECURITY); + // ODS 14 + if (!X.RDB$TABLESPACE_NAME.NULL) // For older ODS NULL is expected here + PUT_TEXT(att_relation_tablespace_name, X.RDB$TABLESPACE_NAME); + put(tdgbl, att_end); burp_rel* relation = (burp_rel*) BURP_alloc_zero (sizeof(burp_rel)); relation->rel_next = tdgbl->relations; @@ -4559,6 +4586,62 @@ void write_mapping() MISC_release_request_silent(req_handle); } +void write_tablespaces() +{ +/************************************** + * + * w r i t e _ t a b l e s p a c e s + * + ************************************** + * + * Functional description + * write a record in the burp file for + * each tablespace. + * + **************************************/ + TEXT temp[GDS_NAME_LEN]; + Firebird::IRequest* req_handle1 = nullptr; + + BurpGlobals* tdgbl = BurpGlobals::getSpecific(); + + FOR (REQUEST_HANDLE req_handle1) + X IN RDB$TABLESPACES WITH + (X.RDB$SYSTEM_FLAG MISSING OR X.RDB$SYSTEM_FLAG NE RDB_system) + { + put(tdgbl, rec_tablespace); + + const SSHORT l = PUT_TEXT(att_ts_name, X.RDB$TABLESPACE_NAME); + MISC_terminate(X.RDB$TABLESPACE_NAME, temp, l, sizeof(temp)); + + BURP_verbose(412, temp); // msg 412 writing tablespace @1 + + if (!X.RDB$SECURITY_CLASS.NULL) + PUT_TEXT(att_ts_security_class, X.RDB$SECURITY_CLASS); + + if (!X.RDB$DESCRIPTION.NULL) + put_source_blob(att_ts_description, att_ts_description, X.RDB$DESCRIPTION); + + if (!X.RDB$OWNER_NAME.NULL) + PUT_TEXT(att_ts_owner_name, X.RDB$OWNER_NAME); + + if (!X.RDB$FILE_NAME.NULL) + PUT_TEXT(att_ts_file, X.RDB$FILE_NAME); + + if (!X.RDB$OFFLINE.NULL) + put_boolean(att_ts_offline, X.RDB$OFFLINE); + + if (!X.RDB$READ_ONLY.NULL) + put_boolean(att_ts_readonly, X.RDB$READ_ONLY); + + put(tdgbl, att_end); + } + END_FOR + ON_ERROR + general_on_error(); + END_ERROR + + MISC_release_request_silent(req_handle1); +} void write_db_creators() { diff --git a/src/burp/burp.cpp b/src/burp/burp.cpp index 33daec8c3b5..9109fe0efa8 100644 --- a/src/burp/burp.cpp +++ b/src/burp/burp.cpp @@ -614,6 +614,7 @@ int gbak(Firebird::UtilSvc* uSvc) tdgbl->gbl_sw_mode = false; tdgbl->gbl_sw_skip_count = 0; tdgbl->gbl_sw_par_workers = uSvc->getParallelWorkers(); + tdgbl->gbl_sw_ts_orig_paths = false; tdgbl->action = NULL; burp_fil* file = NULL; @@ -1139,6 +1140,35 @@ int gbak(Firebird::UtilSvc* uSvc) // msg 404: "none", "read_only" or "read_write" required } replicaMode = str; + break; + case IN_SW_BURP_TS_MAPPING_FILE: + if (++itr >= argc) + { + BURP_error(419, true, SafeArg() << in_sw_tab->in_sw_name); + // parameter for option -@1 is missing + } + tdgbl->loadMapping(argv[itr], tdgbl->tablespace_mapping, false); + break; + case IN_SW_BURP_TS_ORIGINAL_PATHS: + if (tdgbl->gbl_sw_ts_orig_paths) + BURP_error(334, true, SafeArg() << in_sw_tab->in_sw_name); + tdgbl->gbl_sw_ts_orig_paths = true; + break; + case IN_SW_BURP_TS_PATH: + if (itr + 2 >= argc) + { + BURP_error(419, true, SafeArg() << in_sw_tab->in_sw_name); + // parameter for option -@1 is missing + } + + { + Firebird::string ts_name = argv[++itr]; + Firebird::string ts_path = argv[++itr]; + + if (ts_name.length() && ts_path.length()) + tdgbl->tablespace_mapping.put(ts_name, ts_path); + } + break; } } // for @@ -2922,6 +2952,70 @@ void BurpGlobals::print_stats_header() burp_output(false, "\n"); } +void BurpGlobals::loadMapping(const char* mapping_file, StringMap& map, bool clearMap, bool caseSensitive) +{ + FILE* f = os_utils::fopen(mapping_file, fopen_read_type); + if (!f) + { + BURP_error(420, true, SafeArg() << mapping_file); // msg 420 cannot open mapping file @1 + } + + //Read lines from file, split by space and add to mapping + if (clearMap) + map.clear(); + + bool end = false; + do + { + Firebird::string line; + char buffer[MAX_USHORT]; + + if (fgets(buffer, sizeof(buffer), f) != NULL) + { + size_t lineSize = strlen(buffer); + if (buffer[lineSize - 1] == '\n') + buffer[--lineSize] = '\0'; + const char* ch = strchr(buffer, ' '); + if (!ch) + continue; + Firebird::string func(buffer, ch - buffer); + Firebird::string args(ch + 1, lineSize - func.size() - 1); + func.trim(); + args.trim(); + if (!func.empty() && !args.empty()) + { + if (!caseSensitive) + func.upper(); + map.put(func, args); + } + } + else + { + end = true; + } + } while (!end); + + fclose(f); +} + +void BURP_makeSymbol(BurpGlobals* tdgbl, Firebird::string& name) // add double quotes to string +{ + if (tdgbl->gbl_dialect < SQL_DIALECT_V6) + return; + + const char dq = '"'; + for (unsigned p = 0; p < name.length(); ++p) + { + if (name[p] == dq) + { + name.insert(p, 1, dq); + ++p; + } + } + name.insert(0u, 1, dq); + name += dq; +} + static void processFetchPass(const SCHAR*& password, int& itr, const int argc, Firebird::UtilSvc::ArgvType& argv) { if (++itr >= argc) diff --git a/src/burp/burp.h b/src/burp/burp.h index e5e4753dc01..d8fc9110e1f 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -49,7 +49,8 @@ #include "../common/status.h" #include "../common/sha.h" #include "../common/classes/ImplementHelper.h" -#include "../jrd/intl.h" +#include "../common/classes/GenericMap.h" +//#include "../jrd/intl.h" #ifdef HAVE_UNISTD_H #include @@ -125,7 +126,8 @@ enum rec_type { rec_publication, // Publication rec_pub_table, // Publication table rec_schema, // Schema - rec_constants // Constants + rec_constants, // Constants + rec_tablespace // Tablespace }; @@ -212,7 +214,7 @@ Version 11: FB4.0. SQL SECURITY feature, tables RDB$PUBLICATIONS/RDB$PUBLICATION_TABLES. Version 12: FB6.0. - Schemas. + Schemas and tablespaces. Packaged constants. @@ -305,6 +307,7 @@ enum att_type { att_relation_schema_name, att_relation_package_name, att_relation_private_flag, + att_relation_tablespace_name, // Field attributes (used for both global and local fields) @@ -365,6 +368,7 @@ enum att_type { att_field_generator_name, att_field_identity_type, att_field_schema_name, + att_field_tablespace_name, // Index attributes @@ -382,6 +386,7 @@ enum att_type { att_index_condition_source, att_index_condition_blr, att_index_foreign_key_schema_name, + att_index_tablespace_name, // Data record @@ -727,6 +732,15 @@ enum att_type { att_constant_source, att_constant_schema_name, att_constant_description, + + // Tablespace attributes + att_ts_name = SERIES, + att_ts_security_class, + att_ts_description, + att_ts_owner_name, + att_ts_file, + att_ts_offline, + att_ts_readonly }; @@ -798,6 +812,7 @@ struct burp_fld CollId fld_collation_id; RCRD_OFFSET fld_sql; RCRD_OFFSET fld_null; + TEXT fld_tablespace[GDS_NAME_LEN]; }; enum fld_flags_vals { @@ -827,7 +842,8 @@ struct burp_rel enum burp_rel_flags_vals { REL_view = 1, - REL_external = 2 + REL_external = 2, + REL_has_tablespace_name = 4 }; // package definition @@ -1046,7 +1062,8 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool flag_on_line(true), firstMap(true), firstDbc(true), - stdIoMode(false) + stdIoMode(false), + tablespace_mapping(*getDefaultMemoryPool()) { // this is VERY dirty hack to keep current (pre-FB2) behaviour memset (&gbl_database_file_name, 0, @@ -1111,6 +1128,7 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool redirect_vals sw_redirect; bool burp_throw; std::optional gbl_sw_replica; + bool gbl_sw_ts_orig_paths; UCHAR* blk_io_ptr; int blk_io_cnt; @@ -1231,6 +1249,8 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool Firebird::IRequest* handles_get_constant_req_handle1; Firebird::IRequest* handles_get_user_privilege_req_handle1; Firebird::IRequest* handles_get_view_req_handle1; + Firebird::IRequest* handles_get_ts_req_handle1; + Firebird::IRequest* handles_get_ts_req_handle2; Firebird::IRequest* handles_activateIndex_req_handle1; // The handles_put.. are for backup. @@ -1254,6 +1274,9 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool TEXT database_security_class[GDS_NAME_LEN]; // To save database security class for deferred update unsigned batchInlineBlobLimit; + typedef Firebird::GenericMap > > StringMap; + static inline BurpGlobals* getSpecific() { return (BurpGlobals*) ThreadData::getSpecific(); @@ -1266,6 +1289,9 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool { ThreadData::restoreSpecific(); } + + void loadMapping(const char* mapping_file, StringMap& map, bool clearMap = true, bool caseSensitive = true); + void setupSkipIncludePattern(const Firebird::string& regexp, USHORT alreadySetErrorCode, Firebird::AutoPtr& matcher); bool skipRelation(const Firebird::QualifiedMetaString& name); @@ -1291,6 +1317,7 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool Firebird::AutoPtr skipDataMatcher; Firebird::AutoPtr includeSchemaDataMatcher; Firebird::AutoPtr includeDataMatcher; + StringMap tablespace_mapping; // Will be used to overwrite filename of tablespace with given name public: Firebird::string toSystem(const Firebird::PathName& from); diff --git a/src/burp/burpswi.h b/src/burp/burpswi.h index 60901a490b6..6935921d020 100644 --- a/src/burp/burpswi.h +++ b/src/burp/burpswi.h @@ -106,6 +106,10 @@ inline constexpr int IN_SW_BURP_SKIP_SCHEMA_DATA = 56; // skip data from schema inline constexpr int IN_SW_BURP_INCLUDE_SCHEMA_DATA = 57; // backup data from schemas inline constexpr int IN_SW_BURP_FAST_PATH = 58; // fast-path backup/restore via VIO +inline constexpr int IN_SW_BURP_TS_MAPPING_FILE = 59; // mapping file for tablespaces +inline constexpr int IN_SW_BURP_TS_ORIGINAL_PATHS = 60; // restore tablespaces to their original paths +inline constexpr int IN_SW_BURP_TS_PATH = 61; // set a path for a tablespace + /**************************************************************************/ static inline constexpr const char* BURP_SW_MODE_NONE = "NONE"; @@ -241,6 +245,9 @@ static inline constexpr Switches::in_sw_tab_t reference_burp_in_sw_table[] = {IN_SW_BURP_HIDDEN_RDONLY, isc_spb_res_am_readonly, "MODE READ_ONLY", 0, 0, 0, false, false, 0, 14, NULL, boRestore}, {IN_SW_BURP_HIDDEN_RDWRITE, isc_spb_res_am_readwrite, "MODE READ_WRITE", 0, 0, 0, false, false, 0, 15, NULL, boRestore}, /**************************************************************************/ + {IN_SW_BURP_TS_MAPPING_FILE, 0, "TS_MAPPING_FILE", 0, 0, 0, false, false, 415, 6, NULL, boRestore}, + {IN_SW_BURP_TS_PATH, 0, "TS", 0, 0, 0, false, false, 418, 2, NULL, boRestore}, + {IN_SW_BURP_TS_ORIGINAL_PATHS, 0, "TS_ORIGINAL_PATHS", 0, 0, 0, false, false, 417, 7, NULL, boRestore}, {IN_SW_BURP_0, 0, NULL, 0, 0, 0, false, false, 0, 0, NULL, boGeneral} }; diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 5519914e98d..7c71d9022a2 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -63,8 +63,10 @@ #include "memory_routines.h" #include "../burp/OdsDetection.h" #include "../auth/trusted/AuthSspi.h" +#include "../common/os/path_utils.h" #include "../common/dsc_proto.h" #include "../common/ThreadStart.h" +#include "../common/db_alias.h" #include "../common/msg_encode.h" #include "../common/classes/BatchCompletionState.h" #include "../burp/FastPathBurpProvider.h" @@ -148,6 +150,7 @@ void get_misc_blob(BurpGlobals* tdgbl, ISC_QUAD&, bool); SLONG get_int32(BurpGlobals* tdgbl); SINT64 get_int64(BurpGlobals* tdgbl); bool get_package(BurpGlobals* tdgbl); +bool get_tablespace(BurpGlobals* tdgbl); bool get_procedure(BurpGlobals* tdgbl); bool get_procedure_prm(BurpGlobals* tdgbl, const QualifiedMetaString&); bool get_publication(BurpGlobals* tdgbl); @@ -3950,6 +3953,9 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation, USHORT id) X.RDB$PACKAGE_NAME.NULL = FALSE; } + strcpy(X.RDB$TABLESPACE_NAME, PRIMARY_TABLESPACE_NAME); + X.RDB$TABLESPACE_NAME.NULL = FALSE; + skip_init(&scan_next_attr); while (get_attribute(&attribute, tdgbl) != att_end) { @@ -4127,6 +4133,20 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation, USHORT id) X.RDB$IDENTITY_TYPE = field->fld_identity_type; break; + // ODS 14 + + case att_field_tablespace_name: + { + BASED_ON RDB$RELATIONS.RDB$TABLESPACE_NAME tablespaceName; + + GET_TEXT(tablespaceName); + const auto str = tdgbl->tablespace_mapping.get(tablespaceName); + + if (str && !str->equals(PRIMARY_TABLESPACE_NAME)) + strcpy(X.RDB$TABLESPACE_NAME, tablespaceName); + break; + } + default: bad_attribute(scan_next_attr, attribute, 84); // msg 84 column @@ -6814,8 +6834,9 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) STORE (REQUEST_HANDLE tdgbl->handles_get_index_req_handle1) X IN RDB$INDICES { - strcpy (X.RDB$RELATION_NAME, relation->rel_name.object.c_str()); + strcpy(X.RDB$RELATION_NAME, relation->rel_name.object.c_str()); X.RDB$UNIQUE_FLAG = 0; + if (!tdgbl->gbl_sw_deactivate_indexes) X.RDB$INDEX_INACTIVE = FALSE; else @@ -6834,6 +6855,9 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) X.RDB$SYSTEM_FLAG = 0; X.RDB$SYSTEM_FLAG.NULL = FALSE; + strcpy(X.RDB$TABLESPACE_NAME, PRIMARY_TABLESPACE_NAME); + X.RDB$TABLESPACE_NAME.NULL = !(relation->rel_flags & REL_has_tablespace_name); + if (relation->rel_name.schema.hasData()) { strcpy(X.RDB$SCHEMA_NAME, relation->rel_name.schema.c_str()); @@ -6973,6 +6997,20 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) GET_TEXT(X.RDB$FOREIGN_KEY); break; + // ODS 14 + + case att_index_tablespace_name: + { + BASED_ON RDB$RELATIONS.RDB$TABLESPACE_NAME tablespaceName; + + GET_TEXT(tablespaceName); + const auto str = tdgbl->tablespace_mapping.get(tablespaceName); + + if (str && !str->equals(PRIMARY_TABLESPACE_NAME)) + strcpy(X.RDB$TABLESPACE_NAME, tablespaceName); + break; + } + default: bad_attribute(scan_next_attr, attribute, 93); // msg 93 index @@ -7239,6 +7277,185 @@ bool get_package(BurpGlobals* tdgbl) return true; } +bool get_tablespace(BurpGlobals* tdgbl) +{ +/************************************** + * + * g e t _ t a b l e s p a c e + * + ************************************** + * + * Functional description + * Reconstruct a tablespace. + * + **************************************/ + if (tdgbl->RESTORE_format < 13) // Probably this check is not needed + return false; + + ISC_QUAD tablespace_desc = fbBlobNull; + bool tablespace_desc_null = true; + GDS_NAME owner_name; + SLONG sys_flag = fb_sysflag_user; + FB_BOOLEAN offline = FB_FALSE; + FB_BOOLEAN read_only = FB_FALSE; + + BASED_ON RDB$TABLESPACES.RDB$TABLESPACE_NAME tablespace_name; + tablespace_name[0] = '\0'; + + BASED_ON RDB$TABLESPACES.RDB$SECURITY_CLASS security_class; + security_class[0] = '\0'; + bool security_class_null = true; + + BASED ON RDB$TABLESPACES.RDB$FILE_NAME tablespace_file; + tablespace_file[0] = '\0'; + + att_type attribute; + scan_attr_t scan_next_attr; + + bool moveToPrimary = false; + + skip_init(&scan_next_attr); + while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end) + { + switch (attribute) + { + case att_ts_name: + { + TEXT temp[GDS_NAME_LEN]; + SSHORT len = GET_TEXT(tablespace_name); + MISC_terminate(tablespace_name, temp, len, sizeof(temp)); + + const auto str = tdgbl->tablespace_mapping.get(tablespace_name); + if (str && str->equals(PRIMARY_TABLESPACE_NAME)) + moveToPrimary = true; // Move the contents of this TS to PRIMARY + else + BURP_verbose(1019, temp); // msg 1019 restoring tablespace %s + break; + } + case att_ts_security_class: + if (moveToPrimary) + eat_text(tdgbl); + else + { + GET_TEXT(security_class); + fix_security_class_name(tdgbl, security_class, false); + } + break; + + case att_ts_description: + if (moveToPrimary) + eat_blob(tdgbl); + else + { + tablespace_desc_null = false; + get_source_blob(tdgbl, tablespace_desc, true); + } + break; + + case att_ts_owner_name: + if (moveToPrimary) + eat_text(tdgbl); + else + GET_TEXT(owner_name); + break; + + case att_ts_file: + if (moveToPrimary) + eat_text(tdgbl); + else + GET_TEXT(tablespace_file); + break; + + case att_ts_offline: + offline = get_boolean(tdgbl, false); + break; + + case att_ts_readonly: + read_only = get_boolean(tdgbl, false); + break; + + default: + bad_attribute(scan_next_attr, attribute, 1020); // msg 1020 tablespace + break; + } + } + + // Do not add a tablespace if its contents are moved to the PRIMARY when restoring it + if (moveToPrimary) + return true; + +// Firebird::ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans; + + STORE (REQUEST_HANDLE tdgbl->handles_get_ts_req_handle1) + X IN RDB$TABLESPACES + { + X.RDB$TABLESPACE_NAME.NULL = FALSE; + strcpy(X.RDB$TABLESPACE_NAME, tablespace_name); + + X.RDB$SECURITY_CLASS.NULL = security_class_null; + strcpy(X.RDB$SECURITY_CLASS, security_class); + + X.RDB$SYSTEM_FLAG.NULL = FALSE; + X.RDB$SYSTEM_FLAG = sys_flag; + + X.RDB$DESCRIPTION.NULL = tablespace_desc_null; + X.RDB$DESCRIPTION = tablespace_desc; + + X.RDB$OWNER_NAME.NULL = FALSE; + strcpy(X.RDB$OWNER_NAME, owner_name); + + X.RDB$FILE_NAME.NULL = FALSE; + strcpy(X.RDB$FILE_NAME, tablespace_file); + + X.RDB$OFFLINE.NULL = FALSE; + X.RDB$OFFLINE = (USHORT) offline; + + X.RDB$READ_ONLY.NULL = FALSE; + X.RDB$READ_ONLY = (USHORT) read_only; + + Firebird::string newFile; + if (tdgbl->tablespace_mapping.get(X.RDB$TABLESPACE_NAME, newFile)) + { + PathUtils::fixupSeparators(newFile.begin()); + Firebird::PathName ts_file_name; + + if (PathUtils::isRelative(newFile.c_str())) + { + Firebird::PathName expanded_db; + expandDatabaseName(tdgbl->gbl_database_file_name, expanded_db, NULL); + + Firebird::PathName db_path, db_file; + PathUtils::splitLastComponent(db_path, db_file, expanded_db); + PathUtils::concatPath(ts_file_name, db_path, newFile.c_str()); + } + else + ts_file_name = newFile.c_str(); + + if (ts_file_name.length() >= sizeof(X.RDB$FILE_NAME)) + { + BURP_error(46, false); // msg 46 string truncated + return false; + } + + X.RDB$FILE_NAME.NULL = FALSE; + strcpy(X.RDB$FILE_NAME, ts_file_name.c_str()); + } + else if (!tdgbl->gbl_sw_ts_orig_paths) + { + BURP_error(416, false, SafeArg() << X.RDB$TABLESPACE_NAME << X.RDB$FILE_NAME); + // msg 416 path to tablespace @1 is not specified (original path: "@2") + return false; + } + } + END_STORE + ON_ERROR + BURP_print_status(&tdgbl->status_vector); + return false; + END_ERROR + + return true; +} + bool get_procedure(BurpGlobals* tdgbl) { /************************************** @@ -8152,6 +8369,9 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t ext_file_name[0] = '\0'; bool ext_file_name_null = true; + BASED_ON RDB$RELATIONS.RDB$TABLESPACE_NAME tableSpace; + strcpy(tableSpace, PRIMARY_TABLESPACE_NAME); + // Before starting to restore relations, commit everything that was restored // prior to this point. This ensures that no pending error can later affect // other metadata being restored. @@ -8268,7 +8488,10 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t case att_relation_type: if (tdgbl->RESTORE_format >= 8) + { type = get_int32(tdgbl); + relation->rel_flags |= fb_utils::hasTablespaceName(rel_t(type)) ? REL_has_tablespace_name : 0; + } else bad_attribute(scan_next_attr, attribute, 111); break; @@ -8284,6 +8507,20 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t private_flag = (USHORT) get_int32(tdgbl); break; + case att_relation_tablespace_name: + { + BASED_ON RDB$RELATIONS.RDB$TABLESPACE_NAME tablespaceName; + + GET_TEXT(tablespaceName); + + const auto str = tdgbl->tablespace_mapping.get(tablespaceName); + + if (str && !str->equals(PRIMARY_TABLESPACE_NAME)) + strcpy(tableSpace, tablespaceName); + + break; + } + default: bad_attribute(scan_next_attr, attribute, 111); // msg 111 table @@ -8321,6 +8558,7 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t X.RDB$SQL_SECURITY.NULL = sql_security_null; X.RDB$PACKAGE_NAME.NULL = TRUE; X.RDB$PRIVATE_FLAG.NULL = private_flag_null; + X.RDB$TABLESPACE_NAME.NULL = TRUE; X.RDB$SYSTEM_FLAG = (USHORT) sys_flag; X.RDB$FLAGS = (USHORT) rel_flags; @@ -8348,8 +8586,14 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t X.RDB$RELATION_TYPE = (USHORT) type; X.RDB$SQL_SECURITY = (FB_BOOLEAN) sql_security; X.RDB$PRIVATE_FLAG = private_flag; + + if (fb_utils::hasTablespaceName(rel_t(type))) + { + X.RDB$TABLESPACE_NAME.NULL = FALSE; + strcpy(X.RDB$TABLESPACE_NAME, tableSpace); + } } - END_STORE + END_STORE; ON_ERROR general_on_error (); END_ERROR @@ -11421,13 +11665,37 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file bool flag_norel = true; // To fix bug 10098 bool flag = false; + bool ts_error = false; rec_type record; Coordinator coord(getDefaultMemoryPool()); RestoreRelationTask task(tdgbl); - while (get_record(&record, tdgbl) != rec_end) + while (true) { + get_record(&record, tdgbl); + + if (ts_error && record != rec_tablespace) + { + // Clean up RDB$TABLESPACES to prevent creation of files during DFW + FOR (REQUEST_HANDLE tdgbl->handles_get_ts_req_handle2) + X IN RDB$TABLESPACES WITH + (X.RDB$SYSTEM_FLAG MISSING OR X.RDB$SYSTEM_FLAG NE RDB_system) + ERASE X; + ON_ERROR + general_on_error(); + END_ERROR; + END_FOR; + ON_ERROR + general_on_error(); + END_ERROR; + + return false; + } + + if (record == rec_end) + break; + switch (record) { case rec_charset: @@ -11491,6 +11759,12 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file flag = true; break; + case rec_tablespace: + if (!get_tablespace(tdgbl)) + ts_error = true; + flag = true; + break; + case rec_procedure: if (!get_procedure(tdgbl)) return false; diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 851c572d80e..035c6d9bbe7 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -586,3 +586,4 @@ PARSER_TOKEN(TOK_CONCATENATE, "||", false) PARSER_TOKEN(TOK_NOT_LSS, "~<", false) // Alias of !< PARSER_TOKEN(TOK_NEQ, "~=", false) // Alias of != PARSER_TOKEN(TOK_NOT_GTR, "~>", false) // Alias of !> +PARSER_TOKEN(TOK_TABLESPACE, "TABLESPACE", true) diff --git a/src/common/SimilarToRegex.cpp b/src/common/SimilarToRegex.cpp index 7c2fa26af5e..c69b09fde88 100644 --- a/src/common/SimilarToRegex.cpp +++ b/src/common/SimilarToRegex.cpp @@ -831,7 +831,7 @@ bool SimilarToRegex::matches(const char* buffer, unsigned bufferLen, Arraypush(MatchPos{ - static_cast(resSp.data() - sp.begin()), + static_cast(resSp.data() - sp.data()), static_cast(resSp.length()) }); } @@ -879,7 +879,7 @@ bool SubstringSimilarRegex::matches(const char* buffer, unsigned bufferLen, if (RE2::FullMatch(sp, *regexp.get(), nullptr, &spResult, nullptr)) { - *resultStart = spResult.begin() - buffer; + *resultStart = spResult.data() - buffer; *resultLength = static_cast(spResult.length()); return true; } diff --git a/src/common/classes/stack.h b/src/common/classes/stack.h index 4dcc0eb9621..9fea42ffc4b 100644 --- a/src/common/classes/stack.h +++ b/src/common/classes/stack.h @@ -28,6 +28,7 @@ #ifndef CLASSES_STACK_H #define CLASSES_STACK_H +#include "../common/classes/alloc.h" #include "../common/classes/vector.h" namespace Firebird { diff --git a/src/common/utils_proto.h b/src/common/utils_proto.h index 8b323c2841e..a35f1f01be4 100644 --- a/src/common/utils_proto.h +++ b/src/common/utils_proto.h @@ -38,6 +38,7 @@ #include "iberror.h" #include "firebird/Interface.h" #include "memory_routines.h" +#include "../jrd/constants.h" namespace fb_utils { @@ -334,6 +335,12 @@ namespace fb_utils int reason; }; + // Determines whether the table type has a non-null parameter in the system table in the field RDB$TABLESPACE_NAME + inline bool hasTablespaceName(rel_t rel_type) + { + return rel_type == rel_persistent; + } + bool isLoopbackAddress(const std::string_view address); template @@ -358,6 +365,7 @@ namespace fb_utils private: T* m_value; }; + } // namespace fb_utils #endif // INCLUDE_UTILS_PROTO_H diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 7b0e093d3b1..b32ec5f42f3 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -106,6 +106,7 @@ static void checkDeferredDdlInReadOnlyReplica(thread_db* tdbb); static void checkForeignKeyTempScope(thread_db* tdbb, const QualifiedName& childRelName, const QualifiedName& masterIndexName); static void checkLttNotInUse(thread_db* tdbb, jrd_tra* transaction, const LocalTemporaryTable* ltt); +static bool checkObjectExist(thread_db* tdbb, const QualifiedName& name, int type); static void checkSpTrigDependency(thread_db* tdbb, const QualifiedName& relationName, const MetaName& fieldName); static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& relationName, const MetaName& fieldName); @@ -121,7 +122,7 @@ static int getGrantorOption(thread_db* tdbb, const MetaName& grantor, static QualifiedName getIndexRelationName(thread_db* tdbb,const QualifiedName& indexName, bool& systemIndex, bool silent = false); static const char* getRelationScopeName(const rel_t type); -static void makeRelationScopeName(string& to, const QualifiedName& name, const rel_t type); +static string makeRelationScopeName(const QualifiedName& name, const rel_t type); static void checkRelationType(const rel_t type, const QualifiedName& name); static void checkFkPairTypes(const rel_t masterType, const QualifiedName& masterName, const rel_t childType, const QualifiedName& childName); @@ -595,10 +596,11 @@ static bool isItSqlRole(thread_db* tdbb, const MetaName& inputName, MetaName& ou } // Make string with relation name and type of its temporary scope. -static void makeRelationScopeName(string& to, const QualifiedName& name, const rel_t type) +static string makeRelationScopeName(const QualifiedName& name, const rel_t type) { - const char* scope = getRelationScopeName(type); - to.printf(scope, name.toQuotedString().c_str()); + string result; + result.printf(getRelationScopeName(type), name.toQuotedString().c_str()); + return result; } // Get relation name of an index. @@ -701,8 +703,7 @@ static void checkRelationType(const rel_t type, const QualifiedName& name) return; } - string scope; - makeRelationScopeName(scope, name, type); + const string scope = makeRelationScopeName(name, type); (Arg::PrivateDyn(289) << scope).raise(); } @@ -713,9 +714,8 @@ static void checkFkPairTypes(const rel_t masterType, const QualifiedName& master if (masterType != childType && !(masterType == rel_temp_preserve && childType == rel_temp_delete)) { - string master, child; - makeRelationScopeName(master, masterName, masterType); - makeRelationScopeName(child, childName, childType); + const string master = makeRelationScopeName(masterName, masterType); + const string child = makeRelationScopeName(childName, childType); // Msg 232 : "%s can't reference %s" status_exception::raise(Arg::PrivateDyn(232) << child << master); } @@ -1490,6 +1490,7 @@ DdlNode* CommentOnNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) case obj_schema: case obj_blob_filter: case obj_sql_role: + case obj_tablespace: fb_assert(name.schema.isEmpty()); break; @@ -1629,6 +1630,10 @@ void CommentOnNode::checkPermission(thread_db* tdbb) SCL_check_package(tdbb, name.getSchemaAndPackage(), SCL_alter); break; + case obj_tablespace: + SCL_check_tablespace(tdbb, name.object, SCL_alter); + break; + default: fb_assert(false); } @@ -1798,6 +1803,13 @@ void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, j status << Arg::Gds(isc_bad_constant_name) << Arg::Str(objNameStr); break; + case obj_tablespace: + tableClause = "rdb$tablespaces"; + columnClause = "rdb$tablespace_name"; + useSchemaClause = false; + status << Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(objNameStr); + break; + default: fb_assert(false); return; @@ -6713,6 +6725,7 @@ RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode) dsqlNode(aDsqlNode), name(p, dsqlNode->dsqlName), clauses(p), + tableSpace(p), indexList(p) { } @@ -6933,6 +6946,7 @@ void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction) RFR.RDB$VIEW_CONTEXT.NULL = TRUE; RFR.RDB$BASE_FIELD.NULL = TRUE; ///RFR.RDB$UPDATE_FLAG.NULL = TRUE; + RFR.RDB$TABLESPACE_NAME.NULL = FALSE; if (collationId.has_value()) { @@ -7001,6 +7015,16 @@ void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction) DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value(), baseField.c_str(), RFR.RDB$FIELD_SOURCE_SCHEMA_NAME, RFR.RDB$FIELD_SOURCE); } + + if (tableSpace.hasData() && applyTablespacesDdl(tdbb)) + { + if (!checkObjectExist(tdbb, QualifiedName(tableSpace), obj_tablespace)) + status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace.c_str()); + + strcpy(RFR.RDB$TABLESPACE_NAME, tableSpace.c_str()); + } + else + strcpy(RFR.RDB$TABLESPACE_NAME, PRIMARY_TABLESPACE_NAME); } END_STORE } @@ -7055,6 +7079,7 @@ DdlNode* RelationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) case Clause::TYPE_DROP_CONSTRAINT: case Clause::TYPE_ALTER_SQL_SECURITY: case Clause::TYPE_ALTER_PUBLICATION: + case Clause::TYPE_SET_TABLESPACE: break; case Clause::TYPE_ADD_INLINE_TABLE_INDEX: @@ -7542,9 +7567,13 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } fieldDefinition.defaultValue = defaultValue; + if (field->typeOfName.object.isEmpty() || field->collate.object.hasData()) fieldDefinition.collationId = field->collationId; + if (field->fld_ts_name.hasData()) + fieldDefinition.tableSpace = field->fld_ts_name; + if (!isCreatedLtt()) { fieldDefinition.store(tdbb, transaction); @@ -7633,6 +7662,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra constraint.create->index->name = constraint.name; constraint.create->columns = clause->columns; + constraint.create->tableSpace = clause->tableSpace; break; } @@ -7759,6 +7789,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra } } + constraint.create->tableSpace = clause->tableSpace; break; } @@ -7845,6 +7876,7 @@ void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlSc definition.columns = constraint.columns; definition.refRelation = constraint.refRelation; definition.refColumns = constraint.refColumns; + definition.tableSpace = constraint.tableSpace; QualifiedName qualifiedIndexName(constraint.index->name, name.schema, name.package); indexList.push(CreateIndexNode::store(tdbb, indexList.getPool(), transaction, @@ -8795,7 +8827,7 @@ void RelationNode::makeVersion(thread_db* tdbb, jrd_tra* transaction, const Qual CSetId(FLD.RDB$CHARACTER_SET_ID), collation)) { if (null_view && REL.RDB$FORMAT.NULL) - DPM_delete_relation(tdbb, getPermanent(relation)); + relation->deletePages(tdbb); ERR_post(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_random) << relName.toQuotedString()); @@ -8805,7 +8837,7 @@ void RelationNode::makeVersion(thread_db* tdbb, jrd_tra* transaction, const Qual if (!validateTextType(tdbb, tfb)) { if (null_view && REL.RDB$FORMAT.NULL) - DPM_delete_relation(tdbb, getPermanent(relation)); + relation->deletePages(tdbb); ERR_post_nothrow(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_random) << relName.toQuotedString()); @@ -8933,7 +8965,7 @@ void RelationNode::makeVersion(thread_db* tdbb, jrd_tra* transaction, const Qual if (null_view && !physical_fields) { if (REL.RDB$FORMAT.NULL) - DPM_delete_relation(tdbb, getPermanent(relation)); + relation->deletePages(tdbb); ERR_post(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_table_name) << relName.toQuotedString() << @@ -9034,26 +9066,29 @@ void RelationNode::makeLttVersion(thread_db* tdbb, jrd_tra* transaction, LocalTe permRel->rel_flags = REL_sql_relation | REL_temp_ltt | (ltt->relationType == rel_temp_preserve ? REL_temp_conn : REL_temp_tran); - permRel->getBasePages()->rel_pg_space_id = dbb->dbb_page_manager.getTempPageSpaceID(tdbb); - DPM_create_relation_pages(tdbb, permRel, permRel->getBasePages()); - AutoPtr versRel = FB_NEW_POOL(attPool) jrd_rel(attPool, permRel); + versRel->rel_current_fmt = 1; + versRel->rel_dbkey_length = 8; + versRel->setPageSpaceId(dbb->dbb_page_manager.getTempPageSpaceID(tdbb)); + DPM_create_relation_pages(tdbb, versRel->getPermanent(), versRel->getBasePages()); + permRel->storeObject(tdbb, versRel, CacheFlag::NOCOMMIT); permRel.release(); relation = versRel.release(); - - relation->rel_current_fmt = 1; - relation->rel_dbkey_length = 8; } else if (!commitChanges) { if (relation->rel_current_fmt == MAX_TABLE_VERSIONS) raiseTooManyVersionsError(obj_relation, ltt->name); + fb_assert(relation->getPageSpaceId() == dbb->dbb_page_manager.getTempPageSpaceID(tdbb)); + AutoPtr versRel = FB_NEW_POOL(attPool) jrd_rel(attPool, relation->getPermanent()); versRel->rel_current_fmt = relation->rel_current_fmt + 1; versRel->rel_dbkey_length = relation->rel_dbkey_length; + versRel->setPages(relation); relation->getPermanent()->storeObject(tdbb, versRel, CacheFlag::NOCOMMIT); + relation = versRel.release(); } @@ -9719,10 +9754,7 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat const rel_t relationType = tempFlag.has_value() ? (tempRowsFlag.value_or(REL_temp_tran) == REL_temp_tran ? - rel_temp_delete : - rel_temp_preserve - ) : - rel_persistent; + rel_temp_delete : rel_temp_preserve) : (externalFile ? rel_external : rel_persistent); checkRelationTempScope(tdbb, name, relationType); @@ -9781,12 +9813,33 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat if (ISC_check_if_remote(externalFile->c_str(), false)) status_exception::raise(Arg::PrivateDyn(163)); + if (tableSpace.hasData()) + { + const string scope = makeRelationScopeName(name, relationType); + status_exception::raise(Arg::Gds(isc_dyn_cant_set_ts_table) << scope); + } + REL.RDB$EXTERNAL_FILE.NULL = FALSE; strcpy(REL.RDB$EXTERNAL_FILE, externalFile->c_str()); - REL.RDB$RELATION_TYPE = rel_external; extFile = true; } + + const bool nullTsName = !fb_utils::hasTablespaceName(relationType); + REL.RDB$TABLESPACE_NAME.NULL = nullTsName ? TRUE : FALSE; + + if (!nullTsName) + { + if (tableSpace.hasData() && applyTablespacesDdl(tdbb)) + { + if (!checkObjectExist(tdbb, QualifiedName(tableSpace), obj_tablespace)) + status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace.c_str()); + + strcpy(REL.RDB$TABLESPACE_NAME, tableSpace.c_str()); + } + else + strcpy(REL.RDB$TABLESPACE_NAME, PRIMARY_TABLESPACE_NAME); + } } END_STORE @@ -9899,8 +9952,11 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat // On contrary rel_flags (and therefore check for view) are OK if (!(extFile || relation->isView())) { + const auto newVersion = relation->getVersioned(tdbb, CacheFlag::NOCOMMIT); + fb_assert(newVersion); + // create the table - DPM_create_relation(tdbb, relation); + DPM_create_relation(tdbb, newVersion); } indexList.exec(tdbb, transaction); @@ -9914,14 +9970,17 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat { if (relation) { - // We need to cleanup RDB$PAGES and pages if they were added. - RelationPages* const relPages = relation->getBasePages(); + const auto newVersion = relation->getVersioned(tdbb, CacheFlag::NOCOMMIT); + fb_assert(newVersion); - if (relPages->rel_index_root) - IDX_delete_indices(tdbb, relation, relPages, true); + // We need to cleanup RDB$PAGES and pages if they were added - if (relPages->rel_pages) - DPM_delete_relation(tdbb, relation); + const auto relPages = newVersion->getPages(tdbb); + + if (const auto rootPage = relPages->getIndexRootPage()) + IDX_delete_indices(tdbb, rootPage.value(), true); + + DPM_delete_relation(tdbb, newVersion->getPermanent(), relPages); relation->rollback(tdbb); } @@ -10373,7 +10432,7 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc AutoSetRestoreFlag dfwFlags(&tdbb->tdbb_flags, TDBB_use_db_page_space, true); // ensures oldVersion is present in cache - auto* rel = MetadataCache::getPerm(tdbb, name, CacheFlag::OLD_ALTER); + const auto rel = MetadataCache::getVersioned(tdbb, name, CacheFlag::OLD_ALTER); dsql_rel* relation = saveRelation(tdbb, dsqlScratch, name, false, false); @@ -10434,6 +10493,8 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc ObjectsArray constraints; + std::optional pageSpaceId; + for (NestConst* i = clauses.begin(); i != clauses.end(); ++i) { switch ((*i)->type) @@ -10762,6 +10823,54 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc break; } + case Clause::TYPE_SET_TABLESPACE: + { + fb_assert(tableSpace.hasData()); + + if (!applyTablespacesDdl(tdbb)) + break; + + if (!checkObjectExist(tdbb, QualifiedName(tableSpace), obj_tablespace)) + status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace); + + AutoRequest request2; + + FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) + REL IN RDB$RELATIONS + WITH REL.RDB$SCHEMA_NAME EQ name.schema.c_str() + AND REL.RDB$RELATION_NAME EQ name.object.c_str() + { + fb_assert(!REL.RDB$RELATION_TYPE.NULL); + const rel_t relType = (rel_t) REL.RDB$RELATION_TYPE; + + if (!fb_utils::hasTablespaceName(relType)) + { + const string scope = makeRelationScopeName(name, relType); + status_exception::raise(Arg::Gds(isc_dyn_cant_set_ts_table) << scope); + } + + fb_assert(!REL.RDB$TABLESPACE_NAME.NULL); + + // Check if we are trying to change tablespace name to the already set + if (tableSpace == REL.RDB$TABLESPACE_NAME) + break; + + pageSpaceId = Tablespace::lookupId(tdbb, tableSpace); + + // Ensure the original page set is initialized + rel->getPages(tdbb); + + MODIFY REL + { + strcpy(REL.RDB$TABLESPACE_NAME, tableSpace.c_str()); + } + END_MODIFY + } + END_FOR + + break; + } + default: fb_assert(false); break; @@ -10809,10 +10918,35 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc } } - MetadataCache::newVersion(tdbb, rel->getId()); + const auto newRel = MetadataCache::newVersion(tdbb, rel->getId()); DFW_post_work(transaction, dfw_commit_relation, nullptr, nullptr, rel->getId()); indexList.exec(tdbb, transaction); - rel->rel_flags |= REL_get_dependencies; + newRel->rel_flags |= REL_get_dependencies; + + if (pageSpaceId) + { + const auto orgPages = rel->getBasePages(); + + const auto newVersion = newRel->getVersioned(tdbb, CacheFlag::NOCOMMIT); + fb_assert(newVersion); + + const auto newPages = newVersion->getBasePages(); + + fb_assert(newPages->getPageSpaceId() == pageSpaceId.value()); + fb_assert(newPages->getPageSpaceId() != orgPages->getPageSpaceId()); + newPages->reset(pageSpaceId.value()); + + // Protect relation from modification to create a consistent table copy. + // The lock is held until the transaction end. + ProtectRelations protectRelations(tdbb, transaction, newRel, false); + + // Disable GC during page copying + GCLock::Exclusive gcLock(tdbb, newRel); + gcLock.acquire(transaction->getLockWait() != 0); + + // Copy pages to the new tablespace + DPM_copy_data(tdbb, newRel, orgPages, newPages); + } if (beforeTriggerWasExecuted) executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TABLE, name, {}); @@ -11771,8 +11905,8 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch AutoSetRestoreFlag dfwFlags(&tdbb->tdbb_flags, TDBB_use_db_page_space, true); - auto* rel = MetadataCache::getVersioned(tdbb, name, CacheFlag::AUTOCREATE); - if (!rel && silent) + const auto relation = MetadataCache::getVersioned(tdbb, name, CacheFlag::AUTOCREATE); + if (!relation && silent) return; checkDeferredDdlInReadOnlyReplica(tdbb); @@ -11780,12 +11914,12 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); - auto* relation = getPermanent(rel); + const auto relPerm = getPermanent(relation); // Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view. if (view) { - if (!relation || (relation && !(relation->isView()))) + if (!relPerm || !relPerm->isView()) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << @@ -11795,7 +11929,7 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } else { - if (!relation || (relation && (relation->isView()))) + if (!relPerm || relPerm->isView()) { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << @@ -11805,7 +11939,7 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE); - MetaId relId = rel->getId(); + const MetaId relId = relation->getId(); AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS); bool found = false; @@ -12030,31 +12164,32 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch for (int wait = 0; wait < 60; wait++) { - fb_assert(relation->isDropped()); + fb_assert(relPerm->isDropped()); - if (!relation->rel_gc_lock.getSweepCount()) + if (!relPerm->rel_gc_lock.getSweepCount()) break; EngineCheckout cout(tdbb, FB_FUNCTION); Thread::sleep(1 * 1000); } - if (relation->rel_gc_lock.getSweepCount()) - DFW_raiseRelationInUseError(relation); + if (relPerm->rel_gc_lock.getSweepCount()) + DFW_raiseRelationInUseError(relPerm); // Free any memory associated with the relation's garbage collection bitmap if (dbb->dbb_garbage_collector) - dbb->dbb_garbage_collector->removeRelation(rel->getId()); + dbb->dbb_garbage_collector->removeRelation(relPerm->getId()); + + const auto relPages = relation->getBasePages(); - RelationPages* const relPages = relation->getBasePages(); - if (relPages->rel_pages) + if (relPages->hasData()) DPM_mark_relation(tdbb, relation); AutoSetRestoreFlag noDfw(&tdbb->tdbb_flags, TDBB_dont_post_dfw, true); // if this is a view (or even if we don't know), delete dependency lists - if (relation->isView() || !relation->isReady(tdbb)) + if (relPerm->isView() || !relPerm->isReady(tdbb)) MET_delete_dependencies(tdbb, name, obj_view); // Now that the data, pointer, and index pages are gone, @@ -12064,7 +12199,7 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch request2.reset(); FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) X IN RDB$FORMATS WITH - X.RDB$RELATION_ID EQ rel->getId() + X.RDB$RELATION_ID EQ relId { ERASE X; } @@ -12087,13 +12222,13 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch END_FOR // Release relation locks - if (relation->rel_partners_lock) - LCK_release(tdbb, relation->rel_partners_lock); + if (relPerm->rel_partners_lock) + LCK_release(tdbb, relPerm->rel_partners_lock); } catch (const Exception&) { - if (relation && relation->isDropped()) - relation->rollback(tdbb); + if (relPerm && relPerm->isDropped()) + relPerm->rollback(tdbb); throw; } @@ -13008,27 +13143,28 @@ void ModifyIndexNode::modify(thread_db* tdbb, jrd_tra* transaction) SET_TDBB(tdbb); fb_assert(indexRelation); - indexRelation->fillPages(tdbb); - if (create) + if (operation == OP_ACTIVE) step2(tdbb, transaction); if (indexRelation->rel_flags & REL_temp_gtt) { AutoSetRestoreFlag preserveFlags(&tdbb->tdbb_flags, TDBB_use_db_page_space, false); - if (indexRelation->getPages(tdbb, MAX_TRA_NUMBER, false)) + const auto relation = indexRelation->getVersioned(tdbb, CacheFlag::AUTOCREATE); + if (relation && relation->getPages(tdbb, MAX_TRA_NUMBER, false)) step2(tdbb, transaction); } - if (!create) + if (operation != OP_ACTIVE) step2(tdbb, transaction); } string ModifyIndexNode::print(Jrd::NodePrinter& printer) const { NODE_PRINT(printer, indexName); - NODE_PRINT(printer, create); + NODE_PRINT(printer, operation); + NODE_PRINT(printer, tableSpace); return "ModifyIndexNode"; } @@ -13060,6 +13196,7 @@ Cached::Relation* ModifyIndexNode::getRelByIndex(thread_db* tdbb, const Qualifie if (relation && (relation->rel_flags & REL_check_partners)) relation->scanPartners(tdbb); + return relation; } @@ -13201,6 +13338,7 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) auto* metaTransaction = tdbb->getAttachment()->getMetaTransaction(tdbb); idx.idx_flags = 0; + idx.idx_pg_space_id = DB_PAGE_SPACE; // Fetch the information necessary to create the index. request.reset(tdbb, irq_c_index, IRQ_REQUESTS); @@ -13225,6 +13363,11 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) fb_assert(IDX.RDB$INDEX_INACTIVE.NULL || !IDX.RDB$INDEX_INACTIVE); } + if (!IDX.RDB$TABLESPACE_NAME.NULL) + { + idx.idx_pg_space_id = Tablespace::lookupId(tdbb, IDX.RDB$TABLESPACE_NAME); + } + idx.idx_count = IDX.RDB$SEGMENT_COUNT; if (!idx.idx_count || idx.idx_count > MAX_INDEX_SEGMENTS) { @@ -13404,9 +13547,7 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) idx.idx_id = idx_invalid; if (MET_lookup_partner(tdbb, indexRelation, &idx, indexName)) - { partnerRelation = MetadataCache::getPerm(tdbb, idx.idx_primary_relation, CacheFlag::AUTOCREATE); - } if (!partnerRelation) { @@ -13421,34 +13562,15 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) protectRelations.addRelation(partnerRelation); - int bad_segment; - if (!IDX_check_master_types(tdbb, idx, partnerRelation, bad_segment)) + if (const auto relVer = partnerRelation->getVersioned(tdbb, idx.idx_primary_relation, CacheFlag::AUTOCREATE)) { - ERR_post(Arg::Gds(isc_no_meta_update) << + int bad_segment; + if (!IDX_check_master_types(tdbb, idx, relVer, bad_segment)) + { + ERR_post(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_partner_idx_incompat_type) << Arg::Num(bad_segment + 1)); + } } - - /*** hvlad: this code was never called but i preserve it for Claudio review and decision - - // CVC: Currently, the server doesn't enforce FK creation more than at DYN level. - // If DYN is bypassed, then FK creation succeeds and operation will fail at run-time. - // The aim is to check REFERENCES at DDL time instead of DML time and behave accordingly - // to ANSI SQL rules for REFERENCES rights. - // For testing purposes, I'm calling SCL_check_index, although most of the DFW ops are - // carried using internal metadata structures that are refreshed from system tables. - - // Don't bother if the master's owner is the same than the detail's owner. - // If both tables aren't defined in the same session, partnerRelation->rel_owner_name - // won't be loaded hence, we need to be careful about null pointers. - - if (indexRelation->rel_owner_name.length() == 0 || - partnerRelation->rel_owner_name.length() == 0 || - indexRelation->rel_owner_name != partnerRelation->rel_owner_name) - { - SCL_check_index(tdbb, partnerRelation->getName(), - idx.idx_id + 1, SCL_references); - } - ***/ } if (active) @@ -13459,10 +13581,11 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) fb_assert(idxId <= dbb->dbb_max_idx); idx.idx_id = idxId; + SelectivityList selectivity(*tdbb->getDefaultPool()); IDX_create_index(tdbb, - concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, - relation, &idx, indexName, &idxId, transaction, selectivity); + concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, + relation, &idx, indexName, &idxId, transaction, selectivity); fb_assert(idxId == idx.idx_id); if (!tempConn) @@ -13516,6 +13639,8 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) MOVE_CLEAR(&idx, sizeof(index_desc)); idx.idx_flags = 0; idx.idx_count = 0; + idx.idx_pg_space_id = DB_PAGE_SPACE; + CompilerScratch* csb = nullptr; const auto dbb = tdbb->getDatabase(); @@ -13548,6 +13673,11 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) fb_assert(IDX.RDB$INDEX_INACTIVE.NULL || !IDX.RDB$INDEX_INACTIVE); } + if (!IDX.RDB$TABLESPACE_NAME.NULL) + { + idx.idx_pg_space_id = Tablespace::lookupId(tdbb, IDX.RDB$TABLESPACE_NAME); + } + if (IDX.RDB$SEGMENT_COUNT) { // Msg359: segments not allowed in expression index %s @@ -13728,6 +13858,9 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* strcpy(IDX.RDB$RELATION_NAME, definition.relation.object.c_str()); IDX.RDB$SYSTEM_FLAG = 0; + MetaName relationTablespace; + bool temporary = false; + // Check if the table is actually a view. AutoCacheRequest request2(tdbb, drq_l_view_idx, DYN_REQUESTS); @@ -13743,6 +13876,15 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* // msg 181: "attempt to index a view" status_exception::raise(Arg::PrivateDyn(181)); } + + if (!VREL.RDB$TABLESPACE_NAME.NULL) + relationTablespace = VREL.RDB$TABLESPACE_NAME; + + if (!VREL.RDB$RELATION_TYPE.NULL) + { + ULONG flags = MET_get_rel_flags_from_TYPE(VREL.RDB$RELATION_TYPE); + temporary = flags & (REL_temp_tran | REL_temp_conn); + } } END_FOR @@ -13769,6 +13911,28 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* IDX.RDB$INDEX_TYPE = SSHORT(definition.descending.asBool()); } + IDX.RDB$TABLESPACE_NAME.NULL = temporary; + + if (definition.tableSpace.hasData() && applyTablespacesDdl(tdbb)) + { + if (temporary) + status_exception::raise(Arg::Gds(isc_dyn_cant_set_ts_index) << IDX.RDB$INDEX_NAME); + + if (!checkObjectExist(tdbb, QualifiedName(definition.tableSpace), obj_tablespace)) + status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << definition.tableSpace.c_str()); + + strcpy(IDX.RDB$TABLESPACE_NAME, definition.tableSpace.c_str()); + } + else + { + /** + * If TABLESPACE clause is omitted, the index uses relation tablespace. + * It's necessary to write it explicitly since later we can move relation + * into another tablespace. + */ + strcpy(IDX.RDB$TABLESPACE_NAME, relationTablespace.c_str()); + } + request2.reset(tdbb, drq_l_lfield, DYN_REQUESTS); for (FB_SIZE_T i = 0; i < definition.columns.getCount(); ++i) @@ -14189,6 +14353,8 @@ void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, attachment->storeBinaryBlob(tdbb, transaction, &definition.expressionBlr, computedValue); } + definition.tableSpace = tableSpace; + if (partial) { const auto dbb = tdbb->getDatabase(); @@ -14360,7 +14526,6 @@ string AlterIndexNode::internalPrint(NodePrinter& printer) const NODE_PRINT(printer, idxId); NODE_PRINT(printer, concurrently); - NODE_PRINT(printer, validateUnique); return "AlterIndexNode"; } @@ -14386,7 +14551,9 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, if (MET_get_ltt_index(transaction->getAttachment(), indexName, <t, <tIndex)) { - if (!validateUnique) + fb_assert(operation != OP_SET_TABLESPACE); + + if (operation != OP_VALIDATE_UNIQUE) alterLocalTempIndex(tdbb, dsqlScratch, transaction, ltt, lttIndex); savePoint.release(); // everything is ok @@ -14396,7 +14563,7 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, if (tdbb->getDatabase()->readOnly()) ERRD_post(Arg::Gds(isc_read_only_database)); - if (validateUnique) + if (operation == OP_VALIDATE_UNIQUE) { validateUniqueIndex(tdbb, transaction, false); } @@ -14425,8 +14592,8 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr { AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS); bool found = false; + IndexStatus currentStatus = MET_index_state_unknown; - IndexStatus desiredStatus = create ? MET_index_active : MET_index_inactive; FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDX IN RDB$INDICES @@ -14435,23 +14602,73 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr IDX.RDB$INDEX_NAME EQ indexName.object.c_str() { found = true; - currentStatus = MetadataCache::getIndexStatus(IDX.RDB$INDEX_INACTIVE.NULL, IDX.RDB$INDEX_INACTIVE); - if (currentStatus != desiredStatus) + auto relName = QualifiedName(IDX.RDB$RELATION_NAME, IDX.RDB$SCHEMA_NAME, IDX.RDB$PACKAGE_NAME); + indexRelation = MetadataCache::getPerm(tdbb, relName, CacheFlag::AUTOCREATE); + fb_assert(indexRelation); + + const auto relation = indexRelation->getVersioned(tdbb, CacheFlag::AUTOCREATE); + + if (!IDX.RDB$INDEX_ID.NULL) { - if (indexName.package.isEmpty()) - executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, indexName, {}); + fb_assert(IDX.RDB$INDEX_ID); + idxId = IDX.RDB$INDEX_ID - 1; + } - auto relName = QualifiedName(IDX.RDB$RELATION_NAME, IDX.RDB$SCHEMA_NAME, IDX.RDB$PACKAGE_NAME); - indexRelation = MetadataCache::getPerm(tdbb, relName, CacheFlag::AUTOCREATE); - fb_assert(indexRelation); + if (operation == OP_SET_TABLESPACE) + { + fb_assert(tableSpace.hasData()); - expressionIndex = !IDX.RDB$EXPRESSION_BLR.NULL; - if (!IDX.RDB$INDEX_ID.NULL) + if (!applyTablespacesDdl(tdbb)) + return false; + + if (!checkObjectExist(tdbb, QualifiedName(tableSpace), obj_tablespace)) + status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace); + + AutoRequest request2; + + FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) + REL IN RDB$RELATIONS + WITH REL.RDB$SCHEMA_NAME EQ IDX.RDB$SCHEMA_NAME + AND REL.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME { - fb_assert(IDX.RDB$INDEX_ID); - idxId = IDX.RDB$INDEX_ID - 1; + if (!REL.RDB$RELATION_TYPE.NULL) + { + const auto flags = MET_get_rel_flags_from_TYPE(REL.RDB$RELATION_TYPE); + + if (flags & (REL_temp_tran | REL_temp_conn)) + status_exception::raise(Arg::Gds(isc_dyn_cant_set_ts_index) << indexName.toQuotedString()); + } } + END_FOR + + fb_assert(!IDX.RDB$TABLESPACE_NAME.NULL); + + // Check if we are trying to change tablespace name to the already set + if (tableSpace == IDX.RDB$TABLESPACE_NAME) + return false; + + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, indexName, {}); + + MODIFY IDX + strcpy(IDX.RDB$TABLESPACE_NAME, tableSpace.c_str()); + END_MODIFY + } + else + { + const IndexStatus desiredStatus = + (operation == OP_ACTIVE) ? MET_index_active : MET_index_inactive; + + currentStatus = MetadataCache::getIndexStatus(IDX.RDB$INDEX_INACTIVE.NULL, IDX.RDB$INDEX_INACTIVE); + + if (currentStatus == desiredStatus) // Index is already in desired state, nothing to do + return false; + + if (indexName.package.isEmpty()) + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, indexName, {}); + + expressionIndex = !IDX.RDB$EXPRESSION_BLR.NULL; + if (indexRelation && idxId.has_value()) { [[maybe_unused]] auto ret = indexRelation->oldIndexVersion(tdbb, idxId.value(), CacheFlag::OLD_ALTER); @@ -14461,7 +14678,8 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr MODIFY IDX IDX.RDB$INDEX_INACTIVE.NULL = FALSE; IDX.RDB$INDEX_INACTIVE = desiredStatus; - if (create) + + if (operation == OP_ACTIVE) { IDX.RDB$STATISTICS.NULL = FALSE; IDX.RDB$STATISTICS = -1.0; @@ -14477,19 +14695,18 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr status_exception::raise(Arg::PrivateDyn(48) << Arg::Gds(isc_index_name) << indexName.toQuotedString()); } - if (currentStatus == desiredStatus) // Index is already in desired state, nothing to do - return false; - - return currentStatus != MET_index_deferred_active; + return (operation == OP_SET_TABLESPACE) ? true : (currentStatus != MET_index_deferred_active); } void AlterIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) { - if (create) + if (operation == OP_ACTIVE) { + const auto relation = indexRelation->getVersioned(tdbb, CacheFlag::AUTOCREATE); + if (idxId.has_value()) { - if (!IDX_activate_index(tdbb, indexRelation, idxId.value())) + if (!BTR_activate_index(tdbb, relation, idxId.value())) { // No index to restore, create a new one from scratch AUTO_HANDLE(rq1); @@ -14512,17 +14729,40 @@ void AlterIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) idxId.reset(); } } + if (!idxId.has_value()) { // One way or another the index was just created, finalize it StoreIndexNode storeNode(indexName, indexRelation, expressionIndex, concurrently); - fb_assert(indexRelation->getBasePages()->rel_index_root); + fb_assert(relation->getBasePages()->getIndexRootPage()); storeNode.step2(tdbb, transaction); return; } } else - IDX_mark_index(tdbb, indexRelation, idxId.value()); + { + const auto relation = indexRelation->getVersioned(tdbb, CacheFlag::AUTOCREATE); + fb_assert(relation); + + if (operation == OP_SET_TABLESPACE) + { + const ULONG newPageSpaceId = Tablespace::lookupId(tdbb, tableSpace); + + // Protect relation from modification to create a consistent index copy. + // The lock is held until the transaction end. + ProtectRelations protectRelations(tdbb, transaction, indexRelation, false); + + // Disable GC during page copying + GCLock::Exclusive gcLock(tdbb, indexRelation); + gcLock.acquire(transaction->getLockWait() != 0); + + BTR_copy_index(tdbb, relation, idxId.value(), newPageSpaceId); + } + else + { + IDX_mark_index(tdbb, relation, idxId.value()); + } + } indexRelation->newIndexVersion(tdbb, idxId.value(), CacheFlag::MINISCAN); } @@ -14541,7 +14781,8 @@ void AlterIndexNode::alterLocalTempIndex(thread_db* tdbb, DsqlCompilerScratch* d LttUndoItem::LTT_UNDO_ALTER, ltt->name, FB_NEW_POOL(attPool) LocalTemporaryTable(attPool, *ltt)); const bool wasInactive = lttIndex->inactive; - const bool willBeInactive = !create; + fb_assert(operation != OP_SET_TABLESPACE); + const bool willBeInactive = (operation == OP_INACTIVE); // Update the inactive flag lttIndex->inactive = willBeInactive; @@ -14902,7 +15143,10 @@ void DropIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) fb_assert(idxId >= 0 && idxId < dbb->dbb_max_idx); - if (IDX_mark_index(tdbb, indexRelation, idxId)) + const auto relation = indexRelation->getVersioned(tdbb, CacheFlag::AUTOCREATE); + fb_assert(relation); + + if (IDX_mark_index(tdbb, relation, idxId)) { if (tdbb->tdbb_flags & TDBB_use_db_page_space) indexRelation->eraseIndex(tdbb, idxId); @@ -15269,8 +15513,7 @@ void CreateShadowNode::checkPermission(thread_db* tdbb) void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction) { - // Should be caught by the parser. - if (number == 0) + if (number == 0) // should be caught by the parser { status_exception::raise( Arg::Gds(isc_sqlerr) << Arg::Num(-607) << @@ -15281,9 +15524,28 @@ void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScrat // run all statements under savepoint control AutoSavePoint savePoint(tdbb, transaction); + const auto attachment = transaction->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + // Shadows are not compatible with user-defined tablespaces + + static const CachedRequestId checkUserTablespacesId; + AutoCacheRequest checkUserTablespaces(tdbb, checkUserTablespacesId); + + FOR (REQUEST_HANDLE checkUserTablespaces TRANSACTION_HANDLE metaTransaction) + FIRST 1 TS IN RDB$TABLESPACES + WITH TS.RDB$SYSTEM_FLAG NE 1 + { + static const auto errorMsg = "Cannot create shadow because the database has user-defined tablespaces"; + status_exception::raise( + Arg::Gds(isc_sqlerr) << Arg::Num(-607) << + Arg::Gds(isc_dsql_command_err) << + Arg::Gds(isc_random) << Arg::Str(errorMsg)); + } + END_FOR + // If a shadow set identified by the shadow number already exists return error - auto* metaTransaction = tdbb->getAttachment()->getMetaTransaction(tdbb); AutoCacheRequest request(tdbb, drq_l_shadow, DYN_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) @@ -16637,6 +16899,22 @@ static bool checkObjectExist(thread_db* tdbb, const QualifiedName& name, int typ END_FOR break; } + + case obj_tablespace: + { + fb_assert(name.object.hasData() && name.schema.isEmpty()); + + static const CachedRequestId requestId; + AutoCacheRequest request(tdbb, requestId); + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + X IN RDB$TABLESPACES + WITH X.RDB$TABLESPACE_NAME EQ name.object.c_str() + { + rc = true; + } + END_FOR + break; + } } return rc; diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 2368d1b3405..f77cb8ef80e 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -1344,7 +1344,8 @@ class RelationNode : public DdlNode fieldSource(p), identitySequence(p), defaultSource(p), - baseField(p) + baseField(p), + tableSpace(p) { } @@ -1364,6 +1365,7 @@ class RelationNode : public DdlNode Firebird::ByteChunk defaultValue; std::optional viewContext; MetaName baseField; + MetaName tableSpace; }; struct IndexConstraintClause @@ -1418,7 +1420,8 @@ class RelationNode : public DdlNode refUpdateAction(RI_RESTRICT), refDeleteAction(RI_RESTRICT), triggers(p), - blrWritersHolder(p) + blrWritersHolder(p), + tableSpace(p) { } @@ -1431,6 +1434,7 @@ class RelationNode : public DdlNode const char* refDeleteAction; Firebird::ObjectsArray triggers; Firebird::ObjectsArray blrWritersHolder; + MetaName tableSpace; }; struct CreateDropConstraint @@ -1459,7 +1463,9 @@ class RelationNode : public DdlNode TYPE_DROP_CONSTRAINT, TYPE_ALTER_SQL_SECURITY, TYPE_ALTER_PUBLICATION, - TYPE_ADD_INLINE_TABLE_INDEX + TYPE_ADD_PACKAGED_TABLE_INDEX, + TYPE_ADD_INLINE_TABLE_INDEX, + TYPE_SET_TABLESPACE }; explicit Clause(MemoryPool& p, Type aType) noexcept @@ -1518,7 +1524,8 @@ class RelationNode : public DdlNode refRelation(p), refColumns(p), refAction(NULL), - check(NULL) + check(NULL), + tableSpace(p) { } @@ -1531,6 +1538,7 @@ class RelationNode : public DdlNode NestConst refAction; NestConst check; bool createIfNotExistsOnly = false; + MetaName tableSpace; }; struct IdentityOptions @@ -1752,6 +1760,7 @@ class RelationNode : public DdlNode std::optional tempRowsFlag; // REL_temp_tran, REL_temp_conn Firebird::TriState ssDefiner; Firebird::TriState replicationState; + MetaName tableSpace; ModifyIndexList indexList; }; @@ -1973,18 +1982,20 @@ class RecreateViewNode final : class ModifyIndexNode { public: - ModifyIndexNode(const QualifiedName& indexName, Cached::Relation* rel, bool create, bool expression) + enum OP { OP_ACTIVE, OP_INACTIVE, OP_VALIDATE_UNIQUE, OP_SET_TABLESPACE }; + + ModifyIndexNode(const QualifiedName& indexName, Cached::Relation* rel, OP op, bool expression) : indexName(indexName), indexRelation(rel), - create(create), + operation(op), expressionIndex(expression) { fb_assert(indexRelation); } - ModifyIndexNode(const QualifiedName& indexName, bool create, bool expression) + ModifyIndexNode(const QualifiedName& indexName, OP op, bool expression) : indexName(indexName), - create(create), + operation(op), expressionIndex(expression) { } @@ -1997,7 +2008,7 @@ class ModifyIndexNode bool check(thread_db*, MetaName iName) { - return create && (indexName.object == iName); + return (operation == OP_ACTIVE) && (indexName.object == iName); } Cached::Relation* getRelation() const @@ -2014,8 +2025,11 @@ class ModifyIndexNode QualifiedName indexName; Cached::Relation* indexRelation = nullptr; - bool create; + const OP operation; bool expressionIndex; + +public: + MetaName tableSpace; }; @@ -2047,6 +2061,7 @@ class CreateIndexNode final : public DdlNode bid conditionSource; QualifiedName refRelation; Firebird::ObjectsArray refColumns; + MetaName tableSpace; }; public: @@ -2091,6 +2106,7 @@ class CreateIndexNode final : public DdlNode NestConst computed; NestConst partial; bool createIfNotExistsOnly = false; + MetaName tableSpace; }; @@ -2098,7 +2114,7 @@ class StoreIndexNode final : public ModifyIndexNode { public: StoreIndexNode(const QualifiedName& indexName, Cached::Relation* rel, bool expressionIndex, bool concurrently) - : ModifyIndexNode(indexName, rel, true, expressionIndex), + : ModifyIndexNode(indexName, rel, OP_ACTIVE, expressionIndex), concurrently(concurrently) { } @@ -2116,8 +2132,8 @@ class StoreIndexNode final : public ModifyIndexNode class AlterIndexNode final : public ModifyIndexNode, public DdlNode { public: - AlterIndexNode(MemoryPool& p, const QualifiedName& name, bool active) - : ModifyIndexNode(name, active, false), + AlterIndexNode(MemoryPool& p, const QualifiedName& name, OP op) + : ModifyIndexNode(name, op, false), DdlNode(p) { } @@ -2158,7 +2174,6 @@ class AlterIndexNode final : public ModifyIndexNode, public DdlNode public: bool concurrently = false; - bool validateUnique = false; }; @@ -2209,7 +2224,7 @@ class DropIndexNode final : public ModifyIndexNode, public DdlNode { public: DropIndexNode(MemoryPool& p, const QualifiedName& name) - : ModifyIndexNode(name, false, false), + : ModifyIndexNode(name, OP_INACTIVE, false), DdlNode(p) { } diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index ee453559449..68a269b16a2 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -6869,17 +6869,6 @@ ValueExprNode* FieldNode::pass1(thread_db* tdbb, CompilerScratch* csb) } } - // if this is a modify or store, check REFERENCES access to any foreign keys - - /* CVC: This is against the SQL standard. REFERENCES should be enforced only at the - time the FK is defined in DDL, not when a DML is going to be executed. - if (((tail->csb_flags & csb_modify) || (tail->csb_flags & csb_store)) && - !(relation->rel_view_rse || relation->rel_file)) - { - IDX_check_access(tdbb, csb, tail->csb_view, relation); - } - */ - // Unless this is a validation expression, post the required privilege access // to the current relation and field diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index f0c258ef0f7..3ef4be2d590 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -202,6 +202,12 @@ class DdlNode : public Node static void deletePrivilegesByRelName(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& name, int type); + static inline bool applyTablespacesDdl(thread_db* tdbb) + { + return !(tdbb->tdbb_flags & TDBB_replicator) || + tdbb->getDatabase()->replConfig()->applyTablespacesDdl; + } + public: // Check permission on DDL operation. Return true if everything is OK. // Raise an exception for bad permission. diff --git a/src/dsql/Parser.cpp b/src/dsql/Parser.cpp index 1e6da087deb..ae947e44844 100644 --- a/src/dsql/Parser.cpp +++ b/src/dsql/Parser.cpp @@ -967,20 +967,22 @@ int Parser::yylexAux() if (signExponent == 0) signExponent = 1; - exponentValue *= decimalConversion; - - if (signExponent == 1) - exponentValue += ch; - else - exponentValue -= ch; - + // The exponent is needed only to choose between DOUBLE PRECISION and DECFLOAT. + // The DECFLOAT range is checked when the literal is converted: it depends on + // the mantissa digits and the scale too (0.1e6145 = 1e6144), subnormal values + // (1e-6176) are allowed. Stopping here also prevents integer overflow. if (!isOverExponent64b) { + exponentValue *= decimalConversion; + + if (signExponent == 1) + exponentValue += ch; + else + exponentValue -= ch; + if (exponentValue > DBL_MAX_10_EXP || exponentValue < -DBL_MAX_10_EXP) isOverExponent64b = true; } - else if (exponentValue > DECQUAD_Emax || exponentValue < DECQUAD_Emin) - exceptionNumericLiterals(Firebird::string("Overflow of the exponent")); } else { diff --git a/src/dsql/Parser.h b/src/dsql/Parser.h index 598cb8888bc..89cedb465d2 100644 --- a/src/dsql/Parser.h +++ b/src/dsql/Parser.h @@ -31,6 +31,7 @@ #include "../dsql/AggNodes.h" #include "../dsql/WinNodes.h" #include "../dsql/PackageNodes.h" +#include "../dsql/TablespaceNodes.h" #include "../dsql/StmtNodes.h" #include "../jrd/RecordSourceNodes.h" #include "../common/classes/PodOptional.h" diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index f9eaa7f2c1a..e09b463f64a 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -2260,6 +2260,8 @@ jrd_rel* DeclareLocalTableNode::getRelation(thread_db* tdbb, Request* request) c if (relation) return relation; + const auto dbb = tdbb->getDatabase(); + auto& pool = request ? *request->req_pool : *tdbb->getDefaultPool(); if (tableNumber >= MAX_DECLARED_LTT_COUNT) @@ -2277,12 +2279,12 @@ jrd_rel* DeclareLocalTableNode::getRelation(thread_db* tdbb, Request* request) c const auto permanent = FB_NEW_POOL(pool) Cached::Relation(tdbb, pool, id); permanent->rel_name = name; permanent->rel_flags = REL_sql_relation | REL_temp_ltt | REL_temp_frame; - permanent->getBasePages()->rel_pg_space_id = tdbb->getDatabase()->dbb_page_manager.getTempPageSpaceID(tdbb); const auto newRelation = FB_NEW_POOL(pool) jrd_rel(pool, permanent); newRelation->rel_current_fmt = 1; newRelation->rel_dbkey_length = 8; newRelation->rel_fields = vec::newVector(pool, newRelation->rel_fields, format->fmt_count); + newRelation->setPageSpaceId(dbb->dbb_page_manager.getTempPageSpaceID(tdbb)); const auto relFormat = Format::newFormat(pool, format->fmt_count); relFormat->fmt_length = format->fmt_length; @@ -2432,7 +2434,7 @@ void DeclareLocalTableNode::reset(thread_db* tdbb, Request* request) const try { - permanent->delPages(tdbb, tempInstanceId); + permanent->deletePages(tdbb, tempInstanceId); if (transaction) transaction->discardTempFrameActions(relation, tempInstanceId); diff --git a/src/dsql/TablespaceNodes.epp b/src/dsql/TablespaceNodes.epp new file mode 100644 index 00000000000..ac496aa8382 --- /dev/null +++ b/src/dsql/TablespaceNodes.epp @@ -0,0 +1,431 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Roman Simakov + * for the RedDatabase project. + * + * Copyright (c) 2018 + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/dyn.h" +#include "../jrd/intl.h" +#include "../jrd/jrd.h" +#include "../jrd/lck.h" +#include "../jrd/met.h" +#include "../jrd/tra.h" +#include "../jrd/btr_proto.h" +#include "../jrd/dfw_proto.h" +#include "../jrd/exe_proto.h" +#include "../jrd/met_proto.h" +#include "../jrd/vio_proto.h" +#include "../dsql/make_proto.h" +#include "../dsql/pass1_proto.h" +#include "../common/StatusArg.h" +#include "../common/os/path_utils.h" +#include "../common/isc_f_proto.h" +#include "../jrd/Attachment.h" +#include "../jrd/scl_proto.h" +#include "../jrd/dyn_ut_proto.h" +#include "../jrd/pag_proto.h" +#include "../jrd/os/pio_proto.h" + +#include "../dsql/TablespaceNodes.h" + +using namespace Firebird; + +namespace Jrd { + +DATABASE DB = STATIC "ODS.RDB"; + + +//---------------------- + +string CreateAlterTablespaceNode::internalPrint(NodePrinter& printer) const +{ + DdlNode::internalPrint(printer); + + NODE_PRINT(printer, name); + NODE_PRINT(printer, create); + NODE_PRINT(printer, alter); + NODE_PRINT(printer, offline); + NODE_PRINT(printer, readonly); + + return "CreateAlterTablespaceNode"; +} + + +void CreateAlterTablespaceNode::checkPermission(thread_db* tdbb) +{ + if (alter) + SCL_check_tablespace(tdbb, name, SCL_alter); + else + SCL_check_create_access(tdbb, obj_tablespaces, {}); +} + + +void CreateAlterTablespaceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, + jrd_tra* transaction) +{ + fb_assert(create || alter); + + if (!applyTablespacesDdl(tdbb)) + return; + + PathUtils::fixupSeparators(fileName); + + if (PathUtils::isRelative(fileName)) + { + PathName temp = fileName; + PathName db_path, db_file; + PathUtils::splitLastComponent(db_path, db_file, tdbb->getDatabase()->dbb_filename); + PathUtils::concatPath(fileName, db_path, temp); + } + + // run all statements under savepoint control + AutoSavePoint savePoint(tdbb, transaction); + + if (alter) + { + if (!executeAlter(tdbb, dsqlScratch, transaction)) + { + if (create) // create or alter + executeCreate(tdbb, dsqlScratch, transaction); + else + { + status_exception::raise( + Arg::Gds(isc_no_meta_update) << + Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(name)); + } + } + } + else + executeCreate(tdbb, dsqlScratch, transaction); + + savePoint.release(); // everything is ok +} + +void CreateAlterTablespaceNode::checkFileName(thread_db* tdbb) +{ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + PathName expandedFileName(fileName); + ISC_expand_filename(expandedFileName, true); + + if (fileName.hasData()) + { + if (!PIO_file_exists(fileName.c_str())) + ERR_post(Arg::Gds(isc_ts_file_not_exists) << Arg::Str(fileName)); + + AutoCacheRequest request(tdbb, irq_chk_ts_filename, IRQ_REQUESTS); + + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + X IN RDB$TABLESPACES + WITH X.RDB$SYSTEM_FLAG NE 1 + AND X.RDB$TABLESPACE_NAME NE name.c_str() + { + PathName otherfileName(X.RDB$FILE_NAME); + ISC_expand_filename(otherfileName, true); + + if (expandedFileName == otherfileName) + { + const MetaName tablespaceName(X.RDB$TABLESPACE_NAME); + + ERR_post(Arg::Gds(isc_ts_file_dup) << + Arg::Str(otherfileName) << Arg::Str(tablespaceName)); + } + } + END_FOR + } +} + +void CreateAlterTablespaceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, + jrd_tra* transaction) +{ + const auto dbb = tdbb->getDatabase(); + const auto attachment = transaction->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + const MetaName& userName = attachment->att_user->getUserName(); + + if (createIfNotExistsOnly && + !DYN_UTIL_check_unique_name_nothrow(tdbb, QualifiedName(name), obj_tablespace)) + { + return; + } + + // User-defined tablespaces are not compatible with shadows + + static const CachedRequestId checkShadowFilesId; + AutoCacheRequest checkShadowFiles(tdbb, checkShadowFilesId); + + FOR (REQUEST_HANDLE checkShadowFiles TRANSACTION_HANDLE metaTransaction) + FIRST 1 F IN RDB$FILES + WITH F.RDB$SHADOW_NUMBER NOT MISSING + AND F.RDB$SHADOW_NUMBER NE 0 + { + static const auto errorMsg = "Cannot create tablespace because the database has shadows defined"; + status_exception::raise( + Arg::Gds(isc_sqlerr) << Arg::Num(-607) << + Arg::Gds(isc_dsql_command_err) << + Arg::Gds(isc_random) << Arg::Str(errorMsg)); + } + END_FOR + + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, + DDL_TRIGGER_CREATE_TABLESPACE, QualifiedName(name), {}); + + DYN_UTIL_check_unique_name(tdbb, QualifiedName(name), obj_tablespace); + + // File with tablespace should not exist. Checking it in DFW is silent and causes + // cleanup of existing files at phase 0. Early checking preserves existing files. + if (PIO_file_exists(fileName.c_str())) + ERR_post(Arg::Gds(isc_ts_file_exists) << Arg::Str(fileName)); + + AutoCacheRequest request(tdbb, drq_s_tablespace, DYN_REQUESTS); + + int faults = 0; + SINT64 id = INVALID_PAGE_SPACE; + + while (true) + { + try + { + id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_ts_id, "RDB$TABLESPACES"); + id %= (MAX_TABLESPACE_ID + 1); + + if (!id || id == 1) // skip DB_PAGE_SPACE + continue; + + STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + X IN RDB$TABLESPACES USING + { + X.RDB$TABLESPACE_ID = id; + strcpy(X.RDB$TABLESPACE_NAME, name.c_str()); + X.RDB$SYSTEM_FLAG = 0; + + X.RDB$OWNER_NAME.NULL = FALSE; + strcpy(X.RDB$OWNER_NAME, userName.c_str()); + + if (fileName.length() >= sizeof(X.RDB$FILE_NAME)) + status_exception::raise(Arg::Gds(isc_dyn_name_longer)); + + X.RDB$FILE_NAME.NULL = FALSE; + strcpy(X.RDB$FILE_NAME, fileName.c_str()); + + X.RDB$OFFLINE.NULL = FALSE; + X.RDB$OFFLINE = offline; + + X.RDB$READ_ONLY.NULL = FALSE; + X.RDB$READ_ONLY = readonly; + } + END_STORE + + break; + } + catch (const status_exception& ex) + { + if (ex.value()[1] != isc_unique_key_violation) + throw; + + if (++faults > MAX_TABLESPACE_ID) + throw; + + fb_utils::init_status(tdbb->tdbb_status_vector); + } + } + + fb_assert(id != INVALID_PAGE_SPACE && id > DB_PAGE_SPACE); + + // Create new pagespace and the backing file, load new tablespace into the cache + const auto tableSpace = Tablespace::create(tdbb, id); + tableSpace->mark(tdbb, Tablespace::Operation::CREATE, transaction, fileName); + + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TABLESPACE, + QualifiedName(name), {}); +} + + +bool CreateAlterTablespaceNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, + jrd_tra* transaction) +{ + const auto attachment = transaction->getAttachment(); + + bool modified = false; + + AutoCacheRequest request(tdbb, drq_m_tablespace, DYN_REQUESTS); + + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + X IN RDB$TABLESPACES + WITH X.RDB$TABLESPACE_NAME EQ name.c_str() + { + if (X.RDB$SYSTEM_FLAG) + ERR_post(Arg::Gds(isc_dyn_cannot_mod_sys_ts) << Arg::Str(name)); + + if (fileName.length() >= sizeof(X.RDB$FILE_NAME)) + status_exception::raise(Arg::Gds(isc_dyn_name_longer)); + + modified = true; + + checkFileName(tdbb); + + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, + DDL_TRIGGER_ALTER_TABLESPACE, QualifiedName(name), {}); + + const auto tableSpace = Tablespace::lookup(tdbb, X.RDB$TABLESPACE_ID, false); + tableSpace->mark(tdbb, Tablespace::Operation::ALTER, transaction, fileName); + + MODIFY X + if (fileName.hasData()) + { + fb_assert(X.RDB$FILE_NAME.NULL == FALSE); + strcpy(X.RDB$FILE_NAME, fileName.c_str()); + } + + X.RDB$OFFLINE.NULL = FALSE; + X.RDB$OFFLINE = offline; + + X.RDB$READ_ONLY.NULL = FALSE; + X.RDB$READ_ONLY = readonly; + END_MODIFY + } + END_FOR + + if (modified) + { + executeDdlTrigger(tdbb, dsqlScratch, transaction, + DTW_AFTER, DDL_TRIGGER_ALTER_TABLESPACE, QualifiedName(name), {}); + } + + return modified; +} + + +//---------------------- + + +string DropTablespaceNode::internalPrint(NodePrinter& printer) const +{ + DdlNode::internalPrint(printer); + + NODE_PRINT(printer, name); + NODE_PRINT(printer, silent); + + return "DropTablespaceNode"; +} + +void DropTablespaceNode::checkDependencies(thread_db* tdbb, jrd_tra* transaction) +{ + static const CachedRequestId requestHandleId1, requestHandleId2; + unsigned total = 0; + + // Find all tables + AutoCacheRequest requestHandle1(tdbb, requestHandleId1); + + FOR (REQUEST_HANDLE requestHandle1 TRANSACTION_HANDLE transaction) + X IN RDB$RELATIONS + WITH X.RDB$TABLESPACE_NAME EQ name.c_str() + { + total++; + } + END_FOR + + // Find all indices + AutoCacheRequest requestHandle2(tdbb, requestHandleId2); + + FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction) + X IN RDB$INDICES + WITH X.RDB$TABLESPACE_NAME EQ name.c_str() + { + if (MetadataCache::getIndexStatus(X.RDB$INDEX_INACTIVE.NULL, X.RDB$INDEX_INACTIVE) == MET_index_deferred_drop) + { + const QualifiedName indexName(X.RDB$RELATION_NAME, X.RDB$SCHEMA_NAME, X.RDB$PACKAGE_NAME); + BTR_cleanup_index(tdbb, indexName, transaction, MetaId(X.RDB$INDEX_ID - 1)); + continue; + } + + total++; + } + END_FOR + + if (total) + { + status_exception::raise(Arg::Gds(isc_no_meta_update) << + Arg::Gds(isc_no_delete) << + Arg::Gds(isc_tablespace_name) << Arg::Str(name) << + Arg::Gds(isc_dependency) << Arg::Num(total)); + } +} + +void DropTablespaceNode::checkPermission(thread_db* tdbb) +{ + SCL_check_tablespace(tdbb, name, SCL_drop); +} + +void DropTablespaceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, + jrd_tra* transaction) +{ + if (!applyTablespacesDdl(tdbb)) + return; + + // run all statements under savepoint control + AutoSavePoint savePoint(tdbb, transaction); + + bool found = false; + AutoCacheRequest request(tdbb, drq_e_tablespace, DYN_REQUESTS); + + FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + X IN RDB$TABLESPACES + WITH X.RDB$TABLESPACE_NAME EQ name.c_str() + { + if (X.RDB$SYSTEM_FLAG) + ERR_post(Arg::Gds(isc_dyn_cannot_mod_sys_ts) << Arg::Str(name)); + + found = true; + + checkDependencies(tdbb, transaction); + + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, + DDL_TRIGGER_DROP_TABLESPACE, QualifiedName(name), {}); + + const auto tableSpace = Tablespace::lookup(tdbb, X.RDB$TABLESPACE_ID, true); + tableSpace->mark(tdbb, Tablespace::Operation::DROP, transaction, ""); + + ERASE X; + + if (!X.RDB$SECURITY_CLASS.NULL) + deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS); + } + END_FOR + + if (found) + { + executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TABLESPACE, + QualifiedName(name), {}); + } + else if (!silent) + { + status_exception::raise( + Arg::Gds(isc_no_meta_update) << + Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(name)); + } + + savePoint.release(); // everything is ok +} + + +} // namespace Jrd diff --git a/src/dsql/TablespaceNodes.h b/src/dsql/TablespaceNodes.h new file mode 100644 index 00000000000..6445438ca14 --- /dev/null +++ b/src/dsql/TablespaceNodes.h @@ -0,0 +1,120 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Roman Simakov + * for the RedDatabase project. + * + * Copyright (c) 2018 + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef DSQL_TABLESPACE_NODES_H +#define DSQL_TABLESPACE_NODES_H + +#include "../dsql/DdlNodes.h" +#include "../common/classes/array.h" + +namespace Jrd { + + +class CreateAlterTablespaceNode : public DdlNode +{ +public: + CreateAlterTablespaceNode(MemoryPool& pool, const MetaName& aName) + : DdlNode(pool), + name(pool, aName), + fileName(pool), + create(true), + alter(false), + offline(false), + readonly(false) + { + } + +public: + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual void checkPermission(thread_db* tdbb); + virtual void execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + +protected: + virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector) + { + statusVector << + Firebird::Arg::Gds(createAlterCode(create, alter, + isc_dsql_create_ts_failed, isc_dsql_alter_ts_failed, + isc_dsql_create_alter_ts_failed)) << + name; + } + +private: + void checkFileName(thread_db* tdbb); + void executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + bool executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + +public: + MetaName name; + Firebird::PathName fileName; + bool create; + bool alter; + bool offline; + bool readonly; + bool createIfNotExistsOnly = false; +}; + + +class DropTablespaceNode : public DdlNode +{ +public: + DropTablespaceNode(MemoryPool& pool, const MetaName& aName) + : DdlNode(pool), name(pool, aName) + { + } + +public: + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual void checkPermission(thread_db* tdbb); + virtual void execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + +protected: + virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector) + { + statusVector << Firebird::Arg::Gds(isc_dsql_drop_ts_failed) << name; + } + +private: + void checkDependencies(thread_db* tdbb, jrd_tra* transaction); + +public: + MetaName name; + bool silent = false; + bool dropDependencies = false; +}; + +template <> +inline RecreateNode:: + RecreateNode(MemoryPool& p, CreateAlterTablespaceNode* aCreateNode) + : DdlNode(p), + createNode(aCreateNode), + dropNode(p, createNode->name) + { + dropNode.silent = true; + } + +typedef RecreateNode + RecreateTablespaceNode; + +} // namespace + +#endif // DSQL_TABLESPACE_NODES_H diff --git a/src/dsql/btyacc_fb.ske b/src/dsql/btyacc_fb.ske index 8bbf430148d..e8a113207d3 100644 --- a/src/dsql/btyacc_fb.ske +++ b/src/dsql/btyacc_fb.ske @@ -24,6 +24,7 @@ #include "../dsql/BoolNodes.h" #include "../dsql/ExprNodes.h" #include "../dsql/PackageNodes.h" +#include "../dsql/TablespaceNodes.h" #include "../dsql/StmtNodes.h" #include "../dsql/WinNodes.h" #include "../jrd/RecordSourceNodes.h" diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index d8d301322cd..b01472a86db 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -252,7 +252,8 @@ class dsql_fld : public TypeClause public: explicit dsql_fld(MemoryPool& p) : TypeClause(p, {}), - fld_name(p) + fld_name(p), + fld_ts_name(p) { } @@ -268,6 +269,7 @@ class dsql_fld : public TypeClause USHORT fld_id = 0; // Field ID in database USHORT fld_pos = 0; // Field position in relation MetaName fld_name; + MetaName fld_ts_name; // Tablespace name for BLOB field }; // values used in fld_flags diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index e9824176ae0..9d335f9fbde 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -153 shift/reduce conflicts, 8 reduce/reduce conflicts. +167 shift/reduce conflicts, 9 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 3df7528b5cc..667aa5b3989 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -709,6 +709,8 @@ using namespace Firebird; %token BIN_XOR_AGG %token BTRIM %token CALL +%token CONCURRENTLY +%token CONSTANT %token CURRENT_SCHEMA %token DOWNTO %token ERROR @@ -723,16 +725,15 @@ using namespace Firebird; %token NAMED_ARG_ASSIGN %token PERCENTILE_CONT %token PERCENTILE_DISC +%token RDB_RESET_CONTEXT %token RTRIM %token SCHEMA %token SEARCH_PATH +%token TABLESPACE %token TRUNCATE %token UNLIST -%token WITHIN -%token RDB_RESET_CONTEXT -%token CONSTANT -%token CONCURRENTLY %token VALIDATE +%token WITHIN // precedence declarations for expression evaluation @@ -902,6 +903,8 @@ using namespace Firebird; Jrd::SessionResetNode* sessionResetNode; Jrd::ForRangeNode::Direction forRangeDirection; Jrd::CreatePackageConstantNode* createPackageConstantNode; + Jrd::CreateAlterTablespaceNode* createAlterTablespaceNode; + Jrd::DropTablespaceNode* dropTablespaceNode; } %include types.y @@ -1154,6 +1157,8 @@ schemaless_object { $$ = newNode(obj_filters, QualifiedName(getDdlSecurityName(obj_filters))); } | SCHEMA { $$ = newNode(obj_schemas, QualifiedName(getDdlSecurityName(obj_schemas))); } + | TABLESPACE + { $$ = newNode(obj_tablespaces, QualifiedName(getDdlSecurityName(obj_tablespaces))); } ; table_noise @@ -1683,8 +1688,11 @@ create_clause node->relation = $8; $$ = node; } - index_definition(static_cast($9)) + index_definition(static_cast($9)) tablespace_name_clause_opt { + if ($11) + static_cast($9)->tableSpace = *$11; + $$ = $9; } | FUNCTION if_not_exists_opt function_clause @@ -1809,6 +1817,12 @@ create_clause node->createIfNotExistsOnly = $2; $$ = node; } + | TABLESPACE if_not_exists_opt tablespace_clause + { + const auto node = $3; + node->createIfNotExistsOnly = $2; + $$ = node; + } ; @@ -1845,6 +1859,8 @@ recreate_clause { $$ = newNode($2); } | SEQUENCE generator_clause { $$ = newNode($2); } + | TABLESPACE tablespace_clause + { $$ = newNode($2); } | USER create_user_clause { $$ = newNode($2); } | SCHEMA schema_clause @@ -1872,6 +1888,7 @@ replace_clause | MAPPING replace_map_clause(false) { $$ = $2; } | GLOBAL MAPPING replace_map_clause(true) { $$ = $3; } | SCHEMA replace_schema_clause { $$ = $2; } + | TABLESPACE replace_tablespace_clause { $$ = $2; } ; @@ -2468,14 +2485,18 @@ table_clause { $$ = newNode($1, $2); } - '(' table_elements($3) ')' table_attributes($3) + '(' table_elements($3) ')' table_attributes_opt($3) { $$ = $3; } | table_as_query_clause - { - $$ = $1; - } + { + $$ = $1; + } + table_attributes_opt($2) + { + $$ = $2; + } ; %type with_data_opt @@ -2485,10 +2506,10 @@ with_data_opt | WITH NO DATA { $$ = false; } ; -%type table_attributes() -table_attributes($relationNode) +%type table_attributes_opt() +table_attributes_opt($relationNode) : /* nothing */ - | table_attribute($relationNode) table_attributes($relationNode) + | table_attribute($relationNode) table_attributes_opt($relationNode) ; %type table_attribute() @@ -2497,6 +2518,8 @@ table_attribute($relationNode) { setClause($relationNode->ssDefiner, "SQL SECURITY", $1); } | publication_state { setClause($relationNode->replicationState, "PUBLICATION", $1); } + | tablespace_name_clause + { setClause($relationNode->tableSpace, "TABLESPACE", *$1); } ; %type sql_security_clause @@ -2831,7 +2854,7 @@ column_constraint($addColumnClause) constraint.check = $1; } | REFERENCES symbol_table_name column_parens_opt - referential_trigger_action constraint_index_opt + referential_trigger_action constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_FK; @@ -2850,18 +2873,27 @@ column_constraint($addColumnClause) } constraint.index = $5; + + if ($6) + constraint.tableSpace = *$6; } - | UNIQUE constraint_index_opt + | UNIQUE constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_UNIQUE; constraint.index = $2; + + if ($3) + constraint.tableSpace = *$3; } - | PRIMARY KEY constraint_index_opt + | PRIMARY KEY constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_PK; constraint.index = $3; + + if ($4) + constraint.tableSpace = *$4; } ; @@ -2886,7 +2918,7 @@ constraint_name_opt %type table_constraint() table_constraint($relationNode) - : UNIQUE column_parens constraint_index_opt + : UNIQUE column_parens constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = *newNode(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_UNIQUE; @@ -2899,10 +2931,13 @@ table_constraint($relationNode) constraint.index = $3; + if ($4) + constraint.tableSpace = *$4; + $relationNode->clauses.add(&constraint); $$ = &constraint; } - | PRIMARY KEY column_parens constraint_index_opt + | PRIMARY KEY column_parens constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = *newNode(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_PK; @@ -2915,11 +2950,14 @@ table_constraint($relationNode) constraint.index = $4; + if ($5) + constraint.tableSpace = *$5; + $relationNode->clauses.add(&constraint); $$ = &constraint; } | FOREIGN KEY column_parens REFERENCES symbol_table_name column_parens_opt - referential_trigger_action constraint_index_opt + referential_trigger_action constraint_index_opt tablespace_name_clause_opt { RelationNode::AddConstraintClause& constraint = *newNode(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_FK; @@ -2944,6 +2982,9 @@ table_constraint($relationNode) constraint.index = $8; + if ($9) + constraint.tableSpace = *$9; + $relationNode->clauses.add(&constraint); $$ = &constraint; } @@ -4736,6 +4777,9 @@ trigger_ddl_type_items | CREATE MAPPING { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_CREATE_MAPPING); } | ALTER MAPPING { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_ALTER_MAPPING); } | DROP MAPPING { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_DROP_MAPPING); } + | CREATE TABLESPACE { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_CREATE_TABLESPACE); } + | ALTER TABLESPACE { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_ALTER_TABLESPACE); } + | DROP TABLESPACE { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_DROP_TABLESPACE); } | trigger_ddl_type OR trigger_ddl_type { $$ = $1 | $3; } ; @@ -4810,6 +4854,7 @@ alter_clause | GLOBAL MAPPING alter_map_clause(true) { $$ = $3; } | EXTERNAL CONNECTIONS POOL alter_eds_conn_pool_clause { $$ = $4; } | SCHEMA alter_schema_clause { $$ = $2; } + | TABLESPACE alter_tablespace_clause { $$ = $2; } ; %type alter_domain @@ -5016,6 +5061,13 @@ alter_op($relationNode) newNode(RelationNode::Clause::TYPE_ALTER_PUBLICATION); $relationNode->clauses.add(clause); } + | SET TABLESPACE to_opt symbol_tablespace_name + { + setClause($relationNode->tableSpace, "TABLESPACE", *$4); + RelationNode::Clause* clause = + newNode(RelationNode::Clause::TYPE_SET_TABLESPACE); + $relationNode->clauses.add(clause); + } ; %type alter_column_name @@ -5198,13 +5250,18 @@ drop_behaviour alter_index_clause : symbol_index_name index_active concurrently_opt { - $$ = newNode(*$1, $2); + const auto operation = ($2 == true) ? AlterIndexNode::OP_ACTIVE : AlterIndexNode::OP_INACTIVE; + $$ = newNode(*$1, operation); $$->concurrently = $3; } | symbol_index_name VALIDATE UNIQUE { - $$ = newNode(*$1, false); - $$->validateUnique = true; + $$ = newNode(*$1, AlterIndexNode::OP_VALIDATE_UNIQUE); + } + | symbol_index_name SET TABLESPACE to_opt symbol_tablespace_name + { + $$ = newNode(*$1, AlterIndexNode::OP_SET_TABLESPACE); + $$->tableSpace = *$5; } ; @@ -5552,6 +5609,12 @@ drop_clause node->cascade = $4; $$ = node; } + | TABLESPACE if_exists_opt drop_tablespace_clause + { + const auto node = $3; + node->silent = $2; + $$ = node; + } ; %type if_exists_opt @@ -5758,7 +5821,7 @@ without_time_zone_opt %type blob_type blob_type - : BLOB { $$ = newNode(); } blob_subtype(NOTRIAL($2)) blob_segsize charset_clause + : BLOB { $$ = newNode(); } blob_subtype(NOTRIAL($2)) blob_segsize charset_clause /*tablespace_name_clause_opt*/ { $$ = $2; $$->dtype = dtype_blob; @@ -5769,16 +5832,22 @@ blob_type $$->charSet = *$5; $$->flags |= FLD_has_chset; } + + //if ($6) + // $$->fld_ts_name = *$6; } - | BLOB '(' unsigned_short_integer ')' + | BLOB '(' unsigned_short_integer ')' /*tablespace_name_clause_opt*/ { $$ = newNode(); $$->dtype = dtype_blob; $$->length = sizeof(ISC_QUAD); $$->segLength = (USHORT) $3; $$->subType = 0; + + //if ($5) + // $$->fld_ts_name = *$5; } - | BLOB '(' unsigned_short_integer ',' signed_short_integer ')' + | BLOB '(' unsigned_short_integer ',' signed_short_integer ')' /*tablespace_name_clause_opt*/ { $$ = newNode(); $$->dtype = dtype_blob; @@ -5786,8 +5855,11 @@ blob_type $$->segLength = (USHORT) $3; $$->subType = (USHORT) $5; $$->flags |= FLD_has_sub; + + //if ($7) + // $$->fld_ts_name = *$7; } - | BLOB '(' ',' signed_short_integer ')' + | BLOB '(' ',' signed_short_integer ')' /*tablespace_name_clause_opt*/ { $$ = newNode(); $$->dtype = dtype_blob; @@ -5795,6 +5867,9 @@ blob_type $$->segLength = 80; $$->subType = (USHORT) $4; $$->flags |= FLD_has_sub; + + //if ($6) + // $$->fld_ts_name = *$6; } ; @@ -6646,6 +6721,7 @@ ddl_type1_schema | CHARACTER SET { $$ = obj_charset; } | COLLATION { $$ = obj_collation; } | PACKAGE { $$ = obj_package_header; } + | TABLESPACE { $$ = obj_tablespace; } ; %type ddl_type1_noschema @@ -8538,6 +8614,96 @@ map_role | USER { $$ = false; } ; +// TABLESPACE +%type tablespace_clause +tablespace_clause + : symbol_tablespace_name FILE utf_string /*tablespace_offline_clause tablespace_readonly_clause*/ + { + $$ = newNode(*$1); + $$->fileName = $3->c_str(); + //$$->offline = $4; + //$$->readonly = $5; + } + ; + +to_opt + : // nothing + | TO + ; + +in_opt + : // nothing + | IN + ; + +%type symbol_tablespace_name +symbol_tablespace_name + : valid_symbol_name + ; + +//%type tablespace_offline_clause +//tablespace_offline_clause +// : { $$ = false; } +// | OFFLINE { $$ = true; } +// | ONLINE { $$ = false; } +// ; + +//%type tablespace_readonly_clause +//tablespace_readonly_clause +// : { $$ = false; } +// | READ ONLY { $$ = true; } +// | READ WRITE { $$ = false; } +// ; + +%type alter_tablespace_clause +alter_tablespace_clause + : symbol_tablespace_name SET FILE to_opt utf_string /*tablespace_offline_clause tablespace_readonly_clause*/ + { + $$ = newNode(*$1); + $$->create = false; + $$->alter = true; + $$->fileName = $5->c_str(); + //$$->offline = $6; + //$$->readonly = $7; + } + | symbol_tablespace_name /*tablespace_offline_clause tablespace_readonly_clause*/ + { + $$ = newNode(*$1); + $$->create = false; + $$->alter = true; + //$$->offline = $2; + //$$->readonly = $3; + } + ; + +%type replace_tablespace_clause +replace_tablespace_clause + : tablespace_clause + { + $$ = $1; + $$->alter = true; + } + ; + +%type tablespace_name_clause +tablespace_name_clause + : in_opt TABLESPACE symbol_tablespace_name { $$ = $3; } + ; + +%type tablespace_name_clause_opt +tablespace_name_clause_opt + : /* nothing */ { $$ = NULL; } + | tablespace_name_clause { $$ = $1; } + ; + +%type drop_tablespace_clause +drop_tablespace_clause + : symbol_tablespace_name + { + DropTablespaceNode* node = newNode(*$1); + $$ = node; + } + ; // value types @@ -10604,6 +10770,8 @@ non_reserved_word | FORMAT | GENERATE_SERIES | OWNER + | PRIMARY + | TABLESPACE | SEARCH_PATH | SCHEMA | UNLIST diff --git a/src/include/firebird/impl/msg/dyn.h b/src/include/firebird/impl/msg/dyn.h index f0b01203aad..09a144e3b1f 100644 --- a/src/include/firebird/impl/msg/dyn.h +++ b/src/include/firebird/impl/msg/dyn.h @@ -321,3 +321,8 @@ FB_IMPL_MSG_SYMBOL(DYN, 328, dyn_index_cant_validate_inactive, "Can't validate i FB_IMPL_MSG_SYMBOL(DYN, 329, dyn_index_cant_validate_non_unique, "Can't validate non-unique index") FB_IMPL_MSG_SYMBOL(DYN, 330, dyn_index_already_validated, "Index uniqueness already validated") FB_IMPL_MSG_SYMBOL(DYN, 331, dyn_index_validation_failed, "Uniqueness validation of the index @1 failed") +FB_IMPL_MSG(DYN, 332, dyn_ts_not_found, -901, "42", "000", "Tablespace @1 not found") +FB_IMPL_MSG(DYN, 333, dyn_cant_set_ts_table, -901, "42", "000", "Cannot set tablespace for @1") +FB_IMPL_MSG(DYN, 334, dyn_cant_set_ts_index, -901, "42", "000", "Cannot set tablespace for temporary index @1") +FB_IMPL_MSG(DYN, 335, dyn_dup_tablespace, -901, "42", "000", "Tablespace @1 already exists") +FB_IMPL_MSG(DYN, 336, dyn_cannot_mod_sys_ts, -901, "42", "000", "Cannot ALTER or DROP system tablespace @1") diff --git a/src/include/firebird/impl/msg/gbak.h b/src/include/firebird/impl/msg/gbak.h index 7ff2b5855c8..b809b1dd0bd 100644 --- a/src/include/firebird/impl/msg/gbak.h +++ b/src/include/firebird/impl/msg/gbak.h @@ -428,3 +428,13 @@ FB_IMPL_MSG(GBAK, 429, gbak_inv_record_length, -901, "00", "000", "invalid recor FB_IMPL_MSG(GBAK, 430, gbak_fast_path_needs_service, -901, "00", "000", "FAST_PATH requires the -service option") FB_IMPL_MSG_NO_SYMBOL(GBAK, 431, " @1FAST(_PATH) fast-path data transfer (requires -service)") FB_IMPL_MSG(GBAK, 432, gbak_fast_path_no_snapshot, -901, "00", "000", "cannot obtain transaction snapshot number for FAST_PATH backup") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 433, "writing tablespaces") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 434, "writing tablespace @1") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 435, "restoring tablespace @1") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 436, "tablespace") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 437, " @1TS_MAP(PING_FILE) mapping file for tablespaces") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 438, "path to tablespace @1 is not specified (original path: \"@2\")") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 439, " @1TS_ORIG(INAL_PATHS) restore tablespaces to their original paths") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 440, " @1TS set a path for a tablespace") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 441, "parameter for option -@1 is missing") +FB_IMPL_MSG_NO_SYMBOL(GBAK, 442, "cannot open mapping file \"@1\"") diff --git a/src/include/firebird/impl/msg/isql.h b/src/include/firebird/impl/msg/isql.h index f10508fc47b..d3e4388533d 100644 --- a/src/include/firebird/impl/msg/isql.h +++ b/src/include/firebird/impl/msg/isql.h @@ -210,3 +210,6 @@ FB_IMPL_MSG_SYMBOL(ISQL, 210, USAGE_SEARCH_PATH, " -(se)arch_path set s FB_IMPL_MSG_SYMBOL(ISQL, 211, MSG_SCHEMAS, "Schemas:") FB_IMPL_MSG_SYMBOL(ISQL, 212, NO_CONSTANTS, "There are no constants in this database") FB_IMPL_MSG_SYMBOL(ISQL, 213, NO_CONSTANT, "There are no constant @1 in this database") +FB_IMPL_MSG_SYMBOL(ISQL, 214, NO_TABLESPACE, "There is no tablespace @1 in this database") +FB_IMPL_MSG_SYMBOL(ISQL, 215, NO_TABLESPACES, "There are no tablespaces in this database") +FB_IMPL_MSG_SYMBOL(ISQL, 216, MSG_TABLESPACES, "Tablespaces:") diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index a9d76243c7b..426101e2448 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1023,3 +1023,7 @@ FB_IMPL_MSG(JRD, 1020, dsql_agg_return, -204, "42", "000", "RETURN is not allowe FB_IMPL_MSG(JRD, 1021, hypfun_args_non_equal_sort_item, -833, "42", "000", "Number of arguments of hypothetical-set aggregate function @1 must match number of sort items in WITHIN GROUP clause") FB_IMPL_MSG(JRD, 1022, old_format, -804, "07", "000", "Statement format outdated, need to be reprepared") FB_IMPL_MSG(JRD, 1023, user_savepoint, -901, "0A", "000", "Running under user savepoint @1, DDL prohibited") +FB_IMPL_MSG(JRD, 1024, ts_file_exists, -902, "08", "001", "Tablespace file \"@1\" already exists") +FB_IMPL_MSG(JRD, 1025, tablespace_name, -901, "42", "000", "TABLESPACE @1") +FB_IMPL_MSG(JRD, 1026, ts_file_not_exists, -902, "08", "001", "Tablespace file \"@1\" does not exist") +FB_IMPL_MSG(JRD, 1027, ts_file_dup, -902, "08", "001", "Tablespace file \"@1\" is already used by tablespace @2") diff --git a/src/include/firebird/impl/msg/jrd_bugchk.h b/src/include/firebird/impl/msg/jrd_bugchk.h index c00c9042822..df234fef67c 100644 --- a/src/include/firebird/impl/msg/jrd_bugchk.h +++ b/src/include/firebird/impl/msg/jrd_bugchk.h @@ -158,3 +158,4 @@ FB_IMPL_MSG_NO_SYMBOL(JRD_BUGCHK, 303, "Invalid expression for evaluation") FB_IMPL_MSG_SYMBOL(JRD_BUGCHK, 304, rdb_triggers_rdb_flags_corrupt, "RDB$FLAGS for trigger @1 in RDB$TRIGGERS is corrupted") FB_IMPL_MSG_NO_SYMBOL(JRD_BUGCHK, 305, "Blobs accounting is inconsistent") FB_IMPL_MSG_NO_SYMBOL(JRD_BUGCHK, 306, "Found array data type with more than 16 dimensions") +FB_IMPL_MSG_NO_SYMBOL(JRD_BUGCHK, 307, "Tablespace not found") diff --git a/src/include/firebird/impl/msg/sqlerr.h b/src/include/firebird/impl/msg/sqlerr.h index b2af71a0334..b4430def208 100644 --- a/src/include/firebird/impl/msg/sqlerr.h +++ b/src/include/firebird/impl/msg/sqlerr.h @@ -292,3 +292,8 @@ FB_IMPL_MSG(SQLERR, 1052, dsql_create_alter_schema_failed, -901, "42", "000", "C FB_IMPL_MSG(SQLERR, 1053, dsql_create_const_failed, -901, "42", "000", "CREATE CONSTANT @1 failed") FB_IMPL_MSG(SQLERR, 1054, dsql_alter_const_failed, -901, "42", "000", "ALTER CONSTANT @1 failed") FB_IMPL_MSG(SQLERR, 1055, dsql_create_alter_const_failed, -901, "42", "000", "CREATE OR ALTER CONSTANT @1 failed") +FB_IMPL_MSG(SQLERR, 1056, dsql_create_ts_failed, -901, "42", "000", "CREATE TABLESPACE @1 failed") +FB_IMPL_MSG(SQLERR, 1057, dsql_alter_ts_failed, -901, "42", "000", "ALTER TABLESPACE @1 failed") +FB_IMPL_MSG(SQLERR, 1058, dsql_create_alter_ts_failed, -901, "42", "000", "CREATE OR ALTER TABLESPACE @1 failed") +FB_IMPL_MSG(SQLERR, 1059, dsql_drop_ts_failed, -901, "42", "000", "DROP TABLESPACE @1 failed") +FB_IMPL_MSG(SQLERR, 1060, dsql_recreate_ts_failed, -901, "42", "000", "RECREATE TABLESPACE @1 failed") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 393399381a7..10b376125fd 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -6075,6 +6075,10 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_hypfun_args_non_equal_sort_item = 335545341; isc_old_format = 335545342; isc_user_savepoint = 335545343; + isc_ts_file_exists = 335545344; + isc_tablespace_name = 335545345; + isc_ts_file_not_exists = 335545346; + isc_ts_file_dup = 335545347; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; @@ -6242,6 +6246,11 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_dyn_cannot_infer_schema = 336068929; isc_dyn_column_name_exists = 336068931; isc_dyn_function_mismatch = 336068935; + isc_dyn_ts_not_found = 336068940; + isc_dyn_cant_set_ts_table = 336068941; + isc_dyn_cant_set_ts_index = 336068942; + isc_dyn_dup_tablespace = 336068943; + isc_dyn_cannot_mod_sys_ts = 336068944; isc_gbak_unknown_switch = 336330753; isc_gbak_page_size_missing = 336330754; isc_gbak_page_size_toobig = 336330755; @@ -6487,6 +6496,11 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_dsql_create_const_failed = 336397341; isc_dsql_alter_const_failed = 336397342; isc_dsql_create_alter_const_failed = 336397343; + isc_dsql_create_ts_failed = 336397344; + isc_dsql_alter_ts_failed = 336397345; + isc_dsql_create_alter_ts_failed = 336397346; + isc_dsql_drop_ts_failed = 336397347; + isc_dsql_recreate_ts_failed = 336397348; isc_gsec_cant_open_db = 336723983; isc_gsec_switches_error = 336723984; isc_gsec_no_op_spec = 336723985; diff --git a/src/isql/FrontendParser.cpp b/src/isql/FrontendParser.cpp index 03da4511fc8..8633904a63a 100644 --- a/src/isql/FrontendParser.cpp +++ b/src/isql/FrontendParser.cpp @@ -484,6 +484,7 @@ FrontendParser::AnyShowNode FrontendParser::parseShow() static constexpr std::string_view TOKEN_SQL("SQL"); static constexpr std::string_view TOKEN_SYSTEM("SYSTEM"); static constexpr std::string_view TOKEN_TABLES("TABLES"); + static constexpr std::string_view TOKEN_TABLESPACES("TABLESPACES"); static constexpr std::string_view TOKEN_TRIGGERS("TRIGGERS"); static constexpr std::string_view TOKEN_USERS("USERS"); static constexpr std::string_view TOKEN_VER("VER"); @@ -624,6 +625,8 @@ FrontendParser::AnyShowNode FrontendParser::parseShow() } else if (const auto parsed = parseShowOptQualifiedName(text, TOKEN_TABLES, 5, true)) return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_TABLESPACES, 10)) + return parsed.value(); else if (const auto parsed = parseShowOptQualifiedName(text, TOKEN_TRIGGERS, 4, false)) return parsed.value(); else if (text == TOKEN_USERS) diff --git a/src/isql/FrontendParser.h b/src/isql/FrontendParser.h index e942291502c..281168620ab 100644 --- a/src/isql/FrontendParser.h +++ b/src/isql/FrontendParser.h @@ -114,6 +114,7 @@ class FrontendParser struct ShowSqlDialectNode {}; struct ShowSystemNode { std::optional objType; }; struct ShowTablesNode { std::optional name; }; + struct ShowTablespacesNode { std::optional name; }; struct ShowTriggersNode { std::optional name; }; struct ShowUsersNode {}; struct ShowVersionNode {}; @@ -180,6 +181,7 @@ class FrontendParser ShowSqlDialectNode, ShowSystemNode, ShowTablesNode, + ShowTablespacesNode, ShowTriggersNode, ShowUsersNode, ShowVersionNode, diff --git a/src/isql/extract.epp b/src/isql/extract.epp index 7148651b094..5d964a5fd7b 100644 --- a/src/isql/extract.epp +++ b/src/isql/extract.epp @@ -99,6 +99,7 @@ static void list_procedure_bodies(GetDefaultCharSetForSchemaFunc getDefaultCharS static void list_procedure_headers(GetDefaultCharSetForSchemaFunc getDefaultCharSetForSchemaFunc); static void list_schemas(); static void list_views(); +static void list_tablespaces(); static const char* const Procterm = "^"; // TXNN: script use only @@ -208,6 +209,7 @@ int EXTRACT_ddl(LegacyTables flag, const QualifiedMetaString& tabname) list_collations(); list_generators(); list_domains(getDefaultCharSetForSchema); + list_tablespaces(); list_all_tables(flag, getDefaultCharSetForSchema); list_functions_legacy(); list_functions_ods12_headers(getDefaultCharSetForSchema); @@ -333,7 +335,8 @@ int EXTRACT_list_table(const QualifiedMetaString& relation_name, bool first = true; string char_sets; rel_t rel_type = rel_persistent; - char ss[28] = ""; + const char* security = nullptr; + MetaString tablespaceName; // Query to obtain relation detail information @@ -380,12 +383,15 @@ int EXTRACT_list_table(const QualifiedMetaString& relation_name, isqlGlob.printf("%s ", IUTILS_name_to_string(new_name.object.hasData() ? new_name : relation_name).c_str()); + if (!REL.RDB$TABLESPACE_NAME.NULL) + tablespaceName = REL.RDB$TABLESPACE_NAME; + if (!REL.RDB$SQL_SECURITY.NULL) { if (REL.RDB$SQL_SECURITY) - strcpy(ss, "SQL SECURITY DEFINER"); + security = "SQL SECURITY DEFINER"; else - strcpy(ss, "SQL SECURITY INVOKER"); + security = "SQL SECURITY INVOKER"; } if (!REL.RDB$EXTERNAL_FILE.NULL) @@ -620,15 +626,20 @@ int EXTRACT_list_table(const QualifiedMetaString& relation_name, if (first) // we extracted nothing return FINI_ERROR; - const char* gtt_scope = (rel_type == rel_temp_preserve) ? "ON COMMIT PRESERVE ROWS" : - ((rel_type == rel_temp_delete) ? "ON COMMIT DELETE ROWS" : ""); + isqlGlob.printf(")"); - const char* opt_delim = *gtt_scope && *ss ? ", " : ""; + if (tablespaceName.hasData() && tablespaceName != PRIMARY_TABLESPACE_NAME) + isqlGlob.printf(" TABLESPACE %s", IUTILS_name_to_string(tablespaceName).c_str()); - if (*gtt_scope || *ss) - isqlGlob.printf(")%s%s%s%s", NEWLINE, gtt_scope, opt_delim , ss); - else - isqlGlob.printf(")"); + const char* gttScope = (rel_type == rel_temp_preserve) ? "ON COMMIT PRESERVE ROWS" : + ((rel_type == rel_temp_delete) ? "ON COMMIT DELETE ROWS" : nullptr); + + if (gttScope && security) + isqlGlob.printf(" %s%s, %s", NEWLINE, gttScope, security); + else if (gttScope) + isqlGlob.printf(" %s%s", NEWLINE, gttScope); + else if (security) + isqlGlob.printf(" %s%s", NEWLINE, security); isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE); return FINI_OK; @@ -1319,7 +1330,8 @@ static processing_state list_all_grants(bool extract, const SCHAR* terminator) static void print_proc_prefix(int obj_type, bool headerOnly) { if (obj_type == obj_procedure || obj_type == obj_udf || - obj_type == obj_package_header || obj_type == obj_package_body) + obj_type == obj_package_header || obj_type == obj_package_body || + obj_type == obj_tablespace) { isqlGlob.printf("%sCOMMIT WORK%s%s", NEWLINE, isqlGlob.global_Term, NEWLINE); } @@ -1347,6 +1359,9 @@ static void print_proc_prefix(int obj_type, bool headerOnly) case obj_package_body: legend = "Package bodies"; break; + case obj_tablespace: + legend = "Tablespaces"; + break; } if (legend) isqlGlob.printf("%s/* %s */%s", NEWLINE, legend, NEWLINE); @@ -3179,6 +3194,13 @@ static void list_indexes() SHOW_print_metadata_text_blob(isqlGlob.Out, &IDX.RDB$CONDITION_SOURCE, false, true); } + if (!IDX.RDB$TABLESPACE_NAME.NULL) + { + const MetaString tablespaceName(IDX.RDB$TABLESPACE_NAME); + if (tablespaceName != PRIMARY_TABLESPACE_NAME) + isqlGlob.printf(" TABLESPACE %s", IUTILS_name_to_string(tablespaceName).c_str()); + } + isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE); } END_FOR @@ -3439,3 +3461,56 @@ static void list_views() return; END_ERROR } + +static void list_tablespaces() +{ +/************************************** + * + * l i s t _ t a b l e s p a c e s + * + ************************************** + * + * Functional description + * Show tablespaces + * Use a SQL query to get the info and print it. + * + **************************************/ + + if (isqlGlob.major_ods < ODS_VERSION14) + return; + + bool first = true; + + FOR X IN RDB$TABLESPACES WITH X.RDB$SYSTEM_FLAG EQ 0 + SORTED BY X.RDB$TABLESPACE_NAME + + if (first) + { + isqlGlob.printf("%s/* Tablespaces */%s", NEWLINE, NEWLINE); + first = false; + } + + const MetaString tablespaceName(X.RDB$TABLESPACE_NAME); + const MetaString ownerName(X.RDB$OWNER_NAME); + + isqlGlob.printf("%s/* Tablespace: %s, Owner: %s */%s", + NEWLINE, + IUTILS_name_to_string(tablespaceName).c_str(), + IUTILS_name_to_string(ownerName).c_str(), + NEWLINE); + + const char* offline = X.RDB$OFFLINE.NULL || !X.RDB$OFFLINE ? "ONLINE" : "OFFLINE"; + const char* readonly = X.RDB$READ_ONLY.NULL || !X.RDB$READ_ONLY ? "READ WRITE" : "READ ONLY"; + + fb_utils::exact_name(X.RDB$FILE_NAME); + isqlGlob.printf("CREATE TABLESPACE %s FILE '%s' %s %s", + IUTILS_name_to_string(tablespaceName).c_str(), X.RDB$FILE_NAME, offline, readonly); + + isqlGlob.printf("%s%s", Procterm, NEWLINE); + + END_FOR + ON_ERROR + ISQL_errmsg(fbStatus); + return; + END_ERROR; +} diff --git a/src/isql/show.epp b/src/isql/show.epp index 78308a5e457..b26bb43401d 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -104,12 +104,13 @@ static processing_state show_functions(const std::optional& static processing_state show_func_legacy(const QualifiedMetaString& name); static processing_state show_func(const QualifiedMetaString& name); static processing_state show_generators(const std::optional& name); -static void show_index(const QualifiedMetaString&, const QualifiedMetaString&, const SSHORT, const SSHORT, const SSHORT); +static void show_index(const QualifiedMetaString&, const QualifiedMetaString&, const MetaString&, const SSHORT, const SSHORT, const SSHORT); static processing_state show_indices(const std::optional& name); static processing_state show_proc(const std::optional& name, bool, const char* msg = nullptr); static processing_state show_packages(const std::optional& name, bool, const SCHAR* = nullptr); static processing_state show_publications(const std::optional& name, bool, const SCHAR* = nullptr); static void show_pub_table(const QualifiedMetaString& name); +static processing_state show_tablespaces(const std::optional& name); static processing_state show_role(const std::optional& name, bool, const char* msg = nullptr); static processing_state show_schemas(const std::optional& name, bool, const char* msg = nullptr); static processing_state show_secclass(const std::optional& object, bool detail); @@ -1860,6 +1861,7 @@ processing_state SHOW_metadata(const FrontendParser::AnyShowNode& node) {0, "SCHEMAS", 4}, // SCHE {0, "MAPPINGS", 3}, // MAP {0, "PUBLICATIONS", 3}, // PUB + {0, "TABLESPACES", 10}, // TABLESPACE {0, "WIRE_STATISTICS", 9}, // WIRE_STAT {0, "WIRE_STATS", 10} }; @@ -2506,6 +2508,24 @@ processing_state SHOW_metadata(const FrontendParser::AnyShowNode& node) return ret; }, + [&](const FrontendParser::ShowTablespacesNode& node) + { + const auto ret = show_tablespaces(node.name); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_TABLESPACE; + notFoundName.object = node.name.value(); + } + else + key = NO_TABLESPACES; + } + + return ret; + }, + [&](const FrontendParser::ShowTriggersNode& node) { const auto ret = show_trigger(node.name, true, true); @@ -2602,6 +2622,7 @@ processing_state SHOW_metadata(const FrontendParser::AnyShowNode& node) static_assert(FrontendParser::AlwaysFalseV, "Add visitor method for that show node type"); } + }, node); if (ret == OBJECT_NOT_FOUND) @@ -3386,6 +3407,22 @@ static processing_state show_comments(const commentMode showextract, const char* ISQL_errmsg(fbStatus); return ps_ERR; END_ERROR + + FOR TS IN RDB$TABLESPACES + WITH TS.RDB$DESCRIPTION NOT MISSING + SORTED BY TS.RDB$TABLESPACE_NAME + { + const QualifiedMetaString name(TS.RDB$TABLESPACE_NAME); + + show_comment("TABLESPACE", name, {}, &TS.RDB$DESCRIPTION, + showextract, first ? banner : 0); + first = false; + } + END_FOR + ON_ERROR + ISQL_errmsg(fbStatus); + return ps_ERR; + END_ERROR } return first ? OBJECT_NOT_FOUND : SKIP; @@ -4715,6 +4752,7 @@ static processing_state show_generators(const std::optional static void show_index(const QualifiedMetaString& relationName, const QualifiedMetaString& indexName, + const MetaString& tablespaceName, const SSHORT unique_flag, const SSHORT index_type, const SSHORT inactive) @@ -4740,6 +4778,9 @@ static void show_index(const QualifiedMetaString& relationName, (index_type == 1 ? " DESCENDING" : ""), IUTILS_name_to_string(relationName).c_str()); + if (tablespaceName.hasData() && tablespaceName != PRIMARY_TABLESPACE_NAME) + isqlGlob.printf(" TABLESPACE %s", IUTILS_name_to_string(tablespaceName).c_str()); + // Get column names SCHAR collist[BUFFER_LENGTH512]; @@ -4787,10 +4828,14 @@ static processing_state show_indices(const std::optional& n const QualifiedMetaString relationName(IDX.RDB$RELATION_NAME, IDX.RDB$SCHEMA_NAME, IDX.RDB$PACKAGE_NAME); + MetaString tablespaceName; + if (!IDX.RDB$TABLESPACE_NAME.NULL) + tablespaceName = IDX.RDB$TABLESPACE_NAME; + if (IDX.RDB$INDEX_INACTIVE.NULL) IDX.RDB$INDEX_INACTIVE = 0; - show_index(relationName, indexName, + show_index(relationName, indexName, tablespaceName, IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE); if (!IDX.RDB$EXPRESSION_BLR.NULL) @@ -4837,7 +4882,11 @@ static processing_state show_indices(const std::optional& n first = false; - show_index(relationName, indexName, + MetaString tablespaceName; + if (!IDX.RDB$TABLESPACE_NAME.NULL) + tablespaceName = IDX.RDB$TABLESPACE_NAME; + + show_index(relationName, indexName, tablespaceName, IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE); if (!IDX.RDB$EXPRESSION_BLR.NULL) @@ -4971,6 +5020,81 @@ static processing_state show_packages(const std::optional& } +static processing_state show_tablespaces(const std::optional& name) +{ +/************************************* +* +* s h o w _ t a b l e s p a c e s +* +************************************** +* +* Functional description +* Show all tablespaces or the named tablespace +************************************/ + if (isqlGlob.major_ods < ODS_VERSION14) + return OBJECT_NOT_FOUND; + + bool first = true; + + if (!name) + { + // List all tablespace names in columns + FOR X IN RDB$TABLESPACES WITH X.RDB$SYSTEM_FLAG EQ 0 + SORTED BY X.RDB$TABLESPACE_NAME + { + first = false; + + const MetaString tablespaceName(X.RDB$TABLESPACE_NAME); + isqlGlob.printf("%s%s", IUTILS_name_to_string(tablespaceName).c_str(), NEWLINE); + } + END_FOR + ON_ERROR + ISQL_errmsg(fbStatus); + return ps_ERR; + END_ERROR; + if (!first) + isqlGlob.printf(NEWLINE); + } + else + { + // List named tablespace + + FOR X IN RDB$TABLESPACES WITH + X.RDB$TABLESPACE_NAME EQ name->c_str() + + first = false; + + const MetaString tablespaceName(X.RDB$TABLESPACE_NAME); + isqlGlob.printf("%s", IUTILS_name_to_string(tablespaceName).c_str()); + + if (!X.RDB$FILE_NAME.NULL) + { + fb_utils::exact_name(X.RDB$FILE_NAME); + isqlGlob.printf(" FILE %s", X.RDB$FILE_NAME); + } + + const char* offline = X.RDB$OFFLINE.NULL || !X.RDB$OFFLINE ? "ONLINE" : "OFFLINE"; + isqlGlob.printf(" %s", offline); + + const char* readonly = X.RDB$READ_ONLY.NULL || !X.RDB$READ_ONLY ? "READ WRITE" : "READ ONLY"; + isqlGlob.printf(" %s", readonly); + + isqlGlob.printf(NEWLINE); + + END_FOR + ON_ERROR + ISQL_errmsg(fbStatus); + return ps_ERR; + END_ERROR; + } + + if (first) + return OBJECT_NOT_FOUND; + + return (SKIP); +} + + static void printMap(bool extract, bool global, const MetaString& name, const char* usng, const MetaString& plugin, const MetaString& db, const MetaString& fromType, const MetaString& from, short toType, const MetaString& to) { @@ -6099,6 +6223,13 @@ static processing_state show_table(const QualifiedMetaString& name, bool isView) if (!REL.RDB$EXTERNAL_FILE.NULL) isqlGlob.printf("External file: %s%s", REL.RDB$EXTERNAL_FILE, NEWLINE); + if (!REL.RDB$TABLESPACE_NAME.NULL) + { + const MetaString tablespaceName(REL.RDB$TABLESPACE_NAME); + if (tablespaceName != PRIMARY_TABLESPACE_NAME) + isqlGlob.printf("TABLESPACE: %s%s", IUTILS_name_to_string(tablespaceName).c_str(), NEWLINE); + } + FOR RFR IN RDB$RELATION_FIELDS CROSS FLD IN RDB$FIELDS WITH FLD.RDB$SCHEMA_NAME EQUIV RFR.RDB$FIELD_SOURCE_SCHEMA_NAME AND diff --git a/src/isql/tests/FrontendParserTest.cpp b/src/isql/tests/FrontendParserTest.cpp index 74ba7ec4f0f..d48be440664 100644 --- a/src/isql/tests/FrontendParserTest.cpp +++ b/src/isql/tests/FrontendParserTest.cpp @@ -597,6 +597,11 @@ BOOST_AUTO_TEST_CASE(ParseShowTest) BOOST_TEST((std::get(parseShow( "show tables name")).name == QualifiedMetaString("NAME"))); + BOOST_TEST(!std::get(parseShow( + "show tablespace")).name); + BOOST_TEST((std::get(parseShow( + "show tablespaces name")).name == MetaString("NAME"))); + BOOST_TEST(!std::get(parseShow( "show trig")).name); BOOST_TEST((std::get(parseShow( diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp index e8535f11e00..596995673fe 100644 --- a/src/jrd/Attachment.cpp +++ b/src/jrd/Attachment.cpp @@ -32,6 +32,7 @@ #include "../jrd/PreparedStatement.h" #include "../jrd/tra.h" #include "../jrd/intl.h" +#include "../jrd/Tablespace.h" #include "../jrd/blb_proto.h" #include "../jrd/exe_proto.h" @@ -385,19 +386,14 @@ void Jrd::Attachment::releaseBatches() void Jrd::Attachment::releaseLocalTempTables(thread_db* tdbb) { - HalfStaticArray tempRelations; - for (auto& lttEntry : att_local_temporary_tables) { const auto ltt = lttEntry.second; if (ltt->relation) - tempRelations.add(ltt->relation->getPermanent()); - } - - for (const auto tempRelation : tempRelations) - { - if (tempRelation->rel_flags & REL_temp_conn) - tempRelation->delPages(tdbb); + { + if (ltt->relation->checkFlags(REL_temp_conn)) + ltt->relation->getPermanent()->deletePages(tdbb); + } } } diff --git a/src/jrd/BulkInsert.cpp b/src/jrd/BulkInsert.cpp index c3f11e6a81d..a86f407d3f5 100644 --- a/src/jrd/BulkInsert.cpp +++ b/src/jrd/BulkInsert.cpp @@ -194,8 +194,8 @@ RecordNumber BulkInsert::Buffer::putBlob(thread_db* tdbb, blb* blob, Record* rec if (record) { - RelationPages* relPages = rpb.rpb_relation->getPages(tdbb); - record->pushPrecedence(PageNumber(relPages->rel_pg_space_id, m_current->dpg_header.pag_pageno)); + const auto relPages = rpb.rpb_relation->getPages(tdbb); + record->pushPrecedence(relPages->toPageNumber(m_current->dpg_header.pag_pageno)); } return rpb.rpb_number; @@ -203,13 +203,13 @@ RecordNumber BulkInsert::Buffer::putBlob(thread_db* tdbb, blb* blob, Record* rec void BulkInsert::Buffer::fragmentRecord(thread_db* tdbb, record_param* rpb, Compressor* dcc) { - Database* dbb = tdbb->getDatabase(); + const auto dbb = tdbb->getDatabase(); // Start compression from the end. const UCHAR* in = rpb->rpb_address + rpb->rpb_length; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); - PageNumber prior(relPages->rel_pg_space_id, 0); + const auto relPages = rpb->rpb_relation->getPages(tdbb); + PageNumber prior(relPages->toPageNumber(0)); // The last fragment should have rhd header because rhd_incomplete flag won't be set for it. // It's important for get_header() function which relies on rhd_incomplete flag to determine header size. @@ -364,10 +364,10 @@ UCHAR* BulkInsert::Buffer::findSpace(thread_db* tdbb, record_param* rpb, USHORT data_page* BulkInsert::Buffer::allocatePages(thread_db* tdbb) { - Database* dbb = tdbb->getDatabase(); - RelationPages* relPages = m_relation->getPages(tdbb); + const auto dbb = tdbb->getDatabase(); + const auto relPages = m_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, 0); + WIN window(relPages->getPageSpaceId()); const auto reserved = DPM_reserve_pages(tdbb, m_relation, &window); @@ -405,8 +405,8 @@ void BulkInsert::Buffer::flush(thread_db* tdbb) if (!m_current) return; - Database* dbb = tdbb->getDatabase(); - RelationPages* relPages = m_relation->getPages(tdbb); + const auto dbb = tdbb->getDatabase(); + const auto relPages = m_relation->getPages(tdbb); const ULONG pp_sequence = m_current->dpg_sequence / dbb->dbb_dp_per_pp; @@ -417,7 +417,7 @@ void BulkInsert::Buffer::flush(thread_db* tdbb) if (m_current->dpg_count == 0) break; - win dpWindow(relPages->rel_pg_space_id, m_current->dpg_header.pag_pageno); + win dpWindow(relPages->toPageNumber(m_current->dpg_header.pag_pageno)); auto dpage = CCH_FETCH(tdbb, &dpWindow, LCK_write, pag_data); @@ -438,7 +438,8 @@ void BulkInsert::Buffer::flush(thread_db* tdbb) m_current = nextPage(m_current, m_pageSize); } - win ppWindow(relPages->rel_pg_space_id, (*relPages->rel_pages)[pp_sequence]); + const auto ppNumber = relPages->getPointerPage(pp_sequence); + win ppWindow(ppNumber.value()); pointer_page* ppage = (pointer_page*) CCH_FETCH(tdbb, &ppWindow, LCK_write, pag_pointer); m_current = m_pages; diff --git a/src/jrd/Database.cpp b/src/jrd/Database.cpp index 7a1437126ff..1f204ee06f7 100644 --- a/src/jrd/Database.cpp +++ b/src/jrd/Database.cpp @@ -97,13 +97,13 @@ namespace Jrd void Database::assignLatestAttachmentId(AttNumber number) { - if (dbb_tip_cache) + if (dbb_tip_cache && dbb_tip_cache->isInitialized()) dbb_tip_cache->assignLatestAttachmentId(number); } StmtNumber Database::generateStatementId() { - if (!dbb_tip_cache) + if (!dbb_tip_cache || !dbb_tip_cache->isInitialized()) return 0; return dbb_tip_cache->generateStatementId(); } @@ -117,14 +117,14 @@ namespace Jrd AttNumber Database::getLatestAttachmentId() const { - if (!dbb_tip_cache) + if (!dbb_tip_cache || !dbb_tip_cache->isInitialized()) return 0; return dbb_tip_cache->getLatestAttachmentId(); } StmtNumber Database::getLatestStatementId() const { - if (!dbb_tip_cache) + if (!dbb_tip_cache || !dbb_tip_cache->isInitialized()) return 0; return dbb_tip_cache->getLatestStatementId(); } @@ -431,7 +431,9 @@ namespace Jrd bool Database::isReplicating(thread_db* tdbb) { - if (!replConfig()) + const auto config = replConfig(); + + if (!config || (!config->isMaster() && config->pluginName.isEmpty())) return false; Sync sync(&dbb_repl_sync, FB_FUNCTION); @@ -859,6 +861,7 @@ namespace Jrd : dbb_permanent(p), dbb_guid(Firebird::Guid::empty()), dbb_page_manager(this, *p), + dbb_tablespaces(*p), dbb_file_id(*p), dbb_modules(*p), dbb_internal(*p), diff --git a/src/jrd/Database.h b/src/jrd/Database.h index 343c1716908..8273f44d6a4 100644 --- a/src/jrd/Database.h +++ b/src/jrd/Database.h @@ -74,6 +74,7 @@ #include "../common/classes/Synchronize.h" #include "../jrd/replication/Manager.h" #include "../jrd/SharedReadVector.h" +#include "../jrd/Tablespace.h" #include "../dsql/Keywords.h" #include "fb_types.h" @@ -296,8 +297,8 @@ class Database : public pool_alloc struct DelPagesMarker { - TraNumber tran; - RelationPermanent* relation; + TraNumber tran; + RelationPermanent* relation; static TraNumber generate(const DelPagesMarker& item) { @@ -368,6 +369,8 @@ class Database : public pool_alloc MonitoringData* dbb_monitoring_data; // monitoring data + Tablespace::Cache dbb_tablespaces; // Tablespaces cache + private: Firebird::string dbb_file_id; // system-wide unique file ID Firebird::RefPtr dbb_gblobj_holder; @@ -475,7 +478,7 @@ class Database : public pool_alloc Firebird::SortedArray, TraNumber, DelPagesMarker> dbb_del_pages; - Firebird::Mutex dbb_del_pages_mutex; + Firebird::Mutex dbb_del_pages_mutex; public: // returns true if primary file is located on raw device diff --git a/src/jrd/Monitoring.cpp b/src/jrd/Monitoring.cpp index e9057c5cc14..52b2cce3a05 100644 --- a/src/jrd/Monitoring.cpp +++ b/src/jrd/Monitoring.cpp @@ -469,6 +469,7 @@ MonitoringSnapshot::MonitoringSnapshot(thread_db* tdbb, MemoryPool& pool) const auto ctx_var_buffer = allocBuffer(tdbb, pool, rel_mon_ctx_vars); const auto mem_usage_buffer = allocBuffer(tdbb, pool, rel_mon_mem_usage); const auto tab_stat_buffer = allocBuffer(tdbb, pool, rel_mon_tab_stats); + const auto ts_stat_buffer = allocBuffer(tdbb, pool, rel_mon_ts_stats); const auto local_temp_tables_buffer = allocBuffer(tdbb, pool, rel_mon_local_temp_tables); const auto local_temp_table_columns_buffer = allocBuffer(tdbb, pool, rel_mon_local_temp_table_columns); @@ -606,6 +607,9 @@ MonitoringSnapshot::MonitoringSnapshot(thread_db* tdbb, MemoryPool& pool) case rel_mon_tab_stats: buffer = tab_stat_buffer; break; + case rel_mon_ts_stats: + buffer = ts_stat_buffer; + break; case rel_mon_local_temp_tables: buffer = local_temp_tables_buffer; break; @@ -879,6 +883,32 @@ void SnapshotData::putField(thread_db* tdbb, Record* record, const DumpField& fi from_desc.makeBoolean(&value); MOV_move(tdbb, &from_desc, &to_desc); } + else if (field.type == VALUE_TABLESPACE_ID) + { + // special case: translate tablespace ID into name + fb_assert(field.length == sizeof(ULONG)); + ULONG pageSpaceId; + memcpy(&pageSpaceId, field.data, field.length); + + MetaName name; + + if (pageSpaceId == 0) + name = TEMP_TABLESPACE_NAME; + else if (pageSpaceId == DB_PAGE_SPACE) + name = PRIMARY_TABLESPACE_NAME; + else if (PageSpace::isTablespace(pageSpaceId)) + { + if (const auto tableSpace = Tablespace::lookup(tdbb, pageSpaceId)) + name = tableSpace->getName(); + } + + if (name.hasData()) + { + dsc from_desc; + from_desc.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str()); + MOV_move(tdbb, &from_desc, &to_desc); + } + } else { fb_assert(false); @@ -1541,7 +1571,33 @@ void Monitoring::putStatistics(thread_db* tdbb, SnapshotData::DumpRecord& record record.storeInteger(f_mon_rec_imgc, statistics[RecordStatType::IMGC]); record.write(); - // logical I/O statistics (table wise) + // physical I/O statistics (per tablespace) + + for (const auto& counts : statistics.getPageCounters()) + { + if (counts.isEmpty()) + continue; + + const auto io_stat_id = getGlobalId(fb_utils::genUniqueId()); + + record.reset(rel_mon_ts_stats); + record.storeGlobalId(f_mon_ts_stat_id, id); + record.storeInteger(f_mon_ts_stat_group, stat_group); + record.storeTablespaceId(f_mon_ts_name, counts.getGroupId()); + record.storeGlobalId(f_mon_tab_io_stat_id, io_stat_id); + record.write(); + + record.reset(rel_mon_io_stats); + record.storeGlobalId(f_mon_io_stat_id, io_stat_id); + record.storeInteger(f_mon_io_stat_group, stat_group); + record.storeInteger(f_mon_io_page_reads, counts[PageStatType::READS]); + record.storeInteger(f_mon_io_page_writes, counts[PageStatType::WRITES]); + record.storeInteger(f_mon_io_page_fetches, counts[PageStatType::FETCHES]); + record.storeInteger(f_mon_io_page_marks, counts[PageStatType::MARKS]); + record.write(); + } + + // logical I/O statistics (per table) for (const auto& counts : statistics.getTableCounters()) { diff --git a/src/jrd/Monitoring.h b/src/jrd/Monitoring.h index 278f6f8e644..5850acc4662 100644 --- a/src/jrd/Monitoring.h +++ b/src/jrd/Monitoring.h @@ -63,6 +63,7 @@ class SnapshotData VALUE_BOOLEAN, VALUE_TABLE_ID_SCHEMA_NAME, VALUE_TABLE_ID_PACKAGE_NAME, + VALUE_TABLESPACE_ID, VALUE_LAST_MARKER // Should be last item }; @@ -142,6 +143,11 @@ class SnapshotData storeField(field_id, VALUE_TABLE_ID_PACKAGE_NAME, sizeof(SLONG), &value); } + void storeTablespaceId(int field_id, ULONG value) + { + storeField(field_id, VALUE_TABLESPACE_ID, sizeof(ULONG), &value); + } + void storeInteger(int field_id, SINT64 value) { storeField(field_id, VALUE_INTEGER, sizeof(SINT64), &value); diff --git a/src/jrd/PageNumber.h b/src/jrd/PageNumber.h new file mode 100644 index 00000000000..1cd85bc6dae --- /dev/null +++ b/src/jrd/PageNumber.h @@ -0,0 +1,152 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Vladyslav Khorsun + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2006 Vladyslav Khorsun + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_PAGE_NUMBER_H +#define JRD_PAGE_NUMBER_H + +//#include "../jrd/ods.h" +#include "../jrd/lls.h" + +namespace Jrd +{ + // Possible page space IDs: + // - DB_PAGE_SPACE == 1 is a main database pagespace (primary tablespace) + // - range [2..MAX_TABLESPACE_ID] identifies user-defined persistent tablespaces + // - TRANS_PAGE_SPACE is a pseudo-space to store transaction numbers in precedence stack + // - TEMP_PAGE_SPACE and above are temporary pages + inline constexpr ULONG INVALID_PAGE_SPACE = 0; + inline constexpr ULONG DB_PAGE_SPACE = 1; + inline constexpr ULONG MAX_TABLESPACE_ID = 254; + inline constexpr ULONG TRANS_PAGE_SPACE = 255; + inline constexpr ULONG TEMP_PAGE_SPACE = 256; + inline constexpr ULONG MAX_PAGE_SPACE_ID = MAX_ULONG; + + // Page number class. Encapsulates both page space ID and page number. + + class PageNumber + { + public: + // CVC: To be completely in sync, the second param would have to be TraNumber + inline PageNumber(const ULONG pageSpaceID, const ULONG pageNumber) noexcept + : m_pageNumber(pageNumber), m_pageSpaceID(pageSpaceID) + {} + + // Required to be able to keep it in Firebird::Stack + inline PageNumber() noexcept + : m_pageNumber(0), m_pageSpaceID(INVALID_PAGE_SPACE) + {} + + inline PageNumber(const PageNumber& from) = default; + + inline bool isValid() const noexcept + { + return (m_pageSpaceID != INVALID_PAGE_SPACE); + } + + inline ULONG getPageNum() const noexcept + { + return m_pageNumber; + } + + inline ULONG getPageSpaceID() const noexcept + { + fb_assert(isValid()); + + return m_pageSpaceID; + } + + inline ULONG setPageSpaceID(const ULONG pageSpaceID) noexcept + { + fb_assert(pageSpaceID != INVALID_PAGE_SPACE); + + m_pageSpaceID = pageSpaceID; + return m_pageSpaceID; + } + + static inline constexpr USHORT getLockLen() noexcept + { + return sizeof(m_pageNumber) + sizeof(m_pageSpaceID); + } + + inline void getLockStr(UCHAR* str) const + { + fb_assert(isValid()); + + memcpy(str, &m_pageNumber, sizeof(ULONG)); + str += sizeof(ULONG); + memcpy(str, &m_pageSpaceID, sizeof(ULONG)); + } + + inline PageNumber& operator=(const PageNumber& from) = default; + + inline ULONG operator=(const ULONG from) noexcept + { + m_pageNumber = from; + return m_pageNumber; + } + + inline bool operator==(const PageNumber& other) const noexcept + { + return (m_pageNumber == other.m_pageNumber) && (m_pageSpaceID == other.m_pageSpaceID); + } + + inline bool operator!=(const PageNumber& other) const noexcept + { + return !(*this == other); + } + + inline bool operator>(const PageNumber& other) const noexcept + { + fb_assert(isValid() && other.isValid()); + + return (m_pageSpaceID > other.m_pageSpaceID) || + ((m_pageSpaceID == other.m_pageSpaceID) && (m_pageNumber > other.m_pageNumber)); + } + + inline bool operator>=(const PageNumber& other) const noexcept + { + fb_assert(isValid() && other.isValid()); + + return (m_pageSpaceID > other.m_pageSpaceID) || + ((m_pageSpaceID == other.m_pageSpaceID) && (m_pageNumber >= other.m_pageNumber)); + } + + inline bool operator<(const PageNumber& other) const noexcept + { + return !(*this >= other); + } + + inline bool operator<=(const PageNumber& other) const noexcept + { + return !(*this > other); + } + + private: + ULONG m_pageNumber; + ULONG m_pageSpaceID; + }; + + typedef Firebird::Stack PageStack; + +} // namespace Jrd + +#endif // JRD_PAGE_NUMBER_H diff --git a/src/jrd/ProtectRelations.h b/src/jrd/ProtectRelations.h index ec7cfc28464..6b0b9e061b7 100644 --- a/src/jrd/ProtectRelations.h +++ b/src/jrd/ProtectRelations.h @@ -44,16 +44,18 @@ class jrd_tra; class ProtectRelations { public: - ProtectRelations(thread_db* tdbb, jrd_tra* transaction) : + ProtectRelations(thread_db* tdbb, jrd_tra* transaction, bool autoRelease = true) : m_tdbb(tdbb), m_transaction(transaction), + m_release(autoRelease), m_locks() { } - ProtectRelations(thread_db* tdbb, jrd_tra* transaction, Cached::Relation* relation) : + ProtectRelations(thread_db* tdbb, jrd_tra* transaction, Cached::Relation* relation, bool autoRelease = true) : m_tdbb(tdbb), m_transaction(transaction), + m_release(autoRelease), m_locks() { addRelation(relation); @@ -62,7 +64,8 @@ class ProtectRelations ~ProtectRelations() { - unlock(); + if (m_release) + unlock(); } void addRelation(Cached::Relation* relation) @@ -70,101 +73,14 @@ class ProtectRelations if (!(relation->rel_flags & REL_temp_ltt)) { FB_SIZE_T pos; - if (!m_locks.find(relation->rel_id, pos)) - m_locks.insert(pos, relLock(relation)); + if (!m_locks.find(relation->getId(), pos)) + m_locks.insert(pos, RelationLock(relation)); } } bool exists(USHORT rel_id) const { - FB_SIZE_T pos; - return m_locks.find(rel_id, pos); - } - - void lock() - { - relLock* item = m_locks.begin(); - const relLock* const end = m_locks.end(); - for (; item < end; item++) - item->takeLock(m_tdbb, m_transaction); - } - - void unlock() - { - relLock* item = m_locks.begin(); - const relLock* const end = m_locks.end(); - for (; item < end; item++) - item->releaseLock(m_tdbb, m_transaction); - } - -private: - struct relLock - { - relLock(Cached::Relation* relation = nullptr) : - m_relation(relation), - m_lock(nullptr), - m_level(LCK_none) - { - } - - void takeLock(thread_db* tdbb, jrd_tra* transaction); - void releaseLock(thread_db* tdbb, jrd_tra* transaction); - - static const USHORT generate(const relLock& item) - { - return item.m_relation->rel_id; - } - - Cached::Relation* m_relation; - Lock* m_lock; - UCHAR m_level; // original lock level - }; - - thread_db* m_tdbb; - jrd_tra* m_transaction; - Firebird::SortedArray, USHORT, relLock> m_locks; -}; - -/* -// Lock relation with protected_read level or raise existing relation lock -// to this level to ensure nobody can write to this relation. -// Used when new index is built. -// releaseLock set to true if there was no existing lock before -class ProtectRelations -{ -public: - ProtectRelations(thread_db* tdbb, jrd_tra* transaction) : - m_tdbb(tdbb), - m_transaction(transaction), - m_locks() - { - } - - ProtectRelations(thread_db* tdbb, jrd_tra* transaction, Cached::Relation* relation) : - m_tdbb(tdbb), - m_transaction(transaction), - m_locks() - { - addRelation(relation); - lock(); - } - - ~ProtectRelations() - { - unlock(); - } - - void addRelation(Cached::Relation* relation) - { - FB_SIZE_T pos; - if (!m_locks.find(relation->getId(), pos)) - m_locks.insert(pos, relLock(relation)); - } - - bool exists(USHORT rel_id) const - { - FB_SIZE_T pos; - return m_locks.find(rel_id, pos); + return m_locks.exist(rel_id); } void lock() @@ -180,41 +96,33 @@ class ProtectRelations } private: - struct relLock + struct RelationLock { - relLock(Cached::Relation* relation = nullptr) : + RelationLock(Cached::Relation* relation = nullptr) : m_relation(relation), - m_lock(NULL), - m_release(false) - { - } - - relLock(MemoryPool&, const Jrd::ProtectRelations::relLock& l) : - m_relation(l.m_relation), - m_lock(l.m_lock), - m_release(l.m_release) + m_lock(nullptr), + m_level(LCK_none) { - fb_assert(!m_lock); } void takeLock(thread_db* tdbb, jrd_tra* transaction); void releaseLock(thread_db* tdbb, jrd_tra* transaction); - static const USHORT generate(const relLock& item) + static const USHORT generate(const RelationLock& item) { return item.m_relation->getId(); } Cached::Relation* m_relation; Lock* m_lock; - bool m_release; + UCHAR m_level; // original lock level }; - thread_db* m_tdbb; - jrd_tra* m_transaction; - SortedArray, USHORT, relLock> m_locks; + thread_db* const m_tdbb; + jrd_tra* const m_transaction; + const bool m_release; + Firebird::SortedArray, USHORT, RelationLock> m_locks; }; -*/ } // namespace Jrd diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp index 7192d265deb..777157ce003 100644 --- a/src/jrd/Relation.cpp +++ b/src/jrd/Relation.cpp @@ -44,93 +44,297 @@ using namespace Jrd; using namespace Firebird; -TrigArray::TrigArray(MemoryPool& p) - : preErase(p), postErase(p), preModify(p), - postModify(p), preStore(p), postStore(p) -{ } +// +// class GCLock +// -Triggers& TrigArray::operator[](int t) +void GCLock::blockingAst() { - switch(t) + /**** + SR - gc forbidden, awaiting moment to re-establish SW lock + SW - gc allowed, usual state + PW - gc allowed to the one connection only + ****/ + + const auto dbb = m_lock->lck_dbb; + + AsyncContextHolder tdbb(dbb, FB_FUNCTION); + + unsigned oldFlags = m_flags.load(std::memory_order_acquire); + do { - case TRIGGER_PRE_STORE: - return preStore; + fb_assert(oldFlags & GC_locked); + if (!(oldFlags & GC_locked)) // work already done synchronously ? + return; + } while (!m_flags.compare_exchange_weak(oldFlags, oldFlags | GC_blocking, + std::memory_order_release, std::memory_order_acquire)); - case TRIGGER_POST_STORE: - return postStore; + if (oldFlags & GC_counterMask) + return; - case TRIGGER_PRE_MODIFY: - return preModify; + if (oldFlags & GC_disabled) + { + // someone acquired EX lock - case TRIGGER_POST_MODIFY: - return postModify; + fb_assert(m_lock->lck_id); + fb_assert(m_lock->lck_physical == LCK_SR); - case TRIGGER_PRE_ERASE: - return preErase; + LCK_release(tdbb, m_lock); + m_flags.fetch_and(~(GC_disabled | GC_blocking | GC_locked)); + } + else + { + // someone acquired PW lock - case TRIGGER_POST_ERASE: - return postErase; + fb_assert(m_lock->lck_id); + fb_assert(m_lock->lck_physical == LCK_SW); + + m_flags.fetch_or(GC_disabled); + downgrade(tdbb); } +} - fb_assert(false); - fatal_exception::raise("Invalid trigger type"); +void GCLock::checkGuard(unsigned flags) +{ + if (flags & GC_guardBit) + { + fatal_exception::raiseFmt + ("Overflow or underflow when changing GC lock atomic counter (guard bit set), flags %08x", flags); + } } -const Triggers& TrigArray::operator[](int t) const +bool GCLock::acquire(thread_db* tdbb, int wait) { - switch(t) + unsigned oldFlags = m_flags.load(std::memory_order_acquire); + for(;;) { - case TRIGGER_PRE_STORE: - return preStore; + if (oldFlags & (GC_blocking | GC_disabled)) // lock should not be obtained + return false; - case TRIGGER_POST_STORE: - return postStore; + const unsigned newFlags = oldFlags + 1; + checkGuard(newFlags); - case TRIGGER_PRE_MODIFY: - return preModify; + if (!m_flags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)) + continue; - case TRIGGER_POST_MODIFY: - return postModify; + if (oldFlags & GC_locked) // lock was already taken when we checked flags + return true; - case TRIGGER_PRE_ERASE: - return preErase; + if (!(oldFlags & GC_counterMask)) // we must take lock + break; - case TRIGGER_POST_ERASE: - return postErase; + // undefined state - someone else is taking/releasing a lock right now: + oldFlags = m_flags.fetch_sub(1, std::memory_order_acquire); // decrement our lock counter, + checkGuard(oldFlags - 1); + Thread::yield(); // wait a bit + oldFlags = m_flags.load(std::memory_order_acquire); // and retry } - fb_assert(false); - fatal_exception::raise("Invalid trigger type"); + // No need to use LM for LTT + if (m_relation->isLTT()) + return true; + + // We incremented counter from 0 to 1 - take care about lck + if (!m_lock) + m_lock = m_relation->createLock(tdbb, LCK_rel_gc, false); + + fb_assert(!m_lock->lck_id); + + ThreadStatusGuard temp_status(tdbb); + + bool ret; + if (oldFlags & GC_disabled) + ret = LCK_lock(tdbb, m_lock, LCK_SR, wait); + else + { + ret = LCK_lock(tdbb, m_lock, LCK_SW, wait); + if (ret) + { + m_flags.fetch_or(GC_locked); + return true; + } + + oldFlags = m_flags.fetch_or(GC_disabled, std::memory_order_seq_cst) | GC_disabled; + ret = LCK_lock(tdbb, m_lock, LCK_SR, wait); + } + + unsigned newFlags; + do + { + newFlags = oldFlags - 1; + checkGuard(newFlags); + + if (!ret) + newFlags &= ~GC_disabled; + + } while (!m_flags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); + + return false; } -jrd_rel::jrd_rel(MemoryPool& p, Cached::Relation* r) - : rel_pool(&p), - rel_perm(r), - rel_current_fmt(0), - rel_current_format(nullptr), - rel_fields(nullptr), - rel_view_rse(nullptr), - rel_view_contexts(p), - rel_triggers(p), - rel_ss_definer(false) -{ } +void GCLock::downgrade(thread_db* tdbb) +{ + unsigned oldFlags = m_flags.load(std::memory_order_acquire); + unsigned newFlags; + do + { + newFlags = oldFlags - 1; + checkGuard(newFlags); + } while (!m_flags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); + + if ((!m_relation->isLTT()) && ((newFlags & GC_counterMask) == 0) && (newFlags & GC_blocking)) + { + fb_assert(newFlags & GC_locked); + fb_assert(m_lock->lck_id); + fb_assert(m_lock->lck_physical == LCK_SW); + + LCK_downgrade(tdbb, m_lock); + + oldFlags = newFlags; + do + { + newFlags = oldFlags; + if (m_lock->lck_physical != LCK_SR) + { + newFlags &= ~GC_disabled; + if (m_lock->lck_physical < LCK_SR) + newFlags &= ~GC_locked; + } + else + newFlags |= GC_disabled; + + newFlags &= ~GC_blocking; + } while (!m_flags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); + } +} + +// violates rules of atomic counters - ok ONLY for ASSERT +unsigned GCLock::getSweepCount() const +{ + return m_flags.load(std::memory_order_relaxed) & GC_counterMask; +} + +bool GCLock::disable(thread_db* tdbb, int wait, Lock*& tempLock) +{ + ThreadStatusGuard temp_status(tdbb); + + // if validation is already running - go out + unsigned oldFlags = m_flags.load(std::memory_order_acquire); + do { + if (oldFlags & GC_disabled) + return false; + } while (m_flags.compare_exchange_weak(oldFlags, oldFlags | GC_disabled, + std::memory_order_release, std::memory_order_acquire)); + + int sleeps = -wait * 10; + while (m_flags.load(std::memory_order_relaxed) & GC_counterMask) + { + EngineCheckout cout(tdbb, FB_FUNCTION); + Thread::sleep(100); + + if (wait < 0 && --sleeps == 0) + break; + } + + if (m_flags.load(std::memory_order_relaxed) & GC_counterMask) + { + m_flags.fetch_and(~GC_disabled); + return false; + } + + if (m_relation->isLTT()) + return true; + + ensureReleased(tdbb); + + // we need no AST here + if (!tempLock) + tempLock = m_relation->createLock(tdbb, LCK_rel_gc, true); + + const bool ret = LCK_lock(tdbb, tempLock, LCK_PW, wait); + if (!ret) + m_flags.fetch_and(~GC_disabled); + + return ret; +} + +void GCLock::ensureReleased(thread_db* tdbb) +{ + if (!m_lock) + return; + + unsigned oldFlags = m_flags.load(std::memory_order_acquire); + for (;;) + { + if (oldFlags & GC_locked) + { + if (!m_flags.compare_exchange_strong(oldFlags, oldFlags & ~GC_locked, + std::memory_order_release, std::memory_order_acquire)) + { + continue; + } + + // exactly one who cleared GC_locked bit releases a lock + LCK_release(tdbb, m_lock); + } + + return; + } +} + +void GCLock::forcedRelease(thread_db* tdbb) +{ + m_flags.fetch_and(~GC_locked); + + if (m_lock) + LCK_release(tdbb, m_lock); +} + +void GCLock::enable(thread_db* tdbb, Lock* tempLock) +{ + if (!(tempLock && tempLock->lck_id)) + return; + + fb_assert(m_flags.load() & GC_disabled); + + ensureReleased(tdbb); + + LCK_convert(tdbb, tempLock, LCK_EX, LCK_WAIT); + m_flags.fetch_and(~GC_disabled); + + LCK_release(tdbb, tempLock); +} + +#ifdef DEV_BUILD +GCLock::State GCLock::isGCEnabled() const +{ + if (getSweepCount() || m_relation->isSystem() || m_relation->isTemporary()) + return State::enabled; + + if (m_flags & GC_disabled) + return State::disabled; + + return State::unknown; +} +#endif //DEV_BUILD + + +// +// class RelationPermanent +// RelationPermanent::RelationPermanent(thread_db* tdbb, MemoryPool& p, MetaId id, NoData) : PermanentStorage(p), - rel_partners_lock(nullptr), - rel_gc_lock(this), rel_gc_records(p), - rel_scan_count(0), rel_formats(/*p*/), rel_indices(p, this), rel_name(p), rel_id(id), rel_flags(REL_check_partners), - rel_pages_inst(nullptr), - rel_pages_base(p), - rel_pages_free(nullptr), - rel_file(nullptr), - rel_clear_deps(p) + rel_scan_count(0), + rel_gc_lock(this), + rel_clear_deps(p), + rel_pagespaces(p, 1) { rel_partners_lock = FB_NEW_RPT(getPool(), 0) Lock(tdbb, sizeof(SLONG), LCK_rel_partners, this, partners_ast_relation); @@ -140,6 +344,9 @@ RelationPermanent::RelationPermanent(thread_db* tdbb, MemoryPool& p, MetaId id, RelationPermanent::~RelationPermanent() { fb_assert(!rel_partners_lock); + + for (const auto pages : rel_pagespaces) + delete pages; } bool RelationPermanent::destroy(thread_db* tdbb, RelationPermanent* rel) @@ -201,24 +408,12 @@ int RelationPermanent::partners_ast_relation(void* ast_object) return 0; } - - Record* RelationPermanent::getGCRecord(thread_db* tdbb, const Format* const format) { -/************************************** - * - * V I O _ g c _ r e c o r d - * - ************************************** - * - * Functional description - * Allocate from a relation's vector of garbage - * collect record blocks. Their scope is strictly - * limited to temporary usage and should never be - * copied to permanent record parameter blocks. - * - **************************************/ - SET_TDBB(tdbb); + // Allocate from a relation's vector of garbage collect record blocks. + // Their scope is strictly limited to temporary usage and should never be + // copied to permanent record parameter blocks. + Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); @@ -281,37 +476,17 @@ bool RelationPermanent::isReplicating(thread_db* tdbb) return oldState == Bool3State::True; } -FB_UINT64 jrd_rel::getTempInstanceId(thread_db* tdbb) const +RelationPages* RelationPermanent::getTempPages(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages) { - return (getPermanent()->rel_flags & REL_temp_frame) ? tdbb->tdbb_temp_frame_id : 0; -} + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); -void RelationPermanent::fillPages(thread_db* tdbb) -{ - if (!rel_file) - { - if (!rel_pages_base.rel_index_root) - DPM_scan_pages(tdbb); - - fb_assert(rel_pages_base.rel_index_root); - } -} - -RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPages::InstanceId instanceId, - bool allocPages) -{ - if (tdbb->tdbb_flags & TDBB_use_db_page_space) - return &rel_pages_base; - - Jrd::Attachment* attachment = tdbb->getAttachment(); - Database* dbb = tdbb->getDatabase(); - - if (rel_flags & REL_temp_frame) + if (rel_flags & REL_temp_frame) { if (tdbb->tdbb_temp_frame_id) instanceId = tdbb->tdbb_temp_frame_id; else // called without a local table execution frame, maybe from OPT or CMP - return &rel_pages_base; + return nullptr; } else if (rel_flags & REL_temp_tran) { @@ -322,13 +497,15 @@ RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPage else if (tdbb->getTransaction()) instanceId = tdbb->getTransaction()->tra_number; else // called without transaction, maybe from OPT or CMP ? - return &rel_pages_base; + return nullptr; } } - else + else if (rel_flags & REL_temp_conn) instanceId = PAG_attachment_id(tdbb); + else + fb_assert(false); - MutexLockGuard g(rel_pages_mutex, FB_FUNCTION); + MutexLockGuard pagesGuard(rel_pages_mutex, FB_FUNCTION); if (!rel_pages_inst) rel_pages_inst = FB_NEW_POOL(getPool()) RelationPagesInstances(getPool()); @@ -339,34 +516,30 @@ RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPage if (!allocPages) return nullptr; + const auto pageSpaceId = dbb->dbb_page_manager.getTempPageSpaceID(tdbb); + RelationPages* newPages = rel_pages_free; - if (!newPages) { - newPages = FB_NEW_POOL(getPool()) RelationPages(getPool()); - } + if (!newPages) + newPages = FB_NEW_POOL(getPool()) RelationPages(getPool(), pageSpaceId, instanceId); else - { - rel_pages_free = newPages->rel_next_free; - newPages->rel_next_free = 0; - } - - fb_assert(newPages->useCount == 0); + rel_pages_free = newPages->reuse(pageSpaceId, instanceId); newPages->addRef(); - newPages->rel_instance_id = instanceId; - newPages->rel_pg_space_id = dbb->dbb_page_manager.getTempPageSpaceID(tdbb); + rel_pages_inst->add(newPages); // create primary pointer page and index root page DPM_create_relation_pages(tdbb, this, newPages); #ifdef VIO_DEBUG + const auto firstPointerPage = pages->getPointerPage(0); + const auto ppNumber = firstPointerPage ? firstPointerPage.value().getPageNum() : 0; + const auto rootPage = pages->getIndexRootPage(); + const auto irpNumber = rootPage ? rootPage.value().getPageNum() : 0; + VIO_trace(DEBUG_WRITES, "jrd_rel::getPages rel_id %u, inst %" UQUADFORMAT", ppp %" ULONGFORMAT", irp %" ULONGFORMAT", addr 0x%x\n", - getId(), - newPages->rel_instance_id, - newPages->rel_pages ? (*newPages->rel_pages)[0] : 0, - newPages->rel_index_root, - newPages); + getId(), instanceId, ppNumber, irpNumber, newPages); #endif if (rel_flags & REL_temp_frame) @@ -384,18 +557,19 @@ RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPage if (!idxTran) idxTran = attachment->getSysTransaction(); - jrd_rel* rel = MetadataCache::getVersioned(tdbb, getId(), CacheFlag::AUTOCREATE); - fb_assert(rel); + const auto relation = MetadataCache::getVersioned(tdbb, getId(), CacheFlag::AUTOCREATE); + fb_assert(relation); + + const auto basePages = relation->getBasePages(); - WIN window(rel_pages_base.rel_pg_space_id, -1); + WIN window(basePages->getPageSpaceId()); index_desc idx; idx.idx_id = idx_invalid; - while (BTR_next_index(tdbb, rel->getPermanent(), idxTran, &idx, &window, &rel_pages_base)) + while (BTR_next_index(tdbb, relation, idxTran, &idx, &window, basePages)) { QualifiedName idx_name; - auto* idp = this->lookupIndex(tdbb, idx.idx_id, CacheFlag::AUTOCREATE); - if (idp) + if (const auto idp = lookupIndex(tdbb, idx.idx_id, CacheFlag::AUTOCREATE)) idx_name = idp->getName(); switch (idx.idx_state) @@ -409,14 +583,14 @@ RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPage idx.idx_root = 0; SelectivityList selectivity(*pool); - IDX_create_index(tdbb, IdxCreate::AtOnce, rel, &idx, idx_name, NULL, idxTran, selectivity); + IDX_create_index(tdbb, IdxCreate::AtOnce, relation, &idx, idx_name, NULL, idxTran, selectivity); #ifdef VIO_DEBUG VIO_trace(DEBUG_WRITES, "jrd_rel::getPages rel_id %u, inst %" UQUADFORMAT", irp %" ULONGFORMAT", idx %u, idx_root %" ULONGFORMAT", addr 0x%x\n", getId(), - newPages->rel_instance_id, - newPages->rel_index_root, + newPages->getInstanceId(), + newPages->getIndexRootPage().value().getPageNum(), idx.idx_id, idx.idx_root, newPages); @@ -429,8 +603,8 @@ RelationPages* RelationPermanent::getPagesInternal(thread_db* tdbb, RelationPage return newPages; } - RelationPages* pages = (*rel_pages_inst)[pos]; - fb_assert(pages->rel_instance_id == instanceId); + const auto pages = (*rel_pages_inst)[pos]; + fb_assert(pages->getInstanceId() == instanceId); return pages; } @@ -438,7 +612,7 @@ RelationPages* RelationPermanent::getAttPages(thread_db* tdbb, RelationPages::In { fb_assert(!(rel_flags & REL_temp_tran)); - MutexLockGuard g(rel_pages_mutex, FB_FUNCTION); + MutexLockGuard pagesGuard(rel_pages_mutex, FB_FUNCTION); if (!rel_pages_inst) return nullptr; @@ -447,34 +621,30 @@ RelationPages* RelationPermanent::getAttPages(thread_db* tdbb, RelationPages::In if (!rel_pages_inst->find(inst_id, pos)) return nullptr; - RelationPages* pages = (*rel_pages_inst)[pos]; - fb_assert(pages->rel_instance_id == inst_id); + const auto pages = (*rel_pages_inst)[pos]; + fb_assert(pages->getInstanceId() == inst_id); return pages; } -bool RelationPermanent::delPages(thread_db* tdbb, RelationPages::InstanceId inst_id, RelationPages* aPages) +bool RelationPermanent::deletePages(thread_db* tdbb, RelationPages* pages = nullptr) { - RelationPages* pages = aPages ? aPages : - (rel_flags & REL_temp_frame ? getAttPages(tdbb, inst_id) : getPages(tdbb, inst_id, false)); - if (!pages || !pages->rel_instance_id) + if (!pages || !pages->getInstanceId()) return false; - fb_assert(inst_id == 0 || inst_id == MAX_TRA_NUMBER || pages->rel_instance_id == inst_id || - (rel_flags & REL_temp_frame)); - - fb_assert(pages->useCount > 0); + fb_assert(pages->m_useCount > 0); - if (--pages->useCount) + if (--pages->m_useCount) return false; #ifdef VIO_DEBUG + const auto firstPointerPage = pages->getPointerPage(0); + const auto ppNumber = firstPointerPage ? firstPointerPage.value().getPageNum() : 0; + const auto rootPage = pages->getIndexRootPage(); + const auto irpNumber = rootPage ? rootPage.value().getPageNum() : 0; + VIO_trace(DEBUG_WRITES, "jrd_rel::delPages rel_id %u, inst %" UQUADFORMAT", ppp %" ULONGFORMAT", irp %" ULONGFORMAT", addr 0x%x\n", - getId(), - pages->rel_instance_id, - pages->rel_pages ? (*pages->rel_pages)[0] : 0, - pages->rel_index_root, - pages); + getId(), pages->getInstanceId(), ppNumber, irpNumber, pages); #endif { // mutex scope @@ -484,16 +654,16 @@ bool RelationPermanent::delPages(thread_db* tdbb, RelationPages::InstanceId inst #ifdef DEV_BUILD const bool found = #endif - rel_pages_inst->find(pages->rel_instance_id, pos); + rel_pages_inst->find(pages->getInstanceId(), pos); fb_assert(found && ((*rel_pages_inst)[pos] == pages) ); rel_pages_inst->remove(pos); } - if (pages->rel_index_root) - IDX_delete_indices(tdbb, this, pages, false); + if (const auto rootPage = pages->getIndexRootPage()) + IDX_delete_indices(tdbb, rootPage.value(), false); - if (pages->rel_pages) + if (pages->hasData()) DPM_delete_relation_pages(tdbb, this, pages); MutexLockGuard g(rel_pages_mutex, FB_FUNCTION); @@ -510,12 +680,12 @@ void RelationPermanent::freePages(thread_db* tdbb) // no need in rel_pages_mutex - it's cleanup after DROP TABLE while (rel_pages_inst->hasData()) { - auto* pages = rel_pages_inst->pop(); + const auto pages = rel_pages_inst->pop(); - if (pages->rel_index_root) - IDX_delete_indices(tdbb, this, pages, false); + if (const auto rootPage = pages->getIndexRootPage()) + IDX_delete_indices(tdbb, rootPage.value(), false); - if (pages->rel_pages) + if (pages->hasData()) DPM_delete_relation_pages(tdbb, this, pages); pages->free(rel_pages_free); @@ -538,11 +708,11 @@ void RelationPermanent::retainPages(thread_db* tdbb, TraNumber oldNumber, TraNum return; RelationPages* pages = (*rel_pages_inst)[pos]; - fb_assert(pages->rel_instance_id == oldNumber); + fb_assert(pages->getInstanceId() == oldNumber); rel_pages_inst->remove(pos); - pages->rel_instance_id = newNumber; + pages->setInstanceId(newNumber); rel_pages_inst->add(pages); } @@ -564,69 +734,44 @@ void RelationPermanent::clearDropMarker(thread_db* tdbb) } } -void RelationPermanent::getRelLockKey(thread_db* tdbb, UCHAR* key) -{ - const ULONG val = getId(); - memcpy(key, &val, sizeof(ULONG)); - key += sizeof(ULONG); - - const RelationPages::InstanceId inst_id = getPages(tdbb)->rel_instance_id; - memcpy(key, &inst_id, sizeof(inst_id)); -} - -constexpr USHORT RelationPermanent::getRelLockKeyLength() noexcept -{ - return sizeof(ULONG) + sizeof(SINT64); -} - -void RelationPermanent::cleanUp() noexcept -{ - delete rel_pages_inst; - rel_pages_inst = NULL; -} - - -void RelationPermanent::fillPagesSnapshot(RelPagesSnapshot& snapshot, const bool attachmentOnly) +bool RelationPermanent::fillPagesSnapshot(PagesSnapshot& snapshot, const bool attachmentOnly) { MutexLockGuard g(rel_pages_mutex, FB_FUNCTION); - if (rel_pages_inst) + if (!rel_pages_inst) + return false; + + for (const auto relPages : *rel_pages_inst) { - for (FB_SIZE_T i = 0; i < rel_pages_inst->getCount(); i++) + if (!attachmentOnly) { - RelationPages* relPages = (*rel_pages_inst)[i]; - - if (!attachmentOnly) - { - snapshot.add(relPages); - relPages->addRef(); - } - else if ((rel_flags & REL_temp_conn) && - PAG_attachment_id(snapshot.spt_tdbb) == relPages->rel_instance_id) - { - snapshot.add(relPages); - relPages->addRef(); - } - else if (rel_flags & REL_temp_tran) + snapshot.add(relPages); + relPages->addRef(); + } + else if ((rel_flags & REL_temp_conn) && + PAG_attachment_id(snapshot.spt_tdbb) == relPages->getInstanceId()) + { + snapshot.add(relPages); + relPages->addRef(); + } + else if (rel_flags & REL_temp_tran) + { + const jrd_tra* tran = snapshot.spt_tdbb->getAttachment()->att_transactions; + for (; tran; tran = tran->tra_next) { - const jrd_tra* tran = snapshot.spt_tdbb->getAttachment()->att_transactions; - for (; tran; tran = tran->tra_next) + if (tran->tra_number == relPages->getInstanceId()) { - if (tran->tra_number == relPages->rel_instance_id) - { - snapshot.add(relPages); - relPages->addRef(); - } + snapshot.add(relPages); + relPages->addRef(); } } } } - else - snapshot.add(&rel_pages_base); -} + return true; +} -void RelationPermanent::RelPagesSnapshot::clear() +void RelationPermanent::PagesSnapshot::clear() { #ifdef DEV_BUILD thread_db* tdbb = NULL; @@ -636,15 +781,54 @@ void RelationPermanent::RelPagesSnapshot::clear() for (FB_SIZE_T i = 0; i < getCount(); i++) { - RelationPages* relPages = (*this)[i]; + auto const relPages = (*this)[i]; (*this)[i] = NULL; - spt_relation->delPages(spt_tdbb, MAX_TRA_NUMBER, relPages); + spt_relation->deletePages(spt_tdbb, relPages); } inherited::clear(); } +void RelationPermanent::getRelLockKey(thread_db* tdbb, UCHAR* key) +{ + const ULONG val = getId(); + memcpy(key, &val, sizeof(ULONG)); + key += sizeof(ULONG); + + const RelationPages::InstanceId inst_id = getInstanceId(tdbb); + memcpy(key, &inst_id, sizeof(inst_id)); +} + +Lock* RelationPermanent::createLock(thread_db* tdbb, lck_t lckType, bool noAst) +{ + return createLock(tdbb, getPool(), lckType, noAst); +} + +Lock* RelationPermanent::createLock(thread_db* tdbb, MemoryPool& pool, lck_t lckType, bool noAst) +{ + const USHORT relLockLen = getRelLockKeyLength(); + + Lock* lock = FB_NEW_RPT(pool, relLockLen) + Lock(tdbb, relLockLen, lckType, lckType == LCK_relation ? (void*)this : (void*)&rel_gc_lock); + getRelLockKey(tdbb, lock->getKeyPtr()); + + lock->lck_type = lckType; + switch (lckType) + { + case LCK_relation: + break; + + case LCK_rel_gc: + lock->lck_ast = noAst ? nullptr : GCLock::ast; + break; + + default: + fb_assert(false); + } + + return lock; +} IndexVersion* RelationPermanent::lookup_index(thread_db* tdbb, MetaId id, ObjectBase::Flag flags) { @@ -657,7 +841,6 @@ Cached::Index* RelationPermanent::lookupIndex(thread_db* tdbb, MetaId id, Object return rel_indices.getData(tdbb, id, flags); } - Cached::Index* RelationPermanent::ensureIndex(thread_db* tdbb, MetaId id) { auto* idp = rel_indices.ensurePermanent(tdbb, id); @@ -665,33 +848,6 @@ Cached::Index* RelationPermanent::ensureIndex(thread_db* tdbb, MetaId id) return idp; } - -PageNumber RelationPermanent::getIndexRootPage(thread_db* tdbb) -{ -/************************************** - * - * g e t _ r o o t _ p a g e - * - ************************************** - * - * Functional description - * Find the root page for a relation. - * - **************************************/ - SET_TDBB(tdbb); - - RelationPages* relPages = getPages(tdbb); - SLONG page = relPages->rel_index_root; - if (!page) - { - DPM_scan_pages(tdbb); - page = relPages->rel_index_root; - } - - return PageNumber(relPages->rel_pg_space_id, page); -} - - Cached::Relation* RelationPermanent::newVersion(thread_db* tdbb, const QualifiedName& name) { auto* relation = MetadataCache::getPerm(tdbb, name, CacheFlag::MINISCAN); @@ -714,7 +870,6 @@ Cached::Relation* RelationPermanent::newVersion(thread_db* tdbb, const Qualified return relation; } - void RelationPermanent::releaseLock(thread_db* tdbb) { if (rel_partners_lock) @@ -729,368 +884,145 @@ void RelationPermanent::releaseLock(thread_db* tdbb) } } - -IndexVersion::IndexVersion(MemoryPool& p, Cached::Index* idp) - : perm(idp) -{ } - -void IndexVersion::destroy(thread_db* tdbb, IndexVersion* idv) +void RelationPermanent::addFormat(Format* fmt) { - delete idv; -} + MutexLockGuard guard(rel_formats_grow, FB_FUNCTION); -ObjectType IndexVersion::objectType() noexcept -{ - return obj_index; + rel_formats.grow(fmt->fmt_version + 1, true); + rel_formats.writeAccessor()->value(fmt->fmt_version) = fmt; } - -void jrd_rel::releaseTriggers(thread_db* tdbb, bool destroy) +RelationPages* RelationPermanent::getPages(ULONG pageSpaceId) { - for (int n = 1; n < TRIGGER_MAX; ++n) + MutexLockGuard guard(rel_pages_mutex, FB_FUNCTION); + + for (const auto pages : rel_pagespaces) { - rel_triggers[n].release(tdbb, destroy); + if (pages->getPageSpaceId() == pageSpaceId) + return pages; } + + rel_pagespaces.add(FB_NEW_POOL(getPool()) RelationPages(getPool(), pageSpaceId)); + return rel_pagespaces.back(); } -void Triggers::release(thread_db* tdbb, bool destroy) + +// +// class jrd_rel +// + +jrd_rel::jrd_rel(MemoryPool& p, Cached::Relation* r) + : rel_pool(&p), + rel_perm(r), + rel_view_contexts(p), + rel_triggers(p), + rel_ss_definer(false) +{} + +jrd_rel* jrd_rel::create(thread_db* tdbb, MemoryPool& pool, Cached::Relation* rlp) { -/*********************************************** - * - * M E T _ r e l e a s e _ t r i g g e r s - * - *********************************************** - * - * Functional description - * Release a possibly null vector of triggers. - * If triggers are still active let someone - * else do the work. - * - **************************************/ - if (destroy) - { - Triggers::destroy(tdbb, this); - return; - } + return FB_NEW_POOL(pool) jrd_rel(pool, rlp); } -Lock* RelationPermanent::createLock(thread_db* tdbb, lck_t lckType, bool noAst) +void jrd_rel::destroy(thread_db* tdbb, jrd_rel* rel) { - return createLock(tdbb, getPool(), lckType, noAst); + rel->releaseTriggers(tdbb, true); + delete rel; } -Lock* RelationPermanent::createLock(thread_db* tdbb, MemoryPool& pool, lck_t lckType, bool noAst) +FB_UINT64 jrd_rel::getTempInstanceId(thread_db* tdbb) const { - const USHORT relLockLen = getRelLockKeyLength(); + return (getPermanent()->rel_flags & REL_temp_frame) ? tdbb->tdbb_temp_frame_id : 0; +} - Lock* lock = FB_NEW_RPT(pool, relLockLen) - Lock(tdbb, relLockLen, lckType, lckType == LCK_relation ? (void*)this : (void*)&rel_gc_lock); - getRelLockKey(tdbb, lock->getKeyPtr()); +void jrd_rel::releaseTriggers(thread_db* tdbb, bool destroy) +{ + for (int n = 1; n < TRIGGER_MAX; ++n) + rel_triggers[n].release(tdbb, destroy); +} - lock->lck_type = lckType; - switch (lckType) - { - case LCK_relation: - break; +RelationPages* jrd_rel::getPages(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages) +{ + const auto relPages = getBasePages(); - case LCK_rel_gc: - lock->lck_ast = noAst ? nullptr : GCLock::ast; - break; + if (!relPages->hasData() || !relPages->hasIndices()) + DPM_scan_pages(tdbb, rel_perm, relPages); - default: - fb_assert(false); - } + Tablespace::lock(tdbb, relPages->getPageSpaceId()); - return lock; + return rel_perm->useTempPages(tdbb) ? rel_perm->getTempPages(tdbb, instanceId, allocPages) : relPages; } -void RelationPermanent::addFormat(Format* fmt) +void jrd_rel::deletePages(thread_db* tdbb) { - MutexLockGuard guard(rel_formats_grow, FB_FUNCTION); - - rel_formats.grow(fmt->fmt_version + 1, true); - rel_formats.writeAccessor()->value(fmt->fmt_version) = fmt; + DPM_delete_relation(tdbb, rel_perm, getBasePages()); } +const char* jrd_rel::objectFamily(RelationPermanent* perm) +{ + return perm->isView() ? "view" : "table"; +} -void GCLock::blockingAst() +ObjectType jrd_rel::objectType() noexcept { - /**** - SR - gc forbidden, awaiting moment to re-establish SW lock - SW - gc allowed, usual state - PW - gc allowed to the one connection only - ****/ + return obj_relation; +} - Database* dbb = gcLck->lck_dbb; +const Format* jrd_rel::currentFormat(thread_db* tdbb) +{ + // Get the current format for a relation. The current format is the format + // in which new records are to be stored. - AsyncContextHolder tdbb(dbb, FB_FUNCTION); + // dimitr: rel_current_format may sometimes get out of sync, + // e.g. after DFW error raised during ALTER TABLE command. + // Thus it makes sense to validate it before usage and + // fetch the proper one if something is suspicious. - unsigned oldFlags = gcFlags.load(std::memory_order_acquire); - do - { - fb_assert(oldFlags & GC_locked); - if (!(oldFlags & GC_locked)) // work already done synchronously ? - return; - } while (!gcFlags.compare_exchange_weak(oldFlags, oldFlags | GC_blocking, - std::memory_order_release, std::memory_order_acquire)); + if (rel_current_format && rel_current_format->fmt_version == rel_current_fmt) + return rel_current_format; - if (oldFlags & GC_counterMask) - return; + // Usually, format numbers start with one and they are present in RDB$FORMATS. + // However, system tables have zero as their initial format and they don't have + // any related records in RDB$FORMATS, instead their rel_formats[0] is initialized + // directly (see ini.epp). Every other case of zero format number found for an already + // scanned table must be catched here and investigated. + fb_assert(rel_current_fmt || isSystem()); - if (oldFlags & GC_disabled) - { - // someone acquired EX lock + if (!tdbb) + tdbb = JRD_get_thread_data(); - fb_assert(gcLck->lck_id); - fb_assert(gcLck->lck_physical == LCK_SR); + rel_current_format = getPermanent()->getFormat(tdbb, rel_current_fmt); - LCK_release(tdbb, gcLck); - gcFlags.fetch_and(~(GC_disabled | GC_blocking | GC_locked)); - } - else - { - // someone acquired PW lock + return rel_current_format; +} - fb_assert(gcLck->lck_id); - fb_assert(gcLck->lck_physical == LCK_SW); +bool jrd_rel::hash(thread_db* tdbb, sha512& digest) +{ + if (!(rel_current_fmt || isSystem())) + return false; - gcFlags.fetch_or(GC_disabled); - downgrade(tdbb); - } + currentFormat(tdbb)->hash(digest); + return true; } -void GCLock::checkGuard(unsigned flags) +const Trigger* jrd_rel::findTrigger(const QualifiedName& trig_name) const { - if (flags & GC_guardBit) + for (int n = TRIGGER_PRE_STORE; n <= TRIGGER_POST_ERASE; ++n) { - fatal_exception::raiseFmt - ("Overflow or underflow when changing GC lock atomic counter (guard bit set), flags %08x", flags); + for (auto t : rel_triggers[n]) + { + if (t->name == trig_name) + return t; + } } -} - -bool GCLock::acquire(thread_db* tdbb, int wait) -{ - unsigned oldFlags = gcFlags.load(std::memory_order_acquire); - for(;;) - { - if (oldFlags & (GC_blocking | GC_disabled)) // lock should not be obtained - return false; - - const unsigned newFlags = oldFlags + 1; - checkGuard(newFlags); - - if (!gcFlags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)) - continue; - - if (oldFlags & GC_locked) // lock was already taken when we checked flags - return true; - - if (!(oldFlags & GC_counterMask)) // we must take lock - break; - - // undefined state - someone else is taking/releasing a lock right now: - oldFlags = gcFlags.fetch_sub(1, std::memory_order_acquire); // decrement our lock counter, - checkGuard(oldFlags - 1); - Thread::yield(); // wait a bit - oldFlags = gcFlags.load(std::memory_order_acquire); // and retry - } - - // No need to use LM for LTT - if (gcRel->isLTT()) - return true; - - // We incremented counter from 0 to 1 - take care about lck - if (!gcLck) - gcLck = gcRel->createLock(tdbb, LCK_rel_gc, false); - - fb_assert(!gcLck->lck_id); - - ThreadStatusGuard temp_status(tdbb); - - bool ret; - if (oldFlags & GC_disabled) - ret = LCK_lock(tdbb, gcLck, LCK_SR, wait); - else - { - ret = LCK_lock(tdbb, gcLck, LCK_SW, wait); - if (ret) - { - gcFlags.fetch_or(GC_locked); - return true; - } - - oldFlags = gcFlags.fetch_or(GC_disabled, std::memory_order_seq_cst) | GC_disabled; - ret = LCK_lock(tdbb, gcLck, LCK_SR, wait); - } - - unsigned newFlags; - do - { - newFlags = oldFlags - 1; - checkGuard(newFlags); - - if (!ret) - newFlags &= ~GC_disabled; - - } while (!gcFlags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); - - return false; -} - -void GCLock::downgrade(thread_db* tdbb) -{ - unsigned oldFlags = gcFlags.load(std::memory_order_acquire); - unsigned newFlags; - do - { - newFlags = oldFlags - 1; - checkGuard(newFlags); - } while (!gcFlags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); - - if ((!gcRel->isLTT()) && ((newFlags & GC_counterMask) == 0) && (newFlags & GC_blocking)) - { - fb_assert(newFlags & GC_locked); - fb_assert(gcLck->lck_id); - fb_assert(gcLck->lck_physical == LCK_SW); - - LCK_downgrade(tdbb, gcLck); - - oldFlags = newFlags; - do - { - newFlags = oldFlags; - if (gcLck->lck_physical != LCK_SR) - { - newFlags &= ~GC_disabled; - if (gcLck->lck_physical < LCK_SR) - newFlags &= ~GC_locked; - } - else - newFlags |= GC_disabled; - - newFlags &= ~GC_blocking; - } while (!gcFlags.compare_exchange_weak(oldFlags, newFlags, std::memory_order_release, std::memory_order_acquire)); - } -} - -// violates rules of atomic counters - ok ONLY for ASSERT -unsigned GCLock::getSweepCount() const -{ - return gcFlags.load(std::memory_order_relaxed) & GC_counterMask; -} - -bool GCLock::disable(thread_db* tdbb, int wait, Lock*& tempLock) -{ - ThreadStatusGuard temp_status(tdbb); - - // if validation is already running - go out - unsigned oldFlags = gcFlags.load(std::memory_order_acquire); - do { - if (oldFlags & GC_disabled) - return false; - } while (gcFlags.compare_exchange_weak(oldFlags, oldFlags | GC_disabled, - std::memory_order_release, std::memory_order_acquire)); - - int sleeps = -wait * 10; - while (gcFlags.load(std::memory_order_relaxed) & GC_counterMask) - { - EngineCheckout cout(tdbb, FB_FUNCTION); - Thread::sleep(100); - - if (wait < 0 && --sleeps == 0) - break; - } - - if (gcFlags.load(std::memory_order_relaxed) & GC_counterMask) - { - gcFlags.fetch_and(~GC_disabled); - return false; - } - - if (gcRel->isLTT()) - return true; - - ensureReleased(tdbb); - - // we need no AST here - if (!tempLock) - tempLock = gcRel->createLock(tdbb, LCK_rel_gc, true); - - const bool ret = LCK_lock(tdbb, tempLock, LCK_PW, wait); - if (!ret) - gcFlags.fetch_and(~GC_disabled); - - return ret; -} - -void GCLock::ensureReleased(thread_db* tdbb) -{ - if (!gcLck) - return; - - unsigned oldFlags = gcFlags.load(std::memory_order_acquire); - for (;;) - { - if (oldFlags & GC_locked) - { - if (!gcFlags.compare_exchange_strong(oldFlags, oldFlags & ~GC_locked, - std::memory_order_release, std::memory_order_acquire)) - { - continue; - } - - // exactly one who cleared GC_locked bit releases a lock - LCK_release(tdbb, gcLck); - } - - return; - } -} - -void GCLock::forcedRelease(thread_db* tdbb) -{ - gcFlags.fetch_and(~GC_locked); - if (gcLck) - LCK_release(tdbb, gcLck); -} - -void GCLock::enable(thread_db* tdbb, Lock* tempLock) -{ - if (!(tempLock && tempLock->lck_id)) - return; - - fb_assert(gcFlags.load() & GC_disabled); - - ensureReleased(tdbb); - - LCK_convert(tdbb, tempLock, LCK_EX, LCK_WAIT); - gcFlags.fetch_and(~GC_disabled); - - LCK_release(tdbb, tempLock); + + return nullptr; } -/// RelationPages - -void RelationPages::free(RelationPages*& nextFree) -{ - rel_next_free = nextFree; - nextFree = this; - - if (rel_pages) - rel_pages->clear(); - - rel_index_root = rel_data_pages = 0; - rel_slot_space = rel_pri_data_space = rel_sec_data_space = 0; - rel_last_free_pri_dp = rel_last_free_blb_dp = 0; - rel_instance_id = 0; - - dpMap.clear(); - dpMapMark = 0; -} - - -/// IndexPermanent +// +// class IndexPermanent +// [[noreturn]] void IndexPermanent::errIndexGone() { @@ -1138,121 +1070,119 @@ void IndexPermanent::reloadAst(thread_db* tdbb, TraNumber tran, bool erase) } -/// jrd_rel +// +// class Trigger +// -void jrd_rel::destroy(thread_db* tdbb, jrd_rel* rel) +void Trigger::free(thread_db* tdbb) { - rel->releaseTriggers(tdbb, true); + extTrigger.reset(); - delete rel; -} + if (releaseInProgress || !statement) + return; -jrd_rel* jrd_rel::create(thread_db* tdbb, MemoryPool& pool, Cached::Relation* rlp) -{ - return FB_NEW_POOL(pool) jrd_rel(pool, rlp); -} + AutoSetRestore autoProgressFlag(&releaseInProgress, true); -const char* jrd_rel::objectFamily(RelationPermanent* perm) -{ - return perm->isView() ? "view" : "table"; + statement->release(tdbb); + statement = nullptr; } -ObjectType jrd_rel::objectType() noexcept + +// +// class Triggers +// + +void Triggers::release(thread_db* tdbb, bool destroy) { - return obj_relation; + // Release a possibly null vector of triggers. + // If triggers are still active let someone else do the work. + + if (destroy) + { + Triggers::destroy(tdbb, this); + return; + } } -const Format* jrd_rel::currentFormat(thread_db* tdbb) +void Triggers::destroy(thread_db* tdbb, Triggers* trigs) { -/************************************** - * - * M E T _ c u r r e n t - * - ************************************** - * - * Functional description - * Get the current format for a relation. The current format is the - * format in which new records are to be stored. - * - **************************************/ + for (auto t : trigs->triggers) + { + t->free(tdbb); + delete t; + } - // dimitr: rel_current_format may sometimes get out of sync, - // e.g. after DFW error raised during ALTER TABLE command. - // Thus it makes sense to validate it before usage and - // fetch the proper one if something is suspicious. + trigs->triggers.clear(); +} - if (rel_current_format && rel_current_format->fmt_version == rel_current_fmt) - return rel_current_format; - // Usually, format numbers start with one and they are present in RDB$FORMATS. - // However, system tables have zero as their initial format and they don't have - // any related records in RDB$FORMATS, instead their rel_formats[0] is initialized - // directly (see ini.epp). Every other case of zero format number found for an already - // scanned table must be catched here and investigated. - fb_assert(rel_current_fmt || isSystem()); +// +// class TrigArray +// - if (!tdbb) - tdbb = JRD_get_thread_data(); +TrigArray::TrigArray(MemoryPool& p) + : preErase(p), postErase(p), preModify(p), + postModify(p), preStore(p), postStore(p) +{ } - rel_current_format = getPermanent()->getFormat(tdbb, rel_current_fmt); +Triggers& TrigArray::operator[](int t) +{ + switch(t) + { + case TRIGGER_PRE_STORE: + return preStore; - return rel_current_format; -} + case TRIGGER_POST_STORE: + return postStore; -bool jrd_rel::hash(thread_db* tdbb, sha512& digest) -{ - if (!(rel_current_fmt || isSystem())) - return false; + case TRIGGER_PRE_MODIFY: + return preModify; - currentFormat(tdbb)->hash(digest); - return true; -} + case TRIGGER_POST_MODIFY: + return postModify; -const Trigger* jrd_rel::findTrigger(const QualifiedName& trig_name) const -{ - for (int n = TRIGGER_PRE_STORE; n <= TRIGGER_POST_ERASE; ++n) - { - for (auto t : rel_triggers[n]) - { - if (t->name == trig_name) - return t; - } + case TRIGGER_PRE_ERASE: + return preErase; + + case TRIGGER_POST_ERASE: + return postErase; } - return nullptr; + fb_assert(false); + fatal_exception::raise("Invalid trigger type"); } - -// class Triggers - -void Triggers::destroy(thread_db* tdbb, Triggers* trigs) +const Triggers& TrigArray::operator[](int t) const { - for (auto t : trigs->triggers) + switch(t) { - t->free(tdbb); - delete t; - } - trigs->triggers.clear(); -} + case TRIGGER_PRE_STORE: + return preStore; + case TRIGGER_POST_STORE: + return postStore; -// class Trigger + case TRIGGER_PRE_MODIFY: + return preModify; -void Trigger::free(thread_db* tdbb) -{ - extTrigger.reset(); + case TRIGGER_POST_MODIFY: + return postModify; - if (releaseInProgress || !statement) - return; + case TRIGGER_PRE_ERASE: + return preErase; - AutoSetRestore autoProgressFlag(&releaseInProgress, true); + case TRIGGER_POST_ERASE: + return postErase; + } - statement->release(tdbb); - statement = nullptr; + fb_assert(false); + fatal_exception::raise("Invalid trigger type"); } +// // class DbTriggers +// DbTriggersHeader::DbTriggersHeader(thread_db* tdbb, MemoryPool& p, MetaId& t, NoData) : Firebird::PermanentStorage(p), @@ -1283,16 +1213,3 @@ ObjectType DbTriggers::objectType() noexcept { return obj_trigger; } - -#ifdef DEV_BUILD -GCLock::State GCLock::isGCEnabled() const -{ - if (getSweepCount() || gcRel->isSystem() || gcRel->isTemporary()) - return State::enabled; - - if (gcFlags & GC_disabled) - return State::disabled; - - return State::unknown; -} -#endif //DEV_BUILD diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 8712195e12e..0dde30ccf5e 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -27,12 +27,15 @@ #include "../jrd/btr.h" #include "../jrd/lck.h" #include "../jrd/pag.h" +#include "../jrd/tdbb.h" #include "../jrd/val.h" #include "../jrd/Attachment.h" #include "../jrd/CacheVector.h" #include "../jrd/ExtEngineManager.h" #include "../jrd/met_proto.h" #include "../jrd/Resources.h" +#include "../jrd/SharedReadVector.h" +#include "../jrd/Tablespace.h" #include "../common/classes/TriState.h" #include "../common/sha2/sha2.h" #include "../jrd/ods.h" @@ -306,8 +309,12 @@ typedef Firebird::SortedArray USHORT, ViewContext> ViewContexts; -class RelationPages +class RelationPages : private Firebird::PermanentStorage { + friend class RelationPermanent; + + typedef SharedReadVector PageList; + public: typedef FB_UINT64 InstanceId; @@ -315,50 +322,275 @@ class RelationPages static_assert(sizeof(InstanceId) >= sizeof(TraNumber), "InstanceId must fit TraNumber"); static_assert(sizeof(InstanceId) >= sizeof(AttNumber), "InstanceId must fit AttNumber"); - vcl* rel_pages; // vector of pointer page numbers - InstanceId rel_instance_id; // 0 or att_attachment_id or tra_number - - ULONG rel_index_root; // index root page number - ULONG rel_data_pages; // count of relation data pages - ULONG rel_slot_space; // lowest pointer page with slot space - ULONG rel_pri_data_space; // lowest pointer page with primary data page space - ULONG rel_sec_data_space; // lowest pointer page with secondary data page space - ULONG rel_last_free_pri_dp; // last primary data page found with space - ULONG rel_last_free_blb_dp; // last blob data page found with space - USHORT rel_pg_space_id; - - RelationPages(Firebird::MemoryPool& pool) - : rel_pages(NULL), rel_instance_id(0), - rel_index_root(0), rel_data_pages(0), rel_slot_space(0), - rel_pri_data_space(0), rel_sec_data_space(0), - rel_last_free_pri_dp(0), rel_last_free_blb_dp(0), - rel_pg_space_id(DB_PAGE_SPACE), rel_next_free(NULL), - dpMap(pool), - dpMapMark(0) + ULONG rel_data_pages = 0; // count of relation data pages + ULONG rel_slot_space = 0; // lowest pointer page sequence with slot space + ULONG rel_pri_data_space = 0; // lowest pointer page sequence with primary data page space + ULONG rel_sec_data_space = 0; // lowest pointer page sequence with secondary data page space + ULONG rel_last_free_pri_dp = 0; // last primary data page found with space + ULONG rel_last_free_blb_dp = 0; // last blob data page found with space + + RelationPages() = delete; + ~RelationPages() = default; + + RelationPages(Firebird::MemoryPool& pool, ULONG pageSpaceId, InstanceId instanceId = 0) + : Firebird::PermanentStorage(pool), + m_pageSpaceId(pageSpaceId), + m_instanceId(instanceId), + m_dpMap(pool) {} - inline SLONG addRef() noexcept + void assign(const RelationPages& from) { - return useCount++; + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + + fb_assert(m_pageSpaceId == from.m_pageSpaceId); + + m_pointerPages.clear(); + + const auto readAccessor = from.m_pointerPages.readAccessor(); + const auto pageCount = readAccessor->getCount(); + m_pointerPages.grow(pageCount, true); + + const auto writeAccessor = m_pointerPages.writeAccessor(); + writeAccessor->add(readAccessor.getPointer()); + + m_indexRootPage = from.m_indexRootPage; + rel_data_pages = from.rel_data_pages; + rel_slot_space = from.rel_slot_space; + rel_pri_data_space = from.rel_pri_data_space; + rel_sec_data_space = from.rel_sec_data_space; + rel_last_free_pri_dp = from.rel_last_free_pri_dp; + rel_last_free_blb_dp = from.rel_last_free_blb_dp; + + m_dpMap.assign(from.m_dpMap); + m_dpMapMark = from.m_dpMapMark; } - void free(RelationPages*& nextFree); + void free(RelationPages*& nextFree) + { + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + + reset(); + + m_freePages = nextFree; + nextFree = this; + + m_dpMap.clear(); + m_dpMapMark = 0; + } + + void reset(ULONG pageSpaceId = INVALID_PAGE_SPACE) + { + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + + m_pageSpaceId = pageSpaceId; + + m_pointerPages.clear(); + m_indexRootPage = 0; + + rel_data_pages = 0; + rel_slot_space = 0; + rel_pri_data_space = 0; + rel_sec_data_space = 0; + rel_last_free_pri_dp = 0; + rel_last_free_blb_dp = 0; + + m_dpMap.clear(); + m_dpMapMark = 0; + } + + ULONG getPageSpaceId() const noexcept + { + return m_pageSpaceId; + } + + void setPageSpace(ULONG pageSpaceId) + { + if (m_pageSpaceId != pageSpaceId) + { + fb_assert(pageSpaceId != INVALID_PAGE_SPACE); + fb_assert(m_instanceId == 0); + + reset(pageSpaceId); + } + } + + // Ensure that at least one slot is allocated inside the page vector and + // initialize the first pointer page slot with the given page number + + void init(ULONG pageNo) + { + fb_assert(pageNo); + + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + + fb_assert(m_pageSpaceId != INVALID_PAGE_SPACE); + + auto accessor = m_pointerPages.writeAccessor(); + + if (!accessor->getCount()) + { + m_pointerPages.grow(1, true); + accessor = m_pointerPages.writeAccessor(); + } + + auto& value = accessor->value(0); + fb_assert(!value || value == pageNo); + value = pageNo; + } + + InstanceId getInstanceId() const noexcept + { + return m_instanceId; + } + + void setInstanceId(InstanceId instanceId) + { + m_instanceId = instanceId; + } + + PageNumber toPageNumber(ULONG pageNo) const noexcept + { + fb_assert(m_pageSpaceId != INVALID_PAGE_SPACE); + return PageNumber(m_pageSpaceId, pageNo); + } + + bool hasIndices() const + { + return (m_indexRootPage != 0); + } + + std::optional getIndexRootPage() + { + if (!m_indexRootPage) + return std::nullopt; + + fb_assert(m_pageSpaceId != INVALID_PAGE_SPACE); + + return PageNumber(m_pageSpaceId, m_indexRootPage); + } + + void setIndexRootPage(ULONG indexRootPage) + { + fb_assert(indexRootPage); + m_indexRootPage = indexRootPage; + } + + void setIndexRootPage(PageNumber indexRootPage) + { + const auto pageNo = indexRootPage.getPageNum(); + fb_assert(indexRootPage.getPageSpaceID() == m_pageSpaceId && pageNo); + + m_indexRootPage = pageNo; + } + + bool hasData() const + { + return (m_pointerPages.readAccessor()->getCount() != 0); + } + + HazardPtr getPointerPages() + { + return m_pointerPages.readAccessor(); + } + + ULONG getPointerPageCount() + { + const auto accessor = m_pointerPages.readAccessor(); + return accessor->getCount(); + } + + std::optional getPointerPage(ULONG sequence) + { + const auto accessor = m_pointerPages.readAccessor(); + if (sequence >= accessor->getCount()) + return std::nullopt; + + const auto pageNo = accessor->value(sequence); + fb_assert(m_pageSpaceId != INVALID_PAGE_SPACE && pageNo); + + return PageNumber(m_pageSpaceId, pageNo); + } + + void resetPointerPages(ULONG count, const ULONG* data) + { + updatePointerPages(0, count, data); + } + + void updatePointerPages(ULONG sequence, ULONG count, const ULONG* data) + { + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + auto accessor = m_pointerPages.writeAccessor(); + + if (sequence + count > accessor->getCount()) + { + m_pointerPages.grow(sequence + count, true); + accessor = m_pointerPages.writeAccessor(); + } + +#ifdef DEV_BUILD + for (unsigned i = 0; i < count; i++) + { + auto& value = accessor->value(sequence + i); + fb_assert(!value || value == data[i]); + fb_assert(data[i]); + value = data[i]; + } +#else + memcpy(accessor->begin() + sequence, data, count * sizeof(ULONG)); +#endif + } + + void setPointerPage(ULONG sequence, ULONG pageNo) + { + fb_assert(pageNo); + + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); + auto accessor = m_pointerPages.writeAccessor(); + + if (sequence >= accessor->getCount()) + { + m_pointerPages.grow(sequence + 1, true); + accessor = m_pointerPages.writeAccessor(); + } + + auto& value = accessor->value(sequence); + fb_assert(!value || value == pageNo); + value = pageNo; + } + + inline int addRef() noexcept + { + return ++m_useCount; + } + + RelationPages* reuse(ULONG pageSpaceId, InstanceId instanceId) + { + fb_assert(m_useCount == 0); + + m_pageSpaceId = pageSpaceId; + m_instanceId = instanceId; + + const auto freePages = m_freePages; + m_freePages = nullptr; + return freePages; + } static inline InstanceId generate(const RelationPages* item) noexcept { - return item->rel_instance_id; + return item->m_instanceId; } ULONG getDPNumber(ULONG dpSequence) { - Firebird::MutexLockGuard g(dpMutex, FB_FUNCTION); + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); FB_SIZE_T pos; - if (dpMap.find(dpSequence, pos)) + if (m_dpMap.find(dpSequence, pos)) { - if (dpMap[pos].mark != dpMapMark) - dpMap[pos].mark = ++dpMapMark; - return dpMap[pos].physNum; + if (m_dpMap[pos].mark != m_dpMapMark) + m_dpMap[pos].mark = ++m_dpMapMark; + return m_dpMap[pos].physNum; } return 0; @@ -366,58 +598,64 @@ class RelationPages void setDPNumber(ULONG dpSequence, ULONG dpNumber) { - Firebird::MutexLockGuard g(dpMutex, FB_FUNCTION); + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); FB_SIZE_T pos; - if (dpMap.find(dpSequence, pos)) + if (m_dpMap.find(dpSequence, pos)) { if (dpNumber) { - dpMap[pos].physNum = dpNumber; - dpMap[pos].mark = ++dpMapMark; + m_dpMap[pos].physNum = dpNumber; + m_dpMap[pos].mark = ++m_dpMapMark; } else - dpMap.remove(pos); + m_dpMap.remove(pos); } else if (dpNumber) { - dpMap.insert(pos, {dpSequence, dpNumber, ++dpMapMark}); + m_dpMap.insert(pos, {dpSequence, dpNumber, ++m_dpMapMark}); - if (dpMap.getCount() == MAX_DPMAP_ITEMS) + if (m_dpMap.getCount() == MAX_DPMAP_ITEMS) freeOldestMapItems(); } } void freeOldestMapItems() noexcept { - Firebird::MutexLockGuard g(dpMutex, FB_FUNCTION); + Firebird::MutexLockGuard guard(m_mutex, FB_FUNCTION); ULONG minMark = MAX_ULONG; FB_SIZE_T i; - for (i = 0; i < dpMap.getCount(); i++) + for (i = 0; i < m_dpMap.getCount(); i++) { - if (minMark > dpMap[i].mark) - minMark = dpMap[i].mark; + if (minMark > m_dpMap[i].mark) + minMark = m_dpMap[i].mark; } - minMark = (minMark + dpMapMark) / 2; + minMark = (minMark + m_dpMapMark) / 2; i = 0; - while (i < dpMap.getCount()) + while (i < m_dpMap.getCount()) { - if (dpMap[i].mark > minMark) - dpMap[i++].mark -= minMark; + if (m_dpMap[i].mark > minMark) + m_dpMap[i++].mark -= minMark; else - dpMap.remove(i); + m_dpMap.remove(i); } - dpMapMark -= minMark; + m_dpMapMark -= minMark; } private: - RelationPages* rel_next_free; - std::atomic useCount = 0; + ULONG m_pageSpaceId; + InstanceId m_instanceId; // 0 or att_attachment_id or tra_number + + PageList m_pointerPages; + ULONG m_indexRootPage = 0; + RelationPages* m_freePages = nullptr; + std::atomic m_useCount = 0; + Firebird::Mutex m_mutex; static constexpr ULONG MAX_DPMAP_ITEMS = 64; @@ -433,11 +671,8 @@ class RelationPages } }; - Firebird::SortedArray, ULONG, DPItem> dpMap; - ULONG dpMapMark; - Firebird::Mutex dpMutex; - -friend class RelationPermanent; + Firebird::SortedArray, ULONG, DPItem> m_dpMap; + ULONG m_dpMapMark = 0; }; @@ -542,17 +777,27 @@ class IndexPermanent : public Firebird::PermanentStorage class IndexVersion final : public ObjectBase { public: - IndexVersion(MemoryPool& p, Cached::Index* idp); + IndexVersion(MemoryPool& p, Cached::Index* idp) + : perm(idp) + {} static IndexVersion* create(thread_db* tdbb, MemoryPool& p, Cached::Index* idp) { return FB_NEW_POOL(p) IndexVersion(p, idp); } - static void destroy(thread_db* tdbb, IndexVersion* idv); + + static void destroy(thread_db* tdbb, IndexVersion* idv) + { + delete idv; + } + + ObjectType objectType() noexcept + { + return obj_index; + } ScanResult scan(thread_db* tdbb, ObjectBase::Flag flags); static std::optional getIdByName(thread_db* tdbb, ExName name); - static ObjectType objectType() noexcept; ScanResult reload(thread_db* tdbb, ObjectBase::Flag flags) { @@ -613,116 +858,19 @@ class IndexVersion final : public ObjectBase }; -// Relation block; one is created for each relation referenced -// in the database, though it is not really filled out until -// the relation is scanned - -class jrd_rel final : public ObjectBase -{ - jrd_rel(const jrd_rel&) = delete; - jrd_rel(const jrd_rel&&) = delete; - -public: - jrd_rel(MemoryPool& p, Cached::Relation* r); - - MemoryPool* rel_pool; - -private: - Cached::Relation* rel_perm; - -public: - USHORT rel_current_fmt; // Current format number - const Format* rel_current_format; // Current record format - USHORT rel_dbkey_length; // RDB$DBKEY length - - vec* rel_fields; // vector of field blocks - RseNode* rel_view_rse; // view record select expression - ViewContexts rel_view_contexts; // sorted array of view contexts - - TrigArray rel_triggers; - - Firebird::TriState rel_ss_definer; - - bool hasData() const; - MetaId getId() const noexcept; - RelationPages* getPages(thread_db* tdbb, RelationPages::InstanceId instanceId = MAX_TRA_NUMBER, bool allocPages = true); - bool isSystem() const noexcept; - bool isTemporary() const noexcept; - bool isLTT() const noexcept; - bool isVirtual() const noexcept; - bool isView() const noexcept; - bool isPrivate() const noexcept; - bool isReplicating(thread_db* tdbb); - FB_UINT64 getTempInstanceId(thread_db* tdbb) const; - - ObjectType getObjectType() const noexcept - { - return isView() ? obj_view : obj_relation; - } - - const QualifiedName& getName() const noexcept; - MemoryPool& getPool() const noexcept; - const QualifiedName& getSecurityName() const noexcept; - MetaName getOwnerName() const noexcept; - ExternalFile* getExtFile() const noexcept; - - static void destroy(thread_db* tdbb, jrd_rel *rel); - static jrd_rel* create(thread_db* tdbb, MemoryPool& p, Cached::Relation* perm); - - static const enum lck_t LOCKTYPE = LCK_rel_rescan; - - ScanResult scan(thread_db* tdbb, ObjectBase::Flag& flags); // Scan the newly loaded relation for meta data - static std::optional getIdByName(thread_db* tdbb, ExName<> name); - ScanResult reload(thread_db* tdbb, ObjectBase::Flag& flags) - { - return scan(tdbb, flags); - } - - bool hash(thread_db* tdbb, Firebird::sha512& digest); - - static const char* objectFamily(RelationPermanent* perm); - static ObjectType objectType() noexcept; - - void releaseTriggers(thread_db* tdbb, bool destroy); - const Trigger* findTrigger(const QualifiedName& trig_name) const; - const Format* currentFormat(thread_db* tdbb); - - decltype(rel_perm) getPermanent() const - { - return rel_perm; - } - - Record* getGCRecord(thread_db* tdbb); -}; - -// rel_flags - -inline constexpr ULONG REL_system = 0x0001; -inline constexpr ULONG REL_get_dependencies = 0x0002; // New relation needs dependencies during scan -inline constexpr ULONG REL_sql_relation = 0x0004; // Relation defined as sql table -inline constexpr ULONG REL_check_partners = 0x0008; // Rescan primary dependencies and foreign references -inline constexpr ULONG REL_temp_tran = 0x0010; // relation is a GTT delete rows -inline constexpr ULONG REL_temp_conn = 0x0020; // relation is a GTT preserve rows -inline constexpr ULONG REL_virtual = 0x0040; // relation is virtual -inline constexpr ULONG REL_jrd_view = 0x0080; // relation is VIEW -inline constexpr ULONG REL_temp_gtt = 0x0100; // relation is a GTT -inline constexpr ULONG REL_temp_ltt = 0x0200; // relation is a LTT -inline constexpr ULONG REL_private = 0x0400; // relation is private to its package -inline constexpr ULONG REL_temp_frame = 0x0800; // relation data is scoped to an execution frame - class GCLock { public: - GCLock(RelationPermanent* rl) - : gcRel(rl) - { } + explicit GCLock(RelationPermanent* relation) + : m_relation(relation) + {} // This guard is used by regular code to prevent online validation while // dead- or back- versions is removed from disk. class Shared { public: - Shared(thread_db* tdbb, RelationPermanent* rl); + Shared(thread_db* tdbb, RelationPermanent* relation); ~Shared(); bool gcEnabled() const @@ -731,9 +879,9 @@ class GCLock } private: - thread_db* m_tdbb; - RelationPermanent* m_rl; - bool m_gcEnabled; + thread_db* const m_tdbb; + RelationPermanent* const m_relation; + bool m_gcEnabled; }; // This guard is used by online validation to prevent any modifications of @@ -741,9 +889,9 @@ class GCLock class Exclusive { public: - Exclusive(thread_db* tdbb, RelationPermanent* rl) - : m_tdbb(tdbb), m_rl(rl), m_lock(nullptr) - { } + Exclusive(thread_db* tdbb, RelationPermanent* relation) + : m_tdbb(tdbb), m_relation(relation) + {} ~Exclusive() { @@ -755,9 +903,9 @@ class GCLock void release(); private: - thread_db* m_tdbb; - RelationPermanent* m_rl; - Lock* m_lock; + thread_db* const m_tdbb; + RelationPermanent* const m_relation; + Lock* m_lock = nullptr; }; friend Shared; @@ -772,7 +920,7 @@ class GCLock public: bool checkDisabled() const { - return gcFlags.load(std::memory_order_acquire) & GC_disabled; + return m_flags.load(std::memory_order_acquire) & GC_disabled; } unsigned getSweepCount() const; // violates rules of atomic counters @@ -789,7 +937,8 @@ class GCLock { reinterpret_cast(self)->blockingAst(); } - catch(const Firebird::Exception&) { } + catch(const Firebird::Exception&) + {} // no-op return 0; } @@ -803,18 +952,33 @@ class GCLock void checkGuard(unsigned flags); private: - Firebird::AutoPtr gcLck; - RelationPermanent* gcRel; - std::atomic gcFlags = 0u; - - static const unsigned GC_counterMask = 0x0FFFFFFF; - static const unsigned GC_guardBit = 0x10000000; - static const unsigned GC_disabled = 0x20000000; - static const unsigned GC_locked = 0x40000000; - static const unsigned GC_blocking = 0x80000000; + Firebird::AutoPtr m_lock; + RelationPermanent* const m_relation; + std::atomic m_flags = 0; + + static constexpr unsigned GC_counterMask = 0x0FFFFFFF; + static constexpr unsigned GC_guardBit = 0x10000000; + static constexpr unsigned GC_disabled = 0x20000000; + static constexpr unsigned GC_locked = 0x40000000; + static constexpr unsigned GC_blocking = 0x80000000; }; +// Relation flags + +inline constexpr ULONG REL_system = 0x0001; +inline constexpr ULONG REL_get_dependencies = 0x0002; // New relation needs dependencies during scan +inline constexpr ULONG REL_sql_relation = 0x0004; // Relation defined as sql table +inline constexpr ULONG REL_check_partners = 0x0008; // Rescan primary dependencies and foreign references +inline constexpr ULONG REL_temp_tran = 0x0010; // relation is a GTT delete rows +inline constexpr ULONG REL_temp_conn = 0x0020; // relation is a GTT preserve rows +inline constexpr ULONG REL_virtual = 0x0040; // relation is virtual +inline constexpr ULONG REL_jrd_view = 0x0080; // relation is VIEW +inline constexpr ULONG REL_temp_gtt = 0x0100; // relation is a GTT +inline constexpr ULONG REL_temp_ltt = 0x0200; // relation is a LTT +inline constexpr ULONG REL_private = 0x0400; // relation is private to its package +inline constexpr ULONG REL_temp_frame = 0x0800; // relation data is scoped to an execution frame + // Non-versioned part of relation in cache class RelationPermanent : public Firebird::PermanentStorage @@ -833,10 +997,31 @@ class RelationPermanent : public Firebird::PermanentStorage dropTempPages(tdbb); } - // void makeLocks(thread_db* tdbb, Cached::Relation* relation); // hvlad: not implemented - static constexpr USHORT getRelLockKeyLength() noexcept; + bool useTempPages(thread_db* tdbb) const + { + return (isTemporary() && !(tdbb->tdbb_flags & TDBB_use_db_page_space)); + } + + RelationPages::InstanceId getInstanceId(thread_db* tdbb) + { + if (useTempPages(tdbb)) + { + const auto relPages = getTempPages(tdbb, MAX_TRA_NUMBER, true); + return relPages->getInstanceId(); + } + + return 0; + } + + static constexpr USHORT getRelLockKeyLength() noexcept + { + return sizeof(ULONG) + sizeof(RelationPages::InstanceId); + } + + void getRelLockKey(thread_db* tdbb, UCHAR* key); Lock* createLock(thread_db* tdbb, lck_t, bool); Lock* createLock(thread_db* tdbb, MemoryPool& pool, lck_t, bool); + void extFile(thread_db* tdbb, const TEXT* file_name); // impl in ext.cpp IndexVersion* lookup_index(thread_db* tdbb, MetaId id, ObjectBase::Flag flags); @@ -862,53 +1047,41 @@ class RelationPermanent : public Firebird::PermanentStorage } Firebird::Mutex rel_scan_partners_mutex; - Lock* rel_partners_lock; // partners lock - GCLock rel_gc_lock; // garbage collection lock + Lock* rel_partners_lock = nullptr; // partners lock void releaseLock(thread_db* tdbb); private: - GCRecordList rel_gc_records; // records for garbage collection - Firebird::Mutex rel_gc_records_mutex; + GCRecordList rel_gc_records; // records for garbage collection + Firebird::Mutex rel_gc_records_mutex; public: - std::atomic rel_scan_count; // concurrent sequential scan count - - class RelPagesSnapshot : public Firebird::Array + class PagesSnapshot : public Firebird::Array { + friend class RelationPermanent; + public: typedef Firebird::Array inherited; - RelPagesSnapshot(thread_db* tdbb, RelationPermanent* relation) + PagesSnapshot(thread_db* tdbb, RelationPermanent* relation) { spt_tdbb = tdbb; spt_relation = relation; } - ~RelPagesSnapshot() { clear(); } + ~PagesSnapshot() + { + clear(); + } void clear(); - private: - thread_db* spt_tdbb; - RelationPermanent* spt_relation; - friend class RelationPermanent; + private: + thread_db* spt_tdbb; + RelationPermanent* spt_relation; }; - RelationPages* getPages(thread_db* tdbb, RelationPages::InstanceId instanceId = MAX_TRA_NUMBER, bool allocPages = true); - void fillPages(thread_db* tdbb); - RelationPages* getAttPages(thread_db* tdbb, RelationPages::InstanceId inst_id); - bool delPages(thread_db* tdbb, RelationPages::InstanceId inst_id = MAX_TRA_NUMBER, RelationPages* aPages = NULL); - void freePages(thread_db* tdbb); - void retainPages(thread_db* tdbb, TraNumber oldNumber, TraNumber newNumber); - void cleanUp() noexcept; - void fillPagesSnapshot(RelPagesSnapshot&, const bool AttachmentOnly = false); - void scanPartners(thread_db* tdbb); // Foreign keys scan - impl. in met.epp - - RelationPages* getBasePages() noexcept - { - return &rel_pages_base; - } + void scanPartners(thread_db* tdbb); // Foreign keys scan - impl. in met.epp bool hasData() const { @@ -946,16 +1119,38 @@ class RelationPermanent : public Firebird::PermanentStorage rel_file = f; } - void getRelLockKey(thread_db* tdbb, UCHAR* key); - PageNumber getIndexRootPage(thread_db* tdbb); Record* getGCRecord(thread_db* tdbb, const Format* const format); - bool isSystem() const noexcept; - bool isTemporary() const noexcept; - bool isLTT() const noexcept; - bool isVirtual() const noexcept; - bool isView() const noexcept; - bool isPrivate() const noexcept; + bool isSystem() const noexcept + { + return rel_flags & REL_system; + } + + bool isTemporary() const noexcept + { + return (rel_flags & (REL_temp_tran | REL_temp_conn | REL_temp_frame)); + } + + bool isVirtual() const noexcept + { + return (rel_flags & REL_virtual); + } + + bool isView() const noexcept + { + return (rel_flags & REL_jrd_view); + } + + bool isPrivate() const noexcept + { + return (rel_flags & REL_private); + } + + bool isLTT() const noexcept + { + return (rel_flags & REL_temp_ltt); + } + bool isReplicating(thread_db* tdbb); static int partners_ast_relation(void* ast_object); @@ -963,6 +1158,22 @@ class RelationPermanent : public Firebird::PermanentStorage // Relation must be updated on next use or commit static Cached::Relation* newVersion(thread_db* tdbb, const QualifiedName& name); + // Page management + RelationPages* getPages(ULONG pageSpaceId); + RelationPages* getAttPages(thread_db* tdbb, RelationPages::InstanceId instanceId); + RelationPages* getTempPages(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages); + bool deletePages(thread_db* tdbb, RelationPages* pages); + + bool deletePages(thread_db* tdbb, RelationPages::InstanceId inst_id = MAX_TRA_NUMBER) + { + return deletePages(tdbb, getTempPages(tdbb, inst_id, false)); + } + + void freePages(thread_db* tdbb); + void retainPages(thread_db* tdbb, TraNumber oldNumber, TraNumber newNumber); + + bool fillPagesSnapshot(PagesSnapshot&, const bool attachmentOnly = false); + // Relation is in process of remove - mark it with current transaction void dropTempPages(thread_db* tdbb); void clearDropMarker(thread_db* tdbb); @@ -990,22 +1201,28 @@ class RelationPermanent : public Firebird::PermanentStorage void addFormat(Format* fmt); const Format* getFormat(thread_db* tdbb, USHORT ); - Indices rel_indices; // Active indices - QualifiedName rel_name; // ascii relation name - MetaId rel_id; + Indices rel_indices; // Active indices + QualifiedName rel_name; // ascii relation name + MetaId rel_id; + + MetaName rel_owner_name; // ascii owner + QualifiedName rel_security_name; // security class name for relation + std::atomic rel_flags; // flags - MetaName rel_owner_name; // ascii owner - QualifiedName rel_security_name; // security class name for relation - std::atomic rel_flags; // flags + std::atomic rel_scan_count; // concurrent sequential scan count + GCLock rel_gc_lock; // garbage collection lock enum class Bool3State {Unknown, False, True}; - std::atomic rel_repl_state; // replication state + std::atomic rel_repl_state; // replication state - PrimaryDeps* rel_primary_dpnds = nullptr; // foreign dependencies on this relation's primary key - ForeignRefs* rel_foreign_refs = nullptr; // foreign references to other relations' primary keys + PrimaryDeps* rel_primary_dpnds = nullptr; // foreign dependencies on this relation's primary key + ForeignRefs* rel_foreign_refs = nullptr; // foreign references to other relations' primary keys private: - Firebird::Mutex rel_pages_mutex; // protects rel_pages_inst and rel_pages_free + ExternalFile* rel_file = nullptr; + Firebird::Array rel_clear_deps; + + Firebird::Mutex rel_pages_mutex; typedef Firebird::SortedArray< RelationPages*, @@ -1014,15 +1231,10 @@ class RelationPermanent : public Firebird::PermanentStorage RelationPages> RelationPagesInstances; - RelationPagesInstances* rel_pages_inst; - RelationPages rel_pages_base; - RelationPages* rel_pages_free; - - RelationPages* getPagesInternal(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages); - - ExternalFile* rel_file; + RelationPagesInstances* rel_pages_inst = nullptr; + RelationPages* rel_pages_free = nullptr; - Firebird::Array rel_clear_deps; + Firebird::Array rel_pagespaces; }; @@ -1043,137 +1255,204 @@ inline FB_UINT64 CacheElement::makeId(MetaId } -inline bool jrd_rel::hasData() const -{ - return rel_perm->rel_name.hasData(); -} +// Relation block; one is created for each relation referenced +// in the database, though it is not really filled out until +// the relation is scanned -inline const QualifiedName& jrd_rel::getName() const noexcept +class jrd_rel final : public ObjectBase { - return rel_perm->getName(); -} + jrd_rel(const jrd_rel&) = delete; + jrd_rel(const jrd_rel&&) = delete; -inline MemoryPool& jrd_rel::getPool() const noexcept -{ - return rel_perm->getPool(); -} +public: + jrd_rel(MemoryPool& p, Cached::Relation* r); -inline ExternalFile* jrd_rel::getExtFile() const noexcept -{ - return rel_perm->getExtFile(); -} + MemoryPool* rel_pool; -inline const QualifiedName& jrd_rel::getSecurityName() const noexcept -{ - return rel_perm->getSecurityName(); -} +private: + Cached::Relation* rel_perm; -inline MetaName jrd_rel::getOwnerName() const noexcept -{ - return rel_perm->getOwnerName(); -} +public: + USHORT rel_current_fmt = 0; // Current format number + const Format* rel_current_format = nullptr; // Current record format + USHORT rel_dbkey_length = 0; // RDB$DBKEY length -inline MetaId jrd_rel::getId() const noexcept -{ - return rel_perm->getId(); -} + vec* rel_fields = nullptr; // vector of field blocks + RseNode* rel_view_rse = nullptr; // view record select expression + ViewContexts rel_view_contexts; // sorted array of view contexts -inline RelationPages* jrd_rel::getPages(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages) -{ - return rel_perm->getPages(tdbb, instanceId, allocPages); -} + TrigArray rel_triggers; -inline bool jrd_rel::isTemporary() const noexcept -{ - return rel_perm->isTemporary(); -} + Firebird::TriState rel_ss_definer; -inline bool jrd_rel::isLTT() const noexcept -{ - return rel_perm->isLTT(); -} + bool hasData() const + { + return rel_perm->rel_name.hasData(); + } -inline bool jrd_rel::isVirtual() const noexcept -{ - return rel_perm->isVirtual(); -} + MetaId getId() const noexcept + { + return rel_perm->getId(); + } -inline bool jrd_rel::isView() const noexcept -{ - return rel_perm->isView(); -} + const QualifiedName& getName() const noexcept + { + return rel_perm->getName(); + } -inline bool jrd_rel::isPrivate() const noexcept -{ - return rel_perm->isPrivate(); -} + MemoryPool& getPool() const noexcept + { + return rel_perm->getPool(); + } -inline bool jrd_rel::isSystem() const noexcept -{ - return rel_perm->isSystem(); -} + ExternalFile* getExtFile() const noexcept + { + return rel_perm->getExtFile(); + } -inline bool jrd_rel::isReplicating(thread_db* tdbb) -{ - return rel_perm->isReplicating(tdbb); -} + const QualifiedName& getSecurityName() const noexcept + { + return rel_perm->getSecurityName(); + } -inline Record* jrd_rel::getGCRecord(thread_db* tdbb) -{ - return rel_perm->getGCRecord(tdbb, currentFormat(tdbb)); -} + MetaName getOwnerName() const noexcept + { + return rel_perm->getOwnerName(); + } + bool isTemporary() const noexcept + { + return rel_perm->isTemporary(); + } -inline bool RelationPermanent::isSystem() const noexcept -{ - return rel_flags & REL_system; -} + bool isLTT() const noexcept + { + return rel_perm->isLTT(); + } -inline bool RelationPermanent::isTemporary() const noexcept -{ - return (rel_flags & (REL_temp_tran | REL_temp_conn | REL_temp_frame)); -} + bool isVirtual() const noexcept + { + return rel_perm->isVirtual(); + } -inline bool RelationPermanent::isVirtual() const noexcept -{ - return (rel_flags & REL_virtual); -} + bool isView() const noexcept + { + return rel_perm->isView(); + } -inline bool RelationPermanent::isView() const noexcept -{ - return (rel_flags & REL_jrd_view); -} + bool isPrivate() const noexcept + { + return rel_perm->isPrivate(); + } -inline bool RelationPermanent::isPrivate() const noexcept -{ - return (rel_flags & REL_private); -} + bool isSystem() const noexcept + { + return rel_perm->isSystem(); + } -inline bool RelationPermanent::isLTT() const noexcept -{ - return (rel_flags & REL_temp_ltt); -} + bool isReplicating(thread_db* tdbb) + { + return rel_perm->isReplicating(tdbb); + } -inline RelationPages* RelationPermanent::getPages(thread_db* tdbb, RelationPages::InstanceId instanceId, bool allocPages) -{ - return !isTemporary() ? - &rel_pages_base : - getPagesInternal(tdbb, instanceId, allocPages); -} + bool checkFlags(ULONG mask) + { + return (rel_perm->rel_flags & mask); + } + + Record* getGCRecord(thread_db* tdbb) + { + return rel_perm->getGCRecord(tdbb, currentFormat(tdbb)); + } + + FB_UINT64 getTempInstanceId(thread_db* tdbb) const; + + ObjectType getObjectType() const noexcept + { + return isView() ? obj_view : obj_relation; + } + + static void destroy(thread_db* tdbb, jrd_rel *rel); + static jrd_rel* create(thread_db* tdbb, MemoryPool& p, Cached::Relation* perm); + + static const enum lck_t LOCKTYPE = LCK_rel_rescan; + + ScanResult scan(thread_db* tdbb, ObjectBase::Flag& flags); // Scan the newly loaded relation for meta data + static std::optional getIdByName(thread_db* tdbb, ExName<> name); + ScanResult reload(thread_db* tdbb, ObjectBase::Flag& flags) + { + return scan(tdbb, flags); + } + + RelationPages* getPages(thread_db* tdbb, RelationPages::InstanceId instanceId = MAX_TRA_NUMBER, bool allocPages = true); + + RelationPages* getBasePages() noexcept + { + if (!m_basePages || m_basePages->getPageSpaceId() != m_pageSpaceId) + m_basePages = rel_perm->getPages(m_pageSpaceId); + + fb_assert(m_basePages); + return m_basePages; + } + + void deletePages(thread_db* tdbb); + + void setPages(const jrd_rel* relation) + { + fb_assert(relation && relation->m_basePages); + setPageSpaceId(relation->m_basePages->getPageSpaceId()); + m_basePages->assign(*relation->m_basePages); + } + + ULONG getPageSpaceId() const noexcept + { + return m_pageSpaceId; + } + + void setPageSpaceId(ULONG pageSpaceId) + { + m_pageSpaceId = pageSpaceId; + m_basePages = rel_perm->getPages(pageSpaceId); + } + + std::optional getIndexRootPage(thread_db* tdbb) + { + const auto relPages = getPages(tdbb); + return relPages->getIndexRootPage(); + } + + bool hash(thread_db* tdbb, Firebird::sha512& digest); + + static const char* objectFamily(RelationPermanent* perm); + static ObjectType objectType() noexcept; + + void releaseTriggers(thread_db* tdbb, bool destroy); + const Trigger* findTrigger(const QualifiedName& trig_name) const; + const Format* currentFormat(thread_db* tdbb); + + decltype(rel_perm) getPermanent() const + { + return rel_perm; + } + +private: + ULONG m_pageSpaceId = DB_PAGE_SPACE; // Current page space + RelationPages* m_basePages = nullptr; +}; /// class GCLock::Shared -inline GCLock::Shared::Shared(thread_db* tdbb, RelationPermanent* rl) +inline GCLock::Shared::Shared(thread_db* tdbb, RelationPermanent* relation) : m_tdbb(tdbb), - m_rl(rl), - m_gcEnabled(m_rl->rel_gc_lock.acquire(m_tdbb, LCK_NO_WAIT)) + m_relation(relation), + m_gcEnabled(m_relation->rel_gc_lock.acquire(m_tdbb, LCK_NO_WAIT)) { } inline GCLock::Shared::~Shared() { if (m_gcEnabled) - m_rl->rel_gc_lock.downgrade(m_tdbb); + m_relation->rel_gc_lock.downgrade(m_tdbb); } @@ -1181,12 +1460,12 @@ inline GCLock::Shared::~Shared() inline bool GCLock::Exclusive::acquire(int wait) { - return m_rl->rel_gc_lock.disable(m_tdbb, wait, m_lock); + return m_relation->rel_gc_lock.disable(m_tdbb, wait, m_lock); } inline void GCLock::Exclusive::release() { - return m_rl->rel_gc_lock.enable(m_tdbb, m_lock); + return m_relation->rel_gc_lock.enable(m_tdbb, m_lock); } diff --git a/src/jrd/SharedReadVector.h b/src/jrd/SharedReadVector.h index a65acc50d0c..cba33693bef 100644 --- a/src/jrd/SharedReadVector.h +++ b/src/jrd/SharedReadVector.h @@ -103,6 +103,15 @@ class SharedReadVector return count + needs <= capacity; } + bool add(const T& value) + { + if (!hasSpace()) + return false; + data[count] = value; + ++count; // don't update the count before the assignment due to possible races with concurrent readers + return true; + } + bool add(const Generation* from) { if (!hasSpace(from->count)) diff --git a/src/jrd/Statement.cpp b/src/jrd/Statement.cpp index ee656276bdb..74c7f0b9ce4 100644 --- a/src/jrd/Statement.cpp +++ b/src/jrd/Statement.cpp @@ -36,6 +36,7 @@ #include "../jrd/met_proto.h" #include "../jrd/scl_proto.h" #include "../jrd/Collation.h" +#include "../jrd/Tablespace.h" #include "../jrd/met.h" #include "../jrd/recsrc/Cursor.h" #include "../common/classes/auto.h" @@ -79,6 +80,7 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) blr(*p), mapFieldInfo(*p), resources(nullptr), + tablespaces(*p), messages(*p, 2) // Most statements have two messages, preallocate space for them { if (csb->csb_resources) @@ -166,6 +168,7 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) // versioned metadata support if (csb->csb_g_flags & csb_internal) flags |= FLAG_INTERNAL; + loadResources(tdbb, nullptr, false); messages.grow(csb->csb_rpt.getCount()); @@ -764,7 +767,7 @@ void Statement::release(thread_db* tdbb) { SET_TDBB(tdbb); - // Release sub statements. + // Release sub statements for (Statement** subStatement = subStatements.begin(); subStatement != subStatements.end(); ++subStatement) diff --git a/src/jrd/Statement.h b/src/jrd/Statement.h index 98ac296b517..c74c344a6f1 100644 --- a/src/jrd/Statement.h +++ b/src/jrd/Statement.h @@ -155,6 +155,7 @@ class Statement : public pool_alloc private: Resources* resources; // Resources (relations, routines, etc.) + Firebird::Array tablespaces; // Tablespaces used by the statement Firebird::RefPtr latestVer; Firebird::Mutex lvMutex; // Protects upgrade of latestVer Firebird::Array messages; // Input/output messages diff --git a/src/jrd/SystemTriggers.epp b/src/jrd/SystemTriggers.epp index fbcbb50c6c4..088fb208d58 100644 --- a/src/jrd/SystemTriggers.epp +++ b/src/jrd/SystemTriggers.epp @@ -1569,29 +1569,29 @@ void beforeInsertRelation(thread_db* tdbb, Record* record) // care about a kind of appropriate locking to be taken by GBAK. dsc desc; - bool idSet = EVL_field(nullptr, record, f_rel_id, &desc); + const bool idSet = EVL_field(nullptr, record, f_rel_id, &desc); fb_assert(!idSet); if (idSet) // How can it be? return; dsc desc2; - bool nameSet = EVL_field(nullptr, record, f_rel_name, &desc2); + const bool nameSet = EVL_field(nullptr, record, f_rel_name, &desc2); if (!nameSet) return; MetaName name; MOV_get_metaname(tdbb, &desc2, name); // Use datatype that satisfies dsc::makeShort() - short rel_id = RelationNode::generateRelId(tdbb, name); + short relId = RelationNode::generateRelId(tdbb, name); - desc2.makeShort(0, &rel_id); + desc2.makeShort(0, &relId); MOV_move(tdbb, &desc2, &desc); record->clearNull(f_rel_id); - auto* rel = MetadataCache::newVersion(tdbb, rel_id); - fb_assert(rel); - if (rel) - rel->rel_flags |= REL_get_dependencies; + auto* relation = MetadataCache::newVersion(tdbb, relId); + fb_assert(relation); + if (relation) + relation->rel_flags |= REL_get_dependencies; } void afterInsertRelation(thread_db* tdbb, Record* record) @@ -1599,34 +1599,26 @@ void afterInsertRelation(thread_db* tdbb, Record* record) if (tdbb->getAttachment()->isRWGbak()) { dsc desc; - bool idSet = EVL_field(nullptr, record, f_rel_id, &desc); + const bool idSet = EVL_field(nullptr, record, f_rel_id, &desc); fb_assert(idSet); if (!idSet) // How can it be? return; - MetaId id = MetaId(MOV_get_long(tdbb, &desc, 0)); - fb_assert(id > 127); + const MetaId relId(MOV_get_long(tdbb, &desc, 0)); + fb_assert(relId >= MIN_RELATION_ID); // this is user relation - // Here minor optimization is possible - do it in single cache call - MetadataCache::newVersion(tdbb, id); - auto* relation = MetadataCache::getPerm(tdbb, id, CacheFlag::MINISCAN); - - // Do not access external file info from cache - it may be not usable yet - // On contrary rel_flags (and therefore check for view) are OK + const auto relation = MetadataCache::getVersioned(tdbb, relId, + CacheFlag::NOCOMMIT | CacheFlag::MINISCAN); fb_assert(relation); - if (relation) - { - if (!EVL_field(nullptr, record, f_rel_ext_file, &desc) && - !relation->isView()) - { - // create the table - DPM_create_relation(tdbb, relation); - } - relation->rel_flags |= REL_get_dependencies; + // Do not access external file info from cache - it may be not usable yet. + // On contrary rel_flags (and therefore check for view) are OK. + if (relation && !relation->isView() && !EVL_field(nullptr, record, f_rel_ext_file, &desc)) + { + // create the table + DPM_create_relation(tdbb, relation); } } - } template diff --git a/src/jrd/Tablespace.cpp b/src/jrd/Tablespace.cpp new file mode 100644 index 00000000000..0bbbe1bcb80 --- /dev/null +++ b/src/jrd/Tablespace.cpp @@ -0,0 +1,520 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Roman Simakov + * for the RedDatabase project. + * + * Copyright (c) 2018 + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/jrd.h" +#include "../jrd/lck.h" +#include "../jrd/tra.h" +#include "../jrd/cch_proto.h" +#include "../jrd/met_proto.h" + +#include "../jrd/Tablespace.h" + +using namespace Firebird; +using namespace Jrd; + + +// +// Tablespace class +// + +Tablespace::~Tablespace() +{ + fb_assert(m_useCount == 0); +} + +void Tablespace::lock(thread_db* tdbb, ULONG pageSpaceId) +{ + fb_assert(pageSpaceId != INVALID_PAGE_SPACE); + + if (PageSpace::isTablespace(pageSpaceId)) + { + // Protect the tablespace from being modified or dropped until the current transaction ends + if (const auto transaction = tdbb->getTransaction()) + transaction->lockTablespace(tdbb, pageSpaceId); + // if transaction is unknown, at least ensure that the tablespace is initialized and usable + else + Tablespace::lookup(tdbb, pageSpaceId); + } +} + +Tablespace* Tablespace::create(thread_db* tdbb, ULONG id) +{ + // Check that pageSpaceId has reasonable value + fb_assert(PageSpace::isTablespace(id)); + + return Tablespace::init(tdbb, id, "", true); +} + +Tablespace* Tablespace::lookup(thread_db* tdbb, ULONG id, bool open) +{ + const auto dbb = tdbb->getDatabase(); + auto tableSpace = dbb->dbb_tablespaces.get(id); + + if (tableSpace) + { + if (!tableSpace->isVisible(tdbb)) + { + (Arg::Gds(isc_dyn_ts_not_found) << tableSpace->getName()).raise(); + } + + if (!tableSpace->isReady()) + tableSpace->rescan(tdbb, open); + + return tableSpace; + } + + // Check that pageSpaceId has reasonable value + fb_assert(PageSpace::isTablespace(id)); + + return Tablespace::init(tdbb, id, "", open ? std::optional(false) : std::nullopt); +} + +Tablespace* Tablespace::lookup(thread_db* tdbb, const MetaName& name) +{ + const auto dbb = tdbb->getDatabase(); + + // Tablespace name can be empty for view, gtt, virtual tables + if (name.isEmpty() || name == PRIMARY_TABLESPACE_NAME) + return dbb->dbb_tablespaces.get(DB_PAGE_SPACE); + + auto tableSpace = dbb->dbb_tablespaces.get(name); + + if (tableSpace) + { + if (!tableSpace->isVisible(tdbb)) + { + (Arg::Gds(isc_dyn_ts_not_found) << name).raise(); + } + + if (!tableSpace->isReady()) + tableSpace->rescan(tdbb, false); + + return tableSpace; + } + + return Tablespace::init(tdbb, 0, name); +} + +void Tablespace::mark(thread_db* tdbb, Operation operation, jrd_tra* transaction, const PathName& fileName) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + fb_assert(m_lock && m_lock->lck_logical >= LCK_SR); + + // Get the EX lock to prevent other attachments from using this tablespace + if (isUsed() || !LCK_convert(tdbb, m_lock, LCK_EX, transaction->getLockWait())) + { + string name; + name.printf("TABLESPACE \"%s\"", m_name.c_str()); + + (Arg::Gds(isc_obj_in_use) << Arg::Str(name)).raise(); + } + + fb_assert(!m_transaction || m_transaction == transaction); + fb_assert(m_useCount == 0); + + m_transaction = transaction; + + if (operation == Operation::CREATE) + { + m_flags |= CREATED; + } + else if (operation == Operation::ALTER) + { + m_flags |= MODIFIED; + + CCH_flush(tdbb, FLUSH_ALL, 0, m_id); + + const auto dbb = tdbb->getDatabase(); + dbb->dbb_page_manager.deletePageSpace(m_id); + dbb->dbb_page_manager.allocatePageSpace(tdbb, m_id, false, fileName); + } + else if (operation == Operation::DROP) + { + m_flags |= DELETED; + } +} + +void Tablespace::commit(thread_db* tdbb, jrd_tra* transaction) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (m_transaction != transaction) + return; + + fb_assert(m_flags & (CREATED | MODIFIED | DELETED)); + fb_assert(m_lock && m_lock->lck_logical == LCK_EX); + + if (m_flags & DELETED) + { + m_flags |= CLEANUP | (isUsed() ? BLOCKING : 0); + guard.release(); + + CCH_flush(tdbb, FLUSH_ALL, 0, m_id); + + const auto dbb = tdbb->getDatabase(); + dbb->dbb_page_manager.deletePageSpace(m_id); + dbb->dbb_tablespaces.remove(m_id); + + if (!isUsed()) + release(tdbb, true); + + return; + } + + LCK_release(tdbb, m_lock); + m_flags = OBSOLETE; + m_transaction = nullptr; +} + +void Tablespace::rollback(thread_db* tdbb, jrd_tra* transaction) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (!m_transaction || m_transaction != transaction) + return; + + fb_assert(m_flags & (CREATED | MODIFIED | DELETED)); + fb_assert(m_lock && m_lock->lck_logical == LCK_EX); + fb_assert(m_useCount == 0); + + if (m_flags & CREATED) + { + m_flags |= CLEANUP | (isUsed() ? BLOCKING : 0); + guard.release(); + + CCH_flush(tdbb, FLUSH_ALL, 0, m_id); + + const auto dbb = tdbb->getDatabase(); + dbb->dbb_page_manager.deletePageSpace(m_id, true); + dbb->dbb_tablespaces.remove(m_id); + + if (!isUsed()) + release(tdbb, true); + + return; + } + + if (m_flags & MODIFIED) + { + CCH_flush(tdbb, FLUSH_ALL, 0, m_id); + + const auto dbb = tdbb->getDatabase(); + dbb->dbb_page_manager.deletePageSpace(m_id); + } + + LCK_release(tdbb, m_lock); + m_flags = OBSOLETE; + m_transaction = nullptr; +} + +void Tablespace::allocate(thread_db* tdbb, bool create) +{ + fb_assert(!(m_flags & (OBSOLETE | ALLOCATED))); + + try + { + const auto dbb = tdbb->getDatabase(); + dbb->dbb_page_manager.allocatePageSpace(tdbb, m_id, create, m_fileName); + m_flags |= ALLOCATED; + } + catch (const Exception&) + { + LCK_release(tdbb, m_lock); + m_flags |= OBSOLETE; + throw; + } +} + +inline bool Tablespace::isVisible(thread_db* tdbb) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (!m_transaction) + return true; + + const auto transaction = tdbb->getTransaction(); + + if (m_flags & DELETED) + return (m_transaction != transaction); + + if (m_flags & CREATED) + return (m_transaction == transaction); + + fb_assert(m_flags & MODIFIED); + return true; +} + +void Tablespace::astHandler() +{ + const auto dbb = m_lock->lck_dbb; + + AsyncContextHolder tdbb(dbb, FB_FUNCTION, m_lock); + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (isUsed()) + { + m_flags |= BLOCKING; + } + else + { + LCK_release(tdbb, m_lock); + m_flags &= ~BLOCKING; + m_flags |= OBSOLETE; + } +} + +void Tablespace::rescan(thread_db* tdbb, bool open) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (isReady()) + return; + + const auto dbb = tdbb->getDatabase(); + + fb_assert(m_lock); + + if (m_flags & OBSOLETE) + { + // Get the SR lock to let other attachments know that we use the tablespace + + if (!LCK_lock(tdbb, m_lock, LCK_SR, LCK_WAIT)) + status_exception::raise(tdbb->tdbb_status_vector); + + MetaName tsName; + if (!MET_tablespace(tdbb, m_id, tsName, m_fileName)) + { + (Arg::Gds(isc_dyn_ts_not_found) << m_name).raise(); + } + + fb_assert(tsName == m_name); + + m_flags &= ~OBSOLETE; + } + + if (open && !(m_flags & ALLOCATED)) + allocate(tdbb, false); +} + +Tablespace* Tablespace::init(thread_db* tdbb, ULONG id, const MetaName& name, + std::optional alloc) +{ + const auto dbb = tdbb->getDatabase(); + + MetaName tsName = name; + PathName fileName; + + if (!id) + { + if (!(id = MET_tablespace_id(tdbb, name, fileName))) + { + (Arg::Gds(isc_dyn_ts_not_found) << name).raise(); + } + } + + // Serialize creation of the new tablespace + Cache::LockGuard guard(dbb->dbb_tablespaces, FB_FUNCTION); + + // Double check it wasn't created concurrently in the meantime + if (const auto tableSpace = dbb->dbb_tablespaces.get(id)) + return tableSpace; + + AutoPtr tableSpace(FB_NEW_POOL(*dbb->dbb_permanent) Tablespace(*dbb->dbb_permanent, id)); + + AutoPtr lock(FB_NEW_RPT(*dbb->dbb_permanent, 0) + Lock(tdbb, sizeof(ULONG), LCK_tablespace, tableSpace, blockingAst)); + lock->setKey(id); + + // Get the SR lock to let other attachments know that we use the tablespace + + if (!LCK_lock(tdbb, lock, LCK_SR, LCK_WAIT)) + status_exception::raise(tdbb->tdbb_status_vector); + + if (name.isEmpty() && !MET_tablespace(tdbb, id, tsName, fileName)) + { + LCK_release(tdbb, lock); + + string errName; + errName.printf("TABLESPACE (id=%)" ULONGFORMAT, id); + + (Arg::Gds(isc_dyn_ts_not_found) << errName).raise(); + } + + fb_assert(PageSpace::isTablespace(id) && tsName.hasData() && fileName.hasData()); + fb_assert(name.isEmpty() || tsName == name); + + // Reinitialize tablespace with proper metadata + + tableSpace->init(tsName, fileName, lock.release()); + + // Allocate the pagespace, if requested + + if (alloc) + tableSpace->allocate(tdbb, alloc.value()); + + // Cache the tablespace + dbb->dbb_tablespaces.store(tableSpace); + + return tableSpace.release(); +} + +void Tablespace::addRef(thread_db* tdbb) +{ + if (++m_useCount == 1) + { + if (!isReady()) + rescan(tdbb, true); + } + + fb_assert(!(m_flags & OBSOLETE)); +} + +void Tablespace::release(thread_db* tdbb, bool force) +{ + AutoPtr cleanup; + + if (force) + { + fb_assert(m_useCount == 0); + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (m_lock) + LCK_release(tdbb, m_lock); + + m_useCount = 0; + m_flags = 0; + m_transaction = nullptr; + + cleanup = this; + } + else + { + fb_assert(m_useCount != 0); + + if (--m_useCount == 0) + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (m_flags & BLOCKING) + { + LCK_release(tdbb, m_lock); + m_flags &= ~BLOCKING; + m_flags |= OBSOLETE; + } + + if (m_flags & CLEANUP) + cleanup = this; + } + } +} + + +// +// Tablespace::Cache class +// + +Tablespace* Tablespace::Cache::get(ULONG id) +{ + const auto accessor = m_tablespaces.readAccessor(); + + // Tablespace IDs start from 1 + if (id <= accessor->getCount()) + { + if (const auto tableSpace = accessor->value(id - 1)) + { + fb_assert(tableSpace->getId() == id); + return tableSpace; + } + } + + return nullptr; +} + +Tablespace* Tablespace::Cache::get(const MetaName& name) +{ + const auto accessor = m_tablespaces.readAccessor(); + + for (const auto tableSpace : *(accessor.getPointer())) + { + if (tableSpace && tableSpace->getName() == name) + return tableSpace; + } + + return nullptr; +} + +void Tablespace::Cache::store(Tablespace* tableSpace) +{ + const auto id = tableSpace->getId(); + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + m_tablespaces.grow(id, true); + + const auto accessor = m_tablespaces.writeAccessor(); + + auto& currentValue = accessor->value(id - 1); + fb_assert(!currentValue); + currentValue = tableSpace; +} + +void Tablespace::Cache::remove(ULONG id) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + const auto accessor = m_tablespaces.writeAccessor(); + + if (id <= accessor->getCount()) + { + auto& tableSpace = accessor->value(id - 1); + tableSpace = nullptr; + } +} + +void Tablespace::Cache::release(thread_db* tdbb) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + const auto accessor = m_tablespaces.writeAccessor(); + + for (auto& tableSpace : *accessor) + { + if (tableSpace) + { + tableSpace->release(tdbb, true); + tableSpace = nullptr; + } + } +} + +void Tablespace::Cache::rollback(thread_db* tdbb, jrd_tra* transaction) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + const auto accessor = m_tablespaces.writeAccessor(); + + for (const auto tableSpace : *accessor) + { + if (tableSpace) + tableSpace->rollback(tdbb, transaction); + } +} diff --git a/src/jrd/Tablespace.h b/src/jrd/Tablespace.h new file mode 100644 index 00000000000..3c87ffb4873 --- /dev/null +++ b/src/jrd/Tablespace.h @@ -0,0 +1,248 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Roman Simakov + * for the RedDatabase project. + * + * Copyright (c) 2018 + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_TABLESPACE_H +#define JRD_TABLESPACE_H + +#include "../common/classes/alloc.h" +#include "../jrd/MetaName.h" +#include "../jrd/pag.h" +#include "../jrd/SharedReadVector.h" + +namespace Jrd +{ + class thread_db; + class jrd_tra; + class Lock; + + class Tablespace + { + static constexpr unsigned CREATED = 0x0001U; // tablespace is created but not yet committed + static constexpr unsigned MODIFIED = 0x0002U; // tablespace is altered in the given transaction + static constexpr unsigned DELETED = 0x0004U; // tablespace is dropped in the given transaction + static constexpr unsigned BLOCKING = 0x0008U; // tablespace is being locked concurrently but is currently used + static constexpr unsigned OBSOLETE = 0x0010U; // cached metadata is outdated and must be actualized + static constexpr unsigned ALLOCATED = 0x0020U; // tablespace pages are allocated + static constexpr unsigned CLEANUP = 0x0040U; // tablespace must be deleted as soon as no longer used + + public: + enum class Operation { CREATE, ALTER, DROP }; + + static void lock(thread_db* tdbb, ULONG pageSpaceId); + + class UsageList + { + public: + explicit UsageList(MemoryPool& pool) + : m_tablespaces(pool, 1) // preallocate slot for DB_PAGE_SPACE + {} + + ~UsageList() + { + fb_assert(m_tablespaces.isEmpty()); + } + + void add(thread_db* tdbb, ULONG pageSpaceId) + { + fb_assert(PageSpace::isTablespace(pageSpaceId)); + + if (!test(pageSpaceId)) + { + const auto tableSpace = Tablespace::lookup(tdbb, pageSpaceId); + tableSpace->addRef(tdbb); + m_tablespaces.add(tableSpace); + set(pageSpaceId); + } + } + + void releaseAll(thread_db* tdbb) + { + for (const auto tableSpace : m_tablespaces) + tableSpace->release(tdbb); + + m_tablespaces.clear(); + memset(m_bits, 0, sizeof(m_bits)); + } + + private: + static const auto TABLESPACE_CAPACITY = ROUNDUP_LONG(MAX_TABLESPACE_ID); + static_assert(TABLESPACE_CAPACITY % BITS_PER_LONG == 0); + ULONG m_bits[TABLESPACE_CAPACITY / BITS_PER_LONG]; + Firebird::Array m_tablespaces; + + inline void set(ULONG id) + { + m_bits[id / BITS_PER_LONG] |= (1UL << (id % BITS_PER_LONG)); + } + + inline bool test(ULONG id) + { + return (m_bits[id / BITS_PER_LONG] & (1UL << (id % BITS_PER_LONG))) != 0; + } + }; + + class Cache + { + public: + explicit Cache(MemoryPool& pool) + { + // predefined DB_PAGE_SPACE + if (!m_tablespaces.writeAccessor()->add(FB_NEW_POOL(pool) Tablespace(pool, DB_PAGE_SPACE))) + fb_assert(false); + } + + ~Cache() = default; + + Cache(const Cache&) = delete; + Cache& operator=(const Cache&) = delete; + + // Retrieve tablespace from the cache by either its ID or its name + Tablespace* get(ULONG id); + Tablespace* get(const MetaName& name); + + // Add tablespace to the shared cache + void store(Tablespace* tableSpace); + // Remove tablespace from the shared cache + void remove(ULONG id); + // Release all cached tablespaces and remove them from the cache + void release(thread_db* tdbb); + // Broadcast the Tablespace::rollback() call through all the cached tablespaces + void rollback(thread_db* tdbb, jrd_tra* transaction); + + class LockGuard : public Firebird::MutexLockGuard + { + public: + LockGuard(Cache& cache, const char* reason) + : Firebird::MutexLockGuard(cache.m_mutex, reason) + {} + }; + + private: + SharedReadVector m_tablespaces; + Firebird::Mutex m_mutex; + }; + + Tablespace(MemoryPool& pool, ULONG id) + : m_id(id), m_name(pool), m_fileName(pool) + {} + + ~Tablespace(); + + Tablespace(const Tablespace&) = delete; + Tablespace& operator=(const Tablespace&) = delete; + + ULONG getId() const + { + return m_id; + } + + const MetaName& getName() const + { + static const MetaName primaryName = PRIMARY_TABLESPACE_NAME; + return m_name.hasData() ? m_name : primaryName; + } + + static Tablespace* create(thread_db* tdbb, ULONG id); + + static Tablespace* lookup(thread_db* tdbb, ULONG id, bool open = true); + static Tablespace* lookup(thread_db* tdbb, const MetaName& name); + + static ULONG lookupId(thread_db* tdbb, const MetaName& name) + { + if (const auto tableSpace = lookup(tdbb, name)) + return tableSpace->getId(); + + fb_assert(false); + return DB_PAGE_SPACE; + } + + void mark(thread_db* tdbb, Operation operation, jrd_tra* transaction, const Firebird::PathName& fileName); + void commit(thread_db* tdbb, jrd_tra* transaction); + void rollback(thread_db* tdbb, jrd_tra* transaction); + + void addRef(thread_db* tdbb); + void release(thread_db* tdbb, bool force = false); + + bool isUsed() const + { + return (m_useCount != 0); + } + + private: + const ULONG m_id; // tablespace ID (aka pagespace ID) + MetaName m_name; // tablespace name + Firebird::PathName m_fileName; // file name + Firebird::AutoPtr m_lock; // existence lock + std::atomic m_useCount = 0; + std::atomic m_flags = 0; + jrd_tra* m_transaction = nullptr; + Firebird::Mutex m_mutex; + + void init(const MetaName& name, const Firebird::PathName& fileName, Lock* lock) + { + fb_assert(m_name.isEmpty() && m_fileName.isEmpty() && !m_lock); + + m_name = name; + m_fileName = fileName; + m_lock = lock; + + m_flags &= ~ALLOCATED; + } + + void allocate(thread_db* tdbb, bool create); + + inline bool isVisible(thread_db* tdbb); + + inline bool isReady() const + { + return (m_flags & ALLOCATED) && !(m_flags & OBSOLETE); + } + + void setLock(Lock* lock) + { + fb_assert(!m_lock); + m_lock = lock; + } + + void astHandler(); + void rescan(thread_db* tdbb, bool open); + + static Tablespace* init(thread_db* tdbb, ULONG id, const MetaName& name, + std::optional alloc = std::nullopt); + + static int blockingAst(void* arg) + { + const auto tableSpace = static_cast(arg); + + try + { + tableSpace->astHandler(); + } + catch (const Firebird::Exception&) + {} // no-op + + return 0; + } + }; +} + +#endif // JRD_TABLESPACE_H diff --git a/src/jrd/blb.cpp b/src/jrd/blb.cpp index dfcb58164af..f1c7f9929be 100644 --- a/src/jrd/blb.cpp +++ b/src/jrd/blb.cpp @@ -1094,7 +1094,7 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, BLB_gen_bpb_from_descs(from_desc, to_desc, bpb); Database* dbb = tdbb->getDatabase(); - const USHORT pageSpace = dbb->readOnly() ? + const ULONG pageSpace = dbb->readOnly() ? dbb->dbb_page_manager.getTempPageSpaceID(tdbb) : DB_PAGE_SPACE; copy_blob(tdbb, source, destination, bpb.getCount(), bpb.begin(), pageSpace); @@ -1107,9 +1107,10 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, Request* request = tdbb->getRequest(); - if (relation->getPermanent()->isVirtual()) { + if (relation->getPermanent()->isVirtual()) ERR_post(Arg::Gds(isc_read_only)); - } + + const auto relPages = relation->getPages(tdbb); // If either the source value is null or the blob id itself is null // (all zeros), then the blob is null. @@ -1198,7 +1199,7 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, BLB_gen_bpb_from_descs(from_desc, to_desc, bpb); Database* dbb = tdbb->getDatabase(); - const USHORT pageSpace = dbb->readOnly() ? + const ULONG pageSpace = dbb->readOnly() ? dbb->dbb_page_manager.getTempPageSpaceID(tdbb) : DB_PAGE_SPACE; copy_blob(tdbb, source, destination, bpb.getCount(), bpb.begin(), pageSpace); @@ -1209,8 +1210,6 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, return; } - RelationPages* relPages = relation->getPermanent()->getPages(tdbb); - // If the source is a permanent blob, then the blob must be copied. // Otherwise find the temporary blob referenced. @@ -1230,7 +1229,7 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, if (source->bid_internal.bid_relation_id || needFilter) { blob = copy_blob(tdbb, source, destination, - bpb.getCount(), bpb.begin(), relPages->rel_pg_space_id); + bpb.getCount(), bpb.begin(), relPages->getPageSpaceId()); } else if ((to_desc->dsc_dtype == dtype_array) && (array = find_array(transaction, source)) && (blob = store_array(tdbb, transaction, source))) @@ -1280,11 +1279,11 @@ void blb::move(thread_db* tdbb, dsc* from_desc, dsc* to_desc, ERR_post(Arg::Gds(isc_bad_segstr_id)); } - if (blob->blb_level && (blob->blb_pg_space_id != relPages->rel_pg_space_id)) + if (blob->blb_level && (blob->blb_pg_space_id != relPages->getPageSpaceId())) { const ULONG oldTempID = blob->blb_temp_id; blb* newBlob = copy_blob(tdbb, source, destination, - bpb.getCount(), bpb.begin(), relPages->rel_pg_space_id); + bpb.getCount(), bpb.begin(), relPages->getPageSpaceId()); transaction->tra_blobs->locate(newBlob->blb_temp_id); BlobIndex* newBlobIndex = &transaction->tra_blobs->current(); @@ -1535,7 +1534,7 @@ blb* blb::open2(thread_db* tdbb, if (!blob->blb_relation) ERR_post(Arg::Gds(isc_bad_segstr_id)); - blob->blb_pg_space_id = blob->blb_relation->getPages(tdbb)->rel_pg_space_id; + blob->blb_pg_space_id = blob->blb_relation->getPages(tdbb)->getPageSpaceId(); DPM_get_blob(tdbb, blob, blob->blb_relation, blobId.get_permanent_number(), false, 0); #ifdef CHECK_BLOB_FIELD_ACCESS_FOR_SELECT @@ -2218,7 +2217,7 @@ static ISC_STATUS blob_filter(USHORT action, BlobControl* control) blb* blb::copy_blob(thread_db* tdbb, const bid* source, bid* destination, USHORT bpb_length, const UCHAR* bpb, - USHORT destPageSpaceID) + ULONG destPageSpaceID) { /************************************** * @@ -2285,11 +2284,11 @@ void blb::delete_blob(thread_db* tdbb, ULONG prior_page) Database* const dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - const USHORT pageSpaceID = blb_pg_space_id; + const ULONG pageSpaceID = blb_pg_space_id; if (dbb->readOnly()) { - const USHORT tempSpaceID = dbb->dbb_page_manager.getTempPageSpaceID(tdbb); + const ULONG tempSpaceID = dbb->dbb_page_manager.getTempPageSpaceID(tdbb); if (pageSpaceID != tempSpaceID) { @@ -2382,7 +2381,7 @@ void blb::delete_blob_id(thread_db* tdbb, const bid* blob_id, ULONG prior_page, // Fetch blob blb* blob = allocate_blob(tdbb, attachment->getSysTransaction()); - blob->blb_pg_space_id = relation->getPages(tdbb)->rel_pg_space_id; + blob->blb_pg_space_id = relation->getPages(tdbb)->getPageSpaceId(); prior_page = DPM_get_blob(tdbb, blob, relation, blob_id->get_permanent_number(), true, prior_page); if (!(blob->blb_flags & BLB_damaged)) @@ -2566,7 +2565,7 @@ void blb::insert_page(thread_db* tdbb) // Allocate a page for the now full blob data page. Move the page // image to the buffer, and release the page. - const USHORT pageSpaceID = blb_pg_space_id; + const ULONG pageSpaceID = blb_pg_space_id; WIN window(pageSpaceID, -1); blob_page* page = (blob_page*) DPM_allocate(tdbb, &window); diff --git a/src/jrd/blb.h b/src/jrd/blb.h index b0b294d40b3..31eb3ad84a8 100644 --- a/src/jrd/blb.h +++ b/src/jrd/blb.h @@ -142,7 +142,7 @@ class blb : public pool_alloc private: static blb* allocate_blob(thread_db*, jrd_tra*); static blb* copy_blob(thread_db* tdbb, const bid* source, bid* destination, - USHORT bpb_length, const UCHAR* bpb, USHORT destPageSpaceID); + USHORT bpb_length, const UCHAR* bpb, ULONG destPageSpaceID); void delete_blob(thread_db*, ULONG); Ods::blob_page* get_next_page(thread_db*, win*); void insert_page(thread_db*); @@ -171,7 +171,7 @@ class blb : public pool_alloc USHORT blb_space_remaining = 0; // Data space left USHORT blb_max_pages = 0; // Max pages in vector USHORT blb_level = 0; // Storage type - USHORT blb_pg_space_id = 0; // page space + ULONG blb_pg_space_id = 0; // page space USHORT blb_fragment_size = 0; // Residual fragment size USHORT blb_max_segment = 0; // Longest segment #ifdef CHECK_BLOB_FIELD_ACCESS_FOR_SELECT diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 0bb2c733b51..b00b1694af0 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -221,6 +221,28 @@ namespace return TipCache::traState(tdbb, descTrans, checkPresence, creating); } + const index_root_page::irt_repeat* getIndexSlot(thread_db* tdbb, const index_root_page* root, MetaId indexId) + { + const auto rpt = &root->irt_rpt[indexId]; + + if (rpt->getState() == irt_migrate) + { + const auto transaction = tdbb->getTransaction(); + const auto traNumber = transaction ? transaction->tra_number : 0; + fb_assert(traNumber); + + if (traNumber && rpt->getTransaction() != traNumber) + { + const auto backId = rpt->irt_backslot; + fb_assert(backId < root->irt_count); + + return &root->irt_rpt[backId]; + } + } + + return rpt; + } + } // namespace static ULONG add_node(thread_db*, WIN*, index_insertion*, temporary_key*, RecordNumber*, @@ -231,9 +253,10 @@ static USHORT compress_root(thread_db*, index_root_page*); static void copy_key(const temporary_mini_key*, temporary_mini_key*); static contents delete_node(thread_db*, WIN*, UCHAR*); static void delete_tree(thread_db*, MetaId, MetaId, PageNumber, PageNumber); -static ULONG fast_load(thread_db*, IndexCreation&, SelectivityList&); -static const index_root_page* fetch_root(thread_db*, WIN*, const RelationPermanent*, const RelationPages*); +template +static ULONG fast_load(thread_db*, IndexCreation&, SelectivityList&, RS* scb); + static UCHAR* find_node_start_point(btree_page*, temporary_key*, UCHAR*, USHORT*, bool, int, bool = false, RecordNumber = NO_VALUE); @@ -288,6 +311,7 @@ class Flags case irt_rollback: return "irt_rollback"; case irt_normal: return "irt_normal"; case irt_kill: return "irt_kill"; + case irt_migrate: return "irt_migrate"; case irt_unused: return "irt_unused"; } @@ -299,19 +323,24 @@ class Flags void dumpIndexRoot(const char* up, const char* from, thread_db* tdbb, WIN* window, const index_root_page* root) { - if (root->irt_relation > 127) + if (root->irt_relation >= MIN_RELATION_ID) { - auto* rel = MetadataCache::getPerm(tdbb, root->irt_relation, 0); + const auto* rel = MetadataCache::getPerm(tdbb, root->irt_relation, 0); printf("\n%sFrom %s page=%" ULONGFORMAT " len=%d rel=%s(%d) tra=%" SQUADFORMAT "\n", up, from, window->win_page.getPageNum(), root->irt_count, rel->getName().toQuotedString().c_str(), root->irt_relation, tdbb->getTransaction() ? tdbb->getTransaction()->tra_number : 0); + for (MetaId i = 0; i < root->irt_count; ++i) { - auto* idp = rel->lookupIndex(tdbb, i, 0); - auto& rpt = root->irt_rpt[i]; - printf("Index %d '%s' root %d tra %" SQUADFORMAT " %s\n", i, idp ? idp->getName().toQuotedString().c_str() : "not-found", - rpt.getRoot(), rpt.getTransaction(), Flags::state(rpt)); + const auto* idp = rel->lookupIndex(tdbb, i, 0); + const auto& rpt = *getIndexSlot(tdbb, root, i); + const auto page = rpt.getRootPage(); + + printf("Index %d '%s' root %d:%d tra %" SQUADFORMAT " %s\n", + i, idp ? idp->getName().toQuotedString().c_str() : "not-found", + page.getPageSpaceID(), page.getPageNum(), rpt.getTransaction(), Flags::state(rpt)); } + printf("\n"); } } @@ -999,7 +1028,7 @@ bool IndexDuplicateScanner::find(thread_db* tdbb, RecordBitmap& recs) { recs.clear(); - win window(m_relation->getPages(tdbb)->rel_pg_space_id, 0); + win window(m_relation->getPages(tdbb)->getPageSpaceId()); UCHAR* pointer = m_initialized ? restorePosition(tdbb, &window) : init(tdbb, &window); if (!pointer) @@ -1098,9 +1127,16 @@ UCHAR* IndexDuplicateScanner::init(thread_db* tdbb, win* window) { m_initialized = true; - auto relPerm = m_relation->getPermanent(); + const auto relPages = m_relation->getPages(tdbb); + const auto rootPage = relPages->getIndexRootPage(); + if (!rootPage) + return nullptr; + + window->win_page = rootPage.value(); + const auto root = BTR_fetch_root(FB_FUNCTION, tdbb, window); + + const auto relPerm = m_relation->getPermanent(); - auto root = fetch_root(tdbb, window, relPerm, m_relation->getPages(tdbb)); if (!BTR_description(tdbb, relPerm, root, &m_index, m_indexId)) { CCH_RELEASE(tdbb, window); @@ -1243,7 +1279,7 @@ bool IndexDuplicateScanner::checkRecord(thread_db* tdbb, record_param* rpb) } -void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList, RelationPages* relPages, bool sysRq) +void BTR_all(thread_db* tdbb, jrd_rel* relation, IndexDescList& idxList, bool sysRq) { /************************************** * @@ -1261,25 +1297,29 @@ void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList const Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - WIN window(relPages->rel_pg_space_id, -1); - - const index_root_page* const root = fetch_root(tdbb, &window, relation, relPages); - if (!root) + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) return; + WIN window(rootPage.value()); + const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + Cleanup release_root([&] { if (window.win_bdb) CCH_RELEASE(tdbb, &window); }); + const auto relPerm = relation->getPermanent(); + for (MetaId id = 0; id < root->irt_count; id++) { - const index_root_page::irt_repeat* irt_desc = root->irt_rpt + id; + const auto irt_desc = getIndexSlot(tdbb, root, id); const TraNumber descTrans = irt_desc->getTransaction(); switch (irt_desc->getState()) { case irt_normal: + case irt_migrate: break; case irt_in_progress: // index creation - skip @@ -1288,7 +1328,7 @@ void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList continue; case irt_rollback: // to be removed when irt_transaction dead - switch (indexCacheState(tdbb, descTrans, relation, id, true)) + switch (indexCacheState(tdbb, descTrans, relPerm, id, true)) { case tra_unknown: // skip - index in unknown state case tra_dead: // skip - index failed creation @@ -1297,7 +1337,7 @@ void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList break; case irt_commit: // change state on irt_transaction completion - switch (indexCacheState(tdbb, descTrans, relation, id, false)) + switch (indexCacheState(tdbb, descTrans, relPerm, id, false)) { case tra_unknown: // skip - index in unknown state case tra_committed: // skip - index to be dropped @@ -1307,7 +1347,7 @@ void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList } index_desc idx; - if (BTR_description(tdbb, relation, root, &idx, id, + if (BTR_description(tdbb, relPerm, root, &idx, id, BTR_DESCRIBE_NO_THROW | (sysRq ? BTR_DESCRIBE_SYSTEM_RQ : 0))) { idxList.add(idx); @@ -1319,7 +1359,7 @@ void BTR_all(thread_db* tdbb, Cached::Relation* relation, IndexDescList& idxList for (auto n = 0u; n < idxList.getCount(); ) { auto& idx = idxList[n]; - if (!relation->lookup_index(tdbb, idx.idx_id, CacheFlag::AUTOCREATE)) + if (!relation->getPermanent()->lookup_index(tdbb, idx.idx_id, CacheFlag::AUTOCREATE)) idxList.remove(n); else ++n; @@ -1370,38 +1410,52 @@ void BTR_create(thread_db* tdbb, jrd_rel* const relation = creation.relation; index_desc* const idx = creation.index; + const auto rootPage = relation->getIndexRootPage(tdbb); + fb_assert(rootPage); + + if (relation->isTemporary()) + idx->idx_pg_space_id = rootPage.value().getPageSpaceID(); + + Tablespace::lock(tdbb, idx->idx_pg_space_id); + // Now that the index id has been checked out, create the index. - idx->idx_root = fast_load(tdbb, creation, selectivity); + idx->idx_root = fast_load(tdbb, creation, selectivity, creation.sort); if (creation.isConcurrently()) creation.lockWrites(LCK_WAIT); // Index is created. Go back to the index root page and update it to // point to the index. - WIN window(relation->getPermanent()->getIndexRootPage(tdbb)); - index_root_page* const root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + + WIN window(rootPage.value()); + const auto root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); CCH_MARK(tdbb, &window); - const jrd_tra* tra = creation.transaction; - auto* const irt_desc = root->irt_rpt + idx->idx_id; + const auto transaction = creation.transaction; + const auto traNumber = transaction ? transaction->tra_number : 0; + const auto indexRpt = &root->irt_rpt[idx->idx_id]; + const PageNumber rootPageNumber(idx->idx_pg_space_id, idx->idx_root); - switch(creation.createMethod) + switch (creation.createMethod) { case IdxCreate::AtOnce: - irt_desc->setNormal(idx->idx_root); + indexRpt->setNormal(rootPageNumber); break; case IdxCreate::ForRollback: - fb_assert(tra && tra->tra_number); - irt_desc->setRollback(idx->idx_root, tra ? tra->tra_number : 0); + fb_assert(traNumber); + indexRpt->setRollback(rootPageNumber, traNumber); break; case IdxCreate::Concurrently: - fb_assert(tra && irt_desc->getTransaction() == tra->tra_number); + fb_assert(traNumber && indexRpt->getTransaction() == traNumber); // put root page of the new index into irt_desc - irt_desc->setConcurrentlyMain(idx->idx_root, tra->tra_number); - idx->idx_flags = irt_desc->irt_flags; + indexRpt->setConcurrentlyMain(rootPageNumber, traNumber); + idx->idx_flags = indexRpt->irt_flags; break; + + default: + fb_assert(false); } update_selectivity(root, idx->idx_id, selectivity); @@ -1428,31 +1482,42 @@ bool BTR_delete_index(thread_db* tdbb, WIN* window, MetaId id, bool withCleanup) CHECK_DBB(dbb); // Get index descriptor. If index doesn't exist, just leave. - index_root_page* const root = (index_root_page*) window->win_buffer; + const auto root = (index_root_page*) window->win_buffer; if (id < root->irt_count) { - index_root_page::irt_repeat* irt_desc = root->irt_rpt + id; + const auto irt_desc = &root->irt_rpt[id]; + const auto rootPage = irt_desc->getRootPage(); - CCH_MARK(tdbb, window); - const auto rootPage = irt_desc->getRoot(); - const PageNumber next(window->win_page.getPageSpaceID(), rootPage); - bool tree_exists = (rootPage != 0); + if (rootPage) + { + try + { + Tablespace::lock(tdbb, rootPage.value().getPageSpaceID()); + } + catch (...) + { + CCH_RELEASE(tdbb, window); + throw; + } + } // remove the pointer to the top-level index page before we delete it irt_desc->setEmpty(); + const PageNumber prior(window->win_page); const USHORT relation_id = root->irt_relation; CCH_RELEASE(tdbb, window); - delete_tree(tdbb, relation_id, id, next, prior); + if (rootPage) + delete_tree(tdbb, relation_id, id, rootPage.value(), prior); // clear RDB$INDEX_ID and related stuff if (withCleanup) DropIndexNode::clearId(tdbb, relation_id, id); - return tree_exists; + return rootPage.has_value(); } CCH_RELEASE(tdbb, window); @@ -1476,15 +1541,16 @@ static void checkTransactionNumber(const index_root_page::irt_repeat* irt_desc, } -[[noreturn]] static void badState (const index_root_page::irt_repeat* irt_desc, const char* set, const char* msg) +[[noreturn]] static void badState(const index_root_page::irt_repeat* irt_desc, const char* set, const char* msg) { fb_assert(false); - fatal_exception::raiseFmt("Invalid index state %s (%d, %s) when %s", Flags::state(*irt_desc), irt_desc->getState(), set, msg); + fatal_exception::raiseFmt("Invalid index state %s (%d, %s) when %s", + Flags::state(*irt_desc), irt_desc->getState(), set, msg); } -void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId id, WIN* window, index_root_page* root, - TraNumber tran) +void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* relation, MetaId id, + WIN* window, index_root_page* root, TraNumber traNumber) { /*********************************************************** * @@ -1509,22 +1575,22 @@ void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId i // Get index descriptor. If index doesn't exist, just leave. if (id < root->irt_count) { - auto* irt_desc = root->irt_rpt + id; + auto irt_desc = &root->irt_rpt[id]; - if (!tran) + if (!traNumber) { - jrd_tra* tra = tdbb->getTransaction(); - fb_assert(tra); - if (tra) - tran = tra->tra_number; + const auto transaction = tdbb->getTransaction(); + fb_assert(transaction); + traNumber = transaction->tra_number; } - fb_assert(tran); - if (tran) + fb_assert(traNumber); + + if (traNumber) { bool marked = false; - switch(modifyIrtRepeat(tdbb, irt_desc, rel, window, id)) + switch (modifyIrtRepeat(tdbb, irt_desc, relation, window, id)) { case ModifyIrtRepeatValue::Skip: // buffer could be released and refetched by modifyIrtRepeat @@ -1538,7 +1604,7 @@ void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId i case ModifyIrtRepeatValue::Relock: root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, window); - irt_desc = root->irt_rpt + id; + irt_desc = &root->irt_rpt[id]; break; case ModifyIrtRepeatValue::Deleted: @@ -1555,7 +1621,7 @@ void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId i break; case irt_commit: - if (tran == irt_desc->getTransaction()) + if (traNumber == irt_desc->getTransaction()) break; // already marked in current transaction [[fallthrough]]; @@ -1565,16 +1631,17 @@ void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId i badState(irt_desc, "not irt_rollback/irt_normal", msg); case irt_rollback: // created not long ago - checkTransactionNumber(irt_desc, tran, msg); + checkTransactionNumber(irt_desc, traNumber, msg); if (!marked) CCH_MARK(tdbb, window); - irt_desc->setKill(tran); + irt_desc->setKill(traNumber); break; case irt_normal: + case irt_migrate: if (!marked) CCH_MARK(tdbb, window); - irt_desc->setCommit(tran); + irt_desc->setCommit(traNumber); break; } } @@ -1582,7 +1649,7 @@ void BTR_mark_index_for_delete(thread_db* tdbb, RelationPermanent* rel, MetaId i } -bool BTR_activate_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) +bool BTR_activate_index(thread_db* tdbb, jrd_rel* relation, MetaId id) { /************************************** * @@ -1599,7 +1666,11 @@ bool BTR_activate_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) const Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - WIN window(relation->getIndexRootPage(tdbb)); + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) + return false; + + WIN window(rootPage.value()); index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); bool rc = false; @@ -1615,7 +1686,7 @@ bool BTR_activate_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) { bool marked = false; - switch(modifyIrtRepeat(tdbb, irt_desc, relation, &window, id, true)) + switch (modifyIrtRepeat(tdbb, irt_desc, relation->getPermanent(), &window, id, true)) { case ModifyIrtRepeatValue::Skip: // buffer could be released and refetched by modifyIrtRepeat @@ -1647,7 +1718,8 @@ bool BTR_activate_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) case irt_concurrently: case irt_rollback: case irt_normal: - badState(irt_desc, "irt_in_progress/irt_concurrently/irt_rollback/irt_normal", msg); + case irt_migrate: + badState(irt_desc, "irt_in_progress/irt_concurrently/irt_rollback/irt_normal/irt_migrate", msg); case irt_commit: // removed not long ago checkTransactionNumber(irt_desc, tra->tra_number, msg); @@ -1693,9 +1765,9 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro if (id >= root->irt_count) return false; - const index_root_page::irt_repeat* irt_desc = &root->irt_rpt[id]; + const auto irt_desc = getIndexSlot(tdbb, root, id); - const ULONG rootPage = irt_desc->getRoot(); + const auto rootPage = irt_desc->getRootPage(); if (!rootPage) return false; @@ -1703,8 +1775,12 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro if ((flags & BTR_DESCRIBE_SYSTEM_RQ) && (irt_desc->irt_state != Ods::irt_normal)) return false; + const auto pageNumber = rootPage.value().getPageNum(); + const auto pageSpaceId = rootPage.value().getPageSpaceID(); + idx->idx_id = id; - idx->idx_root = rootPage; + idx->idx_root = pageNumber; + idx->idx_pg_space_id = pageSpaceId; idx->idx_count = irt_desc->irt_keys; idx->idx_flags = irt_desc->irt_flags; idx->idx_runtime_flags = 0; @@ -1784,6 +1860,8 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro CCH_unwind(tdbb, true); } + Tablespace::lock(tdbb, pageSpaceId); + return true; } @@ -1883,8 +1961,8 @@ void BTR_evaluate(thread_db* tdbb, const IndexRetrieval* retrieval, RecordBitmap **************************************/ SET_TDBB(tdbb); - RelationPages* relPages = retrieval->getPermRelation()->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); + // Proper pagespace is assigned inside BTR_find_page + WIN window(DB_PAGE_SPACE); temporary_key lowerKey, upperKey; lowerKey.key_flags = 0; @@ -2079,10 +2157,13 @@ btree_page* BTR_find_page(thread_db* tdbb, SET_TDBB(tdbb); - RelationPages* relPages = retrieval->getPermRelation()->getPages(tdbb); - fb_assert(window->win_page.getPageSpaceID() == relPages->rel_pg_space_id); + // Now it's possible that win may have index page space + // and we need to change page space for fetching index root page + const auto rootPage = retrieval->getRelation(tdbb)->getIndexRootPage(tdbb); + if (!rootPage) + IBERROR(260); // msg 260 index unexpectedly deleted - window->win_page = relPages->rel_index_root; + window->win_page = rootPage.value(); const index_root_page* rpage = BTR_fetch_root(FB_FUNCTION, tdbb, window); if (!BTR_description(tdbb, retrieval->getPermRelation(), rpage, idx, retrieval->irb_index)) @@ -2091,7 +2172,7 @@ btree_page* BTR_find_page(thread_db* tdbb, IBERROR(260); // msg 260 index unexpectedly deleted } - btree_page* page = (btree_page*) CCH_HANDOFF(tdbb, window, idx->idx_root, LCK_read, pag_index); + btree_page* page = (btree_page*) CCH_HANDOFF(tdbb, window, PageNumber(idx->idx_pg_space_id, idx->idx_root), LCK_read, pag_index); // If there is a starting descriptor, search down index to starting position. // This may involve sibling buckets if splits are in progress. If there @@ -2167,8 +2248,7 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) SET_TDBB(tdbb); index_desc* idx = insertion->iib_descriptor; - RelationPages* relPages = insertion->iib_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, idx->idx_root); + WIN window(idx->idx_pg_space_id, idx->idx_root); btree_page* bucket = (btree_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_index); UCHAR root_level = bucket->btr_level; @@ -2195,7 +2275,10 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) // update the index root page. Oh boy. index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, root_window); - window.win_page = root->irt_rpt[idx->idx_id].getRoot(); + auto indexRpt = &root->irt_rpt[idx->idx_id]; + auto rootPage = indexRpt->getRootPage(); + fb_assert(rootPage); + window.win_page = rootPage.value(); bucket = (btree_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_index); if (window.win_page.getPageNum() != idx->idx_root) @@ -2222,6 +2305,7 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) index_insertion propagate = *insertion; propagate.iib_number.setValue(split_page); + fb_assert(propagate.iib_descriptor->idx_pg_space_id == window.win_page.getPageSpaceID()); propagate.iib_descriptor->idx_root = window.win_page.getPageNum(); propagate.iib_key = &key; propagate.iib_btr_level = root_level + 1; @@ -2245,7 +2329,10 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) BUGCHECK(204); // msg 204 index inconsistent } - window.win_page = root->irt_rpt[idx->idx_id].getRoot(); + indexRpt = &root->irt_rpt[idx->idx_id]; + rootPage = indexRpt->getRootPage(); + fb_assert(rootPage); + window.win_page = rootPage.value(); bucket = (btree_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_index); key.key_length = ret_key.key_length; memcpy(key.key_data, ret_key.key_data, ret_key.key_length); @@ -2259,7 +2346,7 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) // so go ahead and mark it as garbage-collectable now. lock.enablePageGC(tdbb); - WIN new_window(relPages->rel_pg_space_id, split_page); + WIN new_window(idx->idx_pg_space_id, split_page); btree_page* new_bucket = (btree_page*) CCH_FETCH(tdbb, &new_window, LCK_read, pag_index); if (bucket->btr_level != new_bucket->btr_level) @@ -2330,9 +2417,13 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion) // it will be written out first--this will make sure that the // root page doesn't point into space CCH_RELEASE(tdbb, &new_window); - CCH_precedence(tdbb, root_window, new_window.win_page); + + const auto& newRootPage = new_window.win_page; + CCH_precedence(tdbb, root_window, newRootPage); CCH_MARK(tdbb, root_window); - root->irt_rpt[idx->idx_id].setRoot(new_window.win_page.getPageNum()); + + indexRpt = &root->irt_rpt[idx->idx_id]; + indexRpt->setRootPage(newRootPage); CCH_RELEASE(tdbb, root_window); } @@ -2475,8 +2566,7 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) } -bool BTR_lookup(thread_db* tdbb, Cached::Relation* relation, MetaId id, index_desc* buffer, - RelationPages* relPages) +bool BTR_lookup(thread_db* tdbb, jrd_rel* relation, MetaId id, index_desc* buffer) { /************************************** * @@ -2489,14 +2579,15 @@ bool BTR_lookup(thread_db* tdbb, Cached::Relation* relation, MetaId id, index_de * **************************************/ SET_TDBB(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - const index_root_page* const root = fetch_root(tdbb, &window, relation, relPages); - - if (!root) + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) return false; - const bool result = (id < root->irt_count && BTR_description(tdbb, relation, root, buffer, id)); + WIN window(rootPage.value()); + const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + + const bool result = (id < root->irt_count && BTR_description(tdbb, relation->getPermanent(), root, buffer, id)); CCH_RELEASE(tdbb, &window); return result; } @@ -2844,10 +2935,9 @@ void BTR_make_null_key(thread_db* tdbb, const index_desc* idx, temporary_key* ke // Merge b-tree with root page srcRootPage into index described by idx. // The source b-tree is complementary index. -void BTR_merge_index(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation, index_desc* idx, - PageNumber srcRootPage) +void BTR_merge_index(thread_db* tdbb, const IndexCreation& creation, const PageNumber& srcRootPage) { - index_desc srcIdx = *idx; + index_desc srcIdx = *creation.index; srcIdx.idx_root = srcRootPage.getPageNum(); ULONG srcPage = srcIdx.idx_root; @@ -2871,13 +2961,12 @@ void BTR_merge_index(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation, i key.key_length = 0; index_insertion insertion; - insertion.iib_relation = relation; - insertion.iib_descriptor = idx; - insertion.iib_transaction = transaction; + insertion.iib_relation = creation.relation; + insertion.iib_descriptor = creation.index; + insertion.iib_transaction = creation.transaction; insertion.iib_key = &key; - auto relPerm = relation->getPermanent(); - RelationPages* relPages = relPerm->getPages(tdbb); + const auto relPages = creation.relation->getPages(tdbb); FB_UINT64 cntNodes = 0; while (srcPage) @@ -2903,8 +2992,11 @@ void BTR_merge_index(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation, i insertion.iib_btr_level = 0; insertion.iib_duplicates = nullptr; - WIN dstWindow(relPages->rel_pg_space_id, -1); - fetch_root(tdbb, &dstWindow, relPerm, relPages); + const auto rootPage = relPages->getIndexRootPage(); + fb_assert(rootPage); + + win dstWindow(rootPage.value()); + auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &dstWindow); if (!deleted) BTR_insert(tdbb, &dstWindow, &insertion); @@ -2960,7 +3052,8 @@ static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* i case irt_rollback: case irt_commit: case irt_kill: - // this three states require modification if irtTrans is completed + case irt_migrate: + // these states require modification if irtTrans is completed switch (TPC_cache_state(tdbb, irtTrans)) { case tra_committed: @@ -2997,6 +3090,7 @@ static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* i static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::irt_repeat* irt_desc, RelationPermanent* relation, WIN* window, MetaId indexId, bool fromActivate) { + const auto root = (index_root_page*) window->win_buffer; const TraNumber irtTrans = irt_desc->getTransaction(); const TraNumber oldestActive = tdbb->getDatabase()->dbb_oldest_active; @@ -3088,6 +3182,36 @@ static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::ir break; return ModifyIrtRepeatValue::Skip; + case irt_migrate: + { + const auto state = transactionState(tdbb, irtTrans, relation, indexId, false); + if (state != tra_committed && state != tra_dead) + return ModifyIrtRepeatValue::Skip; + + CCH_MARK(tdbb, window); + const auto backId = irt_desc->irt_backslot; + fb_assert(backId < root->irt_count); + const auto backIrtDesc = &root->irt_rpt[backId]; + + if (state == tra_committed) + { + irt_desc->setNormal(); + backIrtDesc->setKill(); + } + else // state == tra_dead + { + const auto orgRootPage = backIrtDesc->getRootPage(); + const auto newRootPage = irt_desc->getRootPage(); + irt_desc->setNormal(orgRootPage.value()); + backIrtDesc->setKill(newRootPage.value()); + } + + // drop index + BTR_delete_index(tdbb, window, backId, false); + return ModifyIrtRepeatValue::Relock; + } + break; + default: fb_assert(false); return ModifyIrtRepeatValue::Skip; @@ -3099,7 +3223,7 @@ static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::ir } -bool BTR_next_index(thread_db* tdbb, Cached::Relation* relation, jrd_tra* transaction, index_desc* idx, +bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, index_desc* idx, WIN* window, RelationPages* relPages) { /************************************** @@ -3132,7 +3256,13 @@ bool BTR_next_index(thread_db* tdbb, Cached::Relation* relation, jrd_tra* transa if (!relPages) relPages = transaction ? relation->getPages(tdbb, transaction->tra_number) : relation->getPages(tdbb); - if (!(root = fetch_root(tdbb, window, relation, relPages))) + const auto rootPage = relPages->getIndexRootPage(); + if (!rootPage) + return false; + + window->win_page = rootPage.value(); + + if (!(root = BTR_fetch_root(FB_FUNCTION, tdbb, window))) return false; } @@ -3140,14 +3270,14 @@ bool BTR_next_index(thread_db* tdbb, Cached::Relation* relation, jrd_tra* transa { for (; id < root->irt_count; ++id) { - const index_root_page::irt_repeat* irt_desc = root->irt_rpt + id; + const auto irt_desc = getIndexSlot(tdbb, root, id); - if (checkIrtRepeat(tdbb, irt_desc, relation, window, id)) + if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), window, id)) { - auto* root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, window); - auto* irt_write = root_write->irt_rpt + id; + const auto root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, window); + const auto irt_write = &root_write->irt_rpt[id]; - auto modValue = modifyIrtRepeat(tdbb, irt_write, relation, window, id); + const auto modValue = modifyIrtRepeat(tdbb, irt_write, relation->getPermanent(), window, id); switch (modValue) { case ModifyIrtRepeatValue::Skip: @@ -3161,7 +3291,7 @@ bool BTR_next_index(thread_db* tdbb, Cached::Relation* relation, jrd_tra* transa continue; } - if (BTR_description(tdbb, relation, root, idx, id)) + if (BTR_description(tdbb, relation->getPermanent(), root, idx, id)) return true; } @@ -3192,26 +3322,25 @@ bool BTR_cleanup_index(thread_db* tdbb, const QualifiedName& relName, jrd_tra* t **************************************/ SET_TDBB(tdbb); - auto* relation = MetadataCache::getPerm(tdbb, relName, CacheFlag::ERASED); + const auto relation = MetadataCache::getVersioned(tdbb, relName, CacheFlag::ERASED); if (!relation) return false; - RelationPages* const relPages = transaction ? - relation->getPages(tdbb, transaction->tra_number) : relation->getPages(tdbb); - - WIN window(relPages->rel_pg_space_id, -1); - const index_root_page* root = fetch_root(tdbb, &window, relation, relPages); - if (!root) + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) return false; + WIN window(rootPage.value()); + const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + const index_root_page::irt_repeat* irt_desc = root->irt_rpt + id; bool rc = false; - if (checkIrtRepeat(tdbb, irt_desc, relation, &window, id)) + if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), &window, id)) { - auto* root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); - auto* irt_write = root_write->irt_rpt + id; + auto root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + auto irt_write = &root_write->irt_rpt[id]; - auto modValue = modifyIrtRepeat(tdbb, irt_write, relation, &window, id); + auto modValue = modifyIrtRepeat(tdbb, irt_write, relation->getPermanent(), &window, id); switch (modValue) { case ModifyIrtRepeatValue::Skip: @@ -3257,8 +3386,7 @@ void BTR_remove(thread_db* tdbb, WIN* root_window, index_insertion* insertion) //const Database* dbb = tdbb->getDatabase(); insertion->iib_removed = false; index_desc* idx = insertion->iib_descriptor; - RelationPages* relPages = insertion->iib_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, idx->idx_root); + WIN window(idx->idx_pg_space_id, idx->idx_root); btree_page* page = (btree_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_index); // If the page is level 0, re-fetch it for write @@ -3304,7 +3432,9 @@ void BTR_remove(thread_db* tdbb, WIN* root_window, index_insertion* insertion) } CCH_MARK(tdbb, root_window); - root->irt_rpt[idx->idx_id].setRoot(number); + + const auto indexRpt = &root->irt_rpt[idx->idx_id]; + indexRpt->setRootPage(PageNumber(idx->idx_pg_space_id, number)); // release the pages, and place the page formerly at the top level // on the free list, making sure the root page is written out first @@ -3345,24 +3475,25 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) CHECK_DBB(dbb); auto* const rel = getPermanent(creation.relation); - index_desc* const idx = creation.index; - jrd_tra* const transaction = creation.transaction; - fb_assert(rel); - RelationPages* const relPages = rel->getPages(tdbb); - fb_assert(relPages && relPages->rel_index_root); - + jrd_tra* const transaction = creation.transaction; fb_assert(transaction); + const auto idx = creation.index; + const auto relPages = creation.relation->getPages(tdbb); + // Get root page, assign an index id, and store the index descriptor. // Leave the root pointer null for the time being. // Index id for temporary index instance of global temporary table is // already assigned, use it. - const bool use_idx_id = (relPages->rel_instance_id != 0) || (rel->rel_flags & REL_temp_ltt); + const bool use_idx_id = (relPages->getInstanceId() != 0) || (rel->rel_flags & REL_temp_ltt); if (use_idx_id) fb_assert(idx->idx_id <= dbb->dbb_max_idx); - WIN window(relPages->rel_pg_space_id, relPages->rel_index_root); + const auto rootPage = creation.relation->getIndexRootPage(tdbb); + fb_assert(rootPage); + + WIN window(rootPage.value()); index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); fb_assert(window.win_bdb); CCH_MARK(tdbb, &window); @@ -3485,8 +3616,9 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) { // Allocate and format root page of the temp index - WIN leaf(relPages->rel_pg_space_id, 0); + WIN leaf(relPages->getPageSpaceId()); btree_page* bucket = (btree_page*) DPM_allocate(tdbb, &leaf); + fb_assert(idx->idx_pg_space_id == leaf.win_page.getPageSpaceID()); idx->idx_root = leaf.win_page.getPageNum(); format_first_leaf(bucket, rel->getId(), idx->idx_id, creation.key_length); @@ -3499,7 +3631,7 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) CCH_RELEASE(tdbb, &leaf); - slot->setConcurrentlyTemp(leaf.win_page.getPageNum(), transaction->tra_number); + slot->setConcurrentlyTemp(leaf.win_page, transaction->tra_number); idx->idx_flags = slot->irt_flags; break; } @@ -3511,7 +3643,7 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) } -void BTR_selectivity(thread_db* tdbb, Cached::Relation* relation, MetaId id, SelectivityList& selectivity) +void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, MetaId id, SelectivityList& selectivity) { /************************************** * @@ -3529,26 +3661,38 @@ void BTR_selectivity(thread_db* tdbb, Cached::Relation* relation, MetaId id, Sel **************************************/ SET_TDBB(tdbb); - RelationPages* relPages = relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - const index_root_page* root = fetch_root(tdbb, &window, relation, relPages); - if (!root) + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) + return; + + WIN window(rootPage.value()); + const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + + if (id >= root->irt_count) + { + CCH_RELEASE(tdbb, &window); return; + } + + const auto indexRpt = getIndexSlot(tdbb, root, id); - if (id >= root->irt_count || !root->irt_rpt[id].getRoot()) + const auto pageNumber = indexRpt->getRootPage(); + if (!pageNumber) { CCH_RELEASE(tdbb, &window); return; } - ULONG page = root->irt_rpt[id].getRoot(); - const bool descending = (root->irt_rpt[id].irt_flags & irt_descending); - const ULONG segments = root->irt_rpt[id].irt_keys; + ULONG page = pageNumber.value().getPageNum(); + Tablespace::lock(tdbb, pageNumber.value().getPageSpaceID()); + + const bool descending = (indexRpt->irt_flags & irt_descending); + const ULONG segments = indexRpt->irt_keys; window.win_flags = WIN_large_scan; window.win_scans = 1; - btree_page* bucket = (btree_page*) CCH_HANDOFF(tdbb, &window, page, LCK_read, pag_index); + btree_page* bucket = (btree_page*) CCH_HANDOFF(tdbb, &window, pageNumber.value(), LCK_read, pag_index); // go down the left side of the index to leaf level UCHAR* pointer = bucket->btr_nodes + bucket->btr_jump_size; @@ -3707,9 +3851,9 @@ void BTR_selectivity(thread_db* tdbb, Cached::Relation* relation, MetaId id, Sel selectivity[0] = (float) (nodes ? 1.0 / (float) (nodes - duplicates) : 0.0); // Store the selectivity on the root page - window.win_page = relPages->rel_index_root; + window.win_page = rootPage.value(); window.win_flags = 0; - auto* write_root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + const auto write_root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); CCH_MARK(tdbb, &window); update_selectivity(write_root, id, selectivity); CCH_RELEASE(tdbb, &window); @@ -3764,6 +3908,193 @@ bool BTR_types_comparable(const dsc& target, const dsc& source) return false; } +class IndexNodes +{ +public: + IndexNodes(Jrd::thread_db* tdbb, const IndexCreation& creation, WIN* window): + window(window), key_length(creation.key_length) + { + recordData.resize(key_length + sizeof(index_sort_record)); + isr = reinterpret_cast(&recordData[key_length]); + btree_page* bucket = (btree_page*)window->win_buffer; + pointer = bucket->btr_nodes + bucket->btr_jump_size; + end = (UCHAR*) bucket + bucket->btr_length; + nullIndLen = !(creation.index->idx_flags & idx_descending) && (creation.index->idx_count == 1) ? 1 : 0; + } + + void get(Jrd::thread_db* tdbb, ULONG** record) + { + pointer = node.readNode(pointer, true); + // Check if pointer is still valid + if (pointer > end) + BUGCHECK(204); // msg 204 index inconsistent + + if (node.isEndBucket) + { + btree_page* bucket = (btree_page*)window->win_buffer; + if (!bucket->btr_sibling) + { + CCH_RELEASE(tdbb, window); + *record = 0; + return; + } + bucket = (btree_page*)CCH_HANDOFF(tdbb, window, bucket->btr_sibling, LCK_read, pag_index); + pointer = bucket->btr_nodes + bucket->btr_jump_size; + end = (UCHAR*) bucket + bucket->btr_length; + pointer = node.readNode(pointer, true); + // Check if pointer is still valid + if (pointer > end) + BUGCHECK(204); // msg 204 index inconsistent + } + + if (!node.isEndLevel) + { + memcpy(&recordData[node.prefix] + nullIndLen, node.data, node.length); + isr->isr_record_number = node.recordNumber.getValue(); + isr->isr_key_length = node.length + node.prefix; + *record = reinterpret_cast(&recordData[0]); + } + else + { + CCH_RELEASE(tdbb, window); + *record = 0; + } + } + +private: + WIN* window; + UCHAR* pointer; + UCHAR* end; + USHORT key_length; + IndexNode node; + Array recordData; + index_sort_record* isr; + int nullIndLen; +}; + +void BTR_copy_index(thread_db* tdbb, jrd_rel* relation, MetaId indexId, ULONG pageSpaceId) +{ +/************************************** + * + * B T R _ c o p y _ i n d e x + * + ************************************** + * + * Functional description + * Copy index pages from its original tablespace to the new one. + * + **************************************/ + const auto dbb = tdbb->getDatabase(); + + const auto transaction = tdbb->getTransaction(); + const auto traNumber = transaction ? transaction->tra_number : 0; + fb_assert(traNumber); + + // Lets start copy index pages + + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) + return; + + WIN rootWindow(rootPage.value()); + index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &rootWindow); + + auto indexRpt = &root->irt_rpt[indexId]; + + if (checkIrtRepeat(tdbb, indexRpt, relation->getPermanent(), &rootWindow, indexId)) + { + root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &rootWindow); + + switch (modifyIrtRepeat(tdbb, indexRpt, relation->getPermanent(), &rootWindow, indexId)) + { + case ModifyIrtRepeatValue::Skip: + case ModifyIrtRepeatValue::Modified: + if (indexRpt->getState() == irt_drop) + { + CCH_RELEASE(tdbb, &rootWindow); + return; + } + break; + + case ModifyIrtRepeatValue::Deleted: + return; + + case ModifyIrtRepeatValue::Relock: + root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &rootWindow); + break; + + default: + break; + } + } + + index_desc idx; + if (BTR_description(tdbb, relation->getPermanent(), root, &idx, indexId)) + { + CCH_RELEASE(tdbb, &rootWindow); + + const PageNumber oldRootPage(idx.idx_pg_space_id, idx.idx_root); + + // Set page space id of the new tablespace + Tablespace::lock(tdbb, pageSpaceId); + idx.idx_pg_space_id = pageSpaceId; + + const USHORT nullIndLen = !(idx.idx_flags & idx_descending) && (idx.idx_count == 1) ? 1 : 0; + const USHORT key_length = ROUNDUP(BTR_key_length(tdbb, relation, &idx) + nullIndLen, sizeof(SINT64)); + + IndexCreation creation; + creation.index = &idx; + creation.relation = relation; + creation.transaction = transaction; + creation.sort = nullptr; + creation.key_length = key_length; + creation.nullIndLen = nullIndLen; + creation.dup_recno = -1; + creation.duplicates.setValue(0); + creation.createMethod = IdxCreate::ForRollback; + + BTR_reserve_slot(tdbb, creation); + const auto backIndexId = idx.idx_id; + + // Fall down to level 0 + WIN window(oldRootPage); + btree_page* page = (btree_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_index); + IndexNode node; + while (page->btr_level > 0) + { + UCHAR* pointer; + const UCHAR* const endPointer = (UCHAR*) page + page->btr_length; + pointer = page->btr_nodes + page->btr_jump_size; + pointer = node.readNode(pointer, false); + + // Check if pointer is still valid + if (pointer > endPointer) + BUGCHECK(204); // msg 204 index inconsistent + + page = (btree_page*) CCH_HANDOFF(tdbb, &window, node.pageNumber, LCK_read, pag_index); + } + + IndexNodes indexNodes(tdbb, creation, &window); + SelectivityList selectivity; + + // window will be released by IndexNodes::get() during fast_load() + const ULONG newRootPage = fast_load(tdbb, creation, selectivity, &indexNodes); + + root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &rootWindow); + CCH_MARK(tdbb, &rootWindow); + + indexRpt = &root->irt_rpt[indexId]; + indexRpt->setMigrate(PageNumber(pageSpaceId, newRootPage), traNumber, backIndexId); + + const auto backRpt = &root->irt_rpt[backIndexId]; + backRpt->setInProgress(oldRootPage, traNumber); + + update_selectivity(root, indexId, selectivity); + } + + CCH_RELEASE(tdbb, &rootWindow); +} + static ULONG add_node(thread_db* tdbb, WIN* window, @@ -4476,7 +4807,7 @@ static USHORT compress_root(thread_db* tdbb, index_root_page* page) for (const index_root_page::irt_repeat* const end = root_idx + page->irt_count; root_idx < end; root_idx++) { - if (root_idx->getRoot()) + if (root_idx->isUsed()) { const USHORT len = root_idx->irt_keys * sizeof(irtd); p -= len; @@ -4868,9 +5199,11 @@ static void delete_tree(thread_db* tdbb, } + +template static ULONG fast_load(thread_db* tdbb, IndexCreation& creation, - SelectivityList& selectivity) + SelectivityList& selectivity, RS* scb) { /************************************** * @@ -4896,7 +5229,7 @@ static ULONG fast_load(thread_db* tdbb, index_desc* const idx = creation.index; const USHORT key_length = creation.key_length; - const USHORT pageSpaceID = relation->getPages(tdbb)->rel_pg_space_id; + const ULONG pageSpaceID = idx->idx_pg_space_id; // leaf-page and pointer-page size limits, we always need to // leave room for the END_LEVEL node. @@ -4989,7 +5322,7 @@ static ULONG fast_load(thread_db* tdbb, // Get the next record in sorted order. UCHAR* record; - creation.sort->get(tdbb, reinterpret_cast(&record)); + scb->get(tdbb, reinterpret_cast(&record)); if (!record || creation.duplicates.value()) break; @@ -5629,40 +5962,6 @@ static ULONG fast_load(thread_db* tdbb, } -static const index_root_page* fetch_root(thread_db* tdbb, WIN* window, const RelationPermanent* relation, - const RelationPages* relPages) -{ -/************************************** - * - * f e t c h _ r o o t - * - ************************************** - * - * Functional description - * Return descriptions of all indices for relation. If there isn't - * a known index root, assume we were called during optimization - * and return no indices. - * - **************************************/ - SET_TDBB(tdbb); - - if ((window->win_page = relPages->rel_index_root) == 0) - { - if (relation->getId() == 0) - return NULL; - - DPM_scan_pages(tdbb); - - if (!relPages->rel_index_root) - return NULL; - - window->win_page = relPages->rel_index_root; - } - - return BTR_fetch_root(FB_FUNCTION, tdbb, window); -} - - static UCHAR* find_node_start_point(btree_page* bucket, temporary_key* key, UCHAR* value, USHORT* return_value, bool descending, int retrieval, bool pointer_by_marker, RecordNumber find_record_number) @@ -6269,7 +6568,7 @@ static contents garbage_collect(thread_db* tdbb, WIN* window, ULONG parent_numbe const Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - const USHORT pageSpaceID = window->win_page.getPageSpaceID(); + const ULONG pageSpaceID = window->win_page.getPageSpaceID(); btree_page* gc_page = (btree_page*) window->win_buffer; contents result = contents_above_threshold; @@ -6926,7 +7225,7 @@ static ULONG insert_node(thread_db* tdbb, const Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - const USHORT pageSpaceID = window->win_page.getPageSpaceID(); + const ULONG pageSpaceID = window->win_page.getPageSpaceID(); // find the insertion point for the specified key btree_page* bucket = (btree_page*) window->win_buffer; diff --git a/src/jrd/btr.h b/src/jrd/btr.h index 41f3092471a..714be71838e 100644 --- a/src/jrd/btr.h +++ b/src/jrd/btr.h @@ -100,8 +100,9 @@ struct index_desc Statement* idx_expression_statement; // stored statement for expression evaluation BoolExprNode* idx_condition_node; // node tree for index condition Statement* idx_condition_statement; // stored statement for index condition - float idx_fraction; // fraction of keys included in the index + float idx_fraction; // fraction of keys included in the index UCHAR idx_state; // state from irt_rpt + ULONG idx_pg_space_id; // page space of index pages // This structure should exactly match IRTD structure for current ODS struct idx_repeat { diff --git a/src/jrd/btr_proto.h b/src/jrd/btr_proto.h index e386038eebe..c8deb401162 100644 --- a/src/jrd/btr_proto.h +++ b/src/jrd/btr_proto.h @@ -30,8 +30,8 @@ #include "../jrd/exe.h" #include "../jrd/QualifiedName.h" -void BTR_all(Jrd::thread_db*, Jrd::Cached::Relation*, Jrd::IndexDescList&, Jrd::RelationPages*, bool sysRq = false); -bool BTR_activate_index(Jrd::thread_db*, Jrd::Cached::Relation*, MetaId); +void BTR_all(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::IndexDescList&, bool sysRq = false); +bool BTR_activate_index(Jrd::thread_db*, Jrd::jrd_rel*, MetaId); bool BTR_cleanup_index(Jrd::thread_db*, const Jrd::QualifiedName&, Jrd::jrd_tra*, MetaId); void BTR_complement_key(Jrd::temporary_key*); void BTR_create(Jrd::thread_db*, Jrd::IndexCreation&, Jrd::SelectivityList&); @@ -47,7 +47,7 @@ Ods::btree_page* BTR_find_page(Jrd::thread_db*, const Jrd::IndexRetrieval*, Jrd: void BTR_insert(Jrd::thread_db*, Jrd::win*, Jrd::index_insertion*); USHORT BTR_key_length(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::index_desc*); Ods::btree_page* BTR_left_handoff(Jrd::thread_db*, Jrd::win*, Ods::btree_page*, SSHORT); -bool BTR_lookup(Jrd::thread_db*, Jrd::Cached::Relation*, MetaId, Jrd::index_desc*, Jrd::RelationPages*); +bool BTR_lookup(Jrd::thread_db*, Jrd::jrd_rel*, MetaId, Jrd::index_desc*); bool BTR_make_bounds(Jrd::thread_db*, const Jrd::IndexRetrieval*, Jrd::IndexScanListIterator*, Jrd::temporary_key*, Jrd::temporary_key*, USHORT&); Jrd::idx_e BTR_make_key(Jrd::thread_db*, USHORT, const Jrd::ValueExprNode* const*, const SSHORT*, @@ -55,14 +55,14 @@ Jrd::idx_e BTR_make_key(Jrd::thread_db*, USHORT, const Jrd::ValueExprNode* const void BTR_make_null_key(Jrd::thread_db*, const Jrd::index_desc*, Jrd::temporary_key*); void BTR_mark_index_for_delete(Jrd::thread_db*, Jrd::RelationPermanent*, MetaId, Jrd::win*, Ods::index_root_page*, TraNumber tran); -void BTR_merge_index(Jrd::thread_db* tdbb, Jrd::jrd_tra* transaction, Jrd::jrd_rel* relation, - Jrd::index_desc* idx, Jrd::PageNumber srcRootPage); -bool BTR_next_index(Jrd::thread_db*, Jrd::Cached::Relation*, Jrd::jrd_tra*, Jrd::index_desc*, Jrd::win*, +void BTR_merge_index(Jrd::thread_db* tdbb, const Jrd::IndexCreation&, const Jrd::PageNumber& srcRootPage); +bool BTR_next_index(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::jrd_tra*, Jrd::index_desc*, Jrd::win*, Jrd::RelationPages* = nullptr); void BTR_remove(Jrd::thread_db*, Jrd::win*, Jrd::index_insertion*); void BTR_reserve_slot(Jrd::thread_db*, Jrd::IndexCreation&); -void BTR_selectivity(Jrd::thread_db*, Jrd::Cached::Relation*, MetaId, Jrd::SelectivityList&); +void BTR_selectivity(Jrd::thread_db*, Jrd::jrd_rel*, MetaId, Jrd::SelectivityList&); bool BTR_types_comparable(const dsc& target, const dsc& source); +void BTR_copy_index(Jrd::thread_db*, Jrd::jrd_rel*, MetaId, ULONG); Ods::index_root_page* BTR_fetch_root_for_update(const char* from, Jrd::thread_db* tdbb, Jrd::win* window); const Ods::index_root_page* BTR_fetch_root(const char* from, Jrd::thread_db* tdbb, Jrd::win* window); diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 74b94d9ac9b..bd0be62a860 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:2210 + FORMAL BUILD NUMBER:2401 */ -#define PRODUCT_VER_STRING "6.0.0.2210" -#define FILE_VER_STRING "WI-T6.0.0.2210" -#define LICENSE_VER_STRING "WI-T6.0.0.2210" -#define FILE_VER_NUMBER 6, 0, 0, 2210 +#define PRODUCT_VER_STRING "6.0.0.2401" +#define FILE_VER_STRING "WI-T6.0.0.2401" +#define LICENSE_VER_STRING "WI-T6.0.0.2401" +#define FILE_VER_NUMBER 6, 0, 0, 2401 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "2210" +#define FB_BUILD_NO "2401" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp index b38f17f5e3e..b52272e6286 100644 --- a/src/jrd/cch.cpp +++ b/src/jrd/cch.cpp @@ -191,7 +191,7 @@ static inline void removeDirty(BufferControl* bcb, BufferDesc* bdb) } static void flushDirty(thread_db* tdbb, SLONG transaction_mask, const bool sys_only); -static void flushAll(thread_db* tdbb, USHORT flush_flag); +static void flushAll(thread_db* tdbb, USHORT flush_flag, ULONG page_space_id); static void flushPages(thread_db* tdbb, USHORT flush_flag, BufferDesc** begin, FB_SIZE_T count); static void recentlyUsed(BufferDesc* bdb); @@ -301,11 +301,11 @@ void CCH_clean_page(thread_db* tdbb, PageNumber page) * **************************************/ SET_TDBB(tdbb); - Database* dbb = tdbb->getDatabase(); + const auto dbb = tdbb->getDatabase(); fb_assert(page.getPageNum() > 0); - fb_assert(page.isTemporary()); - if (!page.isTemporary()) + fb_assert(PageSpace::isTemporary(page.getPageSpaceID())); + if (!PageSpace::isTemporary(page.getPageSpaceID())) return; BufferControl* bcb = dbb->dbb_bcb; @@ -1189,7 +1189,7 @@ void CCH_fini(thread_db* tdbb) } -void CCH_flush(thread_db* tdbb, USHORT flush_flag, TraNumber tra_number) +void CCH_flush(thread_db* tdbb, USHORT flush_flag, TraNumber tra_number, ULONG page_space_id) { /************************************** * @@ -1231,7 +1231,7 @@ void CCH_flush(thread_db* tdbb, USHORT flush_flag, TraNumber tra_number) flushDirty(tdbb, transaction_mask, sys_only); } else - flushAll(tdbb, flush_flag); + flushAll(tdbb, flush_flag, page_space_id); // // Check if flush needed @@ -1440,7 +1440,7 @@ void CCH_get_related(thread_db* tdbb, PageNumber page, PagesArray &lowPages) } -pag* CCH_handoff(thread_db* tdbb, WIN* window, ULONG page, int lock, SCHAR page_type, +pag* CCH_handoff(thread_db* tdbb, WIN* window, PageNumber pageNumber, int lock, SCHAR page_type, int wait, const bool release_tail) { /************************************** @@ -1474,8 +1474,9 @@ pag* CCH_handoff(thread_db* tdbb, WIN* window, ULONG page, int lock, SCHAR page_ SET_TDBB(tdbb); - CCH_TRACE(("HANDOFF %d:%06d->%06d, %s", - window->win_page.getPageSpaceID(), window->win_page.getPageNum(), page, (lock >= LCK_write) ? "EX" : "SH")); + CCH_TRACE(("HANDOFF %d:%06d->%d:%06d, %s", + window->win_page.getPageSpaceID(), window->win_page.getPageNum(), + pageNumber.getPageSpaceID(), pageNumber.getPageNum(), (lock >= LCK_write) ? "EX" : "SH")); BufferDesc *bdb = window->win_bdb; @@ -1489,7 +1490,7 @@ pag* CCH_handoff(thread_db* tdbb, WIN* window, ULONG page, int lock, SCHAR page_ // If the 'from-page' and 'to-page' of the handoff are the // same and the latch requested is shared then downgrade it. - if ((window->win_page.getPageNum() == page) && (lock == LCK_read)) + if ((window->win_page == pageNumber) && (lock == LCK_read)) { if (bdb->ourExclusiveLock()) bdb->downgrade(SYNC_SHARED); @@ -1498,7 +1499,7 @@ pag* CCH_handoff(thread_db* tdbb, WIN* window, ULONG page, int lock, SCHAR page_ } WIN temp = *window; - window->win_page = PageNumber(window->win_page.getPageSpaceID(), page); + window->win_page = pageNumber; LockState must_read; if (bdb->bdb_bcb->bcb_flags & BCB_exclusive) @@ -1797,14 +1798,13 @@ void CCH_must_write(thread_db* tdbb, WIN* window) } bdb->bdb_flags |= BDB_must_write | BDB_dirty; - fb_assert((bdb->bdb_flags & BDB_nbak_state_lock) || - PageSpace::isTemporary(bdb->bdb_page.getPageSpaceID())); + fb_assert((bdb->bdb_flags & BDB_nbak_state_lock) || PageSpace::isTemporary(bdb->bdb_page)); } void CCH_precedence(thread_db* tdbb, WIN* window, ULONG pageNum) { - const USHORT pageSpaceID = pageNum > FIRST_PIP_PAGE ? + const ULONG pageSpaceID = pageNum > FIRST_PIP_PAGE ? window->win_page.getPageSpaceID() : DB_PAGE_SPACE; CCH_precedence(tdbb, window, PageNumber(pageSpaceID, pageNum)); @@ -1845,12 +1845,11 @@ void CCH_precedence(thread_db* tdbb, WIN* window, PageNumber page) * **************************************/ // If the page is zero, the caller isn't really serious - if (page.getPageNum() == 0) return; - // no need to support precedence for temporary pages - if (page.isTemporary() || window->win_page.isTemporary()) + // No need to support precedence for temporary pages + if (PageSpace::isTemporary(page) || PageSpace::isTemporary(window->win_page)) return; check_precedence(tdbb, window, page); @@ -1945,9 +1944,7 @@ bool set_diff_page(thread_db* tdbb, BufferDesc* bdb) BackupManager* const bm = dbb->dbb_backup_manager; // Temporary pages don't write to delta and need no SCN - PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(bdb->bdb_page.getPageSpaceID()); - fb_assert(pageSpace); - if (pageSpace->isTemporary()) + if (PageSpace::isTemporary(bdb->bdb_page)) return true; // Take backup state lock @@ -2711,7 +2708,7 @@ static void flushDirty(thread_db* tdbb, SLONG transaction_mask, const bool sys_o // Collect pages modified by garbage collector or all dirty pages or release page // locks - depending of flush_flag, and write it to disk. // See also comments in flushPages. -static void flushAll(thread_db* tdbb, USHORT flush_flag) +static void flushAll(thread_db* tdbb, USHORT flush_flag, ULONG page_space_id) { SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); @@ -2733,28 +2730,32 @@ static void flushAll(thread_db* tdbb, USHORT flush_flag) { BufferDesc* bdb = &blk.m_bdbs[i]; - if (bdb->bdb_flags & (BDB_db_dirty | BDB_dirty)) + if (page_space_id == INVALID_PAGE_SPACE || + (bdb->bdb_page.isValid() && bdb->bdb_page.getPageSpaceID() == page_space_id)) { - if (bdb->bdb_flags & BDB_dirty) - flush.add(bdb); - else if (bdb->bdb_flags & BDB_db_dirty) + if (bdb->bdb_flags & (BDB_db_dirty | BDB_dirty)) { - // pages modified by sweep\garbage collector are not in dirty list - const bool dirty_list = (bdb->bdb_dirty.que_forward != &bdb->bdb_dirty); - - if (all_flag || (sweep_flag && !dirty_list)) + if (bdb->bdb_flags & BDB_dirty) flush.add(bdb); + else if (bdb->bdb_flags & BDB_db_dirty) + { + // pages modified by sweep\garbage collector are not in dirty list + const bool dirty_list = (bdb->bdb_dirty.que_forward != &bdb->bdb_dirty); + + if (all_flag || (sweep_flag && !dirty_list)) + flush.add(bdb); + } } - } - else if (release_flag) - { - bdb->addRef(tdbb, SYNC_EXCLUSIVE); + else if (release_flag) + { + bdb->addRef(tdbb, SYNC_EXCLUSIVE); - if (bdb->bdb_use_count > 1) - BUGCHECK(210); // msg 210 page in use during flush + if (bdb->bdb_use_count > 1) + BUGCHECK(210); // msg 210 page in use during flush - PAGE_LOCK_RELEASE(tdbb, bcb, bdb->bdb_lock); - bdb->release(tdbb, false); + PAGE_LOCK_RELEASE(tdbb, bcb, bdb->bdb_lock); + bdb->release(tdbb, false); + } } } } @@ -3202,7 +3203,9 @@ static void check_precedence(thread_db* tdbb, WIN* window, PageNumber page) // If this is really a transaction id, sort things out - switch(page.getPageSpaceID()) + const ULONG pageSpaceId = page.getPageSpaceID(); + + switch (pageSpaceId) { case DB_PAGE_SPACE: break; @@ -3214,6 +3217,8 @@ static void check_precedence(thread_db* tdbb, WIN* window, PageNumber page) break; default: + if (PageSpace::isTablespace(pageSpaceId)) + break; fb_assert(false); return; } @@ -5097,7 +5102,7 @@ static void clear_dirty_flag_and_nbak_state(thread_db* tdbb, BufferDesc* bdb) tdbb->getDatabase()->dbb_backup_manager->unlockStateRead(tdbb); } else if ((oldFlags & BDB_dirty) && bdb->bdb_page != HEADER_PAGE_NUMBER) - fb_assert(PageSpace::isTemporary(bdb->bdb_page.getPageSpaceID())); + fb_assert(PageSpace::isTemporary(bdb->bdb_page)); } void recentlyUsed(BufferDesc* bdb) diff --git a/src/jrd/cch_proto.h b/src/jrd/cch_proto.h index ee57bade70d..17868c7c106 100644 --- a/src/jrd/cch_proto.h +++ b/src/jrd/cch_proto.h @@ -51,11 +51,11 @@ LockState CCH_fetch_lock(Jrd::thread_db*, Jrd::win*, int, int, SCHAR); void CCH_fetch_page(Jrd::thread_db*, Jrd::win*, const bool); void CCH_fini(Jrd::thread_db*); void CCH_forget_page(Jrd::thread_db*, Jrd::win*); -void CCH_flush(Jrd::thread_db* tdbb, USHORT flush_flag, TraNumber tra_number); +void CCH_flush(Jrd::thread_db* tdbb, USHORT flush_flag, TraNumber tra_number, ULONG page_space_id = Jrd::INVALID_PAGE_SPACE); bool CCH_free_page(Jrd::thread_db*); SLONG CCH_get_incarnation(Jrd::win*); void CCH_get_related(Jrd::thread_db*, Jrd::PageNumber, Jrd::PagesArray&); -Ods::pag* CCH_handoff(Jrd::thread_db*, Jrd::win*, ULONG, int, SCHAR, int, const bool); +Ods::pag* CCH_handoff(Jrd::thread_db*, Jrd::win*, Jrd::PageNumber, int, SCHAR, int, const bool); void CCH_init(Jrd::thread_db*, ULONG); void CCH_init2(Jrd::thread_db*); void CCH_mark(Jrd::thread_db*, Jrd::win*, bool, bool); @@ -116,17 +116,22 @@ inline void CCH_MARK_SYSTEM(Jrd::thread_db* tdbb, Jrd::win* window) inline Ods::pag* CCH_HANDOFF(Jrd::thread_db* tdbb, Jrd::win* window, ULONG page, SSHORT lock, SCHAR page_type) { - return CCH_handoff (tdbb, window, page, lock, page_type, 1, false); + return CCH_handoff (tdbb, window, Jrd::PageNumber(window->win_page.getPageSpaceID(), page), lock, page_type, 1, false); } inline Ods::pag* CCH_HANDOFF_TIMEOUT(Jrd::thread_db* tdbb, Jrd::win* window, ULONG page, SSHORT lock, SCHAR page_type, SSHORT latch_wait) { - return CCH_handoff (tdbb, window, page, lock, page_type, latch_wait, false); + return CCH_handoff (tdbb, window, Jrd::PageNumber(window->win_page.getPageSpaceID(), page), lock, page_type, latch_wait, false); } inline Ods::pag* CCH_HANDOFF_TAIL(Jrd::thread_db* tdbb, Jrd::win* window, ULONG page, SSHORT lock, SCHAR page_type) { - return CCH_handoff (tdbb, window, page, lock, page_type, 1, true); + return CCH_handoff (tdbb, window, Jrd::PageNumber(window->win_page.getPageSpaceID(), page), lock, page_type, 1, true); +} + +inline Ods::pag* CCH_HANDOFF(Jrd::thread_db* tdbb, Jrd::win* window, Jrd::PageNumber pageNumber, SSHORT lock, SCHAR page_type) +{ + return CCH_handoff (tdbb, window, pageNumber, lock, page_type, 1, false); } inline void CCH_MARK_MUST_WRITE(Jrd::thread_db* tdbb, Jrd::win* window) diff --git a/src/jrd/constants.h b/src/jrd/constants.h index 6d9d135a1e3..0ffbf758057 100644 --- a/src/jrd/constants.h +++ b/src/jrd/constants.h @@ -158,6 +158,7 @@ inline constexpr const char* FUNCTIONS_GENERATOR = "RDB$FUNCTIONS"; inline constexpr const char* PACKAGES_GENERATOR = "RDB$PACKAGES"; inline constexpr SLONG RELATIONS_GENERATOR = 13; +inline constexpr SLONG TABLESPACES_GENERATOR = 14; // Automatically created check constraints for unnamed PRIMARY and UNIQUE declarations. inline constexpr const char* IMPLICIT_INTEGRITY_PREFIX = "INTEG_"; @@ -432,7 +433,10 @@ static inline constexpr const char* DDL_TRIGGER_ACTION_NAMES[][2] = {"DROP", "MAPPING"}, {"CREATE", "SCHEMA"}, {"ALTER", "SCHEMA"}, - {"DROP", "SCHEMA"} + {"DROP", "SCHEMA"}, + {"CREATE", "TABLESPACE"}, + {"ALTER", "TABLESPACE"}, + {"DROP", "TABLESPACE"} }; inline constexpr int DDL_TRIGGER_BEFORE = 0; @@ -488,6 +492,9 @@ inline constexpr int DDL_TRIGGER_DROP_MAPPING = 47; inline constexpr int DDL_TRIGGER_CREATE_SCHEMA = 48; inline constexpr int DDL_TRIGGER_ALTER_SCHEMA = 49; inline constexpr int DDL_TRIGGER_DROP_SCHEMA = 50; +inline constexpr int DDL_TRIGGER_CREATE_TABLESPACE = 51; +inline constexpr int DDL_TRIGGER_ALTER_TABLESPACE = 52; +inline constexpr int DDL_TRIGGER_DROP_TABLESPACE = 53; // that's how database trigger action types are encoded // (TRIGGER_TYPE_DB | type) @@ -525,6 +532,10 @@ inline constexpr int WITH_ADMIN_OPTION = 2; // Max length of the string returned by ERROR_TEXT context variable inline constexpr USHORT MAX_ERROR_MSG_LENGTH = 1024 * METADATA_BYTES_PER_CHAR; // 1024 UTF-8 characters +// Tablespaces +const char* const PRIMARY_TABLESPACE_NAME = "PRIMARY"; +const char* const TEMP_TABLESPACE_NAME = "TEMPORARY"; + // Prefix of index that's getting dropped inline constexpr const char* TEMP_DEPEND = "RDB$TEMP_DEPEND"; diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index da4df039b3a..9c68b5be9b1 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -131,6 +131,8 @@ #include "../jrd/CryptoManager.h" #include "../jrd/Mapping.h" #include "../jrd/shut_proto.h" +#include "../jrd/Tablespace.h" +#include "../jrd/vio_proto.h" #include "../jrd/ProtectRelations.h" #include "../jrd/Package.h" @@ -419,6 +421,7 @@ static bool db_crypt(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool set_linger(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool clear_cache(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool change_repl_state(thread_db*, SSHORT, DeferredWork*, jrd_tra*); +static bool change_tablespace(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool set_statistics(thread_db*, SSHORT, DeferredWork*, jrd_tra*); static bool deps_to_disk(thread_db*, SSHORT, DeferredWork*, jrd_tra*); @@ -502,15 +505,14 @@ static void raiseObjectInUseError(const string& obj_type, const QualifiedName& o Arg::Gds(isc_obj_in_use) << name); } -void DFW_raiseRelationInUseError(const Cached::Relation* relation) +void DFW_raiseRelationInUseError(const RelationPermanent* relation) { - const string obj_type = - relation->isView() ? "VIEW" : "TABLE"; + const string obj_type = relation->isView() ? "VIEW" : "TABLE"; raiseObjectInUseError(obj_type, relation->getName()); } -void Jrd::ProtectRelations::relLock::takeLock(thread_db* tdbb, jrd_tra* transaction) +void ProtectRelations::RelationLock::takeLock(thread_db* tdbb, jrd_tra* transaction) { m_lock = RLCK_transaction_relation_lock(tdbb, transaction, m_relation); m_level = m_lock->lck_logical; @@ -538,7 +540,7 @@ void Jrd::ProtectRelations::relLock::takeLock(thread_db* tdbb, jrd_tra* transact } -void Jrd::ProtectRelations::relLock::releaseLock(thread_db* tdbb, jrd_tra* transaction) +void ProtectRelations::RelationLock::releaseLock(thread_db* tdbb, jrd_tra* transaction) { // Internal "compatible" locks requires special handling, details in lck.cpp @@ -1216,6 +1218,9 @@ static inline constexpr deferred_task task_table[] = { dfw_set_linger, set_linger }, { dfw_clear_cache, clear_cache }, { dfw_change_repl_state, change_repl_state }, + { dfw_create_tablespace, change_tablespace }, + { dfw_delete_tablespace, change_tablespace }, + { dfw_modify_tablespace, change_tablespace }, { dfw_set_statistics, set_statistics }, { dfw_create_package_constant, createOrAlterConstant }, { dfw_modify_package_constant, createOrAlterConstant }, @@ -1530,6 +1535,7 @@ void DFW_perform_work(thread_db* tdbb, jrd_tra* transaction) { case dfw_post_event: case dfw_delete_shadow: + case dfw_delete_tablespace: break; default: @@ -1545,8 +1551,7 @@ void DFW_perform_work(thread_db* tdbb, jrd_tra* transaction) } } - -void DFW_perform_post_commit_work(jrd_tra* transaction) +void DFW_perform_post_commit_work(thread_db* tdbb, jrd_tra* transaction) { /************************************** * @@ -1568,38 +1573,54 @@ void DFW_perform_post_commit_work(jrd_tra* transaction) bool pending_events = false; - Database* dbb = GET_DBB(); + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); for (DeferredWork* itr = transaction->tra_deferred_job->work; itr;) { - DeferredWork* work = itr; + AutoPtr work(itr); itr = itr->getNext(); - switch (work->dfw_type) + try { - case dfw_post_event: - EventManager::init(transaction->tra_attachment); + switch (work->dfw_type) + { + case dfw_post_event: + EventManager::init(transaction->tra_attachment); - dbb->eventManager()->postEvent(work->dfw_name.length(), - work->dfw_name.c_str(), - work->dfw_count); + dbb->eventManager()->postEvent(work->dfw_name.length(), + work->dfw_name.c_str(), + work->dfw_count); - delete work; - pending_events = true; - break; - case dfw_delete_shadow: - if (work->dfw_name.hasData()) - unlink(work->dfw_name.c_str()); - delete work; - break; - default: - break; + pending_events = true; + break; + + case dfw_delete_tablespace: + case dfw_delete_shadow: + if (work->dfw_name.hasData()) + unlink(work->dfw_name.c_str()); + break; + + default: + break; + } + } + catch (const Exception& ex) + { + iscLogException("Post-commit error", ex); } } if (pending_events) { - dbb->eventManager()->deliverEvents(); + try + { + dbb->eventManager()->deliverEvents(); + } + catch (const Exception& ex) + { + iscLogException("Event delivery error", ex); + } } } @@ -2753,7 +2774,7 @@ void DFW_reset_icu(thread_db* tdbb) if (!tables.exists(rel_id)) { - if (auto* relation = MetadataCache::getPerm(tdbb, rel_id, CacheFlag::AUTOCREATE)) + if (const auto relation = MetadataCache::getPerm(tdbb, rel_id, CacheFlag::AUTOCREATE)) tables.addRelation(relation); } } @@ -3611,19 +3632,19 @@ static bool set_statistics(thread_db* tdbb, SSHORT phase, DeferredWork* work, jr if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0) { // we need to know if this relation is temporary or not - Cached::Relation* relation = - MetadataCache::getPerm(tdbb, work->dfw_id, CacheFlag::AUTOCREATE); + const auto relation = + MetadataCache::getVersioned(tdbb, work->dfw_id, CacheFlag::AUTOCREATE); // no need to recalculate statistics for base instance of GTT - RelationPages* relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false); + const auto relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false); const bool isTempInstance = relation->isTemporary() && - relPages && (relPages->rel_instance_id != 0); + relPages && (relPages->getInstanceId() != 0); if (isTempInstance || !relation->isTemporary()) { SelectivityList selectivity(*tdbb->getDefaultPool()); const USHORT id = IDX.RDB$INDEX_ID - 1; - IDX_statistics(tdbb, relation, id, selectivity); + BTR_selectivity(tdbb, relation, id, selectivity); DFW_update_index(work->getQualifiedName(), id, selectivity, transaction); } } @@ -3664,7 +3685,7 @@ static bool delete_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ case 3: { - auto* relation = MetadataCache::getPerm(tdbb, work->dfw_id, CacheFlag::ERASED); + const auto relation = MetadataCache::getPerm(tdbb, work->dfw_id, CacheFlag::ERASED); if (!relation) break; @@ -3675,7 +3696,7 @@ static bool delete_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ case 4: { - auto* relation = MetadataCache::getPerm(tdbb, work->dfw_id, CacheFlag::ERASED); + const auto relation = MetadataCache::getPerm(tdbb, work->dfw_id, CacheFlag::ERASED); if (!relation) break; @@ -4604,7 +4625,7 @@ static bool check_not_null(thread_db* tdbb, SSHORT phase, DeferredWork* work, jr break; // Protect relation from modification - ProtectRelations protectRelation(tdbb, transaction, relation->getPermanent()); + ProtectRelations protectRelation(tdbb, transaction, relation); SortedArray fields; AutoRequest handle; @@ -5037,11 +5058,13 @@ static bool delete_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, // Get the relation pages const auto relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false); - if (!relPages || !relPages->rel_index_root) + const auto rootPage = relPages ? relPages->getIndexRootPage() : std::nullopt; + + if (!rootPage) return false; // Delete the index by its ID - WIN window(relPages->rel_pg_space_id, relPages->rel_index_root); + WIN window(rootPage.value()); CCH_FETCH(tdbb, &window, LCK_write, pag_root); BTR_delete_index(tdbb, &window, indexId, false); @@ -5107,9 +5130,9 @@ static bool modify_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, // Recalculate index statistics SelectivityList selectivity(*tdbb->getDefaultPool()); - IDX_statistics(tdbb, relation->getPermanent(), lttIndex->id, selectivity); + BTR_selectivity(tdbb, relation, lttIndex->id, selectivity); - // The selectivity is stored directly in the index root page by IDX_statistics, + // The selectivity is stored directly in the index root page by BTR_selectivity, // so we don't need to update any system tables (LTT indexes don't have entries in RDB$INDICES) } @@ -5175,3 +5198,36 @@ static bool deps_to_disk(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ return false; } + +static bool change_tablespace(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction) +{ +/************************************** + * + * c h a n g e _ t a b l e s p a c e + * + ************************************** + * + * Functional description + * Make the tablespace modifications permanent. + * + **************************************/ + const auto dbb = tdbb->getDatabase(); + + switch (phase) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + return true; + + case 7: + if (const auto tableSpace = dbb->dbb_tablespaces.get(work->dfw_id)) + tableSpace->commit(tdbb, transaction); + break; + } + + return false; +} diff --git a/src/jrd/dfw_proto.h b/src/jrd/dfw_proto.h index 64d592c4a42..20e346dc30c 100644 --- a/src/jrd/dfw_proto.h +++ b/src/jrd/dfw_proto.h @@ -43,8 +43,8 @@ void DFW_delete_deferred(Jrd::jrd_tra*, SavNumber); Firebird::SortedArray& DFW_get_ids(Jrd::DeferredWork* work); void DFW_merge_work(Jrd::jrd_tra*, SavNumber, SavNumber); void DFW_perform_work(Jrd::thread_db*, Jrd::jrd_tra*); -void DFW_perform_post_commit_work(Jrd::jrd_tra*); -void DFW_raiseRelationInUseError(const Jrd::Cached::Relation*); +void DFW_perform_post_commit_work(Jrd::thread_db*, Jrd::jrd_tra*); +void DFW_raiseRelationInUseError(const Jrd::RelationPermanent*); Jrd::DeferredWork* DFW_post_work(Jrd::jrd_tra*, Jrd::dfw_t, const dsc* nameDesc, const dsc* schemaDesc, USHORT, const Jrd::MetaName& package = {}); Jrd::DeferredWork* DFW_post_work(Jrd::jrd_tra*, Jrd::dfw_t, const Firebird::string&, const Jrd::MetaName& schema, diff --git a/src/jrd/dpm.epp b/src/jrd/dpm.epp index e9ff0f91869..733d3ed49d1 100644 --- a/src/jrd/dpm.epp +++ b/src/jrd/dpm.epp @@ -64,6 +64,7 @@ #include "../jrd/tpc_proto.h" #include "../jrd/replication/Publisher.h" #include "../common/StatusArg.h" +#include "../jrd/ini.h" DATABASE DB = FILENAME "ODS.RDB"; @@ -80,12 +81,12 @@ inline constexpr SSHORT MRK_commit = 256; // Table to go into pag_drop after tr inline constexpr SSHORT MRK_drop = 257; // Table to be dropped when OAT >= next transaction at mark-set time inline constexpr SSHORT MRK_rollback = 258; // Table to be dropped after transaction rollback -static void set_marker(thread_db*, SSHORT, SSHORT, TraNumber); +static void set_marker(thread_db*, MetaId, ULONG, SSHORT, TraNumber); static void check_swept(thread_db*, record_param*); static USHORT compress(thread_db*, data_page*); -static void delete_tail(thread_db*, rhdf*, const USHORT, USHORT); +static void delete_tail(thread_db*, rhdf*, const ULONG, USHORT); static void fragment(thread_db*, record_param*, SSHORT, Compressor&, SSHORT, const jrd_tra*); -static USHORT extend_relation(thread_db*, Cached::Relation*, WIN*, const Jrd::RecordStorageType type, bool reserve); +static USHORT extend_relation(thread_db*, jrd_rel*, WIN*, const Jrd::RecordStorageType type, bool reserve); static UCHAR* find_space(thread_db*, record_param*, SSHORT, PageStack&, Record*, const Jrd::RecordStorageType type); static bool get_header(WIN*, USHORT, record_param*); static pointer_page* get_pointer_page(thread_db*, RelationPermanent*, RelationPages*, WIN*, ULONG, USHORT); @@ -134,10 +135,60 @@ namespace return (TraNumber(sequence) << 32) + number; } - inline bool swept_at_restore(thread_db* tdbb, const Cached::Relation* relation) + inline bool swept_at_restore(thread_db* tdbb, const jrd_rel* relation) { return tdbb->getDatabase()->isRestoring() && !relation->isSystem(); } + + void fixupRdbPages(thread_db* tdbb) + { + // Special case update of RDB$PAGES pointer page vector to avoid + // infinite recursion from this internal request when RDB$PAGES + // has been extended with another pointer page. + + const auto relation = MetadataCache::getVersioned(tdbb, rel_pages, + CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); + const auto relPages = relation->getBasePages(); + + if (const auto ppCount = relPages->getPointerPageCount()) + { + const ULONG sequence = ppCount - 1; + + if (const auto ppNumber = relPages->getPointerPage(sequence)) + { + WIN window(ppNumber.value()); + pointer_page* ppage = (pointer_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_pointer); + + HalfStaticArray newPointerPages; + while (ppage->ppg_next) + { + newPointerPages.add(ppage->ppg_next); + ppage = (pointer_page*) CCH_HANDOFF(tdbb, &window, ppage->ppg_next, LCK_read, pag_pointer); + } + + CCH_RELEASE(tdbb, &window); + + if (const auto newCount = (ULONG) newPointerPages.getCount()) + relPages->updatePointerPages(sequence, newCount, newPointerPages.begin()); + } + + if (!relPages->hasIndices()) + { + // Try to guess IRT number for RDB$PAGES, it should be next page after first PP + if (const auto firstPage = relPages->getPointerPage(0)) + { + const auto firstPageNum = firstPage.value(); + WIN window(firstPageNum.getPageSpaceID(), firstPageNum.getPageNum() + 1); + index_root_page* root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_undefined); + + if (root->irt_header.pag_type == pag_root && root->irt_relation == rel_pages) + relPages->setIndexRootPage(window.win_page); + + CCH_RELEASE(tdbb, &window); + } + } + } + } } @@ -276,23 +327,25 @@ double DPM_cardinality(thread_db* tdbb, jrd_rel* relation, const Format* format) // Get the number of data pages for this relation - const ULONG dataPages = DPM_data_pages(tdbb, getPermanent(relation)); + const ULONG dataPages = DPM_data_pages(tdbb, relation); // Calculate record count and total compressed record length // on the first data page ULONG recordCount = 0, recordLength = 0; - RelationPages* const relPages = relation->getPages(tdbb); - if (relPages->rel_pages) + const auto relPages = relation->getPages(tdbb); + if (relPages->hasData()) { + const ULONG pageSpaceId = relPages->getPageSpaceId(); + bool done = false; for (ULONG sequence = 0; !done; sequence++) { - WIN window(relPages->rel_pg_space_id, -1); + WIN window(pageSpaceId); const pointer_page* ppage = - get_pointer_page(tdbb, getPermanent(relation), relPages, &window, sequence, LCK_read); + get_pointer_page(tdbb, relation->getPermanent(), relPages, &window, sequence, LCK_read); if (!ppage) { BUGCHECK(243); @@ -585,7 +638,7 @@ bool DPM_chain( thread_db* tdbb, record_param* org_rpb, record_param* new_rpb) } -void DPM_create_relation( thread_db* tdbb, Cached::Relation* relation) +void DPM_create_relation( thread_db* tdbb, jrd_rel* relation) { /************************************** * @@ -604,25 +657,29 @@ void DPM_create_relation( thread_db* tdbb, Cached::Relation* relation) "DPM_create_relation (relation %d)\n", relation->getId()); #endif - RelationPages* relPages = relation->getBasePages(); - DPM_create_relation_pages(tdbb, relation, relPages); + const auto relPages = relation->getBasePages(); + const auto pageSpaceId = relPages->getPageSpaceId(); + DPM_create_relation_pages(tdbb, relation->getPermanent(), relPages); // Store page numbers in RDB$PAGES - DPM_pages(tdbb, relation->getId(), pag_pointer, (ULONG) 0, - (*relPages->rel_pages)[0] /*window.win_page*/); - DPM_pages(tdbb, relation->getId(), pag_root, (ULONG) 0, - relPages->rel_index_root /*root_window.win_page*/); + + const auto firstPointerPage = relPages->getPointerPage(0); + fb_assert(firstPointerPage); + DPM_pages(tdbb, relation->getId(), pag_pointer, (ULONG) 0, firstPointerPage.value()); + + const auto rootPage = relPages->getIndexRootPage(); + fb_assert(rootPage); + DPM_pages(tdbb, relation->getId(), pag_root, (ULONG) 0, rootPage.value()); // Special record directing remove table when transaction rolled back - if (auto tra = tdbb->getTransaction()) - { - if (auto num = tra->tra_number) - set_marker(tdbb, relation->getId(), MRK_rollback, num); - } + const auto transaction = tdbb->getTransaction(); + fb_assert(transaction); + if (const auto traNumber = transaction->tra_number) + set_marker(tdbb, relation->getId(), pageSpaceId, MRK_rollback, traNumber); } -void DPM_mark_relation( thread_db* tdbb, Cached::Relation* relation) +void DPM_mark_relation( thread_db* tdbb, jrd_rel* relation) { /************************************** * @@ -637,12 +694,16 @@ void DPM_mark_relation( thread_db* tdbb, Cached::Relation* relation) SET_TDBB(tdbb); // Add special record directing switch to MRK_drop state when transaction committed - if (auto tra = tdbb->getTransaction()) + if (const auto transaction = tdbb->getTransaction()) { - if (auto num = tra->tra_number) + if (const auto traNumber = transaction->tra_number) { DPM_scan_marker(tdbb, relation->getId()); - set_marker(tdbb, relation->getId(), MRK_commit, num); + + const auto relPages = relation->getBasePages(); + const auto pageSpaceId = relPages->getPageSpaceId(); + + set_marker(tdbb, relation->getId(), pageSpaceId, MRK_commit, traNumber); } } } @@ -655,7 +716,7 @@ void DPM_create_relation_pages(thread_db* tdbb, RelationPermanent* relation, Rel CHECK_DBB(dbb); // Allocate first pointer page - WIN window(relPages->rel_pg_space_id, -1); + WIN window(relPages->getPageSpaceId()); pointer_page* page = (pointer_page*) DPM_allocate(tdbb, &window); page->ppg_header.pag_type = pag_pointer; page->ppg_relation = relation->getId(); @@ -664,7 +725,7 @@ void DPM_create_relation_pages(thread_db* tdbb, RelationPermanent* relation, Rel // If this is relation 0 (RDB$PAGES), update the header - if (relation->getId() == 0) + if (relation->getId() == rel_pages) { WIN root_window(HEADER_PAGE_NUMBER); header_page* header = (header_page*) CCH_FETCH(tdbb, &root_window, LCK_write, pag_header); @@ -676,27 +737,22 @@ void DPM_create_relation_pages(thread_db* tdbb, RelationPermanent* relation, Rel // Keep track in memory of the first pointer page - if (!relPages->rel_pages) - { - vcl* vector = vcl::newVector(relation->getPool(), 1); - relPages->rel_pages = vector; - } - (*relPages->rel_pages)[0] = window.win_page.getPageNum(); + relPages->init(window.win_page.getPageNum()); // CVC: AFAIK, DPM_allocate calls PAG_allocate and neither of them cares about win_page. // Therefore, I decided that the root_window in the if() above and this one aren't related. // Create an index root page - WIN root_window(relPages->rel_pg_space_id, -1); + WIN root_window(relPages->getPageSpaceId()); index_root_page* root = (index_root_page*) DPM_allocate(tdbb, &root_window); root->irt_header.pag_type = pag_root; root->irt_relation = relation->getId(); //root->irt_count = 0; CCH_RELEASE(tdbb, &root_window); - relPages->rel_index_root = root_window.win_page.getPageNum(); + relPages->setIndexRootPage(root_window.win_page); } -ULONG DPM_data_pages(thread_db* tdbb, Cached::Relation* relation) +ULONG DPM_data_pages(thread_db* tdbb, jrd_rel* relation) { /************************************** * @@ -718,15 +774,17 @@ ULONG DPM_data_pages(thread_db* tdbb, Cached::Relation* relation) "DPM_data_pages (relation %d)\n", relation->getId()); #endif - RelationPages* relPages = relation->getPages(tdbb); + const auto relPages = relation->getPages(tdbb); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + ULONG pages = relPages->rel_data_pages; if (!pages) { - WIN window(relPages->rel_pg_space_id, -1); + WIN window(pageSpaceId); for (ULONG sequence = 0; true; sequence++) { const pointer_page* ppage = - get_pointer_page(tdbb, relation, relPages, &window, sequence, LCK_read); + get_pointer_page(tdbb, relation->getPermanent(), relPages, &window, sequence, LCK_read); if (!ppage) { BUGCHECK(243); @@ -897,14 +955,14 @@ void DPM_delete( thread_db* tdbb, record_param* rpb, ULONG prior_page) DECOMPOSE(sequence, dbb->dbb_dp_per_pp, pp_sequence, slot); RelationPages* relPages = NULL; - WIN pwindow(DB_PAGE_SPACE, -1); + WIN pwindow(DB_PAGE_SPACE); // Will be initialized by pagespace of relation later for (;;) { relPages = rpb->rpb_relation->getPages(tdbb); - pwindow = WIN(relPages->rel_pg_space_id, -1); + pwindow = WIN(relPages->getPageSpaceId()); - if (!(ppage = get_pointer_page(tdbb, getPermanent(rpb->rpb_relation), relPages, &pwindow, + if (!(ppage = get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, &pwindow, pp_sequence, LCK_write))) { BUGCHECK(245); // msg 245 pointer page disappeared in DPM_delete @@ -992,7 +1050,7 @@ void DPM_delete( thread_db* tdbb, record_param* rpb, ULONG prior_page) { if (s != slot) { - WIN dp_window(relPages->rel_pg_space_id, pages[i]); + WIN dp_window(relPages->toPageNumber(pages[i])); data_page* dpage = (data_page*) CCH_FETCH(tdbb, &dp_window, LCK_read, pag_data); if (dpage->dpg_count) { @@ -1075,12 +1133,12 @@ void DPM_delete( thread_db* tdbb, record_param* rpb, ULONG prior_page) // Earlier, we make sure that the pointer page is written after the data // page being released. - PAG_release_pages(tdbb, relPages->rel_pg_space_id, pages.getCount(), pages.begin(), + PAG_release_pages(tdbb, relPages->getPageSpaceId(), pages.getCount(), pages.begin(), pwindow.win_page.getPageNum()); } -void DPM_delete_relation( thread_db* tdbb, RelationPermanent* relation) +void DPM_delete_relation(thread_db* tdbb, RelationPermanent* relation, RelationPages* relPages) { /************************************** * @@ -1094,19 +1152,19 @@ void DPM_delete_relation( thread_db* tdbb, RelationPermanent* relation) **************************************/ fb_assert(!(relation->rel_flags & REL_ltt_created)); - SET_TDBB(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); - - RelationPages* relPages = relation->getBasePages(); - DPM_delete_relation_pages(tdbb, relation, relPages); + if (relPages->hasData()) + DPM_delete_relation_pages(tdbb, relation, relPages); // Next, cancel out stuff from RDB$PAGES + const auto attachment = tdbb->getAttachment(); + static const CachedRequestId requestCacheId; AutoCacheRequest handle(tdbb, requestCacheId); FOR(REQUEST_HANDLE handle) X IN RDB$PAGES WITH X.RDB$RELATION_ID EQ relation->getId() + AND X.RDB$TABLESPACE_ID EQ relPages->getPageSpaceId() { ERASE X; } @@ -1116,24 +1174,23 @@ void DPM_delete_relation( thread_db* tdbb, RelationPermanent* relation) } -void DPM_delete_relation_pages(Jrd::thread_db* tdbb, Jrd::RelationPermanent* relation, - Jrd::RelationPages* relPages) +void DPM_delete_relation_pages(thread_db* tdbb, RelationPermanent* relation, RelationPages* relPages) { SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); - WIN window(relPages->rel_pg_space_id, -1), data_window(relPages->rel_pg_space_id, -1); + WIN window(relPages->getPageSpaceId()), data_window(relPages->getPageSpaceId()); window.win_flags = data_window.win_flags = WIN_large_scan; window.win_scans = data_window.win_scans = 1; #ifdef VIO_DEBUG VIO_trace(DEBUG_TRACE_ALL, "DPM_delete_relation_pages (relation %d, instance %" SQUADFORMAT")\n", - relation->getId(), relPages->rel_instance_id); + relationId, relPages->getInstanceId()); #endif // Delete all data and pointer pages - SortedArray > pages(relation->getPool()); + SortedArray > pages; for (ULONG sequence = 0; true; sequence++) { @@ -1169,7 +1226,7 @@ void DPM_delete_relation_pages(Jrd::thread_db* tdbb, Jrd::RelationPermanent* rel rhd* header = (rhd*) ((UCHAR *) dpage + line->dpg_offset); if (header->rhd_flags & rhd_large) { - delete_tail(tdbb, (rhdf*)header, relPages->rel_pg_space_id, + delete_tail(tdbb, (rhdf*)header, relPages->getPageSpaceId(), line->dpg_length); } } @@ -1184,28 +1241,26 @@ void DPM_delete_relation_pages(Jrd::thread_db* tdbb, Jrd::RelationPermanent* rel CCH_RELEASE_TAIL(tdbb, &window); pages.add(window.win_page.getPageNum()); - PAG_release_pages(tdbb, relPages->rel_pg_space_id, pages.getCount(), pages.begin(), 0); + PAG_release_pages(tdbb, relPages->getPageSpaceId(), pages.getCount(), pages.begin(), 0); if (pag_flags & ppg_eof) break; } - delete relPages->rel_pages; - relPages->rel_pages = NULL; - relPages->rel_data_pages = 0; - // Now get rid of the index root page - PAG_release_page(tdbb, - PageNumber(relPages->rel_pg_space_id, relPages->rel_index_root), ZERO_PAGE_NUMBER); - relPages->rel_index_root = 0; + const auto rootPage = relPages->getIndexRootPage(); + fb_assert(rootPage); + PAG_release_page(tdbb, rootPage.value(), ZERO_PAGE_NUMBER); + + relPages->reset(); } // Reserve pages for bulk insert, return count of reserved pages. // window points to the first reserved page locked for write. USHORT DPM_reserve_pages(thread_db* tdbb, jrd_rel* relation, WIN* window) { - return extend_relation(tdbb, relation->getPermanent(), window, DPM_primary, true); + return extend_relation(tdbb, relation, window, DPM_primary, true); } bool DPM_fetch(thread_db* tdbb, record_param* rpb, USHORT lock) @@ -1237,8 +1292,8 @@ bool DPM_fetch(thread_db* tdbb, record_param* rpb, USHORT lock) #endif const RecordNumber number = rpb->rpb_number; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); - rpb->getWindow(tdbb).win_page = PageNumber(relPages->rel_pg_space_id, rpb->rpb_page); + const auto relPages = rpb->rpb_relation->getPages(tdbb); + rpb->getWindow(tdbb).win_page = relPages->toPageNumber(rpb->rpb_page); CCH_FETCH(tdbb, &rpb->getWindow(tdbb), lock, pag_data); if (!get_header(&rpb->getWindow(tdbb), rpb->rpb_line, rpb)) @@ -1439,11 +1494,11 @@ SINT64 DPM_gen_id(thread_db* tdbb, SLONG generator, bool initialize, SINT64 val) const USHORT sequence = generator / dbb->dbb_page_manager.gensPerPage; const USHORT offset = generator % dbb->dbb_page_manager.gensPerPage; - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); ULONG pageNumber = dbb->getKnownPage(pag_ids, sequence); if (!pageNumber) { - DPM_scan_pages(tdbb); + DPM_scan_pages(tdbb, pag_ids); pageNumber = dbb->getKnownPage(pag_ids, sequence); if (!pageNumber) @@ -1456,7 +1511,7 @@ SINT64 DPM_gen_id(thread_db* tdbb, SLONG generator, bool initialize, SINT64 val) pageNumber = window.win_page.getPageNum(); dbb->setKnownPage(pag_ids, sequence, pageNumber); - DPM_pages(tdbb, 0, pag_ids, sequence, pageNumber); + DPM_pages(tdbb, 0, pag_ids, sequence, window.win_page); } } @@ -1559,7 +1614,7 @@ bool DPM_get(thread_db* tdbb, record_param* rpb, SSHORT lock_type) if (rpb->rpb_number.getValue() < 0) return false; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); + const auto relPages = rpb->rpb_relation->getPages(tdbb); const ULONG dpSequence = rpb->rpb_number.getValue() / dbb->dbb_max_records; ULONG page_number = relPages->getDPNumber(dpSequence); @@ -1589,7 +1644,7 @@ bool DPM_get(thread_db* tdbb, record_param* rpb, SSHORT lock_type) } // Find the pointer page, data page, and record - pointer_page* page = get_pointer_page(tdbb, getPermanent(rpb->rpb_relation), + pointer_page* page = get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, LCK_read); if (!page) @@ -1643,6 +1698,8 @@ ULONG DPM_get_blob(thread_db* tdbb, Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); + const auto relPages = relation->getPages(tdbb); + record_param rpb; rpb.rpb_relation = relation; rpb.getWindow(tdbb).win_flags = WIN_secondary; @@ -1664,8 +1721,8 @@ ULONG DPM_get_blob(thread_db* tdbb, // record doesn't exist, or the record isn't a blob, give up and // let somebody else complain. - pointer_page* ppage = get_pointer_page(tdbb, relation->getPermanent(), - relation->getPages(tdbb), &rpb.getWindow(tdbb), pp_sequence, LCK_read); + pointer_page* ppage = get_pointer_page(tdbb, relation->getPermanent(), relPages, + &rpb.getWindow(tdbb), pp_sequence, LCK_read); if (!ppage) { blob->blb_flags |= BLB_damaged; @@ -1779,16 +1836,16 @@ bool DPM_next(thread_db* tdbb, record_param* rpb, USHORT lock_type, FindNextReco #endif WIN* window = &rpb->getWindow(tdbb); - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); + const auto relPages = rpb->rpb_relation->getPages(tdbb); if (window->win_flags & WIN_large_scan) { // Try to account for staggered execution of large sequential scans. - window->win_scans = getPermanent(rpb->rpb_relation)->rel_scan_count - rpb->rpb_org_scans; + window->win_scans = rpb->rpb_relation->getPermanent()->rel_scan_count - rpb->rpb_org_scans; if (window->win_scans < 1) - window->win_scans = getPermanent(rpb->rpb_relation)->rel_scan_count; + window->win_scans = rpb->rpb_relation->getPermanent()->rel_scan_count; } rpb->rpb_prior = NULL; @@ -1828,7 +1885,7 @@ bool DPM_next(thread_db* tdbb, record_param* rpb, USHORT lock_type, FindNextReco if (page_number) { - fb_assert(window->win_page.getPageSpaceID() == relPages->rel_pg_space_id); + fb_assert(window->win_page.getPageSpaceID() == relPages->getPageSpaceId()); window->win_page = page_number; const data_page* dpage = (data_page*) CCH_FETCH(tdbb, window, lock_type, pag_undefined); @@ -1864,7 +1921,7 @@ bool DPM_next(thread_db* tdbb, record_param* rpb, USHORT lock_type, FindNextReco while (true) { - const pointer_page* ppage = get_pointer_page(tdbb, getPermanent(rpb->rpb_relation), + const pointer_page* ppage = get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, LCK_read); if (!ppage) BUGCHECK(249); // msg 249 pointer page vanished from DPM_next @@ -1953,7 +2010,7 @@ bool DPM_next(thread_db* tdbb, record_param* rpb, USHORT lock_type, FindNextReco if (scope == DPM_next_data_page) return false; - if (!(ppage = get_pointer_page(tdbb, getPermanent(rpb->rpb_relation), relPages, window, + if (!(ppage = get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, LCK_read))) { BUGCHECK(249); // msg 249 pointer page vanished from DPM_next @@ -1986,15 +2043,16 @@ bool DPM_next(thread_db* tdbb, record_param* rpb, USHORT lock_type, FindNextReco } -static void set_marker(thread_db* tdbb, SSHORT rel_id, SSHORT type, TraNumber transaction) +static void set_marker(thread_db* tdbb, MetaId relationId, ULONG pageSpaceId, SSHORT type, TraNumber transaction) { fb_assert(type >= MRK_commit && type <= MRK_rollback); - DPM_pages(tdbb, rel_id, type, seqPart(transaction), numPart(transaction)); + DPM_pages(tdbb, relationId, type, seqPart(transaction), + PageNumber(pageSpaceId, numPart(transaction))); } -void DPM_pages(thread_db* tdbb, SSHORT rel_id, int type, ULONG sequence, ULONG page) +void DPM_pages(thread_db* tdbb, MetaId relationId, int type, ULONG sequence, const PageNumber& page) { /************************************** * @@ -2007,23 +2065,27 @@ void DPM_pages(thread_db* tdbb, SSHORT rel_id, int type, ULONG sequence, ULONG p * **************************************/ SET_TDBB(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); + const auto attachment = tdbb->getAttachment(); + + fb_assert(page.getPageSpaceID() == DB_PAGE_SPACE || PageSpace::isTablespace(page)); #ifdef VIO_DEBUG VIO_trace(DEBUG_TRACE_ALL, "DPM_pages (rel_id %d, type %d, sequence %" ULONGFORMAT", page %" ULONGFORMAT")\n", - rel_id, type, sequence, page); + relationId, type, sequence, page); #endif - AutoCacheRequest request(tdbb, irq_s_pages, IRQ_REQUESTS); + static const CachedRequestId requestHandleId; + AutoCacheRequest request(tdbb, requestHandleId); STORE(REQUEST_HANDLE request) X IN RDB$PAGES { - X.RDB$RELATION_ID = rel_id; + X.RDB$RELATION_ID = relationId; X.RDB$PAGE_TYPE = type; X.RDB$PAGE_SEQUENCE = sequence; - X.RDB$PAGE_NUMBER = page; + X.RDB$PAGE_NUMBER = page.getPageNum(); + X.RDB$TABLESPACE_ID = page.getPageSpaceID(); } END_STORE } @@ -2070,7 +2132,8 @@ SLONG DPM_prefetch_bitmap(thread_db* tdbb, jrd_rel* relation, PageBitmap* bitmap DECOMPOSE(number, dbb->dbb_max_records, dp_sequence, line); DECOMPOSE(dp_sequence, dbb->dbb_dp_per_pp, pp_sequence, slot); - const pointer_page* ppage = get_pointer_page(tdbb, relation->getPermanent(), &window, pp_sequence, LCK_read); + const pointer_page* ppage = get_pointer_page(tdbb, relation->getPermanent(), + &window, pp_sequence, LCK_read); if (!ppage) BUGCHECK(249); // msg 249 pointer page vanished from DPM_prefetch_bitmap @@ -2112,14 +2175,17 @@ ULONG DPM_pointer_pages(thread_db* tdbb, jrd_rel* relation) "DPM_pointer_pages (relation %d)\n", relation->getId()); #endif - RelationPages* relPages = relation->getPages(tdbb); - ULONG sequence = relPages->rel_pages ? relPages->rel_pages->count() - 1 : 0; - WIN window(relPages->rel_pg_space_id, -1); + const auto relPages = relation->getPages(tdbb); + ULONG sequence = 0; + WIN window(relPages->getPageSpaceId()); + + if (const auto ppCount = relPages->getPointerPageCount()) + sequence = ppCount - 1; while (true) { const pointer_page* ppage = - get_pointer_page(tdbb, getPermanent(relation), relPages, &window, sequence, LCK_read); + get_pointer_page(tdbb, relation->getPermanent(), relPages, &window, sequence, LCK_read); if (!ppage) { @@ -2141,13 +2207,14 @@ ULONG DPM_pointer_pages(thread_db* tdbb, jrd_rel* relation) VIO_trace(DEBUG_TRACE_ALL, " returned pointer pages: %" ULONGFORMAT"\n", sequence + 1); #endif - fb_assert(relPages->rel_pages && relPages->rel_pages->count() == sequence + 1); + + fb_assert(relPages->getPointerPageCount() == sequence + 1); return sequence + 1; } -void DPM_scan_pages( thread_db* tdbb) +void DPM_scan_pages(thread_db* tdbb, SCHAR pagType) { /************************************** * @@ -2156,62 +2223,38 @@ void DPM_scan_pages( thread_db* tdbb) ************************************** * * Functional description - * Scan RDB$PAGES. + * Scan RDB$PAGES for the pages of the given type and update the cached page numbers. + * While scanning, also handle the found deletion markers. * **************************************/ SET_TDBB(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); - Database* dbb = tdbb->getDatabase(); + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); #ifdef VIO_DEBUG VIO_trace(DEBUG_TRACE_ALL, "DPM_scan_pages ()\n"); #endif - // Special case update of RDB$PAGES pointer page vector to avoid - // infinite recursion from this internal request when RDB$PAGES - // has been extended with another pointer page. - - auto* relation = MetadataCache::getPerm(tdbb, 0u, CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); - RelationPages* relPages = relation->getBasePages(); + fb_assert(pagType == 0 || pagType == pag_transactions || pagType == pag_ids); - vcl** address = &relPages->rel_pages; - vcl* vector = *address; - FB_SIZE_T sequence = vector->count() - 1; - WIN window(relPages->rel_pg_space_id, (*vector)[sequence]); - pointer_page* ppage = (pointer_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_pointer); - - while (ppage->ppg_next) - { - ++sequence; - vector->resize(sequence + 1); - (*vector)[sequence] = ppage->ppg_next; - ppage = (pointer_page*) CCH_HANDOFF(tdbb, &window, ppage->ppg_next, LCK_read, pag_pointer); - } - - CCH_RELEASE(tdbb, &window); + fixupRdbPages(tdbb); HalfStaticArray tipSeqList; HalfStaticArray genSeqList; HalfStaticArray markList; - AutoCacheRequest request(tdbb, irq_r_pages, IRQ_REQUESTS); + static const CachedRequestId requestHandleId; + AutoCacheRequest request(tdbb, requestHandleId); FOR(REQUEST_HANDLE request) X IN RDB$PAGES { - relation = MetadataCache::ensureRelation(tdbb, X.RDB$RELATION_ID); - relPages = relation->getBasePages(); - sequence = X.RDB$PAGE_SEQUENCE; + const ULONG sequence = X.RDB$PAGE_SEQUENCE; switch (X.RDB$PAGE_TYPE) { case pag_root: - relPages->rel_index_root = X.RDB$PAGE_NUMBER; - break; - case pag_pointer: - relPages->rel_pages = vcl::newVector(relation->getPool(), relPages->rel_pages, sequence + 1); - (*relPages->rel_pages)[sequence] = X.RDB$PAGE_NUMBER; break; case pag_transactions: @@ -2247,12 +2290,89 @@ void DPM_scan_pages( thread_db* tdbb) if (const auto count = genSeqList.getCount()) dbb->copyKnownPages(pag_ids, count, genSeqList.begin()); - for (auto id : markList) - DPM_scan_marker(tdbb, id); + for (const auto relationId : markList) + DPM_scan_marker(tdbb, relationId); } -void DPM_scan_marker(thread_db* tdbb, MetaId relId) +void DPM_scan_pages(thread_db* tdbb, RelationPermanent* relation, RelationPages* relPages) +{ +/************************************** + * + * D P M _ s c a n _ p a g e s + * + ************************************** + * + * Functional description + * Scan RDB$PAGES for the pages of the given pagespace and update the cached page numbers. + * While scanning, also handle the found deletion markers. + * + **************************************/ + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); + +#ifdef VIO_DEBUG + VIO_trace(DEBUG_TRACE_ALL, + "DPM_scan_pages ()\n"); +#endif + + fixupRdbPages(tdbb); + + const auto pageSpaceId = relPages->getPageSpaceId(); + bool foundMarker = false; + HalfStaticArray ppList; + + static const CachedRequestId requestHandleId; + AutoCacheRequest request(tdbb, requestHandleId); + + FOR(REQUEST_HANDLE request) X IN RDB$PAGES + WITH X.RDB$RELATION_ID = relation->getId() + { + switch (X.RDB$PAGE_TYPE) + { + case pag_root: + if (static_cast(X.RDB$TABLESPACE_ID) == pageSpaceId) + relPages->setIndexRootPage(X.RDB$PAGE_NUMBER); + break; + + case pag_pointer: + if (static_cast(X.RDB$TABLESPACE_ID) == pageSpaceId) + { + const ULONG sequence = X.RDB$PAGE_SEQUENCE; + if (sequence >= ppList.getCount()) + ppList.resize(sequence + 1); + ppList[sequence] = X.RDB$PAGE_NUMBER; + } + break; + + case pag_transactions: + case pag_ids: + break; + + case MRK_commit: + case MRK_drop: + case MRK_rollback: + if (!dbb->dbb_tip_cache) + dbb->dbb_flags |= DBB_rescan_pages; + else + foundMarker = true; + break; + + default: + CORRUPT(257); // msg 257 bad record in RDB$PAGES + } + END_FOR + } + + if (const auto count = ppList.getCount()) + relPages->resetPointerPages(count, ppList.begin()); + + if (foundMarker) + DPM_scan_marker(tdbb, relation->getId()); +} + + +void DPM_scan_marker(thread_db* tdbb, MetaId relationId) { /************************************** * @@ -2264,29 +2384,31 @@ void DPM_scan_marker(thread_db* tdbb, MetaId relId) * Scan marker for particular relation. * **************************************/ - SET_TDBB(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); - Database* dbb = tdbb->getDatabase(); - fb_assert(dbb->dbb_tip_cache); + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); + + const auto relation = MetadataCache::getPerm(tdbb, relationId, + CacheFlag::AUTOCREATE | CacheFlag::NOSCAN | CacheFlag::ERASED); - bool doDel = false; - auto relation = MetadataCache::getPerm(tdbb, relId, CacheFlag::AUTOCREATE | CacheFlag::NOSCAN | CacheFlag::ERASED); - fb_assert(relation); if (!relation) return; - static CachedRequestId id1; - AutoCacheRequest request(tdbb, id1); + fb_assert(dbb->dbb_tip_cache); + + HalfStaticArray deleteList; + + static const CachedRequestId id; + AutoCacheRequest request(tdbb, id); FOR(REQUEST_HANDLE request) X IN RDB$PAGES - WITH X.RDB$RELATION_ID EQ relId + WITH X.RDB$RELATION_ID EQ relation->getId() AND X.RDB$PAGE_TYPE GT MAX_UCHAR { switch (X.RDB$PAGE_TYPE) { case MRK_commit: switch (transactionState(tdbb, transactionFromMarker(X.RDB$PAGE_SEQUENCE, X.RDB$PAGE_NUMBER), - relation, false)) + relation, false)) { case tra_dead: ERASE X; @@ -2313,7 +2435,8 @@ void DPM_scan_marker(thread_db* tdbb, MetaId relId) if (tdbb->getDatabase()->dbb_oldest_active >= transactionFromMarker(X.RDB$PAGE_SEQUENCE, X.RDB$PAGE_NUMBER)) { - doDel = true; + if (!deleteList.exist(X.RDB$TABLESPACE_ID)) + deleteList.add(X.RDB$TABLESPACE_ID); } break; @@ -2323,11 +2446,13 @@ void DPM_scan_marker(thread_db* tdbb, MetaId relId) dbb->dbb_flags |= DBB_rescan_pages; break; } + switch (transactionState(tdbb, transactionFromMarker(X.RDB$PAGE_SEQUENCE, X.RDB$PAGE_NUMBER), - relation, true)) + relation, true)) { case tra_dead: - doDel = true; + if (!deleteList.exist(X.RDB$TABLESPACE_ID)) + deleteList.add(X.RDB$TABLESPACE_ID); break; case tra_committed: ERASE X; @@ -2341,8 +2466,12 @@ void DPM_scan_marker(thread_db* tdbb, MetaId relId) } END_FOR - if (doDel) - DPM_delete_relation(tdbb, relation); + while (deleteList.hasData()) + { + const auto pageSpaceId = deleteList.pop(); + const auto relPages = relation->getPages(pageSpaceId); + DPM_delete_relation(tdbb, relation, relPages); + } } @@ -2433,7 +2562,7 @@ void DPM_store( thread_db* tdbb, record_param* rpb, PageStack& stack, const Jrd: Ods::pag* page = rpb->getWindow(tdbb).win_buffer; // When restoring, mark primary data page as swept, pointer page already marked by locate_space() - if (swept_at_restore(tdbb, rpb->rpb_relation->getPermanent()) && (type == DPM_primary)) + if (swept_at_restore(tdbb, rpb->rpb_relation) && (type == DPM_primary)) { page->pag_flags |= dpg_swept; CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); @@ -2726,7 +2855,7 @@ static void check_swept(thread_db* tdbb, record_param* rpb) Database* dbb = tdbb->getDatabase(); jrd_tra* transaction = tdbb->getTransaction(); WIN* window = &rpb->getWindow(tdbb); - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); + const auto relPages = rpb->rpb_relation->getPages(tdbb); ULONG pp_sequence; USHORT slot, line; @@ -2734,7 +2863,7 @@ static void check_swept(thread_db* tdbb, record_param* rpb) line, slot, pp_sequence); pointer_page* ppage = - get_pointer_page(tdbb, getPermanent(rpb->rpb_relation), relPages, window, pp_sequence, LCK_read); + get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, LCK_read); if (!ppage) return; @@ -2825,7 +2954,7 @@ static USHORT compress(thread_db* tdbb, data_page* page) } -static void delete_tail(thread_db* tdbb, rhdf* header, const USHORT page_space, USHORT length) +static void delete_tail(thread_db* tdbb, rhdf* header, const ULONG page_space, USHORT length) { /************************************** * @@ -3143,7 +3272,7 @@ static void fragment(thread_db* tdbb, } -static USHORT extend_relation(thread_db* tdbb, Cached::Relation* relation, WIN* window, +static USHORT extend_relation(thread_db* tdbb, jrd_rel* relation, WIN* window, RecordStorageType type, bool reserve) { /************************************** @@ -3160,13 +3289,13 @@ static USHORT extend_relation(thread_db* tdbb, Cached::Relation* relation, WIN* SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - RelationPages* relPages = relation->getPages(tdbb); - WIN pp_window(relPages->rel_pg_space_id, -1); + const auto relPages = relation->getPages(tdbb); + WIN pp_window(relPages->getPageSpaceId()); #ifdef VIO_DEBUG VIO_trace(DEBUG_WRITES_INFO, " extend_relation (relation %d, instance %" SQUADFORMAT", window)\n", - relation->getId(), relPages->rel_instance_id); + relation->getId(), relPages->getInstanceId()); #endif // Search pointer pages for an empty slot. @@ -3182,7 +3311,7 @@ static USHORT extend_relation(thread_db* tdbb, Cached::Relation* relation, WIN* for (pp_sequence = relPages->rel_slot_space;; pp_sequence++) { - if (!(ppage = get_pointer_page(tdbb, relation, relPages, &pp_window, + if (!(ppage = get_pointer_page(tdbb, relation->getPermanent(), relPages, &pp_window, pp_sequence, LCK_write))) { BUGCHECK(253); // msg 253 pointer page vanished from extend_relation @@ -3208,7 +3337,7 @@ static USHORT extend_relation(thread_db* tdbb, Cached::Relation* relation, WIN* if (ppage->ppg_header.pag_flags & ppg_eof) { - WIN new_pp_window(relPages->rel_pg_space_id, -1); + WIN new_pp_window(relPages->getPageSpaceId()); pointer_page* new_ppage = (pointer_page*) DPM_allocate(tdbb, &new_pp_window); new_ppage->ppg_header.pag_type = pag_pointer; new_ppage->ppg_header.pag_flags |= ppg_eof; @@ -3218,27 +3347,25 @@ static USHORT extend_relation(thread_db* tdbb, Cached::Relation* relation, WIN* CCH_must_write(tdbb, &new_pp_window); CCH_RELEASE(tdbb, &new_pp_window); - vcl* vector = relPages->rel_pages = - vcl::newVector(relation->getPool(), relPages->rel_pages, pp_sequence + 1); - (*vector)[pp_sequence] = new_pp_window.win_page.getPageNum(); + const auto newPageNum = new_pp_window.win_page.getPageNum(); + relPages->setPointerPage(pp_sequence, newPageNum); // hvlad: temporary tables don't save their pointer pages in RDB$PAGES - if (relation->getId() && (relPages->rel_instance_id == 0)) - { - DPM_pages(tdbb, relation->getId(), pag_pointer, - pp_sequence, new_pp_window.win_page.getPageNum()); - } + if (relation->getId() && relPages->getInstanceId() == 0) + DPM_pages(tdbb, relation->getId(), pag_pointer, pp_sequence, new_pp_window.win_page); + relPages->rel_slot_space = pp_sequence; CCH_MARK(tdbb, &pp_window); ppage->ppg_header.pag_flags &= ~ppg_eof; - ppage->ppg_next = new_pp_window.win_page.getPageNum(); + ppage->ppg_next = newPageNum; --pp_sequence; } CCH_RELEASE(tdbb, &pp_window); } + unsigned cntAlloc = 1; // allocate extent (PAGES_IN_EXTENT contiguous pages) if // - relation already contains at least PAGES_IN_EXTENT pages, and @@ -3554,33 +3681,46 @@ static pointer_page* get_pointer_page(thread_db* tdbb, **************************************/ SET_TDBB(tdbb); - vcl* vector = relPages->rel_pages; - if (!vector || sequence >= vector->count()) + if (sequence >= relPages->getPointerPageCount()) { for (;;) { - DPM_scan_pages(tdbb); - // If the relation is gone, then we can't do anything anymore. - if (!relation || !(vector = relPages->rel_pages)) - return NULL; + DPM_scan_pages(tdbb, relation, relPages); + + // If the relation is gone, then we can't do anything anymore + if (!relPages->hasData()) + return nullptr; - if (sequence < vector->count()) + const auto ppCount = relPages->getPointerPageCount(); + if (sequence < ppCount) break; // we are in business again - window->win_page = (*vector)[vector->count() - 1]; + const auto ppNumber = relPages->getPointerPage(ppCount - 1); + fb_assert(ppNumber); + if (!ppNumber) + return nullptr; + + window->win_page = ppNumber.value(); const pointer_page* page = (pointer_page*) CCH_FETCH(tdbb, window, lock, pag_pointer); const ULONG next_ppg = page->ppg_next; CCH_RELEASE(tdbb, window); if (!next_ppg) - return NULL; + return nullptr; // hvlad: temporary tables don't save their pointer pages in RDB$PAGES - if (relPages->rel_instance_id == 0) - DPM_pages(tdbb, relation->getId(), pag_pointer, vector->count(), next_ppg); + if (relPages->getInstanceId() == 0) + { + const auto pageSpaceId = relPages->getPageSpaceId(); + DPM_pages(tdbb, relation->getId(), pag_pointer, ppCount, PageNumber(pageSpaceId, next_ppg)); + } } } - window->win_page = (*vector)[sequence]; + const auto ppNumber = relPages->getPointerPage(sequence); + if (!ppNumber) + return nullptr; + + window->win_page = ppNumber.value(); pointer_page* page = (pointer_page*) CCH_FETCH(tdbb, window, lock, pag_pointer); if (page->ppg_relation != relation->getId() || page->ppg_sequence != sequence) @@ -3610,8 +3750,7 @@ static rhd* locate_space(thread_db* tdbb, Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); - auto* relation = rpb->rpb_relation->getPermanent(); - RelationPages* relPages = relation->getPages(tdbb); + const auto relPages = rpb->rpb_relation->getPages(tdbb); WIN* window = &rpb->getWindow(tdbb); // If there is a preferred page, try there first @@ -3626,7 +3765,7 @@ static rhd* locate_space(thread_db* tdbb, rpb->rpb_number.decompose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, line, slot, pp_sequence); const pointer_page* ppage = - get_pointer_page(tdbb, relation, relPages, window, pp_sequence, LCK_read); + get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, LCK_read); if (ppage) { @@ -3637,7 +3776,7 @@ static rhd* locate_space(thread_db* tdbb, if (space) return (rhd*) space; - if (!window->win_page.isTemporary()) + if (!PageSpace::isTemporary(window->win_page)) CCH_get_related(tdbb, window->win_page, lowPages); } else @@ -3698,7 +3837,7 @@ static rhd* locate_space(thread_db* tdbb, relPages->rel_sec_data_space = pp_sequence; const pointer_page* ppage = - get_pointer_page(tdbb, relation, relPages, window, pp_sequence, ppLock); + get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, ppLock); if (!ppage) BUGCHECK(254); // msg 254 pointer page vanished from relation list in locate_space @@ -3741,7 +3880,7 @@ static rhd* locate_space(thread_db* tdbb, CCH_RELEASE(tdbb, window); ppLock = LCK_write; - ppage = get_pointer_page(tdbb, relation, relPages, window, pp_sequence, ppLock); + ppage = get_pointer_page(tdbb, rpb->rpb_relation->getPermanent(), relPages, window, pp_sequence, ppLock); if (!ppage) BUGCHECK(254); @@ -3759,7 +3898,7 @@ static rhd* locate_space(thread_db* tdbb, PPG_DP_BIT_CLEAR(bits, slot, ppg_dp_secondary); // When restoring, mark slot as swept, data page will be marked by our caller - if (swept_at_restore(tdbb, relation)) + if (swept_at_restore(tdbb, rpb->rpb_relation)) PPG_DP_BIT_SET(bits, slot, ppg_dp_swept); } else @@ -3817,7 +3956,7 @@ static rhd* locate_space(thread_db* tdbb, int i; for (i = 0; i < 20; ++i) { - extend_relation(tdbb, relation, window, type, false); + extend_relation(tdbb, rpb->rpb_relation, window, type, false); space = find_space(tdbb, rpb, size, stack, record, type); if (space) @@ -3879,8 +4018,8 @@ static void mark_full(thread_db* tdbb, record_param* rpb) const ULONG sequence = dpage->dpg_sequence; CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); - RelationPages* relPages = relation->getPages(tdbb); - WIN pp_window(relPages->rel_pg_space_id, -1); + const auto relPages = relation->getPages(tdbb); + WIN pp_window(relPages->getPageSpaceId()); USHORT slot; ULONG pp_sequence; @@ -4032,8 +4171,8 @@ static void store_big_record(thread_db* tdbb, // Start compression from the end. const UCHAR* in = rpb->rpb_address + rpb->rpb_length; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); - PageNumber prior(relPages->rel_pg_space_id, 0); + const auto relPages = rpb->rpb_relation->getPages(tdbb); + PageNumber prior(relPages->toPageNumber(0)); // The last fragment should have rhd header because rhd_incomplete flag won't be set for it. // It's important for get_header() function which relies on rhd_incomplete flag to determine header size. @@ -4149,7 +4288,7 @@ static void store_big_record(thread_db* tdbb, bool markPP = false; // When restoring, mark primary data page as swept, pointer page already marked by locate_space() - if (swept_at_restore(tdbb, rpb->rpb_relation->getPermanent()) && (type == DPM_primary)) + if (swept_at_restore(tdbb, rpb->rpb_relation) && (type == DPM_primary)) { page->dpg_header.pag_flags |= dpg_swept; } @@ -4170,3 +4309,340 @@ static void store_big_record(thread_db* tdbb, else CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); } + +void DPM_copy_data(thread_db* tdbb, RelationPermanent* relation, RelationPages* orgPages, RelationPages* newPages) +{ + /************************************** + * + * D P M _ c o p y _ d a t a + * + ************************************** + * + * Functional description + * Copy data and blob pages from orgPages to newPages. + * The main steps are: + * - allocate necessary number of pointer pages by extents + * - allocate the rest of pointer pages by pages + * - walking through PPs allocate DPs by pages or extents + * - fix every PP by correcting DP numbers and ppg_next pointer and build a map + * - walk through the map and copy every DP to the new one by fixing b_page and f_page numbers + * We expect that DPM_scan_pages has been called. + * + **************************************/ + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); + + fb_assert(orgPages->getPageSpaceId() != newPages->getPageSpaceId()); + + WIN newWindow(newPages->getPageSpaceId()); + WIN oldWindow(orgPages->getPageSpaceId()); + WIN dpWindow(newPages->getPageSpaceId()); + + // Copy index root page to the new tablespace + oldWindow.win_page = orgPages->getIndexRootPage().value(); + Ods::pag* oldPage = CCH_FETCH(tdbb, &oldWindow, LCK_read, pag_root); + + Ods::pag* newPage = DPM_allocate(tdbb, &newWindow); + memcpy(newPage, oldPage, dbb->dbb_page_size); + const auto rootPage = newWindow.win_page; + newPage->pag_pageno = rootPage.getPageNum(); + newPages->setIndexRootPage(rootPage); + + CCH_RELEASE(tdbb, &newWindow); + CCH_RELEASE(tdbb, &oldWindow); + + typedef Pair > PagesPair; + GenericMap dataPagesMap; + + const ULONG ppCount = orgPages->getPointerPageCount(); + HalfStaticArray newPointerPages(ppCount); + + ULONG sequence = 0; + + // Allocate pointer pages + while (sequence < ppCount) + { + const unsigned toAlloc = MIN(ppCount - sequence, PAGES_IN_EXTENT); + PAG_allocate_pages(tdbb, &newWindow, toAlloc, true); + const ULONG firstPage = newWindow.win_page.getPageNum(); + CCH_RELEASE(tdbb, &newWindow); + + for (unsigned i = 0; i < toAlloc; i++) + newPointerPages.add(firstPage + i); + sequence += toAlloc; + } + + fb_assert(newPointerPages.getCount() == ppCount); + newPages->resetPointerPages(ppCount, newPointerPages.begin()); + + // Copy and fix every pointer page and allocate and map its datapages + sequence = 0; + for (const auto oldPage : *orgPages->getPointerPages().getPointer()) + { + oldWindow.win_page = oldPage; + const pointer_page* oldPP = (pointer_page*) CCH_FETCH(tdbb, &oldWindow, LCK_read, pag_pointer); + + const ULONG newPage = newWindow.win_page = newPointerPages[sequence]; + pointer_page* newPP = (pointer_page*) CCH_FETCH(tdbb, &newWindow, LCK_write, pag_undefined); + + // Now copy the page data + CCH_MARK(tdbb, &newWindow); + memcpy(newPP, oldPP, dbb->dbb_page_size); + newPP->ppg_header.pag_pageno = newPage; + + if (sequence < ppCount - 1) + { + newPP->ppg_next = newPointerPages[sequence + 1]; + fb_assert(!(newPP->ppg_header.pag_flags & ppg_eof)); + newPP->ppg_header.pag_flags &= ~ppg_eof; + } + else + { + // We rely that rel_pages vector is correct and nobody extend relation when we copy its data!!! + fb_assert(!newPP->ppg_next); + newPP->ppg_next = 0; + fb_assert(newPP->ppg_header.pag_flags & ppg_eof); + newPP->ppg_header.pag_flags |= ppg_eof; + } + + CCH_RELEASE_TAIL(tdbb, &oldWindow); + + // Allocate data pages + if (!sequence && newPP->ppg_count < PAGES_IN_EXTENT) + { + // Allocate by pages + for (USHORT slot = 0; slot < newPP->ppg_count; slot++) + { + if (newPP->ppg_page[slot]) + { + DPM_allocate(tdbb, &dpWindow); + // Replace slots by the new data page numbers + const ULONG newPageNumber = dpWindow.win_page.getPageNum(); + CCH_RELEASE(tdbb, &dpWindow); + dataPagesMap.put(newPP->ppg_page[slot], newPageNumber); + newPP->ppg_page[slot] = newPageNumber; + } + } + } + else + { + // Allocate by extents + UCHAR* bits = (UCHAR*) (newPP->ppg_page + dbb->dbb_dp_per_pp); + for (USHORT slot = 0; slot < newPP->ppg_count; slot += PAGES_IN_EXTENT) + { + // We are on the new extent. Check it for empty slots. + unsigned count = 0; + for (unsigned i = 0; i < PAGES_IN_EXTENT; i++) + { + fb_assert(slot + i < newPP->ppg_count); + + if (newPP->ppg_page[slot + i]) + count++; + else + newPP->ppg_min_space = MIN(newPP->ppg_min_space, slot + i); + } + fb_assert(count == PAGES_IN_EXTENT); + + // If new extent is empty go to the next + if (!count) + continue; + + PAG_allocate_pages(tdbb, &dpWindow, PAGES_IN_EXTENT, true); + + const ULONG newPageNumber = dpWindow.win_page.getPageNum(); + CCH_RELEASE(tdbb, &dpWindow); + + // Replace all non-empty slots by new data page numbers in the allocated extent. + // Check if there are empty slots. In general case all slots in extent must be used. + // If an empty slot exists we remove it and replace by empty data page. + for (unsigned i = 0; i < PAGES_IN_EXTENT; i++) + { + if (newPP->ppg_page[slot + i]) + dataPagesMap.put(newPP->ppg_page[slot + i], newPageNumber + i); + else + { + // Original PP has empty slot here. We replace it by an empty data page. + PPG_DP_BIT_CLEAR(bits, slot + i, PPG_DP_ALL_BITS); + PPG_DP_BIT_SET(bits, slot + i, ppg_dp_empty); + dpWindow.win_page = newPageNumber + i; + data_page* dpage = (data_page*) CCH_fake(tdbb, &dpWindow, 1); + dpage->dpg_sequence = sequence * dbb->dbb_dp_per_pp + slot + i; + dpage->dpg_relation = relation->getId(); + dpage->dpg_header.pag_type = pag_data; + CCH_RELEASE_TAIL(tdbb, &dpWindow); + newPP->ppg_count = MAX(newPP->ppg_count, slot + i); + } + + newPP->ppg_page[slot + i] = newPageNumber + i; + } + } + } + + CCH_RELEASE_TAIL(tdbb, &newWindow); + sequence++; + } + + // At this point we have copied all PPs, allocated all DPs and created the allocation map. + // Now we copy and fix every DP. Do it in the natural map order (sorted by page number) to optimize reads. + Firebird::Array dataPagesToCopy(dataPagesMap.count()); + + auto getAllocatedPage = [&](win* window, ULONG page) + { + ULONG newPage; + + if (!dataPagesMap.get(page, newPage)) + { + DPM_allocate(tdbb, window); + newPage = window->win_page.getPageNum(); + CCH_RELEASE(tdbb, window); + dataPagesMap.put(page, newPage); + dataPagesToCopy.add(PagesPair(page, newPage)); + } + + return newPage; + }; + + auto accessor = dataPagesMap.accessor(); + for (bool found = accessor.getFirst(); found; found = accessor.getNext()) + dataPagesToCopy.add(*accessor.current()); + + for (unsigned i = 0; i < dataPagesToCopy.getCount(); i++) + { + const ULONG oldPage = dataPagesToCopy[i].first; + const ULONG newPage = dataPagesToCopy[i].second; + + // Copy data page + oldWindow.win_page = oldPage; + Ods::data_page* oldDP = (Ods::data_page*) CCH_FETCH(tdbb, &oldWindow, LCK_read, pag_data); + + newWindow.win_page = newPage; + Ods::data_page* newDP = (Ods::data_page*) CCH_FETCH(tdbb, &newWindow, LCK_write, pag_undefined); + + CCH_MARK(tdbb, &newWindow); + memcpy(newDP, oldDP, dbb->dbb_page_size); + newDP->dpg_header.pag_pageno = newPage; + + CCH_RELEASE_TAIL(tdbb, &oldWindow); // We don't need old datapage anymore + + // Now we need to fix every b_page and f_page pointers in record versions + const Ods::data_page::dpg_repeat* index = newDP->dpg_rpt; + const Ods::data_page::dpg_repeat* const end = index + newDP->dpg_count; + for (; index < end; index++) + { + if (index->dpg_offset) + { + Ods::rhd* header = (rhd*) ((SCHAR*) newDP + index->dpg_offset); + + // If the record is a blob we need to copy all its pages + if (header->rhd_flags & rhd_blob) + { + blh* blob = (blh*) header; + if (blob->blh_level == 0) + continue; + + fb_assert(blob->blh_level == 1 || blob->blh_level == 2); + + ULONG* page1 = blob->blh_page; + const ULONG* const end1 = page1 + (index->dpg_length - BLH_SIZE) / sizeof(ULONG); + + for (; page1 < end1; page1++) + { + // It's the level 1 or level 2 blob, copy its content + WIN oldWindow1(orgPages->toPageNumber(*page1)); + WIN newWindow1(newPages->getPageSpaceId()); + blob_page* oldBlobPage1 = (blob_page*) CCH_FETCH(tdbb, &oldWindow1, LCK_read, pag_blob); + blob_page* newBlobPage1 = (blob_page*) DPM_allocate(tdbb, &newWindow1); + memcpy(newBlobPage1, oldBlobPage1, dbb->dbb_page_size); + // Fix blob page number on newDP + *page1 = newWindow1.win_page.getPageNum(); + newBlobPage1->blp_header.pag_pageno = *page1; + CCH_RELEASE_TAIL(tdbb, &oldWindow1); + + if (blob->blh_level == 2) + { + // It's a level 2 blob. newBlobPage1 contains numbers of pages with blob data. + // Fix blob page numbers and copy pages of this level. + ULONG* page2 = newBlobPage1->blp_page; + const ULONG* const end2 = page2 + ((newBlobPage1->blp_length) / sizeof(ULONG)); + + for (; page2 < end2; page2++) + { + WIN oldWindow2(orgPages->toPageNumber(*page2)); + WIN newWindow2(newPages->getPageSpaceId()); + blob_page* oldBlobPage2 = (blob_page*) CCH_FETCH(tdbb, &oldWindow2, LCK_read, pag_blob); + blob_page* newBlobPage2 = (blob_page*) DPM_allocate(tdbb, &newWindow2); + memcpy(newBlobPage2, oldBlobPage2, dbb->dbb_page_size); + // Fix page number on newBlobPage1 + *page2 = newWindow2.win_page.getPageNum(); + newBlobPage2->blp_header.pag_pageno = *page2; + CCH_RELEASE_TAIL(tdbb, &oldWindow2); + CCH_RELEASE_TAIL(tdbb, &newWindow2); + } + } + + CCH_RELEASE_TAIL(tdbb, &newWindow1); + } + } + else + { + if (header->rhd_b_page) + { + // If backversion page number is the same as primary then we don't need mapping + header->rhd_b_page = (header->rhd_b_page == oldPage) ? newPage : + getAllocatedPage(&dpWindow, header->rhd_b_page); + } + + if (header->rhd_flags & rhd_incomplete) + { + const auto fragmentHeader = (rhdf*) header; + if (fragmentHeader->rhdf_f_page) + fragmentHeader->rhdf_f_page = getAllocatedPage(&dpWindow, fragmentHeader->rhdf_f_page); + } + } + } + } + + CCH_RELEASE_TAIL(tdbb, &newWindow); + } + + // Now dataPagesMap is full and we can remap helper page markers from orgPages to newPages + + auto remapDataPage = [&dataPagesMap](ULONG fromPage, ULONG& toPage) + { + if (fromPage && !dataPagesMap.get(fromPage, toPage)) + { + toPage = 0; // the page must be found, but zero page number is an acceptable workaround + fb_assert(false); + } + }; + + remapDataPage(orgPages->rel_last_free_pri_dp, newPages->rel_last_free_pri_dp); + remapDataPage(orgPages->rel_last_free_blb_dp, newPages->rel_last_free_blb_dp); + + // rel_slot_space / rel_pri_data_space / rel_sec_data_space are sequence numbers and thus may be just copied + + fb_assert(orgPages->rel_slot_space < ppCount); + newPages->rel_slot_space = orgPages->rel_slot_space; + fb_assert(orgPages->rel_pri_data_space < ppCount); + newPages->rel_pri_data_space = orgPages->rel_pri_data_space; + fb_assert(orgPages->rel_sec_data_space < ppCount); + newPages->rel_sec_data_space = orgPages->rel_sec_data_space; + + // Add pages RDB$PAGES for the new relation + + sequence = 0; + for (const auto pageNo : *newPages->getPointerPages().getPointer()) + DPM_pages(tdbb, relation->getId(), pag_pointer, sequence++, newPages->toPageNumber(pageNo)); + + DPM_pages(tdbb, relation->getId(), pag_root, 0, rootPage); + + // Setup the transition rules between old and new pages + + const auto transaction = tdbb->getTransaction(); + fb_assert(transaction); + if (const auto traNumber = transaction->tra_number) + { + set_marker(tdbb, relation->getId(), orgPages->getPageSpaceId(), MRK_commit, traNumber); + set_marker(tdbb, relation->getId(), newPages->getPageSpaceId(), MRK_rollback, traNumber); + } +} diff --git a/src/jrd/dpm_proto.h b/src/jrd/dpm_proto.h index 20f9c342f41..a4a736d2f37 100644 --- a/src/jrd/dpm_proto.h +++ b/src/jrd/dpm_proto.h @@ -60,10 +60,10 @@ void DPM_backout(Jrd::thread_db*, Jrd::record_param*); void DPM_backout_mark(Jrd::thread_db*, Jrd::record_param*, const Jrd::jrd_tra*); double DPM_cardinality(Jrd::thread_db*, Jrd::jrd_rel*, const Jrd::Format*); bool DPM_chain(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*); -void DPM_create_relation(Jrd::thread_db*, Jrd::Cached::Relation*); -ULONG DPM_data_pages(Jrd::thread_db*, Jrd::Cached::Relation*); +void DPM_create_relation(Jrd::thread_db*, Jrd::jrd_rel*); +ULONG DPM_data_pages(Jrd::thread_db*, Jrd::jrd_rel*); void DPM_delete(Jrd::thread_db*, Jrd::record_param*, ULONG); -void DPM_delete_relation(Jrd::thread_db*, Jrd::RelationPermanent*); +void DPM_delete_relation(Jrd::thread_db*, Jrd::RelationPermanent*, Jrd::RelationPages*); USHORT DPM_reserve_pages(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::win*); bool DPM_fetch(Jrd::thread_db*, Jrd::record_param*, USHORT); bool DPM_fetch_back(Jrd::thread_db*, Jrd::record_param*, USHORT, SSHORT); @@ -71,14 +71,15 @@ void DPM_fetch_fragment(Jrd::thread_db*, Jrd::record_param*, USHORT); SINT64 DPM_gen_id(Jrd::thread_db*, SLONG, bool, SINT64); bool DPM_get(Jrd::thread_db*, Jrd::record_param*, SSHORT); ULONG DPM_get_blob(Jrd::thread_db*, Jrd::blb*, Jrd::jrd_rel*, RecordNumber, bool, ULONG); -void DPM_mark_relation(Jrd::thread_db*, Jrd::Cached::Relation*); +void DPM_mark_relation(Jrd::thread_db*, Jrd::jrd_rel*); bool DPM_next(Jrd::thread_db*, Jrd::record_param*, USHORT, Jrd::FindNextRecordScope); -void DPM_pages(Jrd::thread_db*, SSHORT, int, ULONG, ULONG); +void DPM_pages(Jrd::thread_db*, MetaId, int, ULONG, const Jrd::PageNumber&); #ifdef SUPERSERVER_V2 SLONG DPM_prefetch_bitmap(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::PageBitmap*, SLONG); #endif ULONG DPM_pointer_pages(Jrd::thread_db*, Jrd::jrd_rel*); -void DPM_scan_pages(Jrd::thread_db*); +void DPM_scan_pages(Jrd::thread_db*, SCHAR pagType = 0); +void DPM_scan_pages(Jrd::thread_db*, Jrd::RelationPermanent*, Jrd::RelationPages*); void DPM_store(Jrd::thread_db*, Jrd::record_param*, Jrd::PageStack&, const Jrd::RecordStorageType type); RecordNumber DPM_store_blob(Jrd::thread_db*, Jrd::blb*, Jrd::jrd_rel*, Jrd::Record*); void DPM_rewrite_header(Jrd::thread_db*, Jrd::record_param*); @@ -88,4 +89,7 @@ void DPM_update(Jrd::thread_db*, Jrd::record_param*, Jrd::PageStack*, const Jrd: void DPM_create_relation_pages(Jrd::thread_db*, Jrd::RelationPermanent*, Jrd::RelationPages*); void DPM_delete_relation_pages(Jrd::thread_db*, Jrd::RelationPermanent*, Jrd::RelationPages*); +void DPM_copy_data(Jrd::thread_db* tdbb, Jrd::RelationPermanent*, + Jrd::RelationPages* orgPages, Jrd::RelationPages* newPages); + #endif // JRD_DPM_PROTO_H diff --git a/src/jrd/drq.h b/src/jrd/drq.h index 2219d66dc30..71fe163ab79 100644 --- a/src/jrd/drq.h +++ b/src/jrd/drq.h @@ -231,6 +231,10 @@ enum drq_type_t drq_exception_exist, // check if exception exists drq_generator_exist, // check if generator exists drq_rel_field_exist, // check if a field of relation or view exists + drq_s_tablespace, // store tablespace + drq_m_tablespace, // modify tablespace + drq_e_tablespace, // erase tablespace + drq_g_nxt_ts_id, // generate next tablespace id drq_m_coll_attrs, // modify collation attributes drq_l_pub_mode, // lookup publication auto-enable mode drq_m_pub_state, // modify publication state diff --git a/src/jrd/dyn_util.epp b/src/jrd/dyn_util.epp index 44bfb049ba5..4e5ac1471a9 100644 --- a/src/jrd/dyn_util.epp +++ b/src/jrd/dyn_util.epp @@ -346,6 +346,24 @@ bool DYN_UTIL_check_unique_name_nothrow(thread_db* tdbb, break; } + case obj_tablespace: + { + fb_assert(object_name.schema.isEmpty()); + + static const CachedRequestId tablespaceHandleId; + requestHandle.reset(tdbb, tablespaceHandleId); + + FOR(REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) + TS IN RDB$TABLESPACES + WITH TS.RDB$TABLESPACE_NAME EQ object_name.object.c_str() + { + *errorCode = 326; // isc_dyn_dup_tablespace + } + END_FOR + + break; + } + default: fb_assert(false); } diff --git a/src/jrd/fields.h b/src/jrd/fields.h index f0fd2d66ebb..a1047781e4b 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -238,6 +238,9 @@ FIELD(fld_sch_name , nam_sch_name , dtype_text , MAX_SQL_IDENTIFIER_LEN , dsc_text_type_metadata , NULL , true , ODS_14_0) FIELD(fld_text_max , nam_text_max , dtype_varying, MAX_VARY_COLUMN_SIZE / METADATA_BYTES_PER_CHAR * METADATA_BYTES_PER_CHAR, dsc_text_type_metadata, NULL, true, ODS_14_0) + FIELD(fld_ts_id , nam_ts_id , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_14_0) + FIELD(fld_ts_name , nam_ts_name , dtype_text , MAX_SQL_IDENTIFIER_LEN , dsc_text_type_metadata , NULL , true , ODS_14_0) + FIELD(fld_tab_type , nam_mon_tab_type , dtype_varying , 32 , dsc_text_type_ascii , NULL , true , ODS_14_0) FIELD(fld_pkg_id , nam_pkg_id , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_14_0) diff --git a/src/jrd/grant.epp b/src/jrd/grant.epp index 03b962c08ba..01aae3c7db1 100644 --- a/src/jrd/grant.epp +++ b/src/jrd/grant.epp @@ -575,6 +575,23 @@ static void get_object_info(thread_db* tdbb, } END_FOR } + else if (obj_type == obj_tablespace) + { + fb_assert(object_name.schema.isEmpty()); + + AutoCacheRequest request(tdbb, irq_grant20, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request) + X IN RDB$TABLESPACES WITH + X.RDB$TABLESPACE_NAME EQ object_name.object.c_str() + { + s_class = X.RDB$SECURITY_CLASS; + default_class = ""; + owner = X.RDB$OWNER_NAME; + view = false; + } + END_FOR + } else { s_class = SCL_getDdlSecurityClassName(obj_type, object_name.schema); diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index 24941dd9ae4..afe5718a63c 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -67,6 +67,7 @@ #include "../jrd/tpc_proto.h" #include "../jrd/tra_proto.h" #include "../jrd/Collation.h" +#include "../jrd/pag_proto.h" #include "../common/Task.h" #include "../jrd/WorkerAttachment.h" @@ -79,89 +80,10 @@ static idx_e check_foreign_key(thread_db*, Record*, jrd_rel*, jrd_tra*, index_de static idx_e check_partner_index(thread_db*, jrd_rel*, Record*, jrd_tra*, index_desc*, jrd_rel*, USHORT); static bool cmpRecordKeys(thread_db*, Record*, jrd_rel*, index_desc*, Record*, jrd_rel*, index_desc*); static bool duplicate_key(const UCHAR*, const UCHAR*, void*); -static PageNumber get_root_page(thread_db*, Cached::Relation*); static idx_e insert_key(thread_db*, jrd_rel*, Record*, jrd_tra*, WIN *, index_insertion*, IndexErrorContext&); -void IDX_check_access(thread_db* tdbb, CompilerScratch* csb, Cached::Relation* view, Cached::Relation* relation) -{ -/************************************** - * - * I D X _ c h e c k _ a c c e s s - * - ************************************** - * - * Functional description - * Check the various indices in a relation - * to see if we need REFERENCES access to fields - * in the primary key. Don't call this routine for - * views or external relations, since the mechanism - * ain't there. - * - **************************************/ - SET_TDBB(tdbb); - - index_desc idx; - idx.idx_id = idx_invalid; - RelationPages* relPages = relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - WIN referenced_window(relPages->rel_pg_space_id, -1); - - while (BTR_next_index(tdbb, relation, NULL, &idx, &window)) - { - if (idx.idx_flags & idx_foreign) - { - // find the corresponding primary key index - - if (!MET_lookup_partner(tdbb, relation, &idx, {})) - continue; - - auto referenced_relation = - MetadataCache::getVersioned(tdbb, idx.idx_primary_relation, CacheFlag::AUTOCREATE); - const USHORT index_id = idx.idx_primary_index; - - // get the description of the primary key index - - referenced_window.win_page = get_root_page(tdbb, getPermanent(referenced_relation)); - referenced_window.win_flags = 0; - auto* referenced_root = BTR_fetch_root(FB_FUNCTION, tdbb, &referenced_window); - index_desc referenced_idx; - if (!BTR_description(tdbb, getPermanent(referenced_relation), referenced_root, - &referenced_idx, index_id)) - { - CCH_RELEASE(tdbb, &referenced_window); - BUGCHECK(173); // msg 173 referenced index description not found - } - - // post references access to each field in the index - - const index_desc::idx_repeat* idx_desc = referenced_idx.idx_rpt; - for (USHORT i = 0; i < referenced_idx.idx_count; i++, idx_desc++) - { - const SLONG ssRelationId = view ? view->rel_id : 0; - const jrd_fld* referenced_field = MET_get_field(referenced_relation, idx_desc->idx_field); - - CMP_post_access(tdbb, csb, relation->getSecurityName().schema, ssRelationId, - SCL_usage, obj_schemas, QualifiedName(relation->getName().schema)); - - CMP_post_access(tdbb, csb, - referenced_relation->getSecurityName().object, ssRelationId, - SCL_references, obj_relations, - referenced_relation->getName()); - - CMP_post_access(tdbb, csb, - referenced_field->fld_security_name, 0, - SCL_references, obj_column, - referenced_relation->getName(), referenced_field->fld_name); - } - - CCH_RELEASE(tdbb, &referenced_window); - } - } -} - - -bool IDX_check_master_types(thread_db* tdbb, index_desc& idx, Cached::Relation* partner_relation, int& bad_segment) +bool IDX_check_master_types(thread_db* tdbb, index_desc& idx, jrd_rel* partner_relation, int& bad_segment) { /********************************************** * @@ -182,11 +104,15 @@ bool IDX_check_master_types(thread_db* tdbb, index_desc& idx, Cached::Relation* index_desc partner_idx; // get the index root page for the partner relation - WIN window(get_root_page(tdbb, partner_relation)); - auto* root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + const auto rootPage = partner_relation->getIndexRootPage(tdbb); + if (!rootPage) + BUGCHECK(175); // msg 175 partner index description not found + + WIN window(rootPage.value()); + const auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); // get the description of the partner index - const bool ok = BTR_description(tdbb, partner_relation, root, &partner_idx, idx.idx_primary_index); + const bool ok = BTR_description(tdbb, partner_relation->getPermanent(), root, &partner_idx, idx.idx_primary_index); CCH_RELEASE(tdbb, &window); if (!ok) @@ -385,19 +311,17 @@ class IdxCreationHelper public: void beginPrepare() { - jrd_tra* transaction = m_creation.transaction; + const auto transaction = m_creation.transaction; m_protectRelations.addRelation(m_creation.relation->getPermanent()); if (!lockWrites(transaction->getLockWait())) - { - ERR_post(Arg::Gds(isc_lock_timeout) << Arg::Str(m_creation.index_name.toQuotedString())); // error message - } + status_exception::raise(Arg::Gds(isc_lock_timeout) << Arg::Str(m_creation.index_name.toQuotedString())); // error message - Database* dbb = m_tdbb->getDatabase(); - Attachment* att = transaction->tra_attachment; + const auto dbb = m_tdbb->getDatabase(); + const auto attachment = transaction->tra_attachment; - m_snapHandle = dbb->dbb_tip_cache->beginSnapshot(m_tdbb, att->att_attachment_id, m_snapNumber); + m_snapHandle = dbb->dbb_tip_cache->beginSnapshot(m_tdbb, attachment->att_attachment_id, m_snapNumber); } void endPrepare() @@ -409,7 +333,7 @@ class IdxCreationHelper // Save the root page of the complementary b-tree. const auto relPages = m_creation.relation->getPages(m_tdbb); - m_tempRoot = (PageNumber(relPages->rel_pg_space_id, m_creation.index->idx_root)); + m_tempRoot = relPages->toPageNumber(m_creation.index->idx_root); // Set index id at RDB$INDICES.RDB$INDEX_ID @@ -435,17 +359,18 @@ class IdxCreationHelper // Temp index root page was saved at m_tempRoot by BTR_create() earlier. // Now merge temp index into new index. - BTR_merge_index(m_tdbb, m_creation.transaction, m_creation.relation, m_creation.index, - m_tempRoot); + BTR_merge_index(m_tdbb, m_creation, m_tempRoot); unlockWrites(); // see lockWrites in BTR_create // mark new index as ready for use - RelationPages* relPages = m_creation.relation->getPages(m_tdbb); - WIN window(relPages->rel_pg_space_id, relPages->rel_index_root); + const auto relPages = m_creation.relation->getPages(m_tdbb); + const auto rootPage = relPages->getIndexRootPage(); + fb_assert(rootPage); - index_root_page* root = (index_root_page*) CCH_FETCH(m_tdbb, &window, LCK_write, pag_root); - index_root_page::irt_repeat* irt_desc = root->irt_rpt + m_creation.index->idx_id; + WIN window(rootPage.value()); + index_root_page* const root = BTR_fetch_root_for_update(FB_FUNCTION, m_tdbb, &window); + index_root_page::irt_repeat* const irt_desc = root->irt_rpt + m_creation.index->idx_id; CCH_MARK(m_tdbb, &window); @@ -536,7 +461,7 @@ class IndexCreateTask : public Task // preserving the page working sets of other attachments. if (att && (att != m_dbb->dbb_attachments || att->att_next)) { - if (att->isGbak() || DPM_data_pages(tdbb, getPermanent(m_creation->relation)) > m_dbb->dbb_bcb->bcb_count) + if (att->isGbak() || DPM_data_pages(tdbb, m_creation->relation) > m_dbb->dbb_bcb->bcb_count) m_flags |= IS_LARGE_SCAN; } @@ -846,7 +771,7 @@ bool IndexCreateTask::handler(WorkItem& _item) if (m_flags & IS_LARGE_SCAN) { primary.getWindow(tdbb).win_flags = secondary.getWindow(tdbb).win_flags = WIN_large_scan; - primary.rpb_org_scans = secondary.rpb_org_scans = getPermanent(relation)->rel_scan_count++; + primary.rpb_org_scans = secondary.rpb_org_scans = relation->getPermanent()->rel_scan_count++; } Cleanup cleanAfterScan([&] @@ -1165,8 +1090,6 @@ void IDX_create_index(thread_db* tdbb, Arg::Gds(isc_wish_list)); } - get_root_page(tdbb, getPermanent(relation)); - fb_assert(transaction); const bool isDescending = (idx->idx_flags & idx_descending); @@ -1291,13 +1214,7 @@ void IDX_create_index(thread_db* tdbb, } -bool IDX_activate_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) -{ - return BTR_activate_index(tdbb, relation, id); -} - - -bool IDX_mark_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) +bool IDX_mark_index(thread_db* tdbb, jrd_rel* relation, MetaId id) { /************************************** * @@ -1311,19 +1228,15 @@ bool IDX_mark_index(thread_db* tdbb, Cached::Relation* relation, MetaId id) **************************************/ SET_TDBB(tdbb); - auto* relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false); - if (relPages) - { - fb_assert(relPages->rel_index_root); - - WIN window(relPages->rel_pg_space_id, relPages->rel_index_root); - index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) + return false; - BTR_mark_index_for_delete(tdbb, relation, id, &window, root, 0); - return true; - } + WIN window(rootPage.value()); + index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); - return false; + BTR_mark_index_for_delete(tdbb, relation->getPermanent(), id, &window, root, 0); + return true; } void IDX_mark_temp(thread_db* tdbb, RelationPermanent* relation, MetaId id, Attachment* current, TraNumber tran) @@ -1368,19 +1281,21 @@ void IDX_mark_temp(thread_db* tdbb, RelationPermanent* relation, MetaId id, Atta if (attachment) { - auto* pages = relation->getAttPages(tdbb, attachment->att_attachment_id); - if (pages && pages->rel_index_root) + if (const auto pages = relation->getAttPages(tdbb, attachment->att_attachment_id)) { - WIN window(pages->rel_pg_space_id, pages->rel_index_root); - auto* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); - BTR_mark_index_for_delete(tdbb, relation, id, &window, root, tran); + if (const auto rootPage = pages->getIndexRootPage()) + { + WIN window(rootPage.value()); + const auto root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + BTR_mark_index_for_delete(tdbb, relation, id, &window, root, tran); + } } } } } -void IDX_delete_indices(thread_db* tdbb, RelationPermanent* relation, RelationPages* relPages, bool withCleanup) +void IDX_delete_indices(thread_db* tdbb, const PageNumber& rootPage, bool withCleanup) { /************************************** * @@ -1394,17 +1309,13 @@ void IDX_delete_indices(thread_db* tdbb, RelationPermanent* relation, RelationPa **************************************/ SET_TDBB(tdbb); - fb_assert(relPages->rel_index_root); + WIN window(rootPage); - WIN window(relPages->rel_pg_space_id, relPages->rel_index_root); index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); - // loop through pagespaces and mark for delete %%%%%% - // if ((relation->rel_flags & REL_temp_conn) && (relPages->rel_instance_id != 0)) - for (USHORT i = 0; i < root->irt_count; i++) { - const bool tree_exists = BTR_delete_index(tdbb, &window, i, withCleanup); + BTR_delete_index(tdbb, &window, i, withCleanup); root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); } @@ -1431,10 +1342,12 @@ void IDX_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) index_desc idx; idx.idx_id = idx_invalid; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); + const auto rootPage = rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; - while (BTR_next_index(tdbb, getPermanent(rpb->rpb_relation), transaction, &idx, &window)) + WIN window(rootPage.value().getPageSpaceID(), -1); + while (BTR_next_index(tdbb, rpb->rpb_relation, transaction, &idx, &window)) { if (idx.idx_flags & (idx_primary | idx_unique)) { @@ -1477,9 +1390,12 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, insertion.iib_btr_level = 0; insertion.iib_transaction = tdbb->getTransaction(); - WIN window(get_root_page(tdbb, getPermanent(rpb->rpb_relation))); + const auto rootPage = rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; - auto* root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + WIN window(rootPage.value()); + auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); for (USHORT id = 0; id < root->irt_count; id++) { @@ -1525,7 +1441,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, { // Check if record version was created after the index scan started. - TraNumber recTran = rec->getTransactionNumber(); + const TraNumber recTran = rec->getTransactionNumber(); const auto cnRec = dbb->dbb_tip_cache->cacheState(recTran); if (cnRec > creation->helper->getSnapNumber()) pkey->m_flags |= key_newver; // key is present in new record version @@ -1580,18 +1496,19 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, if (isComplementary && !creatingIndex && !insertion.iib_removed) { - root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_root); - idx.idx_root = root->irt_rpt[id].getRoot(); + root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); // set 'deleted' node marker in lower bit and insert key into b-tree insertion.iib_number.increment(); insertion.iib_btr_level = 0; BTR_insert(tdbb, &window, &insertion); } + insertion.iib_number.setValue(recno); - root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_root); - idx.idx_root = root->irt_rpt[id].getRoot(); + root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); } } } @@ -1629,12 +1546,14 @@ void IDX_modify(thread_db* tdbb, insertion.iib_transaction = transaction; insertion.iib_btr_level = 0; - RelationPages* relPages = org_rpb->rpb_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - new_rpb->rpb_runtime_flags &= ~RPB_uk_updated; - while (BTR_next_index(tdbb, getPermanent(org_rpb->rpb_relation), transaction, &idx, &window)) + const auto rootPage = org_rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; + + WIN window(rootPage.value().getPageSpaceID(), -1); + while (BTR_next_index(tdbb, org_rpb->rpb_relation, transaction, &idx, &window)) { IndexErrorContext context(new_rpb->rpb_relation, &idx); idx_e error_code = idx_e_ok; @@ -1737,13 +1656,15 @@ void IDX_modify_check_constraints(thread_db* tdbb, index_desc idx; idx.idx_id = idx_invalid; - RelationPages* relPages = org_rpb->rpb_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - // Now check all the foreign key constraints. Referential integrity relation // could be established by primary key/foreign key or unique key/foreign key - while (BTR_next_index(tdbb, getPermanent(org_rpb->rpb_relation), transaction, &idx, &window)) + const auto rootPage = org_rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; + + WIN window(rootPage.value().getPageSpaceID(), -1); + while (BTR_next_index(tdbb, org_rpb->rpb_relation, transaction, &idx, &window)) { if (!(idx.idx_flags & (idx_primary | idx_unique)) || !MET_lookup_partner(tdbb, getPermanent(org_rpb->rpb_relation), &idx, {})) @@ -1819,14 +1740,16 @@ void IDX_modify_flag_uk_modified(thread_db* tdbb, jrd_rel* const relation = org_rpb->rpb_relation; fb_assert(new_rpb->rpb_relation == relation); - RelationPages* const relPages = relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); - DSC desc1, desc2; index_desc idx; idx.idx_id = idx_invalid; - while (BTR_next_index(tdbb, getPermanent(relation), transaction, &idx, &window)) + const auto rootPage = org_rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; + + WIN window(rootPage.value().getPageSpaceID(), -1); + while (BTR_next_index(tdbb, relation, transaction, &idx, &window)) { if (!(idx.idx_flags & (idx_primary | idx_unique)) || !MET_lookup_partner(tdbb, getPermanent(relation), &idx, {})) @@ -1852,26 +1775,6 @@ void IDX_modify_flag_uk_modified(thread_db* tdbb, } -void IDX_statistics(thread_db* tdbb, Cached::Relation* relation, USHORT id, SelectivityList& selectivity) -{ -/************************************** - * - * I D X _ s t a t i s t i c s - * - ************************************** - * - * Functional description - * Scan index pages recomputing - * selectivity. - * - **************************************/ - - SET_TDBB(tdbb); - - BTR_selectivity(tdbb, relation, id, selectivity); -} - - void IDX_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) { /************************************** @@ -1898,10 +1801,12 @@ void IDX_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) insertion.iib_transaction = transaction; insertion.iib_btr_level = 0; - RelationPages* relPages = rpb->rpb_relation->getPages(tdbb); - WIN window(relPages->rel_pg_space_id, -1); + const auto rootPage = rpb->rpb_relation->getIndexRootPage(tdbb); + if (!rootPage) + return; - while (BTR_next_index(tdbb, getPermanent(rpb->rpb_relation), transaction, &idx, &window)) + WIN window(rootPage.value().getPageSpaceID(), -1); + while (BTR_next_index(tdbb, rpb->rpb_relation, transaction, &idx, &window)) { IndexErrorContext context(rpb->rpb_relation, &idx); idx_e error_code = idx_e_ok; @@ -2223,47 +2128,64 @@ static idx_e check_foreign_key(thread_db* tdbb, if (!MET_lookup_partner(tdbb, relation->getPermanent(), idx, {})) return result; - jrd_rel* partner_relation = nullptr; + jrd_rel* partnerRelation = nullptr; USHORT index_id = 0; if (idx->idx_flags & idx_foreign) { - partner_relation = MetadataCache::getVersioned(tdbb, idx->idx_primary_relation, CacheFlag::AUTOCREATE); - fb_assert(partner_relation); + partnerRelation = MetadataCache::getVersioned(tdbb, idx->idx_primary_relation, CacheFlag::AUTOCREATE); + fb_assert(partnerRelation); index_id = idx->idx_primary_index; result = check_partner_index(tdbb, relation, record, transaction, idx, - partner_relation, index_id); + partnerRelation, index_id); } - else if ((idx->idx_flags & (idx_primary | idx_unique)) && (idx->idx_foreign_dep.dep_reference_id >= 0)) + else if (idx->idx_flags & (idx_primary | idx_unique)) { - const auto& frgn = idx->idx_foreign_dep; + const auto dependencies = relation->getPermanent()->rel_primary_dpnds; + if (!dependencies) + return result; - partner_relation = MetadataCache::getVersioned(tdbb, frgn.dep_relation, CacheFlag::AUTOCREATE); - fb_assert(partner_relation); - auto* partnerRelation = partner_relation->getPermanent(); - index_id = frgn.dep_index; + // Check every foreign key referencing this primary or unique key - if ((relation->getPermanent()->rel_flags & REL_temp_conn) && - (partnerRelation->rel_flags & REL_temp_tran)) + for (const auto& frgn : *dependencies) { - RelationPermanent::RelPagesSnapshot pagesSnapshot(tdbb, partnerRelation); - partnerRelation->fillPagesSnapshot(pagesSnapshot, true); + if (frgn.dep_reference_id != idx->idx_id) + continue; + + partnerRelation = MetadataCache::getVersioned(tdbb, frgn.dep_relation, CacheFlag::AUTOCREATE); + fb_assert(partnerRelation); + index_id = frgn.dep_index; - for (FB_SIZE_T i = 0; i < pagesSnapshot.getCount(); i++) + if ((relation->getPermanent()->rel_flags & REL_temp_conn) && + (partnerRelation->getPermanent()->rel_flags & REL_temp_tran)) { - RelationPages* partnerPages = pagesSnapshot[i]; - tdbb->tdbb_temp_traid = partnerPages->rel_instance_id; - if ( (result = check_partner_index(tdbb, relation, record, - transaction, idx, partner_relation, index_id)) ) + RelationPermanent::PagesSnapshot pagesSnapshot(tdbb, partnerRelation->getPermanent()); + + if (!partnerRelation->getPermanent()->fillPagesSnapshot(pagesSnapshot, true)) + pagesSnapshot.add(partnerRelation->getBasePages()); + + for (const auto partnerPages : pagesSnapshot) { - break; + tdbb->tdbb_temp_traid = partnerPages->getInstanceId(); + + if ( (result = check_partner_index(tdbb, relation, record, + transaction, idx, partnerRelation, index_id)) ) + { + break; + } } + + tdbb->tdbb_temp_traid = 0; + } + else + { + result = check_partner_index(tdbb, relation, record, transaction, idx, + partnerRelation, index_id); } - tdbb->tdbb_temp_traid = 0; + if (result) + break; } - else - result = check_partner_index(tdbb, relation, record, transaction, idx, partner_relation, index_id); } if (result) @@ -2271,7 +2193,7 @@ static idx_e check_foreign_key(thread_db* tdbb, if (idx->idx_flags & idx_foreign) context.setErrorLocation(relation, idx->idx_id); else - context.setErrorLocation(partner_relation, index_id); + context.setErrorLocation(partnerRelation, index_id); } return result; @@ -2302,8 +2224,12 @@ static idx_e check_partner_index(thread_db* tdbb, // get the index root page for the partner relation - WIN window(get_root_page(tdbb, getPermanent(partner_relation))); - auto* root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); + const auto rootPage = partner_relation->getIndexRootPage(tdbb); + if (!rootPage) + BUGCHECK(175); // msg 175 partner index description not found + + WIN window(rootPage.value()); + const auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); // get the description of the partner index @@ -2437,22 +2363,6 @@ static bool duplicate_key(const UCHAR* record1, const UCHAR* record2, void* ifl_ } -static PageNumber get_root_page(thread_db* tdbb, Cached::Relation* relation) -{ -/************************************** - * - * g e t _ r o o t _ p a g e - * - ************************************** - * - * Functional description - * Find the root page for a relation. - * - **************************************/ - return relation->getIndexRootPage(tdbb); -} - - static idx_e insert_key(thread_db* tdbb, jrd_rel* relation, Record* record, diff --git a/src/jrd/idx.h b/src/jrd/idx.h index f879661d12b..0fd9ee93926 100644 --- a/src/jrd/idx.h +++ b/src/jrd/idx.h @@ -496,7 +496,7 @@ static inline constexpr struct ini_idx_t indices[] = // define index RDB$INDEX_91 for RDB$RELATION_CONSTRAINTS RDB$SCHEMA_NAME, RDB$INDEX_NAME; INDEX(91, rel_rcon, 0, 2, ODS_14_0) SEGMENT(f_rcon_schema, idx_metadata), // schema name - SEGMENT(f_rcon_iname, idx_metadata), // index name + SEGMENT(f_rcon_iname, idx_metadata) // index name }}, // define index RDB$INDEX_92 for RDB$PACKAGES unique RDB$SCHEMA_NAME, RDB$PACKAGE_NAME; INDEX(92, rel_packages, idx_unique, 2, ODS_14_0) @@ -541,6 +541,19 @@ static inline constexpr struct ini_idx_t indices[] = // define index RDB$INDEX_100 for RDB$PACKAGES unique RDB$PACKAGE_ID; INDEX(99, rel_packages, idx_unique, 1, ODS_14_0) SEGMENT(f_pkg_id, idx_numeric) // constant id + }}, + // define index RDB$INDEX_98 for RDB$TABLESPACES unique RDB$TABLESPACE_NAME; + INDEX(100, rel_tablespaces, idx_unique, 1, ODS_14_0) + SEGMENT(f_ts_name, idx_metadata) // tablespace name + }}, + // define index RDB$INDEX_99 for RDB$TABLESPACES unique RDB$TABLESPACE_ID; + INDEX(101, rel_tablespaces, idx_unique, 1, ODS_14_0) + SEGMENT(f_ts_id, idx_numeric) // tablespace id + }}, + // define index RDB$INDEX_100 for RDB$PAGES RDB$PAGE_TYPE, RDB$RELATION_ID; + INDEX(102, rel_pages, 0, 2, ODS_14_0) + SEGMENT(f_pag_type, idx_numeric), // page type + SEGMENT(f_pag_id, idx_numeric) // relation id }} }; diff --git a/src/jrd/idx_proto.h b/src/jrd/idx_proto.h index 347d2bfc142..cc892b9f2af 100644 --- a/src/jrd/idx_proto.h +++ b/src/jrd/idx_proto.h @@ -38,20 +38,17 @@ namespace Jrd class thread_db; } -bool IDX_activate_index(Jrd::thread_db*, Jrd::Cached::Relation*, MetaId); -void IDX_check_access(Jrd::thread_db*, Jrd::CompilerScratch*, Jrd::Cached::Relation*, Jrd::Cached::Relation*); -bool IDX_check_master_types (Jrd::thread_db*, Jrd::index_desc&, Jrd::Cached::Relation*, int&); +bool IDX_check_master_types(Jrd::thread_db*, Jrd::index_desc&, Jrd::jrd_rel*, int&); void IDX_create_index(Jrd::thread_db*, Jrd::IdxCreate createMethod, Jrd::jrd_rel*, Jrd::index_desc*, const Jrd::QualifiedName&, USHORT*, Jrd::jrd_tra*, Jrd::SelectivityList&); -bool IDX_mark_index(Jrd::thread_db*, Jrd::Cached::Relation*, MetaId); +bool IDX_mark_index(Jrd::thread_db*, Jrd::jrd_rel*, MetaId); void IDX_mark_temp(Jrd::thread_db* tdbb, Jrd::RelationPermanent* relation, MetaId id, Jrd::Attachment* current, TraNumber tran); -void IDX_delete_indices(Jrd::thread_db*, Jrd::RelationPermanent*, Jrd::RelationPages*, bool); +void IDX_delete_indices(Jrd::thread_db*, const Jrd::PageNumber&, bool); void IDX_erase(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*); void IDX_garbage_collect(Jrd::thread_db*, Jrd::record_param*, Jrd::RecordStack&, Jrd::RecordStack&); void IDX_modify(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jrd_tra*); void IDX_modify_check_constraints(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jrd_tra*); -void IDX_statistics(Jrd::thread_db*, Jrd::Cached::Relation*, USHORT, Jrd::SelectivityList&); void IDX_store(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*); void IDX_modify_flag_uk_modified(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jrd_tra*); bool IDX_validate_unique(Jrd::thread_db* tdbb, Jrd::jrd_rel* relation, MetaId id); diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp index e4199953b64..b989c3c64d5 100644 --- a/src/jrd/ini.epp +++ b/src/jrd/ini.epp @@ -595,20 +595,6 @@ namespace } } } - - addSecurityClass(tdbb, DATABASE_CLASS); - - handle.reset(); - - FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction) - DB IN RDB$DATABASE - { - MODIFY DB USING - DB.RDB$SECURITY_CLASS.NULL = FALSE; - PAD(DATABASE_CLASS, DB.RDB$SECURITY_CLASS); - END_MODIFY - } - END_FOR } private: @@ -704,8 +690,9 @@ void INI_format(thread_db* tdbb, const string& charset) { if (relfld[RFLD_R_TYPE] == rel_persistent) { - DPM_create_relation(tdbb, MetadataCache::getPerm(tdbb, relfld[RFLD_R_ID], - CacheFlag::AUTOCREATE | CacheFlag::NOSCAN)); + const auto relation = MetadataCache::getVersioned(tdbb, relfld[RFLD_R_ID], + CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); + DPM_create_relation(tdbb, relation); } for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH) @@ -763,6 +750,32 @@ void INI_format(thread_db* tdbb, const string& charset) for (const gfld* gfield = gfields; gfield->gfld_name; gfield++) store_global_field(tdbb, gfield, handle, nonRelSec); + // Store PRIMARY TABLESPACE record + + handle.reset(); + + STORE(REQUEST_HANDLE handle) X IN RDB$TABLESPACES + { + X.RDB$TABLESPACE_ID = DB_PAGE_SPACE; + PAD(PRIMARY_TABLESPACE_NAME, X.RDB$TABLESPACE_NAME); + + X.RDB$SYSTEM_FLAG = RDB_system; + + PAD(ownerName, X.RDB$OWNER_NAME); + X.RDB$OWNER_NAME.NULL = FALSE; + + const auto securityClass = nonRelSec.storeSecurityClass(tdbb); + PAD(securityClass, X.RDB$SECURITY_CLASS); + X.RDB$SECURITY_CLASS.NULL = FALSE; + + X.RDB$OFFLINE.NULL = FALSE; + X.RDB$OFFLINE = FALSE; + + X.RDB$READ_ONLY.NULL = FALSE; + X.RDB$READ_ONLY = FALSE; + } + END_STORE + // Store DATABASE record handle.reset(); @@ -774,15 +787,23 @@ void INI_format(thread_db* tdbb, const string& charset) if (charSetName.schema.isEmpty()) charSetName.schema = SYSTEM_SCHEMA; + nonRelSec.addSecurityClass(tdbb, DATABASE_CLASS); + + handle.reset(); + STORE(REQUEST_HANDLE handle) X IN RDB$DATABASE { X.RDB$RELATION_ID = 0; PAD(charSetName.object.c_str(), X.RDB$CHARACTER_SET_NAME); PAD(charSetName.schema.c_str(), X.RDB$CHARACTER_SET_SCHEMA_NAME); + + PAD(DATABASE_CLASS, X.RDB$SECURITY_CLASS); + X.RDB$SECURITY_CLASS.NULL = FALSE; } END_STORE DPM_gen_id(tdbb, RELATIONS_GENERATOR, true, USER_DEF_REL_INIT_ID); + DPM_gen_id(tdbb, TABLESPACES_GENERATOR, true, DB_PAGE_SPACE); HalfStaticArray predefinedSchemas; @@ -1252,8 +1273,9 @@ void INI_upgrade(thread_db* tdbb) { if (relfld[RFLD_R_TYPE] == rel_persistent && relfld[RFLD_R_ODS] > odsVersion) { - DPM_create_relation(tdbb, MetadataCache::getPerm(tdbb, relfld[RFLD_R_ID], - CacheFlag::AUTOCREATE | CacheFlag::NOSCAN)); + const auto relation = MetadataCache::getVersioned(tdbb, relfld[RFLD_R_ID], + CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); + DPM_create_relation(tdbb, relation); } for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH) @@ -1373,9 +1395,8 @@ void INI_upgrade(thread_db* tdbb) { if (relfld[RFLD_R_TYPE] == rel_persistent && relfld[RFLD_R_ODS] > odsVersion) { - const auto relation = MetadataCache::getPerm(tdbb, relfld[RFLD_R_ID], CacheFlag::NOSCAN); - if (relation && relation->getBasePages()->rel_pages) - DPM_delete_relation(tdbb, relation); + if (const auto relation = MetadataCache::getVersioned(tdbb, relfld[RFLD_R_ID], CacheFlag::NOSCAN)) + relation->deletePages(tdbb); } for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH) @@ -1749,12 +1770,16 @@ static void store_indices(thread_db* tdbb, USHORT odsVersion) idx.idx_count = index->ini_idx_segment_count; idx.idx_flags = index->ini_idx_flags; + idx.idx_pg_space_id = DB_PAGE_SPACE; SelectivityList selectivity(*tdbb->getDefaultPool()); IDX_create_index(tdbb, IdxCreate::AtOnce, relVers, &idx, indexName, NULL, transaction, selectivity); X.RDB$INDEX_ID = idx.idx_id + 1; + + PAD(PRIMARY_TABLESPACE_NAME, X.RDB$TABLESPACE_NAME); + X.RDB$TABLESPACE_NAME.NULL = FALSE; } END_STORE @@ -1927,6 +1952,14 @@ static void store_relation(thread_db* tdbb, X.RDB$FORMAT = getLatestFormat(tdbb, relId, fieldId); X.RDB$SYSTEM_FLAG = RDB_system; X.RDB$DBKEY_LENGTH = 8; + + if (fb_utils::hasTablespaceName(rel_t(relType))) + { + PAD(PRIMARY_TABLESPACE_NAME, X.RDB$TABLESPACE_NAME); + X.RDB$TABLESPACE_NAME.NULL = FALSE; + } + else + X.RDB$TABLESPACE_NAME.NULL = TRUE; } END_STORE; @@ -1957,6 +1990,9 @@ static void store_relation_field(thread_db* tdbb, X.RDB$SYSTEM_FLAG = RDB_system; X.RDB$SYSTEM_FLAG.NULL = FALSE; X.RDB$UPDATE_FLAG = updateFlag; + + PAD(PRIMARY_TABLESPACE_NAME, X.RDB$TABLESPACE_NAME); + X.RDB$TABLESPACE_NAME.NULL = FALSE; } END_STORE } diff --git a/src/jrd/irq.h b/src/jrd/irq.h index b4d05c7767c..2dd51f87764 100644 --- a/src/jrd/irq.h +++ b/src/jrd/irq.h @@ -29,8 +29,6 @@ enum irq_type_t { - irq_s_pages, // store PAGES - irq_r_pages, // read PAGES irq_format1, // make a new format for a record irq_format2, // make a new format for a record irq_format3, // make a new format for a record @@ -170,14 +168,28 @@ enum irq_type_t irq_grant17, // process grant option (database) irq_grant18, // process grant option (filters) irq_grant19, // process grant option (roles) + irq_grant20, // process grant option (tablespaces) + irq_l_curr_format, // lookup table's current format + irq_c_relation3, // lookup relation in phase 0 to cleanup irq_linger, // get database linger value irq_dbb_ss_definer, // get database sql security value + irq_find_rel_ts, // find tablespace options for relation + irq_find_idx_ts, // find tablespace options for index + irq_find_ts, // find tablespace options by name + irq_find_ts_id, // find tablespace options by id irq_proc_param_dep, // check procedure parameter dependency irq_func_param_dep, // check function parameter dependency irq_l_pub_tab_state, // lookup publication state for a table irq_index_scan, // scan index for caching irq_index_id_erase, // cleanup index ID irq_l_index_cnstrt, // lookup index for constraint + irq_list_ts_files, // list tablespace files + irq_find_ts_dfw, // find tablespace options by name in dfw + irq_find_ts_dfw0, // find tablespace options by name in dfw for cleanup + irq_scan_ts, // scan tablespaces + irq_ts_security, // verify security for tablespace + irq_s_first_pp, + irq_chk_ts_filename, irq_MAX }; diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index d489ad491c5..154fd57a260 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -823,6 +823,24 @@ namespace { return callback ? callback : &defCallback; } + + bool dropFile(const Database* dbb, const PathName& fileName) + { + FbLocalStatus status; + + if (unlink(fileName.c_str())) + { + ERR_build_status(&status, + Arg::Gds(isc_io_error) << Arg::Str("unlink") << Arg::Str(fileName) << + Arg::Gds(isc_io_delete_err) << SYS_ERR(errno)); + + const auto pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE); + iscDbLogStatus(pageSpace->file->fil_string, &status); + } + + return status->getState() & IStatus::STATE_ERRORS ? true : false; + } + } // anonymous @@ -1291,7 +1309,6 @@ class TraceFailedConnection final : static void check_database(thread_db* tdbb, bool async = false); static void commit(thread_db*, jrd_tra*, const bool); -static bool drop_file(const Database*, const jrd_file*); static void find_intl_charset(thread_db*, Jrd::Attachment*, const DatabaseOptions*); static void init_database_lock(thread_db*); static void run_commit_triggers(thread_db* tdbb, jrd_tra* transaction); @@ -3455,6 +3472,11 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) Ods::header_page* header = NULL; XThreadEnsureUnlock threadGuard(dbb->dbb_thread_mutex, FB_FUNCTION); + // Use the default memory pool instead of the dbb permanent memory pool + // because the last one will be destroyed below in this function + // before ~ObjectsArray call. + ObjectsArray tsFiles(*getDefaultMemoryPool()); + try { Sync sync(&dbb->dbb_sync, "JAttachment::dropDatabase()"); @@ -3509,6 +3531,9 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) // dbb->dbb_extManager->closeAttachment(tdbb, attachment); // To be reviewed by Adriano - it will be anyway called in release_attachment + // Now under exclusive lock we can get a list of tablespace files to delete them later + MET_get_ts_files(tdbb, tsFiles); + // Forced release of all transactions dbb->dbb_flags |= DBB_dropping; attachment->purgeTransactions(tdbb, true); @@ -3556,12 +3581,13 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) if (JRD_shutdown_database(dbb)) { - // This point on database is useless + // At this point the database becomes useless, so drop all its files - // drop the files here - bool err = drop_file(dbb, file); + bool err = dropFile(dbb, file->fil_string); for (; shadow; shadow = shadow->sdw_next) - err = drop_file(dbb, shadow->sdw_file) || err; + err = dropFile(dbb, shadow->sdw_file->fil_string) || err; + while (tsFiles.hasData()) + err = dropFile(dbb, tsFiles.pop()) || err; tdbb->setDatabase(NULL); Database::destroy(dbb); @@ -6883,34 +6909,6 @@ static void commit(thread_db* tdbb, jrd_tra* transaction, const bool retaining_f } -static bool drop_file(const Database* dbb, const jrd_file* file) -{ -/************************************** - * - * d r o p _ f i l e - * - ************************************** - * - * Functional description - * Drop a file. - * - **************************************/ - FbLocalStatus status; - - if (unlink(file->fil_string)) - { - ERR_build_status(&status, Arg::Gds(isc_io_error) << Arg::Str("unlink") << - Arg::Str(file->fil_string) << - Arg::Gds(isc_io_delete_err) << SYS_ERR(errno)); - - const PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE); - iscDbLogStatus(pageSpace->file->fil_string, &status); - } - - return status->getState() & IStatus::STATE_ERRORS ? true : false; -} - - static void find_intl_charset(thread_db* tdbb, Jrd::Attachment* attachment, const DatabaseOptions* options) { /************************************** @@ -8163,9 +8161,14 @@ bool JRD_shutdown_database(Database* dbb, const unsigned flags) // Release the system requests dbb->releaseSystemRequests(tdbb); - CCH_shutdown(tdbb); + // Release cached metadata objects (procedures, functions, etc.) while + // the buffer manager and lock manager are still alive. + // This is needed because Statement::release may require page access + // (e.g. for LTT cleanup via IDX_delete_indices). + dbb->dbb_mdc->cleanup(tdbb); + dbb->dbb_tablespaces.release(tdbb); - dbb->dbb_mdc->releaseLocks(tdbb); + CCH_shutdown(tdbb); if (dbb->dbb_tip_cache) dbb->dbb_tip_cache->finalizeTpc(tdbb); @@ -8195,18 +8198,7 @@ bool JRD_shutdown_database(Database* dbb, const unsigned flags) LCK_release(tdbb, dbb->dbb_lock); delete dbb->dbb_crypto_manager; - dbb->dbb_crypto_manager = NULL; - - MetadataCache::clear(tdbb); - - // Shut down any extern relations - dbb->dbb_mdc->releaseRelations(tdbb); - - // Release cached metadata objects (procedures, functions, etc.) while - // the buffer manager and lock manager are still alive. - // This is needed because Statement::release may require page access - // (e.g. for LTT cleanup via IDX_delete_indices). - dbb->dbb_mdc->cleanup(tdbb); + dbb->dbb_crypto_manager = nullptr; LCK_fini(tdbb, LCK_OWNER_database); diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index ae4d3a0d568..0b8e41f5914 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -160,10 +160,12 @@ struct win class BufferDesc* win_bdb; SSHORT win_scans; USHORT win_flags; + explicit win(const PageNumber& wp) noexcept : win_page(wp), win_bdb(NULL), win_flags(0) {} - win(const USHORT pageSpaceID, const ULONG pageNum) noexcept + + explicit win(const ULONG pageSpaceID, const ULONG pageNum = MAX_ULONG) noexcept : win_page(pageSpaceID, pageNum), win_bdb(NULL), win_flags(0) {} }; diff --git a/src/jrd/lck.cpp b/src/jrd/lck.cpp index bc29d8555ab..66fedc93abf 100644 --- a/src/jrd/lck.cpp +++ b/src/jrd/lck.cpp @@ -588,6 +588,7 @@ static lck_owner_t get_owner_type(enum lck_t lock_type) case LCK_cs_rescan: case LCK_dbwide_triggers: case LCK_repl_tables: + case LCK_tablespace: owner_type = LCK_OWNER_database; break; diff --git a/src/jrd/lck.h b/src/jrd/lck.h index 26c902c91a3..882fd9af77a 100644 --- a/src/jrd/lck.h +++ b/src/jrd/lck.h @@ -77,7 +77,8 @@ enum lck_t : UCHAR { LCK_repl_tables, // Replication set lock LCK_dsql_statement_cache, // DSQL statement cache lock LCK_profiler_listener, // Remote profiler listener - LCK_dbwide_triggers + LCK_dbwide_triggers, // Database wide triggers rescan lock + LCK_tablespace // Tablespace existance lock }; // Lock owner types diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 27b46d55b4b..322d8773ffb 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -95,6 +95,7 @@ #include "../common/classes/Hash.h" #include "../common/classes/MsgPrint.h" #include "../jrd/Function.h" +#include "../jrd/Tablespace.h" #include "../jrd/trace/TraceJrdHelpers.h" #include "firebird/impl/msg_helper.h" #include "../jrd/LocalTemporaryTable.h" @@ -337,6 +338,19 @@ void MetadataCache::update_partners(thread_db* tdbb) // Done before MDC is deleted void MetadataCache::cleanup(thread_db* tdbb) { + releaseLocks(tdbb); + + // Shut down any extern relations + + for (auto relation : mdc_relations) + { + if (relation) + { + if (const auto ext = relation->getExtFile()) + ext->release(); + } + } + mdc_relations.cleanup(tdbb); mdc_procedures.cleanup(tdbb); mdc_functions.cleanup(tdbb); @@ -3133,6 +3147,23 @@ ScanResult jrd_rel::scan(thread_db* tdbb, ObjectBase::Flag& flags) blb* blob = NULL; jrd_tra* trans = attachment->getMetaTransaction(tdbb); + auto setupPageSpace = [&](const rel_t type, const MetaName& tableSpaceName) + { + ULONG pageSpaceId = DB_PAGE_SPACE; + + if (!rel_perm->isSystem()) + { +#ifdef DEV_BUILD + if (!attachment->isGbak() && fb_utils::hasTablespaceName(type)) + fb_assert(tableSpaceName.hasData()); +#endif + if (tableSpaceName.hasData()) + pageSpaceId = Tablespace::lookupId(tdbb, tableSpaceName); + + setPageSpaceId(pageSpaceId); + } + }; + if ((flags & CacheFlag::MINISCAN) || getName().isEmpty()) { // New version of relation is created currently. @@ -3161,6 +3192,9 @@ ScanResult jrd_rel::scan(thread_db* tdbb, ObjectBase::Flag& flags) if (!REL.RDB$RELATION_TYPE.NULL) rel_perm->rel_flags |= MET_get_rel_flags_from_TYPE(REL.RDB$RELATION_TYPE); + const MetaName tableSpaceName(REL.RDB$TABLESPACE_NAME.NULL ? "" : REL.RDB$TABLESPACE_NAME); + setupPageSpace(rel_t(REL.RDB$RELATION_TYPE), tableSpaceName); + rel_current_fmt = REL.RDB$FORMAT; } END_FOR @@ -3345,6 +3379,9 @@ ScanResult jrd_rel::scan(thread_db* tdbb, ObjectBase::Flag& flags) } } + const MetaName tableSpaceName(REL.RDB$TABLESPACE_NAME.NULL ? "" : REL.RDB$TABLESPACE_NAME); + setupPageSpace(rel_t(REL.RDB$RELATION_TYPE), tableSpaceName); + // Pick up field specific stuff blob = blb::open(tdbb, trans, &REL.RDB$RUNTIME); @@ -4816,13 +4853,10 @@ MetadataCache::~MetadataCache() void MetadataCache::releaseGTTs(thread_db* tdbb) { - for (auto relation : mdc_relations) + for (const auto relation : mdc_relations) { - if (relation && (relation->rel_flags & REL_temp_conn) && - !(relation->isDropped())) - { - relation->delPages(tdbb); - } + if (relation && (relation->rel_flags & REL_temp_conn) && !relation->isDropped()) + relation->deletePages(tdbb); } } @@ -4866,21 +4900,6 @@ void MetadataCache::runDBTriggers(thread_db* tdbb, TriggerAction action) } } -void MetadataCache::releaseRelations(thread_db* tdbb) -{ - // Shut down any extern relations - - for (auto relation : mdc_relations) - { - if (relation) - { - auto* ext = relation->getExtFile(); - if (ext) - ext->release(); - } - } -} - void MetadataCache::releaseLocks(thread_db* tdbb) { // Go through relations and indices and release @@ -5583,16 +5602,215 @@ bool MET_check_schema_exists(thread_db* tdbb, const MetaName& name) return false; } -bool MET_get_ltt_index(Attachment* attachment, const QualifiedName& indexName, - LocalTemporaryTable** outLtt, LocalTemporaryTable::Index** outIndex) +ULONG MET_rel_pagespace(Jrd::thread_db* tdbb, USHORT rel_id) { /************************************** * - * M E T _ g e t _ l t t _ i n d e x + * M E T _ r e l _ p a g e s p a c e + * + ************************************** + * + * Functional description + * Find id and filename of tablespace by ID of relation and allocate page space + * Returns page space id for relation of default DB_PAGE_SPACE if table space + * for relation is not specified + * RS: Probably later it would be useful to implement cache of tablespaces + * + **************************************/ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + ULONG pageSpaceId = DB_PAGE_SPACE; + + AutoCacheRequest request(tdbb, irq_find_rel_ts, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + REL IN RDB$RELATIONS + WITH REL.RDB$RELATION_ID EQ rel_id AND + REL.RDB$TABLESPACE_NAME NOT MISSING + { +#ifdef DEV_BUILD + if (!attachment->isGbak() && fb_utils::hasTablespaceName(rel_t(REL.RDB$RELATION_TYPE))) + fb_assert(!REL.RDB$TABLESPACE_NAME.NULL); +#endif + pageSpaceId = Tablespace::lookupId(tdbb, REL.RDB$TABLESPACE_NAME); + } + END_FOR + + return pageSpaceId; +} + +ULONG MET_index_pagespace(thread_db* tdbb, RelationPermanent* relation, USHORT idx_id) +{ +/************************************** + * + * M E T _ i n d e x _ t a b l e s p a c e + * + ************************************** + * + * Functional description + * Find tablespace name for index and lookup page space for it. + * If there is no tablespace for the index or relation is system + * returns relation pagespace. + * + **************************************/ + // Relation tablespace name must be in system table after index creation + // If it's not so we should use the main DB page space. + ULONG pageSpaceId = DB_PAGE_SPACE; + + // System relations are always inside the default page space + if (relation->isSystem()) + return pageSpaceId; + + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + AutoCacheRequest request(tdbb, irq_find_idx_ts, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + IDX IN RDB$INDICES + WITH IDX.RDB$SCHEMA_NAME EQ relation->rel_name.schema.c_str() AND + IDX.RDB$RELATION_NAME EQ relation->rel_name.object.c_str() AND + IDX.RDB$INDEX_ID EQ idx_id + 1 AND + IDX.RDB$TABLESPACE_NAME NOT MISSING + { + pageSpaceId = Tablespace::lookupId(tdbb, IDX.RDB$TABLESPACE_NAME); + } + END_FOR + + return pageSpaceId; +} + +ULONG MET_tablespace_id(thread_db* tdbb, const MetaName& name, PathName& fileName) +{ +/************************************** + * + * M E T _ t a b l e s p a c e _ i d * ************************************** * * Functional description + * Get tablespace ID by its name. + * + **************************************/ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + fb_assert(name.hasData()); + + ULONG id = 0; + + AutoCacheRequest request(tdbb, irq_find_ts_id, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + TS IN RDB$TABLESPACES + WITH TS.RDB$TABLESPACE_NAME EQ name.c_str() + { + id = TS.RDB$TABLESPACE_ID; + fileName = TS.RDB$FILE_NAME; + } + END_FOR + + return id; +} + + +bool MET_tablespace(thread_db* tdbb, ULONG id, MetaName& name, PathName& fileName) +{ +/************************************** + * + * M E T _ t a b l e s p a c e + * + ************************************** + * + * Functional description + * Read tablespace metadata from disk, return false if not found. + * + **************************************/ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + fb_assert(PageSpace::isTablespace(id)); + + bool found = false; + + AutoCacheRequest request(tdbb, irq_find_ts, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + TS IN RDB$TABLESPACES + WITH TS.RDB$TABLESPACE_ID EQ id + { + found = true; + name = TS.RDB$TABLESPACE_NAME; + fileName = TS.RDB$FILE_NAME; + } + END_FOR + + return found; +} + + +void MET_get_ts_files(Jrd::thread_db* tdbb, ObjectsArray& files) +{ +/************************************** + * + * M E T _ g e t _ t s _ f i l e s + * + ************************************** + * + * Functional description + * List all tablespace file names + * + **************************************/ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + AutoCacheRequest request(tdbb, irq_list_ts_files, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + TS IN RDB$TABLESPACES WITH + (TS.RDB$SYSTEM_FLAG MISSING OR TS.RDB$SYSTEM_FLAG NE RDB_system) + { + files.push(TS.RDB$FILE_NAME); + } + END_FOR +} + +void MET_scan_tablespaces(Jrd::thread_db* tdbb) +{ +/************************************** + * + * M E T _ s c a n _ t a b l e s p a c e s + * + ************************************** + * + * Functional description + * Scan every tablespace to cache it. + * The main usage is to scan every tablespace PIP + * in walk_pip + * + **************************************/ + const auto attachment = tdbb->getAttachment(); + const auto metaTransaction = attachment->getMetaTransaction(tdbb); + + // Now we need to find tablespace in system table + AutoCacheRequest request(tdbb, irq_scan_ts, IRQ_REQUESTS); + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) + TS IN RDB$TABLESPACES + { + if (!Tablespace::lookup(tdbb, TS.RDB$TABLESPACE_NAME)) + fb_assert(false); + } + END_FOR +} + +bool MET_get_ltt_index(Attachment* attachment, const QualifiedName& indexName, + LocalTemporaryTable** outLtt, LocalTemporaryTable::Index** outIndex) +{ +/************************************** + * + * M E T _ g e t _ l t t _ i n d e x * Look up an index by name in all LTTs of the current attachment. * If found, returns true and sets outLtt to the owning LocalTemporaryTable * and outIndex to point to the index. If not found, returns false. diff --git a/src/jrd/met.h b/src/jrd/met.h index cbbb1b6bb90..65d419e2bfd 100644 --- a/src/jrd/met.h +++ b/src/jrd/met.h @@ -253,7 +253,6 @@ class MetadataCache : public Firebird::PermanentStorage mdc_cleanup_queue.check(tdbb, oldest); } - void releaseRelations(thread_db* tdbb); void releaseLocks(thread_db* tdbb); void releaseGTTs(thread_db* tdbb); void runDBTriggers(thread_db* tdbb, TriggerAction action); diff --git a/src/jrd/met_proto.h b/src/jrd/met_proto.h index 702deb102a2..82e372d733d 100644 --- a/src/jrd/met_proto.h +++ b/src/jrd/met_proto.h @@ -62,6 +62,7 @@ namespace Jrd class GeneratorItem; class BlobFilter; class RelationPermanent; + class Tablespace; class Triggers; class TrigArray; @@ -142,4 +143,11 @@ bool MET_check_schema_exists(Jrd::thread_db* tdbb, const Jrd::MetaName& name); bool MET_get_ltt_index(Jrd::Attachment* attachment, const Jrd::QualifiedName& indexName, Jrd::LocalTemporaryTable** outLtt = nullptr, Jrd::LocalTemporaryTable::Index** outIndex = nullptr); +ULONG MET_rel_pagespace(Jrd::thread_db* tdbb, USHORT rel_id); +ULONG MET_index_pagespace(Jrd::thread_db* tdbb, Jrd::RelationPermanent* relation, USHORT idx_id); +ULONG MET_tablespace_id(Jrd::thread_db* tdbb, const Jrd::MetaName& name, Firebird::PathName& fileName); +bool MET_tablespace(Jrd::thread_db* tdbb, ULONG id, Jrd::MetaName& name, Firebird::PathName& fileName); +void MET_get_ts_files(Jrd::thread_db* tdbb, Firebird::ObjectsArray &files); +void MET_scan_tablespaces(Jrd::thread_db*); + #endif // JRD_MET_PROTO_H diff --git a/src/jrd/names.h b/src/jrd/names.h index 920a45d372d..e6bcb67a889 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -465,6 +465,12 @@ NAME("RDB$KEYWORD_RESERVED", nam_keyword_reserved) NAME("MON$COMPILED_STATEMENTS", nam_mon_compiled_statements) NAME("MON$COMPILED_STATEMENT_ID", nam_mon_cmp_stmt_id) +NAME("RDB$TABLESPACES", nam_tablespaces) +NAME("RDB$TABLESPACE_ID", nam_ts_id) +NAME("RDB$TABLESPACE_NAME", nam_ts_name) +NAME("RDB$OFFLINE", nam_ts_offline) +NAME("RDB$READ_ONLY", nam_ts_readonly) + NAME("RDB$SHORT_DESCRIPTION", nam_short_description) NAME("RDB$SECONDS_INTERVAL", nam_seconds_interval) NAME("RDB$PROFILE_SESSION_ID", nam_prof_ses_id) @@ -513,3 +519,7 @@ NAME("MON$CHAR_LENGTH", nam_mon_char_length) NAME("MON$COLLATION_ID", nam_mon_collate_id) NAME("RDB$AGGREGATE_FLAG", nam_aggregate_flag) + +NAME("MON$TABLESPACE_STATS", nam_mon_ts_stats) +NAME("MON$IO_STAT_ID", nam_mon_io_stat_id) +NAME("MON$TABLESPACE_NAME", nam_mon_ts_name) diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 08a026ee234..60308b21160 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -33,6 +33,7 @@ #ifndef JRD_ODS_H #define JRD_ODS_H +#include "../jrd/PageNumber.h" #include "../jrd/RecordNumber.h" #include "../common/classes/fb_string.h" #include "../common/classes/Uuid.h" @@ -410,29 +411,41 @@ struct index_root_page USHORT irt_flags; // index flags UCHAR irt_state; // index state UCHAR irt_keys; // number of keys in index - USHORT irt_dummy; // alignment to 8-byte boundary + USHORT irt_backslot; // slot with original page number private: void setState(UCHAR newState); public: - ULONG getRoot() const; - TraNumber getTransaction() const; + TraNumber getTransaction() const + { + return irt_transaction; + } + + UCHAR getState() const + { + return irt_state; + } + bool isUsed() const; - UCHAR getState() const; + std::optional getRootPage() const; + void setRootPage(const Jrd::PageNumber& rootPage); - void setRoot(ULONG rootPage/*, ULONG pageSpaceId*/); + void setInProgress(const Jrd::PageNumber& rootPage, TraNumber traNumber); void setInProgress(TraNumber traNumber); - void setConcurrentlyTemp(ULONG rootPage, TraNumber traNumber); - void setConcurrentlyMain(ULONG rootPage, TraNumber traNumber); + void setConcurrentlyTemp(const Jrd::PageNumber& rootPage, TraNumber traNumber); + void setConcurrentlyMain(const Jrd::PageNumber& rootPage, TraNumber traNumber); void setConcurrentlyComplete(TraNumber traNumber); - void setRollback(ULONG rootPage/*, ULONG pageSpaceId*/, TraNumber traNumber); + void setRollback(const Jrd::PageNumber& rootPage, TraNumber traNumber); void setRollback(TraNumber traNumber); + void setKill(); + void setKill(const Jrd::PageNumber& rootPage); void setKill(TraNumber traNumber); void setNormal(); - void setNormal(ULONG rootPage/*, ULONG pageSpaceId*/); + void setNormal(const Jrd::PageNumber& rootPage); void setCommit(TraNumber traNumber); void setDrop(TraNumber traNumber); + void setMigrate(const Jrd::PageNumber& rootPage, TraNumber traNumber, USHORT slot); void setEmpty(); } irt_rpt[1]; @@ -475,7 +488,8 @@ inline constexpr UCHAR irt_kill = 4; // index to be removed when irt_transacti inline constexpr UCHAR irt_commit = 5; // start index removal (switch to irt_drop) when irt_transaction committed inline constexpr UCHAR irt_drop = 6; // index to be removed when OAT > irt_transaction inline constexpr UCHAR irt_concurrently = 7; // creating index concurrently -inline constexpr UCHAR irt_last = 7; // last possible state +inline constexpr UCHAR irt_migrate = 8; // index is being migrated to another tablespace +inline constexpr UCHAR irt_last = 8; // last possible state // irt_flags, must match the idx_flags (see btr.h) inline constexpr USHORT irt_unique = 0x01; @@ -504,7 +518,13 @@ states for just created index: states for existing index: irt_normal alter index inactive / drop index => irt_commit + alter index set tablespace => irt_migrate on commit / on rollback no action + irt_migrate + alter index inactive / drop index => irt_commit + alter index set tablespace create and link a new slot + on commit => irt_normal (for a new slot), irt_kill (for an old slot) + on rollback => irt_kill (for a new slot), irt_normal (for an old slot) irt_commit on commit => irt_drop on rollback => irt_normal @@ -518,28 +538,40 @@ access in SELECTs: irt_commit - other transactions */ -inline void index_root_page::irt_repeat::setState(UCHAR newState) +inline bool index_root_page::irt_repeat::isUsed() const { - fb_assert(newState <= irt_last); - irt_state = newState; + return (irt_state != irt_unused); } -inline bool index_root_page::irt_repeat::isUsed() const +inline std::optional index_root_page::irt_repeat::getRootPage() const { - return getState() != irt_unused; + if (irt_page_num && irt_page_space_id) + return Jrd::PageNumber(irt_page_space_id, irt_page_num); + + fb_assert(!irt_page_num && !irt_page_space_id); + fb_assert(irt_state == irt_unused || irt_state == irt_in_progress); + return std::nullopt; } -inline void index_root_page::irt_repeat::setRoot(ULONG rootPage) -{ // hvlad: is irt_in_progress possible here ? +inline void index_root_page::irt_repeat::setRootPage(const Jrd::PageNumber& rootPage) +{ + // hvlad: is irt_in_progress possible here ? fb_assert(getState() == irt_in_progress || getState() == irt_normal || getState() == irt_concurrently); - fb_assert(rootPage); - irt_page_num = rootPage; - irt_page_space_id = 0; + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + if (getState() == irt_in_progress) setState(irt_normal); } +inline void index_root_page::irt_repeat::setState(UCHAR newState) +{ + fb_assert(newState <= irt_last); + irt_state = newState; +} + inline void index_root_page::irt_repeat::setEmpty() { setState(irt_unused); @@ -547,6 +579,21 @@ inline void index_root_page::irt_repeat::setEmpty() irt_page_num = 0; irt_page_space_id = 0; irt_flags = 0; + irt_backslot = 0; +} + +inline void index_root_page::irt_repeat::setInProgress(const Jrd::PageNumber& rootPage, TraNumber traNumber) +{ + fb_assert(getState() == irt_in_progress); + fb_assert(traNumber == irt_transaction); + fb_assert(!irt_page_num && !irt_page_space_id); + + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + + irt_transaction = traNumber; + setState(irt_in_progress); } inline void index_root_page::irt_repeat::setInProgress(TraNumber traNumber) @@ -560,26 +607,30 @@ inline void index_root_page::irt_repeat::setInProgress(TraNumber traNumber) } // Scan phase of concurrent index creation, set temp index root page and complementary flag -inline void index_root_page::irt_repeat::setConcurrentlyTemp(ULONG rootPage, TraNumber traNumber) +inline void index_root_page::irt_repeat::setConcurrentlyTemp(const Jrd::PageNumber& rootPage, TraNumber traNumber) { fb_assert(getState() == irt_unused); - irt_page_num = rootPage; - irt_page_space_id = 0; + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + irt_transaction = traNumber; irt_flags |= irt_complementary; setState(irt_concurrently); } // Merge phase of concurrent index creation, set main index root page and clear complementary flag -inline void index_root_page::irt_repeat::setConcurrentlyMain(ULONG rootPage, TraNumber traNumber) +inline void index_root_page::irt_repeat::setConcurrentlyMain(const Jrd::PageNumber& rootPage, TraNumber traNumber) { fb_assert(getState() == irt_concurrently); fb_assert(irt_transaction == traNumber); fb_assert(irt_flags & irt_complementary); - irt_page_num = rootPage; - irt_page_space_id = 0; + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + irt_transaction = traNumber; irt_flags &= ~irt_complementary; setState(irt_concurrently); @@ -591,20 +642,22 @@ inline void index_root_page::irt_repeat::setConcurrentlyComplete(TraNumber traNu fb_assert(getState() == irt_concurrently); fb_assert(irt_transaction == traNumber); fb_assert(!(irt_flags & irt_complementary)); - fb_assert(irt_page_num); + fb_assert(irt_page_num && irt_page_space_id); irt_transaction = traNumber; setState(irt_rollback); } -inline void index_root_page::irt_repeat::setRollback(ULONG rootPage, TraNumber traNumber) +inline void index_root_page::irt_repeat::setRollback(const Jrd::PageNumber& rootPage, TraNumber traNumber) { fb_assert(getState() == irt_in_progress); fb_assert(traNumber == irt_transaction); - fb_assert(!irt_page_num); + fb_assert(!irt_page_num && !irt_page_space_id); + + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); - irt_page_num = rootPage; - irt_page_space_id = 0; irt_transaction = traNumber; setState(irt_rollback); } @@ -613,16 +666,36 @@ inline void index_root_page::irt_repeat::setRollback(TraNumber traNumber) { fb_assert(getState() == irt_kill); fb_assert(traNumber == irt_transaction); - fb_assert(irt_page_num); + fb_assert(irt_page_num && irt_page_space_id); irt_transaction = traNumber; setState(irt_rollback); } +inline void index_root_page::irt_repeat::setKill() +{ + fb_assert(getState() == irt_in_progress); + fb_assert(irt_transaction); + + setState(irt_kill); +} + +inline void index_root_page::irt_repeat::setKill(const Jrd::PageNumber& rootPage) +{ + fb_assert(getState() == irt_in_progress); + fb_assert(irt_transaction); + + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + + setState(irt_kill); +} + inline void index_root_page::irt_repeat::setKill(TraNumber traNumber) { fb_assert(getState() == irt_rollback); - fb_assert(irt_page_num); + fb_assert(irt_page_num && irt_page_space_id); fb_assert(traNumber == irt_transaction); irt_transaction = traNumber; @@ -631,34 +704,42 @@ inline void index_root_page::irt_repeat::setKill(TraNumber traNumber) inline void index_root_page::irt_repeat::setNormal() { - //create index mode ForRollback + // create index mode ForRollback fb_assert(getState() == irt_rollback // deleted by current tra || getState() == irt_commit // deleted not long ago || getState() == irt_drop); // hvlad: is it possible ? - fb_assert(irt_page_num); + fb_assert(irt_page_num && irt_page_space_id); fb_assert(irt_transaction); + irt_transaction = 0; + irt_backslot = 0; + setState(irt_normal); } -inline void index_root_page::irt_repeat::setNormal(ULONG rootPage) +inline void index_root_page::irt_repeat::setNormal(const Jrd::PageNumber& rootPage) { - //create index mode AtOnce - fb_assert(getState() == irt_in_progress); - fb_assert(!irt_page_num); - fb_assert(rootPage); + // create index mode AtOnce + fb_assert(getState() == irt_in_progress + // completed migration + || getState() == irt_migrate); + + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); + + irt_transaction = 0; + irt_backslot = 0; - irt_page_num = rootPage; - irt_page_space_id = 0; setState(irt_normal); } inline void index_root_page::irt_repeat::setCommit(TraNumber traNumber) { - fb_assert(getState() == irt_normal); - fb_assert(irt_page_num); + fb_assert(getState() == irt_normal || getState() == irt_migrate); + fb_assert(irt_page_num && irt_page_space_id); irt_transaction = traNumber; setState(irt_commit); @@ -667,7 +748,7 @@ inline void index_root_page::irt_repeat::setCommit(TraNumber traNumber) inline void index_root_page::irt_repeat::setDrop(TraNumber traNumber) { fb_assert(getState() == irt_commit); - fb_assert(irt_page_num); + fb_assert(irt_page_num && irt_page_space_id); irt_transaction = traNumber; setState(irt_drop); @@ -676,19 +757,19 @@ inline void index_root_page::irt_repeat::setDrop(TraNumber traNumber) irt_flags &= ~(irt_unique | irt_foreign | irt_primary); } -inline UCHAR index_root_page::irt_repeat::getState() const +inline void index_root_page::irt_repeat::setMigrate(const Jrd::PageNumber& rootPage, TraNumber traNumber, USHORT slot) { - return irt_state; -} + fb_assert(getState() == irt_normal || getState() == irt_rollback || getState() == irt_migrate); + fb_assert(irt_page_num && irt_page_space_id); -inline ULONG index_root_page::irt_repeat::getRoot() const -{ - return getState() == irt_unused ? 0 : irt_page_num; -} + fb_assert(rootPage.getPageNum()); + irt_page_num = rootPage.getPageNum(); + irt_page_space_id = rootPage.getPageSpaceID(); -inline TraNumber index_root_page::irt_repeat::getTransaction() const -{ - return irt_transaction; + irt_transaction = traNumber; + irt_backslot = slot; + + setState(irt_migrate); } inline constexpr int STUFF_COUNT = 4; diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 450d03f1864..d8db587f21c 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1302,9 +1302,10 @@ void Optimizer::compileRelation(StreamType stream) if (needIndices && !relation()->getExtFile() && !relation()->isVirtual()) { - const auto relPages = relation()->getPages(tdbb); + const auto relPages = relation(tdbb)->getPages(tdbb); + IndexDescList idxList; - BTR_all(tdbb, relation(), idxList, relPages, csb->csb_g_flags & csb_internal); + BTR_all(tdbb, relation(tdbb), idxList, csb->csb_g_flags & csb_internal); MetaId n = idxList.getCount(); while (n--) @@ -1331,7 +1332,7 @@ void Optimizer::compileRelation(StreamType stream) if (idx.idx_selectivity <= 0.0f) { SelectivityList selectivity; - BTR_selectivity(tdbb, relation(), idx.idx_id, selectivity); + BTR_selectivity(tdbb, relation(tdbb), idx.idx_id, selectivity); if (selectivity[0] > 0.0f) updated = true; } @@ -1340,7 +1341,7 @@ void Optimizer::compileRelation(StreamType stream) if (updated) { idxList.clear(); - BTR_all(tdbb, relation(), idxList, relPages, csb->csb_g_flags & csb_internal); + BTR_all(tdbb, relation(tdbb), idxList, csb->csb_g_flags & csb_internal); } } diff --git a/src/jrd/os/pio_proto.h b/src/jrd/os/pio_proto.h index 99a7dda760b..eb5c60b5d28 100644 --- a/src/jrd/os/pio_proto.h +++ b/src/jrd/os/pio_proto.h @@ -67,6 +67,7 @@ inline bool PIO_on_raw_device(const Firebird::PathName&) } #endif bool PIO_write(Jrd::thread_db*, Jrd::jrd_file*, Jrd::BufferDesc*, Ods::pag*, Jrd::FbStatusVector*); +bool PIO_file_exists(const Firebird::PathName&); #endif // JRD_PIO_PROTO_H diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index 780eeb90500..1c684b238e7 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -1147,6 +1147,17 @@ static SLONG pwrite(int fd, SCHAR* buf, SLONG nbytes, SLONG offset) #endif // !(HAVE_PREAD && HAVE_PWRITE) +bool PIO_file_exists(const Firebird::PathName& fileName) +{ + const int fd = openFile(fileName.c_str(), false, false, true); + if (fd == -1) + return false; + + close(fd); + return true; +} + + #ifdef SUPPORT_RAW_DEVICES int PIO_unlink(const PathName& file_name) { diff --git a/src/jrd/os/win32/winnt.cpp b/src/jrd/os/win32/winnt.cpp index 3b28d4eea35..da074237dfb 100644 --- a/src/jrd/os/win32/winnt.cpp +++ b/src/jrd/os/win32/winnt.cpp @@ -756,6 +756,18 @@ ULONG PIO_get_number_of_pages(const jrd_file* file, const USHORT pagesize) } +bool PIO_file_exists(const Firebird::PathName& fileName) +{ + const HANDLE fd = CreateFile(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (fd == INVALID_HANDLE_VALUE) + return false; + + CloseHandle(fd); + return true; +} + + static bool seek_file(jrd_file* file, const BufferDesc* bdb, OVERLAPPED* overlapped) { /************************************** diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index 43af9de7839..ec17b41dd96 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -550,6 +550,7 @@ PAG PAG_allocate_pages(thread_db* tdbb, WIN* window, unsigned cntAlloc, bool ali pipMin = lastBit; // assume PAGES_IN_EXTENT == 8 + static_assert(PAGES_IN_EXTENT == 8); if (i == 7 && *bytes == 0xFF && pipExtent > lastBit - 7) pipExtent = lastBit - 7; @@ -964,15 +965,13 @@ void PAG_header(thread_db* tdbb, bool info, const TriState newForceWrite) if (header->hdr_flags & hdr_SQL_dialect_3) dbb->dbb_flags |= DBB_DB_SQL_dialect_3; - auto* relation = MetadataCache::getPerm(tdbb, 0u, CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); - RelationPages* relPages = relation->getBasePages(); - if (!relPages->rel_pages) + const auto relation = MetadataCache::getVersioned(tdbb, rel_pages, CacheFlag::AUTOCREATE | CacheFlag::NOSCAN); + const auto relPages = relation->getBasePages(); + if (!relPages->hasData()) { // NS: There's no need to reassign first page for RDB$PAGES relation since // current code cannot change its location after database creation. - vcl* vector = vcl::newVector(relation->getPool(), 1); - relPages->rel_pages = vector; - (*vector)[0] = header->hdr_PAGES; + relPages->init(header->hdr_PAGES); } dbb->dbb_next_transaction = next_transaction; @@ -1289,15 +1288,16 @@ void PAG_release_page(thread_db* tdbb, const PageNumber& number, const PageNumbe * **************************************/ - fb_assert(number.getPageSpaceID() == prior_page.getPageSpaceID() || - prior_page == ZERO_PAGE_NUMBER); + // RS: When index is on another tablespace this maybe wrong assert if prior is IRP +// fb_assert(number.getPageSpaceID() == prior_page.getPageSpaceID() || +// prior_page == ZERO_PAGE_NUMBER); const ULONG pgNum = number.getPageNum(); PAG_release_pages(tdbb, number.getPageSpaceID(), 1, &pgNum, prior_page.getPageNum()); } -void PAG_release_pages(thread_db* tdbb, USHORT pageSpaceID, int cntRelease, +void PAG_release_pages(thread_db* tdbb, ULONG pageSpaceID, int cntRelease, const ULONG* pgNums, const ULONG prior_page) { /************************************** @@ -2034,7 +2034,7 @@ ULONG PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) } } -ULONG PageSpace::getSCNPageNum(ULONG sequence) noexcept +ULONG PageSpace::getSCNPageNum(ULONG sequence) const noexcept { /************************************** * @@ -2046,32 +2046,51 @@ ULONG PageSpace::getSCNPageNum(ULONG sequence) noexcept * First SCN page number is fixed as FIRST_SCN_PAGE. * **************************************/ - if (!sequence) { - return scnFirst; - } - return sequence * dbb->dbb_page_manager.pagesPerSCN; + + return sequence ? sequence * dbb->dbb_page_manager.pagesPerSCN : scnFirst; } -ULONG PageSpace::getSCNPageNum(const Database* dbb, ULONG sequence) + +PageManager::PageManager(Database* aDbb, Firebird::MemoryPool& aPool) : + dbb(aDbb), + pageSpaces(aPool), + pageSpacesLock(NULL), + pool(aPool) { - PageSpace* pgSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE); - return pgSpace->getSCNPageNum(sequence); + pagesPerPIP = 0; + bytesBitPIP = 0; + transPerTIP = 0; + gensPerPage = 0; + pagesPerSCN = 0; + tempPageSpaceID = 0; + tempFileCreated = false; + + if (dbb->dbb_config->getServerMode() == MODE_SUPER) + pageSpacesLock = FB_NEW_POOL(pool) RWLock(); + + addPageSpace(DB_PAGE_SPACE); } -PageSpace* PageManager::addPageSpace(const USHORT pageSpaceID) +PageSpace* PageManager::addPageSpace(const ULONG pageSpaceID) { - PageSpace* newPageSpace = findPageSpace(pageSpaceID); - if (!newPageSpace) - { - newPageSpace = FB_NEW_POOL(pool) PageSpace(dbb, pageSpaceID); - pageSpaces.add(newPageSpace); + WriteLockGuard guard(pageSpacesLock, FB_FUNCTION); + + FB_SIZE_T pos; + if (pageSpaces.find(pageSpaceID, pos)) { + fb_assert(false); + return pageSpaces[pos]; } + PageSpace* newPageSpace = FB_NEW_POOL(pool) PageSpace(dbb, pageSpaceID); + pageSpaces.add(newPageSpace); + return newPageSpace; } -PageSpace* PageManager::findPageSpace(const USHORT pageSpace) const +PageSpace* PageManager::findPageSpace(const ULONG pageSpace) const { + ReadLockGuard guard(pageSpacesLock, FB_FUNCTION); + FB_SIZE_T pos; if (pageSpaces.find(pageSpace, pos)) { return pageSpaces[pos]; @@ -2080,24 +2099,27 @@ PageSpace* PageManager::findPageSpace(const USHORT pageSpace) const return 0; } -void PageManager::delPageSpace(const USHORT pageSpace) +void PageManager::deletePageSpace(const ULONG pageSpace, bool deleteFile) { + WriteLockGuard guard(pageSpacesLock, FB_FUNCTION); + FB_SIZE_T pos; if (pageSpaces.find(pageSpace, pos)) { - PageSpace* pageSpaceToDelete = pageSpaces[pos]; + AutoPtr pageSpaceToDelete(pageSpaces[pos]); pageSpaces.remove(pos); - delete pageSpaceToDelete; + + if (deleteFile && pageSpaceToDelete->file) + unlink(pageSpaceToDelete->file->fil_string); } } void PageManager::closeAll() { - for (FB_SIZE_T i = 0; i < pageSpaces.getCount(); i++) + for (const auto pageSpace : pageSpaces) { - if (pageSpaces[i]->file) { - PIO_close(pageSpaces[i]->file); - } + if (pageSpace->file) + PIO_close(pageSpace->file); } } @@ -2115,12 +2137,12 @@ void PageManager::initTempPageSpace(thread_db* tdbb) if (!attachment->att_temp_pg_lock) { Lock* const lock = FB_NEW_RPT(*attachment->att_pool, 0) - Lock(tdbb, sizeof(SLONG), LCK_page_space); + Lock(tdbb, sizeof(ULONG), LCK_page_space); while (true) { - const double tmp = rand() * (MAX_USHORT - TEMP_PAGE_SPACE - 1.0) / (RAND_MAX + 1.0); - lock->setKey(static_cast(tmp) + TEMP_PAGE_SPACE + 1); + const double tmp = rand() * (MAX_PAGE_SPACE_ID - TEMP_PAGE_SPACE - 1.0) / (RAND_MAX + 1.0); + lock->setKey(static_cast(tmp) + TEMP_PAGE_SPACE + 1); if (LCK_lock(tdbb, lock, LCK_write, LCK_NO_WAIT)) break; fb_utils::init_status(tdbb->tdbb_status_vector); @@ -2129,7 +2151,7 @@ void PageManager::initTempPageSpace(thread_db* tdbb) attachment->att_temp_pg_lock = lock; } - tempPageSpaceID = (USHORT) attachment->att_temp_pg_lock->getKey(); + tempPageSpaceID = (ULONG) attachment->att_temp_pg_lock->getKey(); } else { @@ -2139,7 +2161,7 @@ void PageManager::initTempPageSpace(thread_db* tdbb) addPageSpace(tempPageSpaceID); } -USHORT PageManager::getTempPageSpaceID(thread_db* tdbb) +ULONG PageManager::getTempPageSpaceID(thread_db* tdbb) { fb_assert(tempPageSpaceID != 0); if (!tempFileCreated) @@ -2171,6 +2193,70 @@ USHORT PageManager::getTempPageSpaceID(thread_db* tdbb) return tempPageSpaceID; } + +void PageManager::allocatePageSpace(thread_db* tdbb, ULONG pageSpaceID, bool create, const PathName& fileName) +{ + if (!findPageSpace(pageSpaceID)) + { + Firebird::MutexLockGuard guard(initTmpMtx, FB_FUNCTION); + + // Double check if someone concurrently have added the tablespaceid + if (findPageSpace(pageSpaceID)) + return; + + // Verify tablespace file path against DatabaseAccess entry of firebird.conf + if (!JRD_verify_database_access(fileName)) + { + ERR_post(Arg::Gds(isc_conf_access_denied) << Arg::Str("tablespace") << + Arg::Str(fileName)); + } + + PageSpace* newPageSpace = FB_NEW_POOL(pool) PageSpace(dbb, pageSpaceID); + + try + { + if (create) + { + newPageSpace->file = PIO_create(tdbb, fileName, false, false); + // When opening an existing TS, a pointer to this PageSpace can be added to pageSpaces at the end of the method. + // When creating a new TS, there is a need to add a new PageSpace to pageSpaces earlier. + // Because of the need to update the SCN (if the database SCN is greater than zero) during the creation of new TS pages. + // This early addition of a pointer to PageSpace will not create a race anywhere + // (due to the fact that there is a pointer to an incompletely constructed object for some time), + // because the TS metadata is not yet in the system table and no one can access this TS. + { + WriteLockGuard writeGuard(pageSpacesLock, FB_FUNCTION); + pageSpaces.add(newPageSpace); + } + PAG_format_pip(tdbb, *newPageSpace); + } + else + { + newPageSpace->file = PIO_open(tdbb, fileName, fileName); + newPageSpace->pipFirst = FIRST_PIP_PAGE; + newPageSpace->scnFirst = FIRST_SCN_PAGE; + } + } + catch (...) + { + if (create) + { + WriteLockGuard writeGuard(pageSpacesLock, FB_FUNCTION); + pageSpaces.findAndRemove(pageSpaceID); + } + delete newPageSpace; + throw; + } + + if (!create) + { + WriteLockGuard writeGuard(pageSpacesLock, FB_FUNCTION); + pageSpaces.add(newPageSpace); + } + } +} + + ULONG PAG_page_count(thread_db* tdbb) { /********************************************* diff --git a/src/jrd/pag.h b/src/jrd/pag.h index e11daf85d07..4c38c2ee912 100644 --- a/src/jrd/pag.h +++ b/src/jrd/pag.h @@ -37,33 +37,15 @@ #include "../include/fb_blk.h" #include "../common/classes/array.h" #include "../common/classes/locks.h" +#include "../common/classes/rwlock.h" #include "../jrd/ods.h" #include "../jrd/lls.h" +#include "../jrd/PageNumber.h" namespace Jrd { -// Page control block -- used by PAG to keep track of critical constants -/** -class PageControl : public pool_alloc -{ - public: - SLONG pgc_high_water; // Lowest PIP with space - SLONG pgc_ppp; // Pages per pip - SLONG pgc_pip; // First pointer page - ULONG pgc_bytes; // Number of bytes of bit in PIP - ULONG pgc_tpt; // Transactions per TIP - ULONG pgc_gpg; // Generators per generator page -}; -**/ - -// page spaces below TRANS_PAGE_SPACE contain regular database pages -// TEMP_PAGE_SPACE and page spaces above TEMP_PAGE_SPACE contain temporary pages -// TRANS_PAGE_SPACE is pseudo space to store transaction numbers in precedence stack -// INVALID_PAGE_SPACE is to ??? -inline constexpr USHORT INVALID_PAGE_SPACE = 0; -inline constexpr USHORT DB_PAGE_SPACE = 1; -inline constexpr USHORT TRANS_PAGE_SPACE = 255; -inline constexpr USHORT TEMP_PAGE_SPACE = 256; +const PageNumber ZERO_PAGE_NUMBER(INVALID_PAGE_SPACE, 0); +const PageNumber HEADER_PAGE_NUMBER(DB_PAGE_SPACE, HEADER_PAGE); inline constexpr USHORT PAGES_IN_EXTENT = 8; @@ -75,7 +57,7 @@ class PageManager; class PageSpace : public pool_alloc { public: - explicit PageSpace(Database* aDbb, USHORT aPageSpaceID) + explicit PageSpace(Database* aDbb, ULONG aPageSpaceID) { pageSpaceID = aPageSpaceID; pipHighWater = 0; @@ -90,7 +72,7 @@ class PageSpace : public pool_alloc ~PageSpace(); - USHORT pageSpaceID; + ULONG pageSpaceID; Firebird::AtomicCounter pipHighWater; // Lowest PIP with space Firebird::AtomicCounter pipWithExtent; // Lowest PIP with free extent ULONG pipFirst; // First pointer page @@ -98,17 +80,37 @@ class PageSpace : public pool_alloc jrd_file* file; - static inline bool isTemporary(USHORT aPageSpaceID) noexcept + static inline bool isTemporary(ULONG aPageSpaceID) noexcept { return (aPageSpaceID >= TEMP_PAGE_SPACE); } + static inline bool isTemporary(const PageNumber& page) noexcept + { + return isTemporary(page.getPageSpaceID()); + } + inline bool isTemporary() const noexcept { return isTemporary(pageSpaceID); } - static inline SLONG generate(const PageSpace* Item) noexcept + static inline bool isTablespace(ULONG aPageSpaceID) + { + return (aPageSpaceID > DB_PAGE_SPACE && aPageSpaceID <= MAX_TABLESPACE_ID); + } + + static inline bool isTablespace(const PageNumber& page) noexcept + { + return isTablespace(page.getPageSpaceID()); + } + + inline bool isTablespace() const + { + return isTablespace(pageSpaceID); + } + + static inline ULONG generate(const PageSpace* Item) noexcept { return Item->pageSpaceID; } @@ -133,8 +135,7 @@ class PageSpace : public pool_alloc ULONG extend(thread_db* tdbb, ULONG pageNum, bool forceSize); // get SCN's page number - ULONG getSCNPageNum(ULONG sequence) noexcept; - static ULONG getSCNPageNum(const Database* dbb, ULONG sequence); + ULONG getSCNPageNum(ULONG sequence) const noexcept; // is pagespace on raw device bool onRawDevice() const noexcept; @@ -148,32 +149,23 @@ class PageSpace : public pool_alloc class PageManager : public pool_alloc { public: - explicit PageManager(Database* aDbb, Firebird::MemoryPool& aPool) : - dbb(aDbb), - pageSpaces(aPool), - pool(aPool) - { - pagesPerPIP = 0; - bytesBitPIP = 0; - transPerTIP = 0; - gensPerPage = 0; - pagesPerSCN = 0; - tempPageSpaceID = 0; - tempFileCreated = false; - - addPageSpace(DB_PAGE_SPACE); - } + explicit PageManager(Database* aDbb, Firebird::MemoryPool& aPool); ~PageManager() { while (pageSpaces.hasData()) delete pageSpaces.pop(); + + delete pageSpacesLock; } - PageSpace* findPageSpace(const USHORT pageSpaceID) const; + PageSpace* findPageSpace(const ULONG pageSpaceID) const; void initTempPageSpace(thread_db* tdbb); - USHORT getTempPageSpaceID(thread_db* tdbb); + ULONG getTempPageSpaceID(thread_db* tdbb); + + void allocatePageSpace(thread_db* tdbb, ULONG pageSpaceID, bool create, const Firebird::PathName& fileName); + void deletePageSpace(const ULONG pageSpaceID, bool deleteFile = false); void closeAll(); @@ -185,144 +177,19 @@ class PageManager : public pool_alloc private: typedef Firebird::SortedArray, - USHORT, PageSpace> PageSpaceArray; + ULONG, PageSpace> PageSpaceArray; - PageSpace* addPageSpace(const USHORT pageSpaceID); - void delPageSpace(const USHORT pageSpaceID); + PageSpace* addPageSpace(const ULONG pageSpaceID); Database* dbb; PageSpaceArray pageSpaces; + Firebird::RWLock* pageSpacesLock; Firebird::MemoryPool& pool; Firebird::Mutex initTmpMtx; - USHORT tempPageSpaceID; + ULONG tempPageSpaceID; bool tempFileCreated; }; -class PageNumber -{ -public: - // CVC: To be completely in sync, the second param would have to be TraNumber - inline PageNumber(const USHORT aPageSpace, const ULONG aPageNum) noexcept - : pageNum(aPageNum), pageSpaceID(aPageSpace) - { - // Some asserts are commented cause 0 was also used as 'does not matter' pagespace - // fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - } - - // Required to be able to keep it in Firebird::Stack - inline PageNumber() noexcept - : pageNum(0), pageSpaceID(INVALID_PAGE_SPACE) - { } - - inline PageNumber(const PageNumber& from) = default; - - inline ULONG getPageNum() const noexcept - { - // fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - return pageNum; - } - - inline USHORT getPageSpaceID() const noexcept - { - fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - return pageSpaceID; - } - - inline USHORT setPageSpaceID(const USHORT aPageSpaceID) noexcept - { - fb_assert(aPageSpaceID != INVALID_PAGE_SPACE); - pageSpaceID = aPageSpaceID; - return pageSpaceID; - } - - inline bool isTemporary() const noexcept - { - fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - return PageSpace::isTemporary(pageSpaceID); - } - - static inline constexpr USHORT getLockLen() noexcept - { - return 2 * sizeof(ULONG); - } - - inline void getLockStr(UCHAR* str) const - { - fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - - memcpy(str, &pageNum, sizeof(ULONG)); - str += sizeof(ULONG); - - const ULONG val = pageSpaceID; - memcpy(str, &val, sizeof(ULONG)); - } - - inline PageNumber& operator=(const PageNumber& from) = default; - - inline ULONG operator=(const ULONG from) noexcept - { - // fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - pageNum = from; - return pageNum; - } - - inline bool operator==(const PageNumber& other) const noexcept - { - return (pageNum == other.pageNum) && (pageSpaceID == other.pageSpaceID); - } - - inline bool operator!=(const PageNumber& other) const noexcept - { - return !(*this == other); - } - - inline bool operator>(const PageNumber& other) const noexcept - { - fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - fb_assert(other.pageSpaceID != INVALID_PAGE_SPACE); - return (pageSpaceID > other.pageSpaceID) || - ((pageSpaceID == other.pageSpaceID) && (pageNum > other.pageNum)); - } - - inline bool operator>=(const PageNumber& other) const noexcept - { - fb_assert(pageSpaceID != INVALID_PAGE_SPACE); - fb_assert(other.pageSpaceID != INVALID_PAGE_SPACE); - return (pageSpaceID > other.pageSpaceID) || - ((pageSpaceID == other.pageSpaceID) && (pageNum >= other.pageNum)); - } - - inline bool operator<(const PageNumber& other) const noexcept - { - return !(*this >= other); - } - - inline bool operator<=(const PageNumber& other) const noexcept - { - return !(*this > other); - } - - /* - inline operator ULONG() const - { - return pageNum; - } - */ - -#ifdef DEB_TDBB_BDBS - void print(const char* text) const; -#endif - -private: - ULONG pageNum; - USHORT pageSpaceID; -}; - -const PageNumber ZERO_PAGE_NUMBER(DB_PAGE_SPACE, 0); -const PageNumber HEADER_PAGE_NUMBER(DB_PAGE_SPACE, HEADER_PAGE); - -typedef Firebird::Stack PageStack; - } //namespace Jrd #endif // JRD_PAG_H diff --git a/src/jrd/pag_proto.h b/src/jrd/pag_proto.h index 025b7fd2519..acec6c7b177 100644 --- a/src/jrd/pag_proto.h +++ b/src/jrd/pag_proto.h @@ -51,7 +51,7 @@ void PAG_init(Jrd::thread_db*); void PAG_init2(Jrd::thread_db*); SLONG PAG_last_page(Jrd::thread_db* tdbb); void PAG_release_page(Jrd::thread_db* tdbb, const Jrd::PageNumber&, const Jrd::PageNumber&); -void PAG_release_pages(Jrd::thread_db* tdbb, USHORT pageSpaceID, int cntRelease, +void PAG_release_pages(Jrd::thread_db* tdbb, ULONG pageSpaceID, int cntRelease, const ULONG* pgNums, const ULONG prior_page); void PAG_set_db_guid(Jrd::thread_db* tdbb, const Firebird::Guid&); void PAG_set_force_write(Jrd::thread_db* tdbb, bool); diff --git a/src/jrd/recsrc/BitmapTableScan.cpp b/src/jrd/recsrc/BitmapTableScan.cpp index 2c021e077f2..75950c992bd 100644 --- a/src/jrd/recsrc/BitmapTableScan.cpp +++ b/src/jrd/recsrc/BitmapTableScan.cpp @@ -55,10 +55,10 @@ void BitmapTableScan::internalOpen(thread_db* tdbb) const impure->irsb_flags = irsb_open; impure->irsb_bitmap = EVL_bitmap(tdbb, m_inversion, NULL); - record_param* const rpb = &request->req_rpb[m_stream]; const auto transaction = m_relation()->isLTT() ? tdbb->getTransaction() : request->req_transaction; RLCK_reserve_relation(tdbb, transaction, m_relation(), false); + record_param* const rpb = &request->req_rpb[m_stream]; rpb->rpb_number.setValue(BOF_NUMBER); } diff --git a/src/jrd/recsrc/FullTableScan.cpp b/src/jrd/recsrc/FullTableScan.cpp index 56ec0435eee..4d217b29526 100644 --- a/src/jrd/recsrc/FullTableScan.cpp +++ b/src/jrd/recsrc/FullTableScan.cpp @@ -62,6 +62,11 @@ void FullTableScan::internalOpen(thread_db* tdbb) const record_param* const rpb = &request->req_rpb[m_stream]; rpb->getWindow(tdbb).win_flags = 0; + rpb->rpb_number.setValue(BOF_NUMBER); + + const auto relation = m_relation(tdbb); + if (!relation) + return; // Unless this is the only attachment, limit the cache flushing // effect of large sequential scans on the page working sets of @@ -80,30 +85,26 @@ void FullTableScan::internalOpen(thread_db* tdbb) const BufferControl* const bcb = dbb->dbb_bcb; - if (attachment->isGbak() || DPM_data_pages(tdbb, m_relation()) > bcb->bcb_count) + if (attachment->isGbak() || DPM_data_pages(tdbb, relation) > bcb->bcb_count) { rpb->getWindow(tdbb).win_flags = WIN_large_scan; - rpb->rpb_org_scans = m_relation()->rel_scan_count++; + rpb->rpb_org_scans = relation->getPermanent()->rel_scan_count++; } } - rpb->rpb_number.setValue(BOF_NUMBER); - if (m_dbkeyRanges.hasData()) { impure->irsb_lower.setValid(false); impure->irsb_upper.setValid(false); - EVL_dbkey_bounds(tdbb, m_dbkeyRanges, rpb->rpb_relation, - impure->irsb_lower, impure->irsb_upper); + EVL_dbkey_bounds(tdbb, m_dbkeyRanges, relation, impure->irsb_lower, impure->irsb_upper); if (impure->irsb_lower.isValid()) { auto number = impure->irsb_lower.getValue(); - const auto ppages = rpb->rpb_relation->getPages(tdbb)->rel_pages; - const auto maxRecno = (SINT64) ppages->count() * - dbb->dbb_dp_per_pp * dbb->dbb_max_records - 1; + const auto ppCount = relation->getPages(tdbb)->getPointerPageCount(); + const auto maxRecno = (SINT64) ppCount * dbb->dbb_dp_per_pp * dbb->dbb_max_records - 1; if (number > maxRecno) number = maxRecno; @@ -124,11 +125,13 @@ void FullTableScan::close(thread_db* tdbb) const { impure->irsb_flags &= ~irsb_open; + const auto relation = m_relation(); + record_param* const rpb = &request->req_rpb[m_stream]; if ((rpb->getWindow(tdbb).win_flags & WIN_large_scan) && - m_relation()->rel_scan_count) + relation->rel_scan_count) { - m_relation()->rel_scan_count--; + relation->rel_scan_count--; } } } diff --git a/src/jrd/recsrc/IndexTableScan.cpp b/src/jrd/recsrc/IndexTableScan.cpp index 02ed32fbd9f..ce781833ceb 100644 --- a/src/jrd/recsrc/IndexTableScan.cpp +++ b/src/jrd/recsrc/IndexTableScan.cpp @@ -68,10 +68,10 @@ void IndexTableScan::internalOpen(thread_db* tdbb) const impure->irsb_flags = irsb_first | irsb_open; - record_param* const rpb = &request->req_rpb[m_stream]; const auto transaction = m_relation()->isLTT() ? tdbb->getTransaction() : request->req_transaction; RLCK_reserve_relation(tdbb, transaction, m_relation(), false); + record_param* const rpb = &request->req_rpb[m_stream]; rpb->rpb_number.setValue(BOF_NUMBER); fb_assert(!impure->irsb_nav_lower); @@ -202,7 +202,7 @@ bool IndexTableScan::internalGetRecord(thread_db* tdbb) const const bool descending = (idx->idx_flags & idx_descending); // find the last fetched position from the index - const USHORT pageSpaceID = m_relation()->getPages(tdbb)->rel_pg_space_id; + const ULONG pageSpaceID = idx->idx_pg_space_id; win window(pageSpaceID, impure->irsb_nav_page); const IndexRetrieval* const retrieval = m_index->retrieval; diff --git a/src/jrd/recsrc/LocalTableStream.cpp b/src/jrd/recsrc/LocalTableStream.cpp index 7955d3191e5..3010a46452a 100644 --- a/src/jrd/recsrc/LocalTableStream.cpp +++ b/src/jrd/recsrc/LocalTableStream.cpp @@ -313,7 +313,6 @@ void LocalTableStream::internalOpen(thread_db* tdbb) const impure->irsb_flags = irsb_open; const auto rpb = &request->req_rpb[m_stream]; - rpb->getWindow(tdbb).win_flags = 0; rpb->rpb_number.setValue(BOF_NUMBER); } diff --git a/src/jrd/recsrc/RecordSource.cpp b/src/jrd/recsrc/RecordSource.cpp index 158433b52b4..2b852f703b7 100644 --- a/src/jrd/recsrc/RecordSource.cpp +++ b/src/jrd/recsrc/RecordSource.cpp @@ -318,7 +318,7 @@ WriteLockResult RecordStream::lockRecord(thread_db* tdbb) const fb_assert(relation && !relation->rel_view_rse); - RLCK_reserve_relation(tdbb, transaction, relation->getPermanent(), true); + RLCK_reserve_relation(tdbb, transaction, getPermanent(relation), true); return VIO_writelock(tdbb, rpb, transaction); } diff --git a/src/jrd/relations.h b/src/jrd/relations.h index c15780805a1..852f2e5845b 100644 --- a/src/jrd/relations.h +++ b/src/jrd/relations.h @@ -30,6 +30,7 @@ RELATION(nam_pages, rel_pages, ODS_8_0, rel_persistent) FIELD(f_pag_id, nam_r_id, fld_r_id, 0, ODS_8_0) FIELD(f_pag_seq, nam_p_sequence, fld_p_sequence, 0, ODS_8_0) FIELD(f_pag_type, nam_p_type, fld_p_type, 0, ODS_8_0) + FIELD(f_pag_ts, nam_ts_id, fld_ts_id, 0, ODS_14_0) END_RELATION // Relation 1 (RDB$DATABASE) @@ -108,6 +109,7 @@ RELATION(nam_indices, rel_indices, ODS_8_0, rel_persistent) FIELD(f_idx_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_idx_foreign_schema, nam_foreign_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_idx_pkg_name, nam_pkg_name, fld_pkg_name, 1, ODS_14_0) + FIELD(f_idx_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0) END_RELATION // Relation 5 (RDB$RELATION_FIELDS) @@ -136,6 +138,7 @@ RELATION(nam_r_fields, rel_rfr, ODS_8_0, rel_persistent) FIELD(f_rfr_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_rfr_field_source_schema, nam_field_source_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_rfr_pkg_name, nam_pkg_name, fld_pkg_name, 1, ODS_14_0) + FIELD(f_rfr_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0) END_RELATION // Relation 6 (RDB$RELATIONS) @@ -161,6 +164,7 @@ RELATION(nam_relations, rel_relations, ODS_8_0, rel_persistent) FIELD(f_rel_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_rel_pkg_name, nam_pkg_name, fld_pkg_name, 1, ODS_14_0) FIELD(f_rel_private_flag, nam_private_flag, fld_flag_nullable, 1, ODS_14_0) + FIELD(f_rel_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0) END_RELATION // Relation 7 (RDB$VIEW_RELATIONS) @@ -860,3 +864,24 @@ RELATION(nam_constants, rel_constants, ODS_14_0, rel_persistent) FIELD(f_const_package_schema, nam_sch_name, fld_sch_name, 0, ODS_14_0) FIELD(f_const_description, nam_description, fld_description, 1, ODS_14_0) END_RELATION + +// Relation 60 (RDB$TABLESPACES) +RELATION(nam_tablespaces, rel_tablespaces, ODS_14_0, rel_persistent) + FIELD(f_ts_id, nam_ts_id, fld_ts_id, 0, ODS_14_0) + FIELD(f_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0) + FIELD(f_ts_class, nam_class, fld_class, 1, ODS_14_0) + FIELD(f_ts_sys_flag, nam_sys_flag, fld_flag, 1, ODS_14_0) + FIELD(f_ts_desc, nam_description, fld_description, 1, ODS_14_0) + FIELD(f_ts_owner, nam_owner, fld_user, 1, ODS_14_0) + FIELD(f_ts_file, nam_file_name, fld_file_name, 1, ODS_14_0) + FIELD(f_ts_offline, nam_ts_offline, fld_bool, 1, ODS_14_0) + FIELD(f_ts_readonly, nam_ts_readonly, fld_bool, 1, ODS_14_0) +END_RELATION + +// Relation 61 (MON$TABLESPACE_STATS) +RELATION(nam_mon_ts_stats, rel_mon_ts_stats, ODS_14_0, rel_virtual) + FIELD(f_mon_ts_stat_id, nam_mon_stat_id, fld_stat_id, 0, ODS_14_0) + FIELD(f_mon_ts_stat_group, nam_mon_stat_group, fld_stat_group, 0, ODS_14_0) + FIELD(f_mon_ts_name, nam_mon_ts_name, fld_ts_name, 0, ODS_14_0) + FIELD(f_mon_tab_io_stat_id, nam_mon_io_stat_id, fld_stat_id, 0, ODS_14_0) +END_RELATION diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp index d5f47718969..a9df67fae8c 100644 --- a/src/jrd/replication/Applier.cpp +++ b/src/jrd/replication/Applier.cpp @@ -1070,18 +1070,13 @@ void Applier::executeSql(thread_db* tdbb, } } -bool Applier::lookupKey(thread_db* tdbb, Cached::Relation* relation, index_desc& key) +bool Applier::lookupKey(thread_db* tdbb, jrd_rel* relation, index_desc& key) { - RelationPages* const relPages = relation->getPages(tdbb); - auto page = relPages->rel_index_root; - if (!page) - { - DPM_scan_pages(tdbb); - page = relPages->rel_index_root; - } + const auto rootPage = relation->getIndexRootPage(tdbb); + if (!rootPage) + return false; - const PageNumber root_page(relPages->rel_pg_space_id, page); - win window(root_page); + win window(rootPage.value()); const auto root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_root); index_desc idx; @@ -1089,7 +1084,7 @@ bool Applier::lookupKey(thread_db* tdbb, Cached::Relation* relation, index_desc& for (USHORT i = 0; i < root->irt_count; i++) { - if (BTR_description(tdbb, relation, root, &idx, i)) + if (BTR_description(tdbb, relation->getPermanent(), root, &idx, i)) { if (idx.idx_flags & idx_primary) { @@ -1179,13 +1174,12 @@ bool Applier::lookupRecord(thread_db* tdbb, auto idxStatus = idv->getActive(); fb_assert(idxStatus == MET_index_active); - haveIdx = (idxStatus == MET_index_active) && - BTR_lookup(tdbb, relation->getPermanent(), idv->getId(), &idx, relation->getPages(tdbb)); + haveIdx = (idxStatus == MET_index_active) && BTR_lookup(tdbb, relation, idv->getId(), &idx); } } if (!haveIdx) - haveIdx = lookupKey(tdbb, relation->getPermanent(), idx); + haveIdx = lookupKey(tdbb, relation, idx); if (haveIdx) { @@ -1409,7 +1403,7 @@ void Applier::doDelete(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) { fb_assert(!(transaction->tra_flags & TRA_system)); - RLCK_reserve_relation(tdbb, transaction, getPermanent(rpb->rpb_relation), true); + RLCK_reserve_relation(tdbb, transaction, rpb->rpb_relation->getPermanent(), true); Savepoint::ChangeMarker marker(transaction->tra_save_point); diff --git a/src/jrd/replication/Applier.h b/src/jrd/replication/Applier.h index b8c6abac2ff..0e8b3e912df 100644 --- a/src/jrd/replication/Applier.h +++ b/src/jrd/replication/Applier.h @@ -187,7 +187,7 @@ namespace Jrd const Firebird::string& sql, const MetaName& owner); - bool lookupKey(thread_db* tdbb, Cached::Relation* relation, index_desc& idx); + bool lookupKey(thread_db* tdbb, jrd_rel* relation, index_desc& idx); bool compareKey(thread_db* tdbb, jrd_rel* relation, const index_desc& idx, Record* record1, Record* record2); diff --git a/src/jrd/replication/Config.cpp b/src/jrd/replication/Config.cpp index 2798a2cdd1d..c779780d58a 100644 --- a/src/jrd/replication/Config.cpp +++ b/src/jrd/replication/Config.cpp @@ -240,7 +240,8 @@ Config::Config() logErrors(true), reportErrors(DEFAULT_REPORT_ERRORS), disableOnError(true), - cascadeReplication(false) + cascadeReplication(false), + applyTablespacesDdl(true) { } @@ -269,7 +270,8 @@ Config::Config(const Config& other) logErrors(other.logErrors), reportErrors(other.reportErrors), disableOnError(other.disableOnError), - cascadeReplication(other.cascadeReplication) + cascadeReplication(other.cascadeReplication), + applyTablespacesDdl(other.applyTablespacesDdl) { } @@ -445,6 +447,10 @@ Config* Config::get(const PathName& lookupName) { parseBoolean(value, config->cascadeReplication); } + else if (key == "apply_tablespaces_ddl") + { + parseBoolean(value, config->applyTablespacesDdl); + } else if ((key != "journal_source_directory") && (key != "source_guid") && (key != "verbose_logging") && @@ -472,7 +478,7 @@ Config* Config::get(const PathName& lookupName) if (config->pluginName.hasData()) return config.release(); - if (config->journalDirectory.hasData() || config->syncReplicas.hasData()) + if (config->isMaster()) { // If either journal_directory or sync_replicas is specified, // then replication is enabled @@ -592,6 +598,10 @@ void Config::enumerate(ReplicaList& replicas) { parseLong(value, config->applyErrorTimeout); } + else if (key == "apply_tablespaces_ddl") + { + parseBoolean(value, config->applyTablespacesDdl); + } else if (key == "schema_search_path") config->schemaSearchPath = value; } @@ -617,6 +627,11 @@ void Config::enumerate(ReplicaList& replicas) logReplicaStatus(dbName, &localStatus); } +bool Config::isMaster() const +{ + return journalDirectory.hasData() || syncReplicas.hasData(); +} + // This routine is used for split input connection string to parts // input => [[:]@] // diff --git a/src/jrd/replication/Config.h b/src/jrd/replication/Config.h index 7a6e4f2ba59..52439717168 100644 --- a/src/jrd/replication/Config.h +++ b/src/jrd/replication/Config.h @@ -60,6 +60,8 @@ namespace Replication static void splitConnectionString(const Firebird::string& input, Firebird::string& database, Firebird::string& username, Firebird::string& password); + bool isMaster() const; + Firebird::PathName dbName; ULONG bufferSize; Firebird::string includeSchemaFilter; @@ -86,6 +88,7 @@ namespace Replication bool reportErrors; bool disableOnError; bool cascadeReplication; + bool applyTablespacesDdl; }; }; diff --git a/src/jrd/replication/Publisher.cpp b/src/jrd/replication/Publisher.cpp index c8c4caf1696..5a2505c4df3 100644 --- a/src/jrd/replication/Publisher.cpp +++ b/src/jrd/replication/Publisher.cpp @@ -399,7 +399,7 @@ void REPL_attach(thread_db* tdbb, bool cleanupTransactions) const auto attachment = tdbb->getAttachment(); const auto replConfig = dbb->replConfig(); - if (!replConfig) + if (!replConfig || (!replConfig->isMaster() && replConfig->pluginName.isEmpty())) return; fb_assert(!attachment->att_repl_matcher); diff --git a/src/jrd/req.h b/src/jrd/req.h index 35386826d1e..8fd0be17b5d 100644 --- a/src/jrd/req.h +++ b/src/jrd/req.h @@ -66,7 +66,7 @@ struct RecordParameterBase rpb_b_page(0), rpb_b_line(0), rpb_address(NULL), rpb_length(0), rpb_flags(0), rpb_stream_flags(0), rpb_runtime_flags(0), - rpb_org_scans(0), rpb_window(DB_PAGE_SPACE, -1) + rpb_org_scans(0), rpb_window(DB_PAGE_SPACE) { } @@ -123,8 +123,10 @@ struct record_param : public RecordParameterBase inline WIN& getWindow(thread_db* tdbb) { - if (rpb_relation) { - rpb_window.win_page.setPageSpaceID(getPermanent(rpb_relation)->getPages(tdbb)->rel_pg_space_id); + if (rpb_relation) + { + const auto relPages = rpb_relation->getPages(tdbb); + rpb_window.win_page.setPageSpaceID(relPages->getPageSpaceId()); } return rpb_window; diff --git a/src/jrd/rlck.cpp b/src/jrd/rlck.cpp index 18b8a15d6c9..2fde64d7231 100644 --- a/src/jrd/rlck.cpp +++ b/src/jrd/rlck.cpp @@ -51,11 +51,8 @@ Lock* RLCK_reserve_relation(thread_db* tdbb, jrd_tra* transaction, Cached::Relat SET_TDBB(tdbb); Database* const dbb = tdbb->getDatabase(); - if ((transaction->tra_flags & TRA_system) || - (relation->rel_flags & REL_temp_ltt)) - { + if ((transaction->tra_flags & TRA_system) || !relation || relation->isLTT()) return nullptr; - } // hvlad: virtual relations are always writable, see below for the other rules diff --git a/src/jrd/scl.epp b/src/jrd/scl.epp index e59ca91d7b1..67ed9d60e60 100644 --- a/src/jrd/scl.epp +++ b/src/jrd/scl.epp @@ -882,6 +882,38 @@ void SCL_check_role(thread_db* tdbb, const MetaName& name, SecurityClass::flags_ SCL_check_access(tdbb, s_class, 0, {}, mask, obj_roles, false, QualifiedName(name)); } +void SCL_check_tablespace(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask) +{ +/************************************** + * + * S C L _ c h e c k _ t a b l e s p a c e + * + ************************************** + * + * Functional description + * Given a tablespace name, check for a set of privileges. + * + **************************************/ + SET_TDBB(tdbb); + + Jrd::Attachment* const attachment = tdbb->getAttachment(); + + const SecurityClass* s_class = NULL; + AutoCacheRequest request(tdbb, irq_ts_security, IRQ_REQUESTS); + + FOR (REQUEST_HANDLE request) + X IN RDB$TABLESPACES + WITH X.RDB$TABLESPACE_NAME EQ name.c_str() + { + if (!X.RDB$SECURITY_CLASS.NULL) + s_class = SCL_get_class(tdbb, X.RDB$SECURITY_CLASS); + } + END_FOR + + SCL_check_access(tdbb, s_class, 0, {}, mask, obj_tablespaces, false, QualifiedName(name)); +} + + SecurityClass* SCL_get_class(thread_db* tdbb, const MetaName& name) { /************************************** diff --git a/src/jrd/scl_proto.h b/src/jrd/scl_proto.h index 8e0597a4cbf..0aecab80372 100644 --- a/src/jrd/scl_proto.h +++ b/src/jrd/scl_proto.h @@ -56,6 +56,7 @@ void SCL_check_relation(Jrd::thread_db* tdbb, const Jrd::QualifiedName& name, Jr bool SCL_check_schema(Jrd::thread_db* tdbb, const Jrd::MetaName&, Jrd::SecurityClass::flags_t); bool SCL_check_view(Jrd::thread_db* tdbb, const Jrd::QualifiedName& name, Jrd::SecurityClass::flags_t); void SCL_check_role(Jrd::thread_db* tdbb, const Jrd::MetaName&, Jrd::SecurityClass::flags_t); +void SCL_check_tablespace(Jrd::thread_db* tdbb, const Jrd::MetaName&, Jrd::SecurityClass::flags_t); Jrd::SecurityClass* SCL_get_class(Jrd::thread_db*, const Jrd::MetaName& name); Jrd::SecurityClass::flags_t SCL_get_mask(Jrd::thread_db* tdbb, const Jrd::QualifiedName&, const TEXT*); void SCL_clear_classes(Jrd::thread_db*, const Jrd::MetaName&); diff --git a/src/jrd/sdw.cpp b/src/jrd/sdw.cpp index 44f3303e7f7..d7a55246f0d 100644 --- a/src/jrd/sdw.cpp +++ b/src/jrd/sdw.cpp @@ -291,7 +291,7 @@ void SDW_dump_pages(thread_db* tdbb) // none of these pages should need any alteration // since header pages for extend files are not handled at this level - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); window.win_flags = WIN_large_scan; window.win_scans = 1; @@ -824,7 +824,7 @@ void SDW_start(thread_db* tdbb, const TEXT* file_name, Array temp; UCHAR* const spare_page = temp.getAlignedBuffer(dbb->dbb_page_size, dbb->getIOBlockSize()); - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); jrd_file* shadow_file = 0; try { diff --git a/src/jrd/tpc_proto.h b/src/jrd/tpc_proto.h index dd54d268908..fede1f57517 100644 --- a/src/jrd/tpc_proto.h +++ b/src/jrd/tpc_proto.h @@ -161,6 +161,11 @@ class TipCache return m_tpcHeader->getHeader()->monitor_generation++ + 1; } + bool isInitialized() + { + return m_tpcHeader; + } + static int cacheState(thread_db* tdbb, TraNumber number) { auto* tipCache = tdbb->getDatabase()->dbb_tip_cache; diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 5ee4ead6c74..87b47b560a9 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -78,6 +78,7 @@ #include "../jrd/DbCreators.h" #include "../jrd/BulkInsert.h" #include "../common/os/fbsyslog.h" +#include "../jrd/Tablespace.h" #include "../jrd/Resources.h" #include "firebird/impl/msg_helper.h" @@ -548,7 +549,7 @@ void TRA_commit(thread_db* tdbb, jrd_tra* transaction, const bool retaining_flag // Perform any post commit work - DFW_perform_post_commit_work(transaction); + DFW_perform_post_commit_work(tdbb, transaction); // notify any waiting locks that this transaction is committing; // there could be no lock if this transaction is being reconnected @@ -582,12 +583,12 @@ void TRA_extend_tip(thread_db* tdbb, ULONG sequence) //, WIN* precedence_window) // Start by fetching prior transaction page, if any tx_inv_page* prior_tip = NULL; - WIN prior_window(DB_PAGE_SPACE, -1); + WIN prior_window(DB_PAGE_SPACE); if (sequence) prior_tip = fetch_inventory_page(tdbb, &prior_window, (sequence - 1), LCK_write); // Allocate and format new page - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); tx_inv_page* tip = (tx_inv_page*) DPM_allocate(tdbb, &window); tip->tip_header.pag_type = pag_transactions; @@ -611,7 +612,7 @@ void TRA_extend_tip(thread_db* tdbb, ULONG sequence) //, WIN* precedence_window) // Write into pages relation - DPM_pages(tdbb, 0, pag_transactions, sequence, pageNumber); + DPM_pages(tdbb, 0, pag_transactions, sequence, window.win_page); } @@ -637,7 +638,7 @@ int TRA_fetch_state(thread_db* tdbb, TraNumber number) const ULONG trans_per_tip = dbb->dbb_page_manager.transPerTIP; const ULONG tip_seq = number / trans_per_tip; - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, tip_seq, LCK_read); // calculate the state of the desired transaction @@ -681,7 +682,7 @@ void TRA_get_inventory(thread_db* tdbb, UCHAR* bit_vector, TraNumber base, TraNu // fetch the first inventory page - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, sequence++, LCK_read); // move the first page into the bit vector @@ -967,9 +968,9 @@ void jrd_tra::postResources(thread_db* tdbb, const Resources* resources) **************************************/ SET_TDBB(tdbb); - for (auto& rel : resources->relations) + for (auto& relation : resources->relations) { - if (auto* ext = rel()->getExtFile()) + if (auto* ext = relation()->getExtFile()) { FB_SIZE_T pos; if (!traExtRel.find(ext, pos)) @@ -979,11 +980,11 @@ void jrd_tra::postResources(thread_db* tdbb, const Resources* resources) } } - if (rel()->rel_flags & REL_temp_ltt) + if (relation()->rel_flags & REL_temp_ltt) { FB_SIZE_T pos; - if (!traLttRel.find(rel(), pos)) - traLttRel.insert(pos, rel()); + if (!traLttRel.find(relation(), pos)) + traLttRel.insert(pos, relation()); } } } @@ -1251,6 +1252,9 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr MetadataCache::release_temp_tables(tdbb, transaction); + // Release tablespaces used by this transaction + transaction->releaseTablespaces(tdbb); + // Release the locks associated with the transaction if (transaction->tra_alter_db_lock) @@ -1339,6 +1343,7 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl * **************************************/ SET_TDBB(tdbb); + const auto dbb = tdbb->getDatabase(); TraceTransactionEnd trace(transaction, false, retaining_flag); @@ -1431,9 +1436,9 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl state = tra_committed; } - if (!(tdbb->getDatabase()->dbb_flags & DBB_dropping)) + if (!(dbb->dbb_flags & DBB_dropping)) { - const jrd_tra* const sysTran = tdbb->getAttachment()->getSysTransaction(); + const auto sysTran = tdbb->getAttachment()->getSysTransaction(); if (sysTran->tra_flags & TRA_write) transaction_flush(tdbb, FLUSH_SYSTEM, 0); } @@ -1450,6 +1455,9 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl REPL_trans_rollback(tdbb, transaction); TRA_set_state(tdbb, transaction, transaction->tra_number, state); + // Undo any uncommitted changes in the tablespace cache + dbb->dbb_tablespaces.rollback(tdbb, transaction); + TRA_release_transaction(tdbb, transaction, &trace); } @@ -1490,7 +1498,7 @@ void TRA_set_state(thread_db* tdbb, jrd_tra* transaction, TraNumber number, int const ULONG byte = TRANS_OFFSET(number % trans_per_tip); const USHORT shift = TRANS_SHIFT(number); - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); tx_inv_page* tip = fetch_inventory_page(tdbb, &window, sequence, LCK_write); UCHAR* address = tip->tip_transactions + byte; @@ -2231,7 +2239,7 @@ static void expand_view_lock(thread_db* tdbb, jrd_tra* transaction, jrd_rel* rel } // set up the lock on the relation/view - Lock* lock = RLCK_transaction_relation_lock(tdbb, transaction, getPermanent(relation)); + Lock* lock = RLCK_transaction_relation_lock(tdbb, transaction, relation->getPermanent()); lock->lck_logical = lock_type; if (!found) @@ -2338,7 +2346,7 @@ static ULONG inventory_page(thread_db* tdbb, ULONG sequence) while (sequence >= dbb->getKnownPagesCount(pag_transactions)) { - DPM_scan_pages(tdbb); + DPM_scan_pages(tdbb, pag_transactions); const ULONG tipCount = dbb->getKnownPagesCount(pag_transactions); if (sequence < tipCount) @@ -2357,7 +2365,7 @@ static ULONG inventory_page(thread_db* tdbb, ULONG sequence) // Type check it tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_transactions); CCH_RELEASE(tdbb, &window); - DPM_pages(tdbb, 0, pag_transactions, tipCount, window.win_page.getPageNum()); + DPM_pages(tdbb, 0, pag_transactions, tipCount, window.win_page); } return dbb->getKnownPage(pag_transactions, sequence); @@ -2389,7 +2397,7 @@ static int limbo_transaction(thread_db* tdbb, TraNumber id) const ULONG page = id / trans_per_tip; const ULONG number = id % trans_per_tip; - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, page, LCK_write); const ULONG trans_offset = TRANS_OFFSET(number); @@ -2463,24 +2471,24 @@ void MetadataCache::release_temp_tables(thread_db* tdbb, jrd_tra* transaction) * **************************************/ // Release GTTs (Global Temporary Tables) with transaction lifetime - for (auto* relation : MetadataCache::get(tdbb)->mdc_relations) + for (const auto relation : MetadataCache::get(tdbb)->mdc_relations) { if (relation->rel_flags & REL_temp_tran) - relation->delPages(tdbb, transaction->tra_number); + relation->deletePages(tdbb, transaction->tra_number); } // Release LTTs (Local Temporary Tables) with transaction lifetime - Attachment* att = tdbb->getAttachment(); + const auto att = tdbb->getAttachment(); for (const auto& lttEntry : att->att_local_temporary_tables) { - const auto ltt = lttEntry.second; - - if (ltt && ltt->relation) + if (const auto ltt = lttEntry.second) { - auto *rel = ltt->relation->getPermanent(); - if (rel->rel_flags & REL_temp_tran) - rel->delPages(tdbb, transaction->tra_number); + if (const auto relation = ltt->relation) + { + if (relation->checkFlags(REL_temp_tran)) + relation->getPermanent()->deletePages(tdbb, transaction->tra_number); + } } } } @@ -2502,21 +2510,28 @@ void MetadataCache::retain_temp_tables(thread_db* tdbb, jrd_tra* transaction, Tr **************************************/ // Retain GTTs (Global Temporary Tables) with transaction lifetime - for (auto* relation : MetadataCache::get(tdbb)->mdc_relations) + for (const auto relation : MetadataCache::get(tdbb)->mdc_relations) { if (relation->rel_flags & REL_temp_tran) relation->retainPages(tdbb, transaction->tra_number, new_number); } // Retain LTTs (Local Temporary Tables) with transaction lifetime - Attachment* att = tdbb->getAttachment(); + const auto att = tdbb->getAttachment(); for (const auto& lttEntry : att->att_local_temporary_tables) { - const auto ltt = lttEntry.second; + if (const auto ltt = lttEntry.second) + { + if (const auto relation = ltt->relation) + { + const auto relPerm = ltt->relation->getPermanent(); + fb_assert(relPerm); - if (ltt && ltt->relation && (ltt->relation->getPermanent()->rel_flags & REL_temp_tran)) - ltt->relation->getPermanent()->retainPages(tdbb, transaction->tra_number, new_number); + if (relPerm->rel_flags & REL_temp_tran) + relPerm->retainPages(tdbb, transaction->tra_number, new_number); + } + } } } @@ -2582,7 +2597,7 @@ static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, i // Create a new transaction lock, inheriting oldest active from transaction being committed. - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); TraNumber new_number; #ifdef SUPERSERVER_V2 new_number = bump_transaction_id(tdbb, &window); @@ -2638,6 +2653,12 @@ static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, i // Set the state on the inventory page TRA_set_state(tdbb, transaction, old_number, state); + + if (!commit) + { + // Undo any uncommitted changes in the tablespace cache + dbb->dbb_tablespaces.rollback(tdbb, transaction); + } } MetadataCache::retain_temp_tables(tdbb, transaction, new_number); @@ -2659,7 +2680,7 @@ static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, i // Perform any post commit work OR delete entries from deferred list if (commit) - DFW_perform_post_commit_work(transaction); + DFW_perform_post_commit_work(tdbb, transaction); else DFW_delete_deferred(transaction, -1); @@ -3550,7 +3571,7 @@ static void transaction_start(thread_db* tdbb, jrd_tra* trans) SET_TDBB(tdbb); Database* const dbb = tdbb->getDatabase(); Jrd::Attachment* const attachment = tdbb->getAttachment(); - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); Lock* lock = FB_NEW_RPT(*tdbb->getDefaultPool(), 0) Lock(tdbb, sizeof(TraNumber), LCK_tra); @@ -4327,7 +4348,7 @@ TraceSweepEvent::~TraceSweepEvent() } -void TraceSweepEvent::beginSweepRelation(const jrd_rel* relation) +void TraceSweepEvent::beginSweepRelation(const RelationPermanent* relation) { if (!m_need_trace) return; diff --git a/src/jrd/tra.h b/src/jrd/tra.h index 876c8b0b0fb..6764f99a12b 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -206,6 +206,7 @@ class jrd_tra final : public pool_alloc tra_dbcreators_list(nullptr), tra_autonomous_pool(NULL), tra_autonomous_cnt(0), + tra_tablespaces(*p), tra_dependencies(*p) { } @@ -340,6 +341,8 @@ class jrd_tra final : public pool_alloc static constexpr USHORT TRA_AUTONOMOUS_PER_POOL = 64; BulkInsert* tra_bulkInsert = nullptr; + Tablespace::UsageList tra_tablespaces; + public: Firebird::Array tra_dependencies; @@ -429,6 +432,16 @@ class jrd_tra final : public pool_alloc return tra_gen_ids; } + void lockTablespace(thread_db* tdbb, ULONG pageSpaceId) + { + tra_tablespaces.add(tdbb, pageSpaceId); + } + + void releaseTablespaces(thread_db* tdbb) + { + tra_tablespaces.releaseAll(tdbb); + } + // Get existing or create new BulkInsert for the relation. BulkInsert* getBulkInsert(thread_db* tdbb, jrd_rel* relation, bool create); @@ -562,6 +575,9 @@ enum dfw_t : int { dfw_store_view_context_type, dfw_set_generator, dfw_change_repl_state, + dfw_create_tablespace, + dfw_delete_tablespace, + dfw_modify_tablespace, // deferred works argument types dfw_arg_proc_name, // procedure name for dfw_delete_prm, mandatory diff --git a/src/jrd/trace/TraceJrdHelpers.h b/src/jrd/trace/TraceJrdHelpers.h index 9fcc1ee2975..37225360281 100644 --- a/src/jrd/trace/TraceJrdHelpers.h +++ b/src/jrd/trace/TraceJrdHelpers.h @@ -722,7 +722,7 @@ class TraceSweepEvent // implementation is in tra.cpp m_sweep_info.update(header); } - void beginSweepRelation(const jrd_rel* relation); + void beginSweepRelation(const RelationPermanent* relation); void endSweepRelation(); void finish() diff --git a/src/jrd/trace/TraceObjects.cpp b/src/jrd/trace/TraceObjects.cpp index 6c74e837433..61b99609e86 100644 --- a/src/jrd/trace/TraceObjects.cpp +++ b/src/jrd/trace/TraceObjects.cpp @@ -648,6 +648,8 @@ TraceRuntimeStats::TraceRuntimeStats(Attachment* attachment, RuntimeStatistics* baseline, RuntimeStatistics* stats, SINT64 clock, SINT64 recordsFetched) { + const auto dbb = attachment->att_database; + memset(&m_info, 0, sizeof(m_info)); m_info.pin_time = clock * 1000 / fb_utils::query_performance_frequency(); m_info.pin_records_fetched = recordsFetched; @@ -664,15 +666,26 @@ TraceRuntimeStats::TraceRuntimeStats(Attachment* attachment, auto getTablespaceName = [&](MetaId id) -> Firebird::string { - return ""; // TODO + if (id == 0) + return TEMP_TABLESPACE_NAME; + + if (id == DB_PAGE_SPACE) + return PRIMARY_TABLESPACE_NAME; + + if (PageSpace::isTablespace(id)) + { + if (const auto tablespace = dbb->dbb_tablespaces.get(id)) + return tablespace->getName().c_str(); + } + + return ""; }; m_pageCounters.reset(&baseline->getPageCounters(), getTablespaceName); auto getTableName = [&](MetaId id) -> Firebird::string { - auto* mdc = attachment->att_database->dbb_mdc; - if (const auto* relation = mdc->lookupRelationNoChecks(id)) + if (const auto* relation = dbb->dbb_mdc->lookupRelationNoChecks(id)) return relation->getName().toQuotedString(); return ""; diff --git a/src/jrd/trig.h b/src/jrd/trig.h index 2d6acc8df75..0af70b8459d 100644 --- a/src/jrd/trig.h +++ b/src/jrd/trig.h @@ -84,6 +84,7 @@ static inline constexpr Jrd::gen generators[] = { "RDB$GENERATOR_NAME", 11, "Implicit generator name", ODS_13_0 }, { PACKAGES_GENERATOR, 12, "Package ID", ODS_14_0 }, { "RDB$RELATIONS", RELATIONS_GENERATOR, "Relation ID", ODS_14_0 }, + { "RDB$TABLESPACES", TABLESPACES_GENERATOR, "Tablespace ID", ODS_14_0 }, { nullptr, 0, nullptr, 0 } }; diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index b020cc5b3cc..95d04c79992 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -570,6 +570,8 @@ VI. ADDITIONAL NOTES #include "../common/classes/ClumpletWriter.h" #include "../common/db_alias.h" #include "../jrd/intl_proto.h" +#include "../jrd/PreparedStatement.h" +#include "../jrd/ResultSet.h" #include "../jrd/lck.h" #ifdef DEBUG_VAL_VERBOSE @@ -817,20 +819,20 @@ namespace Jrd const Validation::MSG_ENTRY Validation::vdr_msg_table[VAL_MAX_ERROR] = { - {true, isc_info_page_errors, "Page %" ULONGFORMAT" wrong type (expected %s encountered %s)"}, // 0 - {true, isc_info_page_errors, "Checksum error on page %" ULONGFORMAT}, - {true, isc_info_page_errors, "Page %" ULONGFORMAT" doubly allocated"}, - {true, isc_info_page_errors, "Page %" ULONGFORMAT" is used but marked free"}, - {false, fb_info_page_warns, "Page %" ULONGFORMAT" is an orphan"}, + {true, isc_info_page_errors, "Page (%d, %" ULONGFORMAT") wrong type (expected %s encountered %s)"}, // 0 + {true, isc_info_page_errors, "Checksum error on page (%d, %" ULONGFORMAT")"}, + {true, isc_info_page_errors, "Page (%d, %" ULONGFORMAT") doubly allocated"}, + {true, isc_info_page_errors, "Page (%d, %" ULONGFORMAT") is used but marked free"}, + {false, fb_info_page_warns, "Page (%d, %" ULONGFORMAT") is an orphan"}, {false, fb_info_bpage_warns, "Blob %" SQUADFORMAT" appears inconsistent"}, // 5 {true, isc_info_bpage_errors, "Blob %" SQUADFORMAT" is corrupt"}, {true, isc_info_bpage_errors, "Blob %" SQUADFORMAT" is truncated"}, {true, isc_info_record_errors, "Chain for record %" SQUADFORMAT" is broken"}, - {true, isc_info_dpage_errors, "Data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} is confused"}, - {true, isc_info_dpage_errors, "Data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"}, line %" ULONGFORMAT" is bad"}, // 10 - {true, isc_info_ipage_errors, "Index %d is corrupt on page %" ULONGFORMAT" level %d at offset %" ULONGFORMAT". File: %s, line: %d\n\t"}, + {true, isc_info_dpage_errors, "Data page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} is confused"}, + {true, isc_info_dpage_errors, "Data page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"}, line %" ULONGFORMAT" is bad"}, // 10 + {true, isc_info_ipage_errors, "Index %d is corrupt on page (%d, %" ULONGFORMAT") level %d at offset %" ULONGFORMAT". File: %s, line: %d\n\t"}, {true, isc_info_ppage_errors, "Pointer page {sequence %" ULONGFORMAT"} lost"}, - {true, isc_info_ppage_errors, "Pointer page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} inconsistent"}, + {true, isc_info_ppage_errors, "Pointer page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} inconsistent"}, {true, isc_info_record_errors, "Record %" SQUADFORMAT" is marked as damaged"}, {true, isc_info_record_errors, "Record %" SQUADFORMAT" has bad transaction %" SQUADFORMAT}, // 15 {true, isc_info_record_errors, "Fragmented record %" SQUADFORMAT" is corrupt"}, @@ -841,30 +843,30 @@ const Validation::MSG_ENTRY Validation::vdr_msg_table[VAL_MAX_ERROR] = {true, isc_info_tpage_errors, "Transaction inventory pages confused, sequence %" ULONGFORMAT}, {false, fb_info_record_warns, "Relation has %" UQUADFORMAT" orphan backversions {%" UQUADFORMAT" in use}"}, {true, isc_info_ipage_errors, "Index %d is corrupt {missing entries for record %" SQUADFORMAT"}"}, - {false, fb_info_ipage_warns, "Index %d has orphan child page at page %" ULONGFORMAT}, - {true, isc_info_ipage_errors, "Index %d has a circular reference at page %" ULONGFORMAT}, // 25 - {true, isc_info_page_errors, "SCN's page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} inconsistent"}, - {false, fb_info_page_warns, "Page %" ULONGFORMAT" has SCN %" ULONGFORMAT" while at SCN's page it is %" ULONGFORMAT}, + {false, fb_info_ipage_warns, "Index %d has orphan child page at page (%d, %" ULONGFORMAT")"}, + {true, isc_info_ipage_errors, "Index %d has a circular reference at page (%d, %" ULONGFORMAT")"}, // 25 + {true, isc_info_page_errors, "SCN's page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} inconsistent"}, + {false, fb_info_page_warns, "Page (%d, %" ULONGFORMAT") has SCN %" ULONGFORMAT" while at SCN's page it is %" ULONGFORMAT}, {true, isc_info_bpage_errors, "Blob %" SQUADFORMAT" has unknown level %d instead of {0, 1, 2}"}, - {false, fb_info_ipage_warns, "Index %d has inconsistent left sibling pointer, page %" ULONGFORMAT" level %d at offset %" ULONGFORMAT}, - {false, fb_info_ipage_warns, "Index %d misses node on page %" ULONGFORMAT" level %d at offset %" ULONGFORMAT}, // 30 - {false, fb_info_pip_warns, "PIP %" ULONGFORMAT" (seq %d) have wrong pip_min (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, - {false, fb_info_pip_warns, "PIP %" ULONGFORMAT" (seq %d) have wrong pip_extent (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, - {false, fb_info_pip_warns, "PIP %" ULONGFORMAT" (seq %d) have wrong pip_used (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, - {false, fb_info_ppage_warns, "Pointer page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} bits {0x%02X %s} are not consistent with data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} state {0x%02X %s}"}, - {true, fb_info_pip_errors, "Data page %" ULONGFORMAT" marked as free in PIP (%" ULONGFORMAT":%" ULONGFORMAT")"}, - {true, isc_info_ppage_errors, "Data page %" ULONGFORMAT" is not in PP (%" ULONGFORMAT"). Slot (%d) is not found"}, - {true, isc_info_ppage_errors, "Data page %" ULONGFORMAT" is not in PP (%" ULONGFORMAT"). Slot (%d) has value %" ULONGFORMAT}, - {true, isc_info_ppage_errors, "Pointer page is not found for data page %" ULONGFORMAT". dpg_sequence (%" ULONGFORMAT") is invalid"}, - {true, isc_info_dpage_errors, "Data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} marked as secondary but contains primary record versions"} + {false, fb_info_ipage_warns, "Index %d has inconsistent left sibling pointer, page (%d, %" ULONGFORMAT") level %d at offset %" ULONGFORMAT}, + {false, fb_info_ipage_warns, "Index %d misses node on page (%d, %" ULONGFORMAT") level %d at offset %" ULONGFORMAT}, // 30 + {false, fb_info_pip_warns, "PIP (%d, %" ULONGFORMAT") (seq %d) have wrong pip_min (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, + {false, fb_info_pip_warns, "PIP (%d, %" ULONGFORMAT") (seq %d) have wrong pip_extent (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, + {false, fb_info_pip_warns, "PIP (%d, %" ULONGFORMAT") (seq %d) have wrong pip_used (%" ULONGFORMAT"). Correct is %" ULONGFORMAT}, + {false, fb_info_ppage_warns, "Pointer page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} bits {0x%02X %s} are not consistent with data page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} state {0x%02X %s}"}, + {true, fb_info_pip_errors, "Data page (%d, %" ULONGFORMAT") marked as free in PIP (%" ULONGFORMAT":%" ULONGFORMAT")"}, // 35 + {true, isc_info_ppage_errors, "Data page (%d, %" ULONGFORMAT") is not in PP (%" ULONGFORMAT"). Slot (%d) is not found"}, + {true, isc_info_ppage_errors, "Data page (%d, %" ULONGFORMAT") is not in PP (%" ULONGFORMAT"). Slot (%d) has value %" ULONGFORMAT}, + {true, isc_info_ppage_errors, "Pointer page is not found for data page (%d, %" ULONGFORMAT"). dpg_sequence (%" ULONGFORMAT") is invalid"}, + {true, isc_info_dpage_errors, "Data page (%d, %" ULONGFORMAT") {sequence %" ULONGFORMAT"} marked as secondary but contains primary record versions"} }; Validation::Validation(thread_db* tdbb, UtilSvc* uSvc) : vdr_cond_idx(*tdbb->getDefaultPool()), + vdr_page_spaces(*tdbb->getDefaultPool()), vdr_used_bdbs(*tdbb->getDefaultPool()) { vdr_tdbb = tdbb; - vdr_max_page = 0; vdr_flags = 0; vdr_errors = 0; vdr_warns = 0; @@ -876,7 +878,6 @@ Validation::Validation(thread_db* tdbb, UtilSvc* uSvc) vdr_chain_pages = NULL; vdr_rel_records = NULL; vdr_idx_records = NULL; - vdr_page_bitmap = NULL; vdr_service = uSvc; vdr_lock_tout = -10; @@ -1088,14 +1089,23 @@ bool Validation::run(thread_db* tdbb, USHORT flags) void Validation::cleanup() { - delete vdr_page_bitmap; - vdr_page_bitmap = NULL; + PageSpaceMap::Accessor accessor(&vdr_page_spaces); + + if (accessor.getFirst()) + { + do { + const auto& info = accessor.current()->second; + delete info.bitmap; + } while (accessor.getNext()); + } + + vdr_page_spaces.clear(); delete vdr_rel_records; - vdr_rel_records = NULL; + vdr_rel_records = nullptr; delete vdr_idx_records; - vdr_idx_records = NULL; + vdr_idx_records = nullptr; for (auto& item : vdr_cond_idx) { @@ -1185,7 +1195,7 @@ Validation::RTN Validation::corrupt(int err_code, const jrd_rel* relation, ...) return rtn_corrupt; } -Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, +Validation::FETCH_CODE Validation::fetch_page(bool mark, PageNumber page_number, USHORT type, WIN* window, void* aPage_pointer) { /************************************** @@ -1220,7 +1230,7 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, vdr_used_bdbs[pos].count++; BufferDesc* bdb = vdr_used_bdbs[pos].bdb; - fb_assert(bdb->bdb_page == PageNumber(DB_PAGE_SPACE, page_number)); + fb_assert(bdb->bdb_page == page_number); window->win_bdb = bdb; *page_pointer = window->win_buffer = bdb->bdb_buffer; @@ -1236,7 +1246,7 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, if ((*page_pointer)->pag_type != type && type != pag_undefined) { - corrupt(VAL_PAG_WRONG_TYPE, 0, page_number, + corrupt(VAL_PAG_WRONG_TYPE, 0, page_number.getPageSpaceID(), page_number.getPageNum(), pagtype(type).c_str(), pagtype((*page_pointer)->pag_type).c_str()); return fetch_type; } @@ -1248,12 +1258,20 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, if ((dbb->dbb_flags & DBB_damaged) && !CCH_validate(window)) { - corrupt(VAL_PAG_CHECKSUM_ERR, 0, page_number); + corrupt(VAL_PAG_CHECKSUM_ERR, 0, page_number.getPageSpaceID(), page_number.getPageNum()); if (vdr_flags & VDR_repair) CCH_MARK(vdr_tdbb, window); } - vdr_max_page = MAX(vdr_max_page, page_number); + const ULONG pageSpaceId = page_number.getPageSpaceID(); + const PageManager& pageMgr = dbb->dbb_page_manager; + const PageSpace* pageSpace = pageMgr.findPageSpace(pageSpaceId); + + auto pageSpaceInfo = vdr_page_spaces.get(pageSpaceId); + if (!pageSpaceInfo) + pageSpaceInfo = vdr_page_spaces.put(pageSpaceId); + + pageSpaceInfo->maxPage = MAX(pageSpaceInfo->maxPage, page_number.getPageNum()); // For walking back versions & record fragments on data pages we // sometimes will fetch the same page more than once. In that @@ -1265,22 +1283,21 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, // non pag_scns type. if (type != pag_data && type != pag_scns && - PageBitmap::test(vdr_page_bitmap, page_number)) + PageBitmap::test(pageSpaceInfo->bitmap, page_number.getPageNum())) { - corrupt(VAL_PAG_DOUBLE_ALLOC, 0, page_number); + corrupt(VAL_PAG_DOUBLE_ALLOC, 0, page_number.getPageSpaceID(), page_number.getPageNum()); return fetch_duplicate; } // Check SCN's page - if (page_number) + if (page_number.getPageNum()) { - const PageManager& pageMgr = dbb->dbb_page_manager; - const ULONG scn_seq = page_number / pageMgr.pagesPerSCN; - const ULONG scn_slot = page_number % pageMgr.pagesPerSCN; - const ULONG scn_page_num = PageSpace::getSCNPageNum(dbb, scn_seq); + const ULONG scn_seq = page_number.getPageNum() / pageMgr.pagesPerSCN; + const ULONG scn_slot = page_number.getPageNum() % pageMgr.pagesPerSCN; + const PageNumber scn_page_num(pageSpaceId, pageSpace->getSCNPageNum(scn_seq)); const ULONG page_scn = (*page_pointer)->pag_scn; - WIN scns_window(DB_PAGE_SPACE, scn_page_num); + WIN scns_window(scn_page_num); scns_page* scns = (scns_page*) *page_pointer; if (scn_page_num != page_number) { @@ -1289,7 +1306,7 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, if (scns->scn_pages[scn_slot] != page_scn) { - corrupt(VAL_PAG_WRONG_SCN, 0, page_number, page_scn, scns->scn_pages[scn_slot]); + corrupt(VAL_PAG_WRONG_SCN, 0, page_number.getPageSpaceID(), page_number.getPageNum(), page_scn, scns->scn_pages[scn_slot]); if (vdr_flags & VDR_update) { @@ -1306,7 +1323,7 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, } } - PBM_SET(vdr_tdbb->getDefaultPool(), &vdr_page_bitmap, page_number); + PBM_SET(vdr_tdbb->getDefaultPool(), &pageSpaceInfo->bitmap, page_number.getPageNum()); return fetch_ok; } @@ -1314,7 +1331,7 @@ Validation::FETCH_CODE Validation::fetch_page(bool mark, ULONG page_number, void Validation::release_page(WIN* window) { FB_SIZE_T pos; - if (!vdr_used_bdbs.find(window->win_page.getPageNum(), pos)) + if (!vdr_used_bdbs.find(window->win_page, pos)) { fb_assert(false); return; // BUG @@ -1344,74 +1361,98 @@ void Validation::garbage_collect() Database* dbb = vdr_tdbb->getDatabase(); PageManager& pageSpaceMgr = dbb->dbb_page_manager; - PageSpace* pageSpace = pageSpaceMgr.findPageSpace(DB_PAGE_SPACE); - fb_assert(pageSpace); - WIN window(DB_PAGE_SPACE, -1); + PageSpaceMap::Accessor accessor(&vdr_page_spaces); - for (ULONG sequence = 0, number = 0; number < vdr_max_page; sequence++) + if (accessor.getFirst()) { - const ULONG page_number = sequence ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->pipFirst; - page_inv_page* page = 0; - fetch_page(false, page_number, pag_pages, &window, &page); - UCHAR* p = page->pip_bits; - const UCHAR* const end = p + pageSpaceMgr.bytesBitPIP; - while (p < end && number < vdr_max_page) - { - UCHAR byte = *p++; - for (int i = 8; i; --i, byte >>= 1, number++) + do { + const auto pageSpaceId = accessor.current()->first; + const auto& info = accessor.current()->second; + + if (!info.maxPage) + continue; + + const auto pageSpace = pageSpaceMgr.findPageSpace(pageSpaceId); + fb_assert(pageSpace); // probably here we need to continue if NULL + WIN window(pageSpaceId); + + for (ULONG sequence = 0, number = 0; number < info.maxPage; sequence++) { - if (PageBitmap::test(vdr_page_bitmap, number)) + const ULONG page_number = sequence ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->pipFirst; + page_inv_page* page = 0; + fetch_page(false, PageNumber(pageSpaceId, page_number), pag_pages, &window, &page); + UCHAR* p = page->pip_bits; + const UCHAR* const end = p + pageSpaceMgr.bytesBitPIP; + while (p < end && number < info.maxPage) { - if (byte & 1) + UCHAR byte = *p++; + for (int i = 8; i; --i, byte >>= 1, number++) { - corrupt(VAL_PAG_IN_USE, 0, number); - if (vdr_flags & VDR_update) + if (PageBitmap::test(info.bitmap, number)) // this page was fetched { - CCH_MARK(vdr_tdbb, &window); - p[-1] &= ~(1 << (number & 7)); - vdr_fixed++; + if (byte & 1) // The page mark as free in PIP + { + corrupt(VAL_PAG_IN_USE, 0, pageSpaceId, page_number); + if (vdr_flags & VDR_update) + { + CCH_MARK(vdr_tdbb, &window); + p[-1] &= ~(1 << (number & 7)); + vdr_fixed++; + } + } + } + else if (!(byte & 1) && (vdr_flags & VDR_records) && + (pageSpaceId == DB_PAGE_SPACE || number > HEADER_PAGE)) + { + // Page is potentially an orphan - but don't declare it as such + // unless we think we walked all pages (with full validation - VDR_records) + // For non main tablespace we skip header page. It's reserved for now + + corrupt(VAL_PAG_ORPHAN, 0, pageSpaceId, number); + if (vdr_flags & VDR_update) + { + CCH_MARK(vdr_tdbb, &window); + p[-1] |= 1 << (number & 7); + vdr_fixed++; + + const ULONG bit = number - sequence * pageSpaceMgr.pagesPerPIP; + if (page->pip_min > bit) + page->pip_min = bit; + + if (p[-1] == 0xFF && page->pip_extent > bit) + page->pip_extent = bit & ((ULONG) ~7); + } } } } - else if (!(byte & 1) && (vdr_flags & VDR_records)) - { - // Page is potentially an orphan - but don't declare it as such - // unless we think we walked all pages - - corrupt(VAL_PAG_ORPHAN, 0, number); - if (vdr_flags & VDR_update) - { - CCH_MARK(vdr_tdbb, &window); - p[-1] |= 1 << (number & 7); - vdr_fixed++; - - const ULONG bit = number - sequence * pageSpaceMgr.pagesPerPIP; - if (page->pip_min > bit) - page->pip_min = bit; - - if (p[-1] == 0xFF && page->pip_extent > bit) - page->pip_extent = bit & ((ULONG) ~7); - } - } + const UCHAR test_byte = p[-1]; + release_page(&window); + if (test_byte & 0x80) + break; } - } - const UCHAR test_byte = p[-1]; - release_page(&window); - if (test_byte & 0x80) - break; + } while (accessor.getNext()); } #ifdef DEBUG_VAL_VERBOSE // Dump verbose output of all the pages fetched if (VAL_debug_level >= 2) { - if (vdr_page_bitmap->getFirst()) + PageSpaceMap::Accessor accessor(&vdr_page_spaces); + + if (accessor.getFirst()) { do { - ULONG dmp_page_number = vdr_page_bitmap->current(); - DMP_page(dmp_page_number, dbb->dbb_page_size); - } while (vdr_page_bitmap->getNext()); + const auto& pageSpaceInfo = accessor.current()->second; + + if (pageSpaceInfo.bitmap && pageSpaceInfo.bitmap->getFirst()) + { + do { + ULONG dmp_page_number = pageSpaceInfo.bitmap->current(); + DMP_page(dmp_page_number, dbb->dbb_page_size); + } while (pageSpaceInfo.bitmap->getNext()); + } + } while (accessor.getNext()); } } #endif @@ -1495,8 +1536,11 @@ Validation::RTN Validation::walk_blob(jrd_rel* relation, const blh* header, USHO corrupt(VAL_BLOB_UNKNOWN_LEVEL, relation, number.getValue(), header->blh_level); } + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + // Level 1 blobs are a little more complicated - WIN window1(DB_PAGE_SPACE, -1), window2(DB_PAGE_SPACE, -1); + WIN window1(pageSpaceId), window2(pageSpaceId); window1.win_flags = window2.win_flags = WIN_garbage_collector; const ULONG* pages1 = header->blh_page; @@ -1506,7 +1550,7 @@ Validation::RTN Validation::walk_blob(jrd_rel* relation, const blh* header, USHO for (; pages1 < end1; pages1++) { blob_page* page1 = 0; - fetch_page(true, *pages1, pag_blob, &window1, &page1); + fetch_page(true, PageNumber(pageSpaceId, *pages1), pag_blob, &window1, &page1); if (page1->blp_lead_page != header->blh_lead_page) { corrupt(VAL_BLOB_INCONSISTENT, relation, number.getValue()); } @@ -1525,7 +1569,7 @@ Validation::RTN Validation::walk_blob(jrd_rel* relation, const blh* header, USHO for (; pages2 < end2; pages2++, sequence++) { blob_page* page2 = 0; - fetch_page(true, *pages2, pag_blob, &window2, &page2); + fetch_page(true, PageNumber(pageSpaceId, *pages2), pag_blob, &window2, &page2); if (page2->blp_lead_page != header->blh_lead_page || page2->blp_sequence != sequence) { corrupt(VAL_BLOB_CORRUPT, relation, number.getValue()); @@ -1562,9 +1606,12 @@ Validation::RTN Validation::walk_chain(jrd_rel* relation, const rhd* header, USHORT counter = 0; #endif + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + ULONG page_number = header->rhd_b_page; USHORT line_number = header->rhd_b_line; - WIN window(DB_PAGE_SPACE, -1); + WIN window(pageSpaceId); window.win_flags = WIN_garbage_collector; while (page_number) @@ -1576,12 +1623,12 @@ Validation::RTN Validation::walk_chain(jrd_rel* relation, const rhd* header, #endif vdr_rel_chain_counter++; data_page* page = 0; - fetch_page(true, page_number, pag_data, &window, &page); + fetch_page(true, PageNumber(pageSpaceId, page_number), pag_data, &window, &page); if (page->dpg_relation != relation->getId()) { - release_page(&window); - return corrupt(VAL_DATA_PAGE_CONFUSED, relation, page_number, page->dpg_sequence); + release_page(&window); + return corrupt(VAL_DATA_PAGE_CONFUSED, relation, pageSpaceId, page_number, page->dpg_sequence); } vdr_rel_chain_counter++; @@ -1630,9 +1677,10 @@ void Validation::walk_database() #endif DPM_scan_pages(vdr_tdbb); - WIN window(DB_PAGE_SPACE, -1); + + WIN window(DB_PAGE_SPACE); header_page* page = 0; - fetch_page(true, HEADER_PAGE, pag_header, &window, &page); + fetch_page(true, PageNumber(DB_PAGE_SPACE, HEADER_PAGE), pag_header, &window, &page); const TraNumber next = vdr_max_transaction = page->hdr_next_transaction; if (vdr_flags & VDR_online) @@ -1691,10 +1739,17 @@ void Validation::walk_database() continue; } - // We can't realiable track double allocated page's when validating online. + // We can't reliably track double allocated page's when validating online. // All we can check is that page is not double allocated at the same relation. if (vdr_flags & VDR_online) - vdr_page_bitmap->clear(); + { + const auto relPages = relation->getBasePages(); + if (const auto pageSpaceInfo = vdr_page_spaces.get(relPages->getPageSpaceId())) + { + if (pageSpaceInfo->bitmap) + pageSpaceInfo->bitmap->clear(); // Should all array be cleared? + } + } string relName; relName.printf("Relation %d (%s)", relation->getId(), relation->getName().toQuotedString().c_str()); @@ -1727,11 +1782,14 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number, **************************************/ Database* dbb = vdr_tdbb->getDatabase(); - WIN window(DB_PAGE_SPACE, -1); + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + + WIN window(pageSpaceId); window.win_flags = WIN_garbage_collector; data_page* page = 0; - fetch_page(true, page_number, pag_data, &window, &page); + fetch_page(true, PageNumber(pageSpaceId, page_number), pag_data, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -1746,7 +1804,7 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number, if (page->dpg_relation != relation->getId() || page->dpg_sequence != sequence) { release_page(&window); - return corrupt(VAL_DATA_PAGE_CONFUSED, relation, page_number, sequence); + return corrupt(VAL_DATA_PAGE_CONFUSED, relation, pageSpaceId, page_number, sequence); } pp_bits = 0; @@ -1798,7 +1856,7 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number, if ((UCHAR*) header < (UCHAR*) end || (UCHAR*) header + line->dpg_length > end_page) { release_page(&window); - return corrupt(VAL_DATA_PAGE_LINE_ERR, relation, page_number, + return corrupt(VAL_DATA_PAGE_LINE_ERR, relation, pageSpaceId, page_number, sequence, (ULONG) (line - page->dpg_rpt)); } if (header->rhd_flags & rhd_chain) @@ -1883,7 +1941,7 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number, if (primary_versions && (dp_flags & dpg_secondary)) { - corrupt(VAL_DATA_PAGE_SEC_PRI, relation, page_number, sequence); + corrupt(VAL_DATA_PAGE_SEC_PRI, relation, pageSpaceId, page_number, sequence); if (vdr_flags & VDR_update) { @@ -1962,7 +2020,7 @@ void Validation::walk_generators() **************************************/ Database* dbb = vdr_tdbb->getDatabase(); - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); if (const auto idsCount = dbb->getKnownPagesCount(pag_ids)) { @@ -1976,7 +2034,7 @@ void Validation::walk_generators() #endif // It doesn't make a difference generator_page or pointer_page because it's not used. generator_page* page = NULL; - fetch_page(true, pageNumber, pag_ids, &window, &page); + fetch_page(true, PageNumber(DB_PAGE_SPACE, pageNumber), pag_ids, &window, &page); release_page(&window); } } @@ -2003,15 +2061,22 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ **************************************/ Database* dbb = vdr_tdbb->getDatabase(); - const ULONG page_number = root_page->irt_rpt[id].getRoot(); - if (!page_number) + const auto rootPageNumber = root_page->irt_rpt[id].getRootPage(); + if (!rootPageNumber) return rtn_ok; + fb_assert(rootPageNumber.value().isValid()); + const ULONG pageNumber = rootPageNumber.value().getPageNum(); + const ULONG pageSpaceId = rootPageNumber.value().getPageSpaceID(); + + Tablespace::lock(vdr_tdbb, pageSpaceId); + const bool unique = (root_page->irt_rpt[id].irt_flags & (irt_unique | idx_primary)); const bool descending = (root_page->irt_rpt[id].irt_flags & irt_descending); const bool condition = (root_page->irt_rpt[id].irt_flags & irt_condition); temporary_key nullKey, *null_key = nullptr; + if (unique) { index_desc idx; @@ -2027,8 +2092,8 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ BTR_make_null_key(vdr_tdbb, &idx, null_key); } - ULONG next = page_number; - ULONG down = page_number; + ULONG next = pageNumber; + ULONG down = pageNumber; temporary_key key; key.key_length = 0; ULONG previous_number = 0; @@ -2045,20 +2110,20 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ while (next) { - WIN window(DB_PAGE_SPACE, -1); + WIN window(pageSpaceId); window.win_flags = WIN_garbage_collector; btree_page* page = 0; - fetch_page(true, next, pag_index, &window, &page); + fetch_page(true, PageNumber(pageSpaceId, next), pag_index, &window, &page); // remember each page for circular reference detection visited_pages.set(next); - //if ((next != page_number) && + //if ((next != pageNumber) && // (page->btr_header.pag_flags & BTR_FLAG_COPY_MASK) != (flags & BTR_FLAG_COPY_MASK)) //{ // corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - // id + 1, next, page->btr_level, 0, __FILE__, __LINE__); + // id + 1, pageSpaceId, next, page->btr_level, 0, __FILE__, __LINE__); //} if (level == 255) { @@ -2067,14 +2132,14 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ else if (level != page->btr_level) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, 0, __FILE__, __LINE__); + id + 1, pageSpaceId, next, page->btr_level, 0, __FILE__, __LINE__); } const bool leafPage = (page->btr_level == 0); if (page->btr_relation != relation->getId() || page->btr_id != (UCHAR) (id % 256)) { - corrupt(VAL_INDEX_PAGE_CORRUPT, relation, id + 1, + corrupt(VAL_INDEX_PAGE_CORRUPT, relation, id + 1, pageSpaceId, next, page->btr_level, 0, __FILE__, __LINE__); release_page(&window); return rtn_corrupt; @@ -2085,7 +2150,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (BTR_SIZE + page->btr_jump_size > page->btr_length) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), __FILE__, __LINE__); } @@ -2102,7 +2167,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ (jumpNode.offset > page->btr_length)) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), __FILE__, __LINE__); } else @@ -2112,7 +2177,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if ((jumpNode.prefix + jumpNode.length) != checknode.prefix) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) jumpNode.offset, + id + 1, pageSpaceId, next, page->btr_level, (ULONG) jumpNode.offset, __FILE__, __LINE__); } @@ -2120,7 +2185,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (n == page->btr_jump_count && jumpNode.prefix) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) jumpNode.offset, + id + 1, pageSpaceId, next, page->btr_level, (ULONG) jumpNode.offset, __FILE__, __LINE__); } @@ -2128,7 +2193,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (n != page->btr_jump_count && jumpNode.prefix > jumpDataLen) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) jumpNode.offset, + id + 1, pageSpaceId, next, page->btr_level, (ULONG) jumpNode.offset, __FILE__, __LINE__); } @@ -2140,7 +2205,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (jumpersSize > page->btr_jump_size) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) page->btr_jump_size + BTR_SIZE, + id + 1, pageSpaceId, next, page->btr_level, (ULONG) page->btr_jump_size + BTR_SIZE, __FILE__, __LINE__); } @@ -2160,7 +2225,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (node.prefix > key.key_length) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, node.nodePointer - (UCHAR*) page, __FILE__, __LINE__); + id + 1, pageSpaceId, next, page->btr_level, node.nodePointer - (UCHAR*) page, __FILE__, __LINE__); release_page(&window); return rtn_corrupt; } @@ -2181,7 +2246,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ { duplicateNode = false; corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (q - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (q - (UCHAR*) page), __FILE__, __LINE__); } else if (*p < *q) @@ -2203,7 +2268,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ { duplicateNode = false; corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), __FILE__, __LINE__); } @@ -2227,7 +2292,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ { duplicateNode = false; corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), __FILE__, __LINE__); } } @@ -2248,7 +2313,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ (node.recordNumber < lastNode.recordNumber)) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), __FILE__, __LINE__); } } @@ -2287,11 +2352,11 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ // Note: mark == false for the fetch_page() call here // as we don't want to mark the page as visited yet - we'll // mark it when we visit it for real later on - WIN down_window(DB_PAGE_SPACE, -1); + WIN down_window(pageSpaceId); down_window.win_flags = WIN_garbage_collector; btree_page* down_page = 0; - fetch_page(false, down_number, pag_index, &down_window, &down_page); + fetch_page(false, PageNumber(pageSpaceId, down_number), pag_index, &down_window, &down_page); const bool downLeafPage = (down_page->btr_level == 0); // make sure the initial key is greater than the pointer key @@ -2308,7 +2373,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (*p < *q) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), __FILE__, __LINE__); } else if (*p > *q) @@ -2329,7 +2394,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ (downNode.recordNumber < down_record_number)) { corrupt(VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page), __FILE__, __LINE__); } } @@ -2338,7 +2403,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (previous_number != down_page->btr_left_sibling) { corrupt(VAL_INDEX_BAD_LEFT_SIBLING, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page)); + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page)); } downNode.readNode(pointer, leafPage); @@ -2348,11 +2413,11 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ (next_number != down_page->btr_sibling)) { corrupt(VAL_INDEX_MISSES_NODE, relation, - id + 1, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page)); + id + 1, pageSpaceId, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page)); } if (downNode.isEndLevel && down_page->btr_sibling) { - corrupt(VAL_INDEX_ORPHAN_CHILD, relation, id + 1, next); + corrupt(VAL_INDEX_ORPHAN_CHILD, relation, id + 1, pageSpaceId, next); } previous_number = down_number; @@ -2362,7 +2427,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ if (pointer != endPointer || page->btr_length > dbb->dbb_page_size) { - corrupt(VAL_INDEX_PAGE_CORRUPT, relation, id + 1, + corrupt(VAL_INDEX_PAGE_CORRUPT, relation, id + 1, pageSpaceId, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), __FILE__, __LINE__); } @@ -2393,7 +2458,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ // check for circular referenes if (next && visited_pages.test(next)) { - corrupt(VAL_INDEX_CYCLE, relation, id + 1, next); + corrupt(VAL_INDEX_CYCLE, relation, id + 1, pageSpaceId, next); next = 0; } release_page(&window); @@ -2448,117 +2513,124 @@ void Validation::walk_pip() Database* dbb = vdr_tdbb->getDatabase(); PageManager& pageSpaceMgr = dbb->dbb_page_manager; - const PageSpace* pageSpace = pageSpaceMgr.findPageSpace(DB_PAGE_SPACE); - fb_assert(pageSpace); - page_inv_page* page = 0; + MET_scan_tablespaces(vdr_tdbb); - for (USHORT sequence = 0; true; sequence++) + for (ULONG pageSpaceId = DB_PAGE_SPACE; pageSpaceId <= MAX_TABLESPACE_ID; pageSpaceId++) { - const ULONG page_number = - sequence ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->pipFirst; -#ifdef DEBUG_VAL_VERBOSE - if (VAL_debug_level) - fprintf(stdout, "walk_pip: page %d\n", page_number); -#endif - WIN window(DB_PAGE_SPACE, -1); - fetch_page(true, page_number, pag_pages, &window, &page); + const PageSpace* pageSpace = pageSpaceMgr.findPageSpace(pageSpaceId); + if (!pageSpace) + continue; - ULONG pipMin = MAX_ULONG; - ULONG pipExtent = MAX_ULONG; - ULONG pipUsed = 0; + page_inv_page* page = 0; - UCHAR* bytes = page->pip_bits; - const UCHAR* end = (UCHAR*) page + dbb->dbb_page_size; - for (; bytes < end; bytes++) + for (USHORT sequence = 0; true; sequence++) { - if (*bytes == 0) + const ULONG page_number = + sequence ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->pipFirst; + #ifdef DEBUG_VAL_VERBOSE + if (VAL_debug_level) + fprintf(stdout, "walk_pip: page %d\n", page_number); + #endif + WIN window(pageSpaceId); + fetch_page(true, PageNumber(pageSpaceId, page_number), pag_pages, &window, &page); + + ULONG pipMin = MAX_ULONG; + ULONG pipExtent = MAX_ULONG; + ULONG pipUsed = 0; + + UCHAR* bytes = page->pip_bits; + const UCHAR* end = (UCHAR*) page + dbb->dbb_page_size; + for (; bytes < end; bytes++) { - pipUsed = (bytes - page->pip_bits + 1) * 8; - continue; - } + if (*bytes == 0) + { + pipUsed = (bytes - page->pip_bits + 1) * 8; + continue; + } - if (*bytes == 0xFF && pipExtent == MAX_ULONG) - pipExtent = (bytes - page->pip_bits) * 8; + if (*bytes == 0xFF && pipExtent == MAX_ULONG) + pipExtent = (bytes - page->pip_bits) * 8; - if (pipMin == MAX_ULONG) - { - UCHAR mask = 1; - for (int i = 0; i < 8; i++, mask <<= 1) + if (pipMin == MAX_ULONG) { - if (*bytes & mask) + UCHAR mask = 1; + for (int i = 0; i < 8; i++, mask <<= 1) { - pipMin = (bytes - page->pip_bits) * 8 + i; - break; + if (*bytes & mask) + { + pipMin = (bytes - page->pip_bits) * 8 + i; + break; + } } } - } - if (*bytes != 0xFF) - { - UCHAR mask = 0x80; - for (int i = 8; i > 0; i--, mask >>= 1) + if (*bytes != 0xFF) { - if ((*bytes & mask) == 0) + UCHAR mask = 0x80; + for (int i = 8; i > 0; i--, mask >>= 1) { - pipUsed = (bytes - page->pip_bits) * 8 + i; - break; + if ((*bytes & mask) == 0) + { + pipUsed = (bytes - page->pip_bits) * 8 + i; + break; + } } } } - } - - if (pipMin == MAX_ULONG) { - pipMin = pageSpaceMgr.pagesPerPIP; - } - if (pipExtent == MAX_ULONG) { - pipExtent = pageSpaceMgr.pagesPerPIP; - } - - bool fixme = false; - if (pipMin < page->pip_min) - { - corrupt(VAL_PIP_WRONG_MIN, NULL, page_number, sequence, page->pip_min, pipMin); - fixme = (vdr_flags & VDR_update); - } - - if (pipExtent < page->pip_extent) - { - corrupt(VAL_PIP_WRONG_EXTENT, NULL, page_number, sequence, page->pip_extent, pipExtent); - fixme = (vdr_flags & VDR_update); - } + if (pipMin == MAX_ULONG) { + pipMin = pageSpaceMgr.pagesPerPIP; + } - if (pipUsed > page->pip_used) - { - corrupt(VAL_PIP_WRONG_USED, NULL, page_number, sequence, page->pip_used, pipUsed); - fixme = (vdr_flags & VDR_update); - } + if (pipExtent == MAX_ULONG) { + pipExtent = pageSpaceMgr.pagesPerPIP; + } - if (fixme) - { - CCH_MARK(vdr_tdbb, &window); + bool fixme = false; if (pipMin < page->pip_min) { - page->pip_min = pipMin; - vdr_fixed++; + corrupt(VAL_PIP_WRONG_MIN, NULL, pageSpaceId, page_number, sequence, page->pip_min, pipMin); + fixme = (vdr_flags & VDR_update); } + if (pipExtent < page->pip_extent) { - page->pip_extent = pipExtent; - vdr_fixed++; + corrupt(VAL_PIP_WRONG_EXTENT, NULL, pageSpaceId, page_number, sequence, page->pip_extent, pipExtent); + fixme = (vdr_flags & VDR_update); } + if (pipUsed > page->pip_used) { - page->pip_used = pipUsed; - vdr_fixed++; + corrupt(VAL_PIP_WRONG_USED, NULL, pageSpaceId, page_number, sequence, page->pip_used, pipUsed); + fixme = (vdr_flags & VDR_update); } - } - const UCHAR byte = page->pip_bits[pageSpaceMgr.bytesBitPIP - 1]; - release_page(&window); - if (byte & 0x80) - break; + if (fixme) + { + CCH_MARK(vdr_tdbb, &window); + if (pipMin < page->pip_min) + { + page->pip_min = pipMin; + vdr_fixed++; + } + if (pipExtent < page->pip_extent) + { + page->pip_extent = pipExtent; + vdr_fixed++; + } + if (pipUsed > page->pip_used) + { + page->pip_used = pipUsed; + vdr_fixed++; + } + } + + const UCHAR byte = page->pip_bits[pageSpaceMgr.bytesBitPIP - 1]; + release_page(&window); + if (byte & 0x80) + break; + } } } @@ -2576,16 +2648,19 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) **************************************/ Database* dbb = vdr_tdbb->getDatabase(); - const vcl* vector = getPermanent(relation)->getBasePages()->rel_pages; + const auto relPages = relation->getBasePages(); - if (!vector || sequence >= vector->count()) - return corrupt(VAL_P_PAGE_LOST, relation, sequence); + pointer_page* page = nullptr; + const ULONG pageSpaceId = relPages->getPageSpaceId(); - pointer_page* page = 0; - WIN window(DB_PAGE_SPACE, -1); + WIN window(pageSpaceId); window.win_flags = WIN_garbage_collector; - fetch_page(true, (*vector)[sequence], pag_pointer, &window, &page); + auto ppNumber = relPages->getPointerPage(sequence); + if (!ppNumber) + return corrupt(VAL_P_PAGE_LOST, relation, sequence); + + fetch_page(true, ppNumber.value(), pag_pointer, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -2600,7 +2675,7 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) if (page->ppg_relation != relation->getId() || page->ppg_sequence != sequence) { release_page(&window); - return corrupt(VAL_P_PAGE_INCONSISTENT, relation, (*vector)[sequence], sequence); + return corrupt(VAL_P_PAGE_INCONSISTENT, relation, pageSpaceId, ppNumber.value().getPageNum(), sequence); } // Walk the data pages (someday we may optionally walk pages with "large objects" @@ -2629,7 +2704,7 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) if (releasePP) { - fetch_page(false, (*vector)[sequence], pag_pointer, &window, &page); + fetch_page(false, ppNumber.value(), pag_pointer, &window, &page); bits = (UCHAR*) (page->ppg_page + dbb->dbb_dp_per_pp); pages = &page->ppg_page[slot]; } @@ -2654,9 +2729,9 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) explain_pp_bits(pp_bits, s_pp); explain_pp_bits(new_pp_bits, s_dp); - corrupt(VAL_P_PAGE_WRONG_BITS, relation, + corrupt(VAL_P_PAGE_WRONG_BITS, relation, pageSpaceId, page->ppg_header.pag_pageno, sequence, pp_bits, s_pp.c_str(), - *pages, seq, new_pp_bits, s_dp.c_str()); + pageSpaceId, *pages, seq, new_pp_bits, s_dp.c_str()); if ((vdr_flags & VDR_update)) { @@ -2683,8 +2758,8 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) // Make sure the "next" pointer agrees with the pages relation - if (++sequence >= vector->count() || - (page->ppg_next && page->ppg_next != (*vector)[sequence])) + ppNumber = relPages->getPointerPage(++sequence); + if (!ppNumber || (page->ppg_next && page->ppg_next != ppNumber.value().getPageNum())) { release_page(&window); @@ -2693,20 +2768,16 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) // relation could be extended before we acquired its lock in PR mode // let's re-read pointer pages and check again - DPM_scan_pages(vdr_tdbb); + DPM_scan_pages(vdr_tdbb, relation->getPermanent(), relPages); - vector = getPermanent(relation)->getBasePages()->rel_pages; - - --sequence; - if (!vector || sequence >= vector->count()) { + ppNumber = relPages->getPointerPage(--sequence); + if (!ppNumber) return corrupt(VAL_P_PAGE_LOST, relation, sequence); - } - fetch_page(false, (*vector)[sequence], pag_pointer, &window, &page); + fetch_page(false, ppNumber.value(), pag_pointer, &window, &page); - ++sequence; - const bool error = (sequence >= vector->count()) || - (page->ppg_next && page->ppg_next != (*vector)[sequence]); + ppNumber = relPages->getPointerPage(++sequence); + const bool error = (!ppNumber || (page->ppg_next && page->ppg_next != ppNumber.value().getPageNum())); release_page(&window); @@ -2714,7 +2785,7 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence) return rtn_ok; } - return corrupt(VAL_P_PAGE_INCONSISTENT, relation, page->ppg_next, sequence); + return corrupt(VAL_P_PAGE_INCONSISTENT, relation, pageSpaceId, page->ppg_next, sequence); } release_page(&window); @@ -2831,14 +2902,16 @@ Validation::RTN Validation::walk_record(jrd_rel* relation, const rhd* header, US USHORT line_number = fragment->rhdf_f_line; USHORT flags = fragment->rhdf_flags; + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + data_page* page = 0; while (flags & rhd_incomplete) { - WIN window(DB_PAGE_SPACE, -1); + WIN window(pageSpaceId); window.win_flags = WIN_garbage_collector; - fetch_page(true, page_number, pag_data, &window, &page); - + fetch_page(true, PageNumber(pageSpaceId, page_number), pag_data, &window, &page); const data_page::dpg_repeat* line = &page->dpg_rpt[line_number]; if (page->dpg_relation != relation->getId() || @@ -2938,9 +3011,12 @@ void Validation::checkDPinPP(jrd_rel* relation, ULONG page_number) * Early in walk_chain we observed that this page in related to the relation so we skip such kind of check here. **************************************/ - WIN window(DB_PAGE_SPACE, page_number); + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + + WIN window(pageSpaceId, page_number); data_page* dpage; - fetch_page(false, page_number, pag_data, &window, &dpage); + fetch_page(false, window.win_page, pag_data, &window, &dpage); const ULONG sequence = dpage->dpg_sequence; const bool dpEmpty = (dpage->dpg_count == 0); release_page(&window); @@ -2950,51 +3026,58 @@ void Validation::checkDPinPP(jrd_rel* relation, ULONG page_number) Database* dbb = vdr_tdbb->getDatabase(); DECOMPOSE(sequence, dbb->dbb_dp_per_pp, pp_sequence, slot); - const vcl* vector = getPermanent(relation)->getBasePages()->rel_pages; pointer_page* ppage = 0; - if (pp_sequence < vector->count()) + if (pp_sequence < relPages->getPointerPageCount()) { - fetch_page(false, (*vector)[pp_sequence], pag_pointer, &window, &ppage); - if (slot >= ppage->ppg_count) + if (const auto ppNumber = relPages->getPointerPage(pp_sequence)) { - corrupt(VAL_DATA_PAGE_SLOT_NOT_FOUND, relation, page_number, window.win_page.getPageNum(), slot); - if ((vdr_flags & VDR_update) && slot < dbb->dbb_dp_per_pp) + fetch_page(false, ppNumber.value(), pag_pointer, &window, &ppage); + + if (slot >= ppage->ppg_count) { - CCH_MARK(vdr_tdbb, &window); - for (USHORT i = ppage->ppg_count; i < slot; i++) + corrupt(VAL_DATA_PAGE_SLOT_NOT_FOUND, relation, pageSpaceId, page_number, + window.win_page.getPageNum(), slot); + if ((vdr_flags & VDR_update) && slot < dbb->dbb_dp_per_pp) { - ppage->ppg_page[i] = 0; + CCH_MARK(vdr_tdbb, &window); + for (USHORT i = ppage->ppg_count; i < slot; i++) + { + ppage->ppg_page[i] = 0; - // Clear control fields + // Clear control fields + UCHAR* byte = &PPG_DP_BITS_BYTE((UCHAR*) &ppage->ppg_page[dbb->dbb_dp_per_pp], slot); + *byte = 0; + } + ppage->ppg_page[slot] = page_number; + ppage->ppg_count = slot + 1; + + // Restore control fields UCHAR* byte = &PPG_DP_BITS_BYTE((UCHAR*) &ppage->ppg_page[dbb->dbb_dp_per_pp], slot); - *byte = 0; + restoreFlags(byte, dpage->dpg_header.pag_flags, dpEmpty); + vdr_fixed++; } - ppage->ppg_page[slot] = page_number; - ppage->ppg_count = slot + 1; - - // Restore control fields - UCHAR* byte = &PPG_DP_BITS_BYTE((UCHAR*) &ppage->ppg_page[dbb->dbb_dp_per_pp], slot); - restoreFlags(byte, dpage->dpg_header.pag_flags, dpEmpty); - vdr_fixed++; } - } - else if (page_number != ppage->ppg_page[slot]) - { - corrupt(VAL_DATA_PAGE_SLOT_BAD_VAL, relation, page_number, window.win_page.getPageNum(), slot, ppage->ppg_page[slot]); - if ((vdr_flags & VDR_update) && !ppage->ppg_page[slot]) + else if (page_number != ppage->ppg_page[slot]) { - CCH_MARK(vdr_tdbb, &window); - ppage->ppg_page[slot] = page_number; + corrupt(VAL_DATA_PAGE_SLOT_BAD_VAL, relation, pageSpaceId, page_number, + window.win_page.getPageNum(), slot, ppage->ppg_page[slot]); + if ((vdr_flags & VDR_update) && !ppage->ppg_page[slot]) + { + CCH_MARK(vdr_tdbb, &window); + ppage->ppg_page[slot] = page_number; - // Restore control fields - UCHAR* byte = &PPG_DP_BITS_BYTE((UCHAR*) &ppage->ppg_page[dbb->dbb_dp_per_pp], slot); - restoreFlags(byte, dpage->dpg_header.pag_flags, dpEmpty); - vdr_fixed++; + // Restore control fields + UCHAR* byte = &PPG_DP_BITS_BYTE((UCHAR*) &ppage->ppg_page[dbb->dbb_dp_per_pp], slot); + restoreFlags(byte, dpage->dpg_header.pag_flags, dpEmpty); + vdr_fixed++; + } } } + else + corrupt(VAL_DATA_PAGE_HASNO_PP, relation, pageSpaceId, page_number, dpage->dpg_sequence); } else - corrupt(VAL_DATA_PAGE_HASNO_PP, relation, page_number, dpage->dpg_sequence); + corrupt(VAL_DATA_PAGE_HASNO_PP, relation, pageSpaceId, page_number, dpage->dpg_sequence); release_page(&window); } @@ -3010,20 +3093,24 @@ void Validation::checkDPinPIP(jrd_rel* relation, ULONG page_number) Database* dbb = vdr_tdbb->getDatabase(); + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + PageManager& pageMgr = dbb->dbb_page_manager; - PageSpace* pageSpace = pageMgr.findPageSpace(DB_PAGE_SPACE); + PageSpace* pageSpace = pageMgr.findPageSpace(pageSpaceId); fb_assert(pageSpace); const ULONG sequence = page_number / pageMgr.pagesPerPIP; const ULONG relative_bit = page_number % pageMgr.pagesPerPIP; - WIN pip_window(DB_PAGE_SPACE, (sequence == 0) ? pageSpace->pipFirst : sequence * pageMgr.pagesPerPIP - 1); + WIN pip_window(pageSpaceId, (sequence == 0) ? pageSpace->pipFirst : sequence * pageMgr.pagesPerPIP - 1); page_inv_page* pages; - fetch_page(false, pip_window.win_page.getPageNum(), pag_pages, &pip_window, &pages); + fetch_page(false, pip_window.win_page, pag_pages, &pip_window, &pages); if (pages->pip_bits[relative_bit >> 3] & (1 << (relative_bit & 7))) { - corrupt(VAL_DATA_PAGE_ISNT_IN_PIP, relation, page_number, pip_window.win_page.getPageNum(), relative_bit); + corrupt(VAL_DATA_PAGE_ISNT_IN_PIP, relation, pageSpaceId, page_number, + pip_window.win_page.getPageNum(), relative_bit); if (vdr_flags & VDR_update) { CCH_MARK(vdr_tdbb, &pip_window); @@ -3047,13 +3134,13 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation) * Walk all pages associated with a given relation. * **************************************/ + const auto relPerm = relation->getPermanent(); try { // skip deleted relations - if (getPermanent(relation)->isDropped()) { + if (relPerm->isDropped()) return rtn_ok; - } #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -3064,15 +3151,15 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation) // If it's a view, external file or virtual table, skip this - if (relation->isView() || relation->getExtFile() || relation->isVirtual()) { + if (relPerm->isView() || relPerm->getExtFile() || relPerm->isVirtual()) return rtn_ok; - } AutoLock lckRead(vdr_tdbb); - GCLock::Exclusive lckGC(vdr_tdbb, getPermanent(relation)); + GCLock::Exclusive lckGC(vdr_tdbb, relPerm); + if (vdr_flags & VDR_online) { - lckRead = getPermanent(relation)->createLock(vdr_tdbb, LCK_relation, false); + lckRead = relPerm->createLock(vdr_tdbb, LCK_relation, false); if (!LCK_lock(vdr_tdbb, lckRead, LCK_PR, vdr_lock_tout)) { output("Acquire relation lock failed\n"); @@ -3087,9 +3174,9 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation) return rtn_ok; } - WIN window(DB_PAGE_SPACE, -1); + WIN window(DB_PAGE_SPACE); header_page* page = NULL; - fetch_page(false, HEADER_PAGE, pag_header, &window, &page); + fetch_page(false, PageNumber(DB_PAGE_SPACE, HEADER_PAGE), pag_header, &window, &page); vdr_max_transaction = page->hdr_next_transaction; release_page(&window); } @@ -3105,7 +3192,12 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation) vdr_cond_idx.clear(); - const bool idxRootOk = (vdr_flags & VDR_records) && !relation->isSystem() ? + const auto relPages = relation->getBasePages(); + const ULONG pageSpaceId = relPages->getPageSpaceId(); + + DPM_scan_pages(vdr_tdbb, relPerm, relPages); + + const bool idxRootOk = (vdr_flags & VDR_records) && !relPerm->isSystem() ? walk_root(relation, true) == rtn_ok : true; // Walk pointer and selected data pages associated with relation @@ -3118,9 +3210,7 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation) for (ULONG sequence = 0; true; sequence++) { - const vcl* vector = getPermanent(relation)->getBasePages()->rel_pages; - const int ppCnt = vector ? vector->count() : 0; - + const int ppCnt = relPages->getPointerPageCount(); output(" process pointer page %4d of %4d\n", sequence, ppCnt); const RTN result = walk_pointer_page(relation, sequence); @@ -3227,18 +3317,20 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo) **************************************/ // If the relation has an index root, walk it - RelationPages* relPages = getPermanent(relation)->getBasePages(); + const auto relPages = relation->getBasePages(); + const auto rootPage = relPages->getIndexRootPage(); - if (!relPages->rel_index_root) + if (!rootPage) return corrupt(VAL_INDEX_ROOT_MISSING, relation); + const ULONG pageSpaceId = relPages->getPageSpaceId(); index_root_page* page = nullptr; - WIN window(DB_PAGE_SPACE, -1); - fetch_page(!getInfo, relPages->rel_index_root, pag_root, &window, &page); + WIN window(pageSpaceId); + fetch_page(!getInfo, rootPage.value(), pag_root, &window, &page); for (USHORT i = 0; i < page->irt_count; i++) { - if (!page->irt_rpt[i].getRoot()) + if (!page->irt_rpt[i].isUsed()) continue; release_page(&window); @@ -3247,7 +3339,7 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo) QualifiedName index; if (idx) index = idx->getName(); - fetch_page(false, relPages->rel_index_root, pag_root, &window, &page); + fetch_page(false, rootPage.value(), pag_root, &window, &page); if (vdr_sch_incl) { @@ -3331,8 +3423,8 @@ Validation::RTN Validation::walk_tip(TraNumber transaction) pageNumber = dbb->getKnownPage(pag_transactions, sequence); } - WIN window(DB_PAGE_SPACE, -1); - fetch_page(true, pageNumber, pag_transactions, &window, &page); + WIN window(DB_PAGE_SPACE); + fetch_page(true, PageNumber(DB_PAGE_SPACE, pageNumber), pag_transactions, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -3366,31 +3458,37 @@ Validation::RTN Validation::walk_scns() Database* dbb = vdr_tdbb->getDatabase(); PageManager& pageMgr = dbb->dbb_page_manager; - PageSpace* pageSpace = pageMgr.findPageSpace(DB_PAGE_SPACE); - const ULONG lastPage = pageSpace->lastUsedPage(); - const ULONG cntSCNs = lastPage / pageMgr.pagesPerSCN + 1; - - for (ULONG sequence = 0; sequence < cntSCNs; sequence++) + for (ULONG pageSpaceId = DB_PAGE_SPACE; pageSpaceId <= MAX_TABLESPACE_ID; pageSpaceId++) { - const ULONG scnPage = pageSpace->getSCNPageNum(sequence); - WIN scnWindow(pageSpace->pageSpaceID, scnPage); - scns_page* scns = NULL; - fetch_page(true, scnPage, pag_scns, &scnWindow, &scns); + PageSpace* pageSpace = pageMgr.findPageSpace(pageSpaceId); + if (!pageSpace) + continue; - if (scns->scn_sequence != sequence) + const ULONG lastPage = pageSpace->lastUsedPage(); + const ULONG cntSCNs = lastPage / pageMgr.pagesPerSCN + 1; + + for (ULONG sequence = 0; sequence < cntSCNs; sequence++) { - corrupt(VAL_SCNS_PAGE_INCONSISTENT, 0, scnPage, sequence); + const ULONG scnPage = pageSpace->getSCNPageNum(sequence); + WIN scnWindow(pageSpace->pageSpaceID, scnPage); + scns_page* scns = NULL; + fetch_page(true, PageNumber(pageSpaceId, scnPage), pag_scns, &scnWindow, &scns); - if (vdr_flags & VDR_update) + if (scns->scn_sequence != sequence) { - CCH_MARK(vdr_tdbb, &scnWindow); - scns->scn_sequence = sequence; - vdr_fixed++; + corrupt(VAL_SCNS_PAGE_INCONSISTENT, 0, pageSpaceId, scnPage, sequence); + + if (vdr_flags & VDR_update) + { + CCH_MARK(vdr_tdbb, &scnWindow); + scns->scn_sequence = sequence; + vdr_fixed++; + } } - } - release_page(&scnWindow); + release_page(&scnWindow); + } } return rtn_ok; diff --git a/src/jrd/validation.h b/src/jrd/validation.h index b1f42913d2f..6063209bd8e 100644 --- a/src/jrd/validation.h +++ b/src/jrd/validation.h @@ -132,7 +132,7 @@ class Validation VAL_DATA_PAGE_HASNO_PP = 38, VAL_DATA_PAGE_SEC_PRI = 39, - VAL_MAX_ERROR = 40 + VAL_MAX_ERROR }; struct MSG_ENTRY @@ -144,21 +144,32 @@ class Validation static const MSG_ENTRY vdr_msg_table[VAL_MAX_ERROR]; + struct PageSpaceInfo + { + PageBitmap* bitmap = nullptr; + ULONG maxPage = 0; + }; + + typedef Firebird::GenericMap > > PageSpaceMap; + thread_db* vdr_tdbb; - ULONG vdr_max_page; USHORT vdr_flags; int vdr_errors; int vdr_warns; int vdr_fixed; TraNumber vdr_max_transaction; + + // Note vdr_backversion_pages and vdr_chain_pages are reset check every relation + // so it's not necessary to keep page space but we can know it from relation context FB_UINT64 vdr_rel_backversion_counter; // Counts slots w/rhd_chain PageBitmap* vdr_backversion_pages; // 1 bit per visited table page FB_UINT64 vdr_rel_chain_counter; // Counts chains w/rdr_chain - PageBitmap* vdr_chain_pages; // 1 bit per visited record chain page + PageBitmap* vdr_chain_pages; // 1 bit per visited record chain page + RecordBitmap* vdr_rel_records; // 1 bit per valid record RecordBitmap* vdr_idx_records; // 1 bit per index item Firebird::Array vdr_cond_idx; // one entry per condition index for current relation - PageBitmap* vdr_page_bitmap; + PageSpaceMap vdr_page_spaces; // fetched pages and max page number in every tablespace ULONG vdr_err_counts[VAL_MAX_ERROR]; Firebird::UtilSvc* vdr_service; @@ -187,23 +198,23 @@ class Validation BufferDesc* bdb; int count; - static const ULONG generate(const UsedBdb& p) + static const PageNumber generate(const UsedBdb& p) { - return p.bdb ? p.bdb->bdb_page.getPageNum() : 0; + return p.bdb ? p.bdb->bdb_page : PageNumber(0, 0); } }; typedef Firebird::SortedArray< UsedBdb, Firebird::EmptyStorage, - ULONG, + PageNumber, UsedBdb> UsedBdbs; UsedBdbs vdr_used_bdbs; void cleanup(); RTN corrupt(int, const jrd_rel*, ...); - FETCH_CODE fetch_page(bool mark, ULONG, USHORT, WIN*, void*); + FETCH_CODE fetch_page(bool mark, PageNumber, USHORT, WIN*, void*); void release_page(WIN*); void garbage_collect(); diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index 06abceed0e6..26362c7d988 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -85,6 +85,7 @@ #include "../jrd/tpc_proto.h" #include "../jrd/tra_proto.h" #include "../jrd/vio_proto.h" +#include "../jrd/os/pio_proto.h" #include "../jrd/dyn_ut_proto.h" #include "../jrd/Function.h" #include "../common/StatusArg.h" @@ -411,9 +412,9 @@ class SweepTask : public Task bool SweepTask::handler(WorkItem& _item) { - Item* item = reinterpret_cast(&_item); + const auto item = reinterpret_cast(&_item); - ThreadContextHolder tdbb(NULL); + ThreadContextHolder tdbb; if (!item->init(tdbb)) { @@ -422,71 +423,73 @@ bool SweepTask::handler(WorkItem& _item) } WorkerContextHolder wrkHolder(tdbb, FB_FUNCTION); + const auto dbb = tdbb->getDatabase(); record_param rpb; - jrd_rel* relation = NULL; + Cached::Relation* relation = nullptr; try { RelInfo* relInfo = item->m_relInfo; - Database* dbb = tdbb->getDatabase(); + const auto relVersion = MetadataCache::getVersioned(tdbb, relInfo->rel_id, + CacheFlag::AUTOCREATE); + relation = getPermanent(relVersion); - relation = MetadataCache::getVersioned(tdbb, relInfo->rel_id, CacheFlag::AUTOCREATE); + if (!relation || relation->isDropped() || relation->isTemporary()) + return !m_stop; - if (relation && - !getPermanent(relation)->isDropped() && - !relation->isTemporary() && - relation->getPages(tdbb)->rel_pages) - { - GCLock::Shared gcGuard(tdbb, getPermanent(relation)); - if (!gcGuard.gcEnabled()) - { - string str; - str.printf("Acquire garbage collection lock failed (%s)", relation->getName().toQuotedString().c_str()); - status_exception::raise(Arg::Gds(isc_random) << Arg::Str(str)); - } + const auto relPages = relVersion->getPages(tdbb); + if (!relPages || !relPages->hasData()) + return !m_stop; - jrd_tra* tran = tdbb->getTransaction(); + GCLock::Shared gcGuard(tdbb, relation); + if (!gcGuard.gcEnabled()) + { + string str; + str.printf("Acquire garbage collection lock failed (%s)", relation->getName().toQuotedString().c_str()); + status_exception::raise(Arg::Gds(isc_random) << Arg::Str(str)); + } - if (relInfo->countPP == 0) - relInfo->countPP = relation->getPages(tdbb)->rel_pages->count(); + const auto tran = tdbb->getTransaction(); - rpb.rpb_relation = relation; - rpb.rpb_org_scans = getPermanent(relation)->rel_scan_count++; - rpb.rpb_record = NULL; - rpb.rpb_stream_flags = RPB_s_no_data | RPB_s_sweeper; - rpb.getWindow(tdbb).win_flags = WIN_large_scan; + if (relInfo->countPP == 0) + relInfo->countPP = relPages->getPointerPageCount(); - rpb.rpb_number.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, 0, 0, item->m_firstPP); - rpb.rpb_number.decrement(); + rpb.rpb_relation = relVersion; + rpb.rpb_org_scans = relation->rel_scan_count++; + rpb.rpb_record = NULL; + rpb.rpb_stream_flags = RPB_s_no_data | RPB_s_sweeper; + rpb.getWindow(tdbb).win_flags = WIN_large_scan; - RecordNumber lastRecNo; - lastRecNo.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, 0, 0, item->m_lastPP + 1); - lastRecNo.decrement(); + rpb.rpb_number.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, 0, 0, item->m_firstPP); + rpb.rpb_number.decrement(); - while (VIO_next_record(tdbb, &rpb, tran, NULL, DPM_next_pointer_page)) - { - CCH_RELEASE(tdbb, &rpb.getWindow(tdbb)); + RecordNumber lastRecNo; + lastRecNo.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, 0, 0, item->m_lastPP + 1); + lastRecNo.decrement(); - if (getPermanent(relation)->isDropped()) - break; + while (VIO_next_record(tdbb, &rpb, tran, NULL, DPM_next_pointer_page)) + { + CCH_RELEASE(tdbb, &rpb.getWindow(tdbb)); - if (rpb.rpb_number >= lastRecNo) - break; + if (relation->isDropped()) + break; - if (m_stop) - break; + if (rpb.rpb_number >= lastRecNo) + break; - JRD_reschedule(tdbb); + if (m_stop) + break; - tran->tra_oldest_active = dbb->dbb_oldest_snapshot; - } + JRD_reschedule(tdbb); - delete rpb.rpb_record; - --getPermanent(relation)->rel_scan_count; + tran->tra_oldest_active = dbb->dbb_oldest_snapshot; } + delete rpb.rpb_record; + --relation->rel_scan_count; + return !m_stop; } catch(const Exception& ex) @@ -494,12 +497,9 @@ bool SweepTask::handler(WorkItem& _item) ex.stuffException(tdbb->tdbb_status_vector); delete rpb.rpb_record; - if (relation) - { - if (getPermanent(relation)->rel_scan_count) { - --getPermanent(relation)->rel_scan_count; - } - } + + if (relation && relation->rel_scan_count) + --relation->rel_scan_count; } setError(tdbb->tdbb_status_vector, true); @@ -627,7 +627,7 @@ static bool assert_gc_enabled(const jrd_tra* transaction, const jrd_rel* relatio * in this case online validation is not run against given relation. * **************************************/ - switch (getPermanent(relation)->rel_gc_lock.isGCEnabled()) + switch (relation->getPermanent()->rel_gc_lock.isGCEnabled()) { case GCLock::State::enabled: return true; @@ -647,7 +647,8 @@ static bool assert_gc_enabled(const jrd_tra* transaction, const jrd_rel* relatio if (!lock) return false; - return (lock->lck_physical == LCK_SW) || (lock->lck_physical == LCK_EX); + // SW, PW, EX modes are incompatible with PR that's taken by the online validation + return (lock->lck_physical >= LCK_SW); } #endif //DEV_BUILD @@ -683,13 +684,15 @@ inline void check_gbak_cheating_delete(thread_db* tdbb, const jrd_rel* relation) if (tdbb->tdbb_flags & TDBB_dont_post_dfw) return; - // There are 2 tables whose contents gbak might delete: + // There are 3 tables whose contents gbak might delete: // - RDB$INDEX_SEGMENTS if it detects inconsistencies while restoring // - RDB$FILES if switch -k is set + // - RDB$TABLESPACES if errors occur while restoring tablespaces switch(relation->getId()) { case rel_segments: case rel_files: + case rel_tablespaces: return; // fix_plugins_schemas may also delete these objects: @@ -1270,7 +1273,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, ((tdbb->tdbb_flags & TDBB_sweeper) && state == tra_committed && rpb->rpb_b_page != 0 && rpb->rpb_transaction_nr >= oldest_snapshot))) { - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); int_gc_done = true; if (gcGuard.gcEnabled()) @@ -1382,7 +1385,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, case tra_precommitted: { // scope - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if ((attachment->att_flags & ATT_NO_CLEANUP) || !gcGuard.gcEnabled() || (rpb->rpb_flags & (rpb_chained | rpb_gc_active))) @@ -1638,7 +1641,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, { CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if (!gcGuard.gcEnabled()) return false; @@ -1686,7 +1689,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, } { // scope - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if (!gcGuard.gcEnabled()) return true; @@ -2368,6 +2371,14 @@ bool VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) DFW_post_work(transaction, dfw_delete_package_constant, &desc, &schemaDesc, 0, object_name.package); break; + case rel_tablespaces: + protect_system_table_delupd(tdbb, relation, "DELETE"); + EVL_field(0, rpb->rpb_record, f_ts_id, &desc); + id = MOV_get_long(tdbb, &desc, 0); + EVL_field(0, rpb->rpb_record, f_ts_file, &desc); + DFW_post_work(transaction, dfw_delete_tablespace, &desc, nullptr, id); + break; + default: // Shut up compiler warnings break; } @@ -2488,7 +2499,7 @@ bool VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) if (backVersion && !(tdbb->getAttachment()->att_flags & ATT_no_cleanup) && (dbb->dbb_flags & DBB_gc_cooperative)) { - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if (gcGuard.gcEnabled()) { temp = *rpb; @@ -2736,7 +2747,7 @@ void VIO_intermediate_gc(thread_db* tdbb, record_param* rpb, jrd_tra* transactio staying_chain_rpb.rpb_number = rpb->rpb_number; DPM_store(tdbb, &staying_chain_rpb, precedence_stack, DPM_secondary); - precedence_stack.push(PageNumber(relPages->rel_pg_space_id, staying_chain_rpb.rpb_page)); + precedence_stack.push(relPages->toPageNumber(staying_chain_rpb.rpb_page)); ++const_i; } @@ -2760,7 +2771,7 @@ void VIO_intermediate_gc(thread_db* tdbb, record_param* rpb, jrd_tra* transactio staying_chain_rpb.rpb_number = rpb->rpb_number; DPM_store(tdbb, &staying_chain_rpb, precedence_stack, DPM_secondary); - precedence_stack.push(PageNumber(relPages->rel_pg_space_id, staying_chain_rpb.rpb_page)); + precedence_stack.push(relPages->toPageNumber(staying_chain_rpb.rpb_page)); } // Read head version with write lock and check if it is still the same version @@ -2852,7 +2863,7 @@ bool VIO_garbage_collect(thread_db* tdbb, record_param* rpb, jrd_tra* transactio rpb->rpb_f_page, rpb->rpb_f_line); #endif - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if ((attachment->att_flags & ATT_no_cleanup) || !gcGuard.gcEnabled()) return true; @@ -3092,7 +3103,7 @@ bool VIO_get_current(thread_db* tdbb, // return !foreign_key; { - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if (!gcGuard.gcEnabled()) return !foreign_key; @@ -3190,7 +3201,7 @@ bool VIO_get_current(thread_db* tdbb, // return !foreign_key; { - GCLock::Shared gcGuard(tdbb, getPermanent(rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, rpb->rpb_relation->getPermanent()); if (!gcGuard.gcEnabled()) return !foreign_key; @@ -3650,30 +3661,33 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j EVL_field(0, new_rpb->rpb_record, f_idx_relation, &desc2); EVL_field(0, new_rpb->rpb_record, f_idx_id, &dscId); - MOV_get_metaname(tdbb, &schemaDesc, object_name.schema); - MOV_get_metaname(tdbb, &desc2, object_name.object); - auto* irel = MetadataCache::getPerm(tdbb, object_name, CacheFlag::AUTOCREATE); - fb_assert(irel); - int idxId = MOV_get_long(tdbb, &dscId, 0); - - if (EVL_field(0, new_rpb->rpb_record, f_idx_statistics, &desc2) && - MOV_get_double(tdbb, &desc2) < 0) + if (dfw_should_know(tdbb, org_rpb, new_rpb, f_idx_ts_name, true)) { - indexDfw(transaction, dfw_set_statistics, desc1, schemaDesc, irel, idxId); - } - else - { - bool nullFl = !EVL_field(0, new_rpb->rpb_record, f_idx_inactive, &desc2); - auto newStat = nullFl ? 0 : MOV_get_long(tdbb, &desc2, 0); - if (newStat == MET_index_deferred_drop) + MOV_get_metaname(tdbb, &schemaDesc, object_name.schema); + MOV_get_metaname(tdbb, &desc2, object_name.object); + auto* irel = MetadataCache::getPerm(tdbb, object_name, CacheFlag::AUTOCREATE); + fb_assert(irel); + const int idxId = MOV_get_long(tdbb, &dscId, 0); + + if (EVL_field(0, new_rpb->rpb_record, f_idx_statistics, &desc2) && + MOV_get_double(tdbb, &desc2) < 0) { - nullFl = !EVL_field(0, org_rpb->rpb_record, f_idx_inactive, &desc2); - auto oldStat = nullFl ? 0 : MOV_get_long(tdbb, &desc2, 0); - if (newStat != oldStat) - indexDfw(transaction, dfw_delete_index, desc1, schemaDesc, irel, idxId); + indexDfw(transaction, dfw_set_statistics, desc1, schemaDesc, irel, idxId); } else - indexDfw(transaction, dfw_create_index, desc1, schemaDesc, irel, idxId); + { + bool nullFl = !EVL_field(0, new_rpb->rpb_record, f_idx_inactive, &desc2); + auto newStat = nullFl ? 0 : MOV_get_long(tdbb, &desc2, 0); + if (newStat == MET_index_deferred_drop) + { + nullFl = !EVL_field(0, org_rpb->rpb_record, f_idx_inactive, &desc2); + auto oldStat = nullFl ? 0 : MOV_get_long(tdbb, &desc2, 0); + if (newStat != oldStat) + indexDfw(transaction, dfw_delete_index, desc1, schemaDesc, irel, idxId); + } + else + indexDfw(transaction, dfw_create_index, desc1, schemaDesc, irel, idxId); + } } } break; @@ -3778,6 +3792,20 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j DFW_post_work(transaction, dfw_modify_package_constant, &desc1, &schemaDesc, 0, object_name.package); break; + case rel_tablespaces: + protect_system_table_delupd(tdbb, relation, "UPDATE"); + check_class(tdbb, transaction, org_rpb, new_rpb, f_ts_class); + check_owner(tdbb, transaction, org_rpb, new_rpb, f_ts_owner); + + if (dfw_should_know(tdbb, org_rpb, new_rpb, f_ts_desc, true)) + { + EVL_field(0, new_rpb->rpb_record, f_ts_id, &desc1); + const ULONG id = MOV_get_long(tdbb, &desc1, 0); + EVL_field(0, new_rpb->rpb_record, f_ts_file, &desc1); + DFW_post_work(transaction, dfw_modify_tablespace, &desc1, nullptr, id); + } + break; + default: break; } @@ -3873,7 +3901,7 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j if (backVersion && !(tdbb->getAttachment()->att_flags & ATT_no_cleanup) && (dbb->dbb_flags & DBB_gc_cooperative)) { - GCLock::Shared gcGuard(tdbb, getPermanent(org_rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, org_rpb->rpb_relation->getPermanent()); if (gcGuard.gcEnabled()) { temp.rpb_number = org_rpb->rpb_number; @@ -4726,6 +4754,20 @@ void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) DFW_post_work(transaction, dfw_create_package_constant, &desc, &schemaDesc, 0, object_name.package); break; + case rel_tablespaces: + { + protect_system_table_insert(tdbb, request, relation); + EVL_field(0, rpb->rpb_record, f_ts_name, &desc); + object_id = set_metadata_id(tdbb, rpb->rpb_record, + f_ts_id, drq_g_nxt_ts_id, "RDB$TABLESPACES"); + DFW_post_work(transaction, dfw_create_tablespace, &desc, nullptr, object_id); + set_system_flag(tdbb, rpb->rpb_record, f_ts_sys_flag); + set_owner_name(tdbb, rpb->rpb_record, f_ts_owner); + if (set_security_class(tdbb, rpb->rpb_record, f_ts_class)) + DFW_post_work(transaction, dfw_grant, &desc, nullptr, obj_tablespace); + break; + } + default: // Shut up compiler warnings break; } @@ -4833,8 +4875,6 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee // hvlad: restore tdbb->transaction since it can be used later tdbb->setTransaction(transaction); - jrd_rel* relation; - record_param rpb; rpb.rpb_record = NULL; rpb.rpb_stream_flags = RPB_s_no_data | RPB_s_sweeper; @@ -4843,52 +4883,56 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee GarbageCollector* gc = dbb->dbb_garbage_collector; bool ret = true; - try { + Cached::Relation* relation = nullptr; + + try + { MetadataCache* mdc = MetadataCache::get(tdbb); for (FB_SIZE_T i = 1; i < mdc->relCount(); i++) { - relation = MetadataCache::getVersioned(tdbb, i, CacheFlag::AUTOCREATE); + const auto relVersion = MetadataCache::getVersioned(tdbb, i, CacheFlag::AUTOCREATE); + relation = getPermanent(relVersion); - if (relation && - !(relation->getPermanent()->isDropped()) && - !relation->isTemporary() && - relation->getPermanent()->getPages(tdbb)->rel_pages) - { - GCLock::Shared gcGuard(tdbb, getPermanent(relation)); - if (!gcGuard.gcEnabled()) - { - ret = false; - break; - } + if (!relation || relation->isDropped() || relation->isTemporary()) + continue; - rpb.rpb_relation = relation; - rpb.rpb_number.setValue(BOF_NUMBER); - rpb.rpb_org_scans = relation->getPermanent()->rel_scan_count++; + const auto relPages = relVersion->getPages(tdbb); + if (!relPages || !relPages->hasData()) + continue; - traceSweep->beginSweepRelation(relation); + GCLock::Shared gcGuard(tdbb, relation); + if (!gcGuard.gcEnabled()) + { + ret = false; + break; + } - if (gc) { - gc->sweptRelation(transaction->tra_oldest_active, relation->getId()); - } + rpb.rpb_relation = relVersion; + rpb.rpb_number.setValue(BOF_NUMBER); + rpb.rpb_org_scans = relation->rel_scan_count++; - while (VIO_next_record(tdbb, &rpb, transaction, 0, DPM_next_all)) - { - CCH_RELEASE(tdbb, &rpb.getWindow(tdbb)); + traceSweep->beginSweepRelation(relation); - if (relation->getPermanent()->isDropped()) - break; + if (gc) + gc->sweptRelation(transaction->tra_oldest_active, relation->getId()); - JRD_reschedule(tdbb); + while (VIO_next_record(tdbb, &rpb, transaction, 0, DPM_next_all)) + { + CCH_RELEASE(tdbb, &rpb.getWindow(tdbb)); - transaction->tra_oldest_active = dbb->dbb_oldest_snapshot; - if (TipCache* cache = dbb->dbb_tip_cache) - cache->updateActiveSnapshots(tdbb, &attachment->att_active_snapshots); - } + if (relation->isDropped()) + break; - traceSweep->endSweepRelation(); + JRD_reschedule(tdbb); - relation->getPermanent()->rel_scan_count--; + transaction->tra_oldest_active = dbb->dbb_oldest_snapshot; + if (TipCache* cache = dbb->dbb_tip_cache) + cache->updateActiveSnapshots(tdbb, &attachment->att_active_snapshots); } + + traceSweep->endSweepRelation(); + + relation->rel_scan_count--; } delete rpb.rpb_record; @@ -4898,11 +4942,8 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee { delete rpb.rpb_record; - if (relation) - { - if (getPermanent(relation)->rel_scan_count) - --getPermanent(relation)->rel_scan_count; - } + if (relation && relation->rel_scan_count) + --relation->rel_scan_count; ERR_punt(); } @@ -5080,7 +5121,7 @@ WriteLockResult VIO_writelock(thread_db* tdbb, record_param* org_rpb, jrd_tra* t if (backVersion && !(tdbb->getAttachment()->att_flags & ATT_no_cleanup) && (dbb->dbb_flags & DBB_gc_cooperative)) { - GCLock::Shared gcGuard(tdbb, getPermanent(org_rpb->rpb_relation)); + GCLock::Shared gcGuard(tdbb, org_rpb->rpb_relation->getPermanent()); if (gcGuard.gcEnabled()) { temp.rpb_number = org_rpb->rpb_number; @@ -5792,7 +5833,9 @@ void Database::garbage_collector(Database* dbb) (gc_bitmap = gc->getPages(dbb->dbb_oldest_snapshot, relID))) { relation = MetadataCache::getVersioned(tdbb, relID, CacheFlag::AUTOCREATE); - if (!relation || getPermanent(relation)->isDropped()) + const auto relPerm = getPermanent(relation); + + if (!relation || relPerm->isDropped()) { delete gc_bitmap; gc_bitmap = NULL; @@ -5801,7 +5844,7 @@ void Database::garbage_collector(Database* dbb) if (gc_bitmap) { - GCLock::Shared gcGuard(tdbb, getPermanent(relation)); + GCLock::Shared gcGuard(tdbb, relPerm); if (!gcGuard.gcEnabled()) continue; @@ -5851,13 +5894,13 @@ void Database::garbage_collector(Database* dbb) break; } - if (getPermanent(relation)->isDropped()) + if (relPerm->isDropped()) { rel_exit = true; break; } - if (getPermanent(relation)->rel_gc_lock.checkDisabled()) + if (relPerm->rel_gc_lock.checkDisabled()) { rel_exit = true; break; @@ -6816,7 +6859,7 @@ static PrepareResult prepare_update(thread_db* tdbb, jrd_tra* transaction, TraNu } { - const USHORT pageSpaceID = temp->getWindow(tdbb).win_page.getPageSpaceID(); + const ULONG pageSpaceID = temp->getWindow(tdbb).win_page.getPageSpaceID(); stack.push(PageNumber(pageSpaceID, temp->rpb_page)); } return PrepareResult::SUCCESS; @@ -7123,31 +7166,30 @@ static void refresh_changed_fields(thread_db* tdbb, Record* old_rec, record_para const Database* dbb = tdbb->getDatabase(); const auto allowOverwrite = dbb->dbb_config->getAllowUpdateOverwrite(); - jrd_rel* relation = cur_rpb->rpb_relation; - auto* relPerm = relation->getPermanent(); + jrd_rel* const relation = cur_rpb->rpb_relation; + const auto relPerm = relation->getPermanent(); relPerm->scanPartners(tdbb); // Collect all fields of self-referenced foreign keys - SortedArray > fields; + SortedArray > fields; if (const auto* frgn = relPerm->rel_foreign_refs) { - RelationPages* relPages = relation->getPages(tdbb); for (auto& dep : *frgn) { - if (dep.dep_relation == relPerm->getId()) + if (dep.dep_relation == relation->getId()) { index_desc idx; idx.idx_id = idx_invalid; - if (BTR_lookup(tdbb, relPerm, dep.dep_reference_id, &idx, relPages)) + if (BTR_lookup(tdbb, relation, dep.dep_reference_id, &idx)) { fb_assert(idx.idx_flags & idx_foreign); - for (int fld = 0; fld < idx.idx_count; fld++) + for (USHORT fld = 0; fld < idx.idx_count; fld++) { - const int fldNum = idx.idx_rpt[fld].idx_field; + const USHORT fldNum = idx.idx_rpt[fld].idx_field; if (!fields.exist(fldNum)) fields.add(fldNum); } @@ -7171,13 +7213,14 @@ static void refresh_changed_fields(thread_db* tdbb, Record* old_rec, record_para // Else compare field-by-field } - for (FB_SIZE_T fld = 0, frn = 0; fld < relation->rel_current_format->fmt_count; fld++) + FB_SIZE_T frn = 0; + for (USHORT fld = 0; fld < relation->rel_current_format->fmt_count; fld++) { dsc dsc_old; const bool flag_old = EVL_field(relation, old_rec, fld, &dsc_old); - const bool is_fk = (frn < fields.getCount() && fields[frn] == fld); - if (!is_fk) + const bool isFK = (frn < fields.getCount() && fields[frn] == fld); + if (!isFK) { if (allowOverwrite) continue; @@ -7429,8 +7472,11 @@ void VIO_update_in_place(thread_db* tdbb, temp2.rpb_number = org_rpb->rpb_number; DPM_store(tdbb, &temp2, *stack, DPM_secondary); - const USHORT pageSpaceID = temp2.getWindow(tdbb).win_page.getPageSpaceID(); - stack->push(PageNumber(pageSpaceID, temp2.rpb_page)); + if (stack) + { + const ULONG pageSpaceID = temp2.getWindow(tdbb).win_page.getPageSpaceID(); + stack->push(PageNumber(pageSpaceID, temp2.rpb_page)); + } } if (!DPM_get(tdbb, org_rpb, LCK_write)) diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 94a4e43bfea..8338bec422d 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=2210 +BuildNum=2401 NowAt=`pwd` cd `dirname $0` diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp index d6e54a9a141..01ceaa869ec 100644 --- a/src/utilities/gstat/dba.epp +++ b/src/utilities/gstat/dba.epp @@ -41,6 +41,7 @@ #include "ibase.h" #include "../jrd/ods.h" #include "../jrd/btn.h" +#include "../jrd/pag.h" #include "../jrd/license.h" #include "../common/msg_encode.h" #include "../common/gdsassert.h" @@ -100,6 +101,7 @@ struct dba_idx { dba_idx* idx_next; SSHORT idx_id; + ULONG idx_pagespace_id; ULONG idx_root; SSHORT idx_depth; ULONG idx_leaf_buckets; @@ -151,6 +153,7 @@ struct dba_rel dba_rel* rel_next; dba_idx* rel_indexes; dba_fmt* rel_formats; + ULONG rel_pagespace_id; ULONG rel_index_root; ULONG rel_pointer_page; ULONG rel_slots; @@ -211,13 +214,9 @@ static ULONG lastUsedPage(ULONG); static USHORT get_format_length(ISC_STATUS*, isc_db_handle, isc_tr_handle, ISC_QUAD&); -static dba_fil* db_open(const char*, USHORT); -static const pag* db_read(ULONG page_number, bool ok_enc = false); -#ifdef WIN_NT -static void db_close(void* file_desc); -#else -static void db_close(int); -#endif +static void db_open(ULONG pageSpaceId, const MetaString& name, const PathName& fileName); +static const pag* db_read(const PageNumber& page_number, bool ok_enc = false); +static void db_close(dba_fil* file); [[noreturn]] static void dba_error(USHORT, const SafeArg& arg = SafeArg()); static void dba_print(bool, USHORT, const SafeArg& arg = SafeArg()); static void print_distribution(const SCHAR*, const ULONG*); @@ -239,19 +238,26 @@ struct dba_mem dba_mem* mem_next; }; +struct TableSpace +{ + ULONG id = INVALID_PAGE_SPACE; + MetaString name; + dba_fil* file = nullptr; +}; + // threading declarations for thread data class tdba final : public ThreadData { public: explicit tdba(Firebird::UtilSvc* us) - : ThreadData(tddDBA), uSvc(us) + : ThreadData(tddDBA), uSvc(us), tablespaces(us->getPool()) { dba_status = dba_status_vector; } Firebird::UtilSvc* uSvc; - dba_fil* file = nullptr; + Firebird::Array tablespaces; dba_rel* relations = nullptr; dba_schema* schemas = nullptr; USHORT page_size = 0; @@ -375,6 +381,7 @@ namespace error: missing thread-safe version of ctime() #endif } + } // namespace constexpr USHORT GSTAT_MSG_FAC = FB_IMPL_MSG_FACILITY_GSTAT; @@ -705,13 +712,13 @@ int gstat(Firebird::UtilSvc* uSvc) expandDatabaseName(fileName, tempStr, NULL); fileName = tempStr; - db_open(fileName.c_str(), fileName.length()); + db_open(DB_PAGE_SPACE, PRIMARY_TABLESPACE_NAME, fileName); alignas(DIRECT_IO_BLOCK_SIZE) SCHAR temp[MAX(RAW_HEADER_SIZE, DIRECT_IO_BLOCK_SIZE)]; tddba->page_size = MAX(RAW_HEADER_SIZE, DIRECT_IO_BLOCK_SIZE); tddba->global_buffer = (pag*) temp; tddba->page_number = -1; - const header_page* header = (const header_page*) db_read(HEADER_PAGE); + const header_page* header = (const header_page*) db_read(HEADER_PAGE_NUMBER); if (!Ods::isSupported(header)) { @@ -746,8 +753,7 @@ int gstat(Firebird::UtilSvc* uSvc) // Print header page - ULONG page = HEADER_PAGE; - header = (const header_page*) db_read(page); + header = (const header_page*) db_read(HEADER_PAGE_NUMBER); PPG_print_header(header, sw_nocreation, uSvc); if (sw_header) @@ -788,9 +794,9 @@ int gstat(Firebird::UtilSvc* uSvc) Statist data, index, blob, generator, other; const ULONG last = lastUsedPage(header->hdr_page_size); - for (page = 0; page <= last; ++page) + for (ULONG page = 0; page <= last; ++page) { - const pag* p = db_read(page, true); + const pag* p = db_read(PageNumber(DB_PAGE_SPACE, page), true); if (!p) { break; @@ -852,6 +858,8 @@ int gstat(Firebird::UtilSvc* uSvc) isc_req_handle request2 = 0; isc_req_handle request3 = 0; isc_req_handle request4 = 0; + isc_req_handle request5 = 0; + isc_req_handle request6 = 0; FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request1) X IN RDB$RELATIONS @@ -927,12 +935,24 @@ int gstat(Firebird::UtilSvc* uSvc) Y IN RDB$PAGES WITH Y.RDB$RELATION_ID EQ relation->rel_id AND Y.RDB$PAGE_SEQUENCE EQ 0 { - if (Y.RDB$PAGE_TYPE == pag_pointer) { + if (Y.RDB$PAGE_TYPE == pag_pointer) relation->rel_pointer_page = Y.RDB$PAGE_NUMBER; - } - if (Y.RDB$PAGE_TYPE == pag_root) { + + if (Y.RDB$PAGE_TYPE == pag_root) relation->rel_index_root = Y.RDB$PAGE_NUMBER; - } + } + END_FOR + ON_ERROR + dba_exit(FINI_ERROR, tddba); + END_ERROR + + relation->rel_pagespace_id = DB_PAGE_SPACE; + + FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request5) + TS IN RDB$TABLESPACES + WITH TS.RDB$TABLESPACE_NAME = X.RDB$TABLESPACE_NAME + { + relation->rel_pagespace_id = TS.RDB$TABLESPACE_ID; } END_FOR ON_ERROR @@ -949,6 +969,7 @@ int gstat(Firebird::UtilSvc* uSvc) { if (Y.RDB$INDEX_INACTIVE) continue; + dba_idx* index = (dba_idx*) alloc(sizeof(struct dba_idx)); index->idx_next = relation->rel_indexes; relation->rel_indexes = index; @@ -991,18 +1012,47 @@ int gstat(Firebird::UtilSvc* uSvc) dba_exit(FINI_ERROR, tddba); END_ERROR - if (request1) { - isc_release_request(status_vector, &request1); + FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request6) + TS IN RDB$TABLESPACES + { + const char* const tsName = fb_utils::exact_name(TS.RDB$TABLESPACE_NAME); + const char* const fileName = fb_utils::exact_name(TS.RDB$FILE_NAME); + db_open(TS.RDB$TABLESPACE_ID, tsName, fileName); } - if (request2) { + END_FOR + ON_ERROR + dba_exit(FINI_ERROR, tddba); + END_ERROR + + auto getTablespaceName = [&tddba](ULONG id) -> const char* + { + for (const auto& tablespace : tddba->tablespaces) + { + if (tablespace.id == id) + return tablespace.name.c_str(); + } + + fb_assert(false); + return ""; + }; + + if (request1) + isc_release_request(status_vector, &request1); + + if (request2) isc_release_request(status_vector, &request2); - } - if (request3) { + + if (request3) isc_release_request(status_vector, &request3); - } - if (request4) { + + if (request4) isc_release_request(status_vector, &request4); - } + + if (request5) + isc_release_request(status_vector, &request5); + + if (request6) + isc_release_request(status_vector, &request6); COMMIT transact1; ON_ERROR @@ -1076,6 +1126,8 @@ int gstat(Firebird::UtilSvc* uSvc) if (sw_data) { + uSvc->printf(false, " Tablespace: %s\n", getTablespaceName(relation->rel_pagespace_id)); + dba_print(false, 11, SafeArg() << relation->rel_pointer_page << relation->rel_index_root); // msg 11: " Primary pointer page: %ld, Index root page: %ld" @@ -1184,6 +1236,7 @@ int gstat(Firebird::UtilSvc* uSvc) QualifiedMetaString(index->idx_name, relation->rel_schema).toQuotedString().c_str() << index->idx_id); // msg 14: " Index %s (%d)" + uSvc->printf(false, "\tTablespace: %s\n", getTablespaceName(index->idx_pagespace_id)); uSvc->printf(false, "\tRoot page: %d, depth: %d, leaf buckets: %ld, nodes: %" UQUADFORMAT "\n", index->idx_root, index->idx_depth, index->idx_leaf_buckets, index->idx_nodes); @@ -1271,8 +1324,11 @@ int gstat(Firebird::UtilSvc* uSvc) } // close file - if (tddba->file) - db_close(tddba->file->fil_desc); + while (tddba->tablespaces.hasData()) + { + const auto tablespace = tddba->tablespaces.pop(); + db_close(tablespace.file); + } // free linked lists while (tddba->head_of_mem_list != 0) @@ -1306,7 +1362,7 @@ static ULONG lastUsedPage(ULONG pageSize) while (true) { - const pag* page = db_read(pipLast, true); + const pag* page = db_read(PageNumber(DB_PAGE_SPACE, pipLast), true); if (page->pag_type != pag_pages) { dba_print(true, 61, SafeArg() << pipLast); @@ -1411,15 +1467,18 @@ static void analyze_data( dba_rel* relation, bool sw_record) for (ULONG next_pp = relation->rel_pointer_page; next_pp; next_pp = ptr_page->ppg_next) { + PageNumber pageNum(relation->rel_pagespace_id, next_pp); + ++relation->rel_pointer_pages; - memcpy(ptr_page, (const SCHAR*) db_read(next_pp), tddba->page_size); + memcpy(ptr_page, (const SCHAR*) db_read(pageNum), tddba->page_size); const ULONG* ptr = ptr_page->ppg_page; for (const ULONG* const end = ptr + ptr_page->ppg_count; ptr < end; ptr++) { ++relation->rel_slots; if (*ptr) { - if (!analyze_data_page(relation, (const data_page*) db_read(*ptr), sw_record)) + pageNum = *ptr; + if (!analyze_data_page(relation, (const data_page*) db_read(pageNum), sw_record)) { dba_print(false, 18, SafeArg() << *ptr); // msg 18: " Expected data on page %ld" @@ -1557,7 +1616,8 @@ static void analyze_blob(dba_rel* relation, const blh* blob, int length) for (unsigned i = 0; i < slots; i++) { - const auto bpage = (const blob_page*) db_read(pages[i]); + const PageNumber pageNum(relation->rel_pagespace_id, pages[i]); + const auto bpage = (const blob_page*) db_read(pageNum); blobPages += bpage->blp_length / sizeof(ULONG); } } @@ -1587,7 +1647,8 @@ static ULONG analyze_fragments(dba_rel* relation, const rhdf* header) { const ULONG f_page = header->rhdf_f_page; const USHORT f_line = header->rhdf_f_line; - const data_page* page = (const data_page*) db_read(f_page); + const PageNumber pageNum(relation->rel_pagespace_id, f_page); + const data_page* page = (const data_page*) db_read(pageNum); if (page->dpg_header.pag_type != pag_data || page->dpg_relation != relation->rel_id || page->dpg_count <= f_line) { @@ -1631,19 +1692,23 @@ static void analyze_index( const dba_rel* relation, dba_idx* index) **************************************/ const tdba* tddba = tdba::getSpecific(); - const index_root_page* index_root = (const index_root_page*) db_read(relation->rel_index_root); + PageNumber pageNum(relation->rel_pagespace_id, relation->rel_index_root); + const index_root_page* index_root = (const index_root_page*) db_read(pageNum); if (index_root->irt_count <= index->idx_id) return; - ULONG page = index_root->irt_rpt[index->idx_id].getRoot(); - if (!page) + const auto rootPageNumber = index_root->irt_rpt[index->idx_id].getRootPage(); + if (!rootPageNumber) return; + pageNum = rootPageNumber.value(); + // CVC: The two const_cast's for bucket can go away if BTreeNode's functions // are overloaded for constness. They don't modify bucket and pointer's contents. - const btree_page* bucket = (const btree_page*) db_read(page); - index->idx_root = page; + const btree_page* bucket = (const btree_page*) db_read(pageNum); + index->idx_pagespace_id = pageNum.getPageSpaceID(); + index->idx_root = pageNum.getPageNum(); index->idx_depth = bucket->btr_level + 1; UCHAR* pointer; @@ -1652,7 +1717,8 @@ static void analyze_index( const dba_rel* relation, dba_idx* index) { pointer = const_cast(bucket->btr_nodes) + bucket->btr_jump_size; node.readNode(pointer, false); - bucket = (const btree_page*) db_read(node.pageNumber); + pageNum = node.pageNumber; + bucket = (const btree_page*) db_read(pageNum); } bool firstLeafNode = true; @@ -1674,9 +1740,8 @@ static void analyze_index( const dba_rel* relation, dba_idx* index) { pointer = node.readNode(pointer, true); - if (node.isEndBucket || node.isEndLevel) { + if (node.isEndBucket || node.isEndLevel) break; - } ++index->idx_nodes; index->idx_total_length += pointer - node.nodePointer; @@ -1725,39 +1790,37 @@ static void analyze_index( const dba_rel* relation, dba_idx* index) } else { - if (duplicates > index->idx_max_duplicates) { + if (duplicates > index->idx_max_duplicates) index->idx_max_duplicates = duplicates; - } + duplicates = 0; } key_length = l; - if (node.length) { + if (node.length) memcpy(key + node.prefix, node.data, node.length); - } } - if (duplicates > index->idx_max_duplicates) { + if (duplicates > index->idx_max_duplicates) index->idx_max_duplicates = duplicates; - } const USHORT header = (USHORT)(firstNode - (UCHAR*) bucket); const USHORT space = bucket->btr_length - header; USHORT n = (space * BUCKETS) / (tddba->page_size - header); - if (n == BUCKETS) { + if (n == BUCKETS) --n; - } + ++index->idx_fill_distribution[n]; - if (node.isEndLevel) { + if (node.isEndLevel) break; - } - number = page; - page = bucket->btr_sibling; - bucket = (const btree_page*) db_read(page); + + number = pageNum.getPageNum(); + pageNum = bucket->btr_sibling; + bucket = (const btree_page*) db_read(pageNum); if (bucket->btr_header.pag_type != pag_index) { - dba_print(false, 19, SafeArg() << page << number); + dba_print(false, 19, SafeArg() << pageNum.getPageNum() << number); // mag 19: " Expected b-tree bucket on page %ld from %ld" break; } @@ -1784,7 +1847,8 @@ static ULONG analyze_versions( dba_rel* relation, const rhdf* header) while (b_page) { - const data_page* page = (const data_page*) db_read(b_page); + const PageNumber pageNum(relation->rel_pagespace_id, b_page); + const data_page* page = (const data_page*) db_read(pageNum); if (page->dpg_header.pag_type != pag_data || page->dpg_relation != relation->rel_id || page->dpg_count <= b_line) { @@ -1853,7 +1917,7 @@ static USHORT get_format_length(ISC_STATUS* status_vector, isc_db_handle databas #ifdef WIN_NT -static void db_close(void* file_desc) +static void db_close(dba_fil* file) { /************************************** * @@ -1865,7 +1929,7 @@ static void db_close(void* file_desc) * Close an open file * **************************************/ - CloseHandle(file_desc); + CloseHandle(file->fil_desc); } [[noreturn]] static void db_error( SLONG status) @@ -1910,10 +1974,7 @@ static void db_close(void* file_desc) } -// CVC: This function was using cast to char* for the first param always -// and the callers had to cast their char*'s to UCHAR*, too. Since the -// real parameter is char* and always the usage is char*, I changed signature. -static dba_fil* db_open(const char* file_name, USHORT file_length) +static void db_open(ULONG pageSpaceId, const MetaString& name, const PathName& fileName) { /************************************** * @@ -1927,12 +1988,22 @@ static dba_fil* db_open(const char* file_name, USHORT file_length) **************************************/ tdba* tddba = tdba::getSpecific(); + for (const auto& tablespace : tddba->tablespaces) + { + if (tablespace.id == pageSpaceId) + { + fb_assert(tablespace.file); + return; + } + } + + const auto file_length = fileName.length(); const auto file = (dba_fil*) alloc(sizeof(dba_fil) + file_length + 1); - strcpy(file->fil_string, file_name); + strcpy(file->fil_string, fileName.c_str()); file->fil_length = file_length; - file->fil_desc = CreateFile(file_name, + file->fil_desc = CreateFile(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, @@ -1942,17 +2013,16 @@ static dba_fil* db_open(const char* file_name, USHORT file_length) if (file->fil_desc == INVALID_HANDLE_VALUE) { - tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 29, SafeArg() << file_name); + tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 29, SafeArg() << fileName.c_str()); // msg 29: Can't open database file %s db_error(GetLastError()); } - tddba->file = file; - return file; + tddba->tablespaces.add({pageSpaceId, name, file}); } -static const pag* db_read( ULONG page_number, bool ok_enc) +static const pag* db_read(const PageNumber& page_number, bool ok_enc) { /************************************** * @@ -1969,14 +2039,28 @@ static const pag* db_read( ULONG page_number, bool ok_enc) if (tddba->uSvc->finished()) dba_exit(FINI_OK, tddba); - if (tddba->page_number == page_number) + ULONG pageNum = page_number.getPageNum(); + + if (tddba->page_number == pageNum) return tddba->global_buffer; - tddba->page_number = page_number; + tddba->page_number = pageNum; + dba_fil* file = nullptr; + + for (const auto& tablespace : tddba->tablespaces) + { + if (tablespace.id == page_number.getPageSpaceID()) + { + file = tablespace.file; + break; + } + } + + fb_assert(file); LARGE_INTEGER liOffset; - liOffset.QuadPart = UInt32x32To64((DWORD) page_number, (DWORD) tddba->page_size); - if (SetFilePointer(tddba->file->fil_desc, (LONG) liOffset.LowPart, &liOffset.HighPart, FILE_BEGIN) == + liOffset.QuadPart = UInt32x32To64((DWORD) pageNum, (DWORD) tddba->page_size); + if (SetFilePointer(file->fil_desc, (LONG) liOffset.LowPart, &liOffset.HighPart, FILE_BEGIN) == (DWORD) -1) { const int lastError = GetLastError(); @@ -1989,7 +2073,7 @@ static const pag* db_read( ULONG page_number, bool ok_enc) } SLONG actual_length; - if (!ReadFile( tddba->file->fil_desc, + if (!ReadFile( file->fil_desc, tddba->global_buffer, tddba->page_size, reinterpret_cast(&actual_length), @@ -2020,7 +2104,7 @@ static const pag* db_read( ULONG page_number, bool ok_enc) #ifndef WIN_NT -static void db_close( int file_desc) +static void db_close(dba_fil* file) { /************************************** * @@ -2032,7 +2116,7 @@ static void db_close( int file_desc) * Close an open file * **************************************/ - close(file_desc); + close(file->fil_desc); } [[noreturn]] static void db_error( int status) @@ -2060,10 +2144,7 @@ static void db_close( int file_desc) } -// CVC: This function was using cast to char* for the first param always -// and the callers had to cast their char*'s to UCHAR*, too. Since the -// real parameter is char* and always the usage is char*, I changed signature. -static dba_fil* db_open(const char* file_name, USHORT file_length) +static void db_open(ULONG pageSpaceId, const MetaString& name, const PathName& fileName) { /************************************** * @@ -2078,24 +2159,33 @@ static dba_fil* db_open(const char* file_name, USHORT file_length) **************************************/ tdba* tddba = tdba::getSpecific(); + for (const auto& tablespace : tddba->tablespaces) + { + if (tablespace.id == pageSpaceId) + { + fb_assert(tablespace.file); + return; + } + } + + const auto file_length = fileName.length(); const auto file = (dba_fil*) alloc(sizeof(dba_fil) + file_length + 1); - strcpy(file->fil_string, file_name); + strcpy(file->fil_string, fileName.c_str()); file->fil_length = file_length; - if ((file->fil_desc = os_utils::open(file_name, O_RDONLY)) == -1) + if ((file->fil_desc = os_utils::open(fileName.c_str(), O_RDONLY)) == -1) { - tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 29, SafeArg() << file_name); + tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 29, SafeArg() << fileName.c_str()); // msg 29: Can't open database file %s db_error(errno); } - tddba->file = file; - return file; + tddba->tablespaces.add({pageSpaceId, name, file}); } -static const pag* db_read( ULONG page_number, bool ok_enc) +static const pag* db_read(const PageNumber& page_number, bool ok_enc) { /************************************** * @@ -2109,13 +2199,27 @@ static const pag* db_read( ULONG page_number, bool ok_enc) **************************************/ tdba* tddba = tdba::getSpecific(); - if (tddba->page_number == page_number) + ULONG pageNum = page_number.getPageNum(); + + if (tddba->page_number == pageNum) return tddba->global_buffer; - tddba->page_number = page_number; + tddba->page_number = pageNum; + dba_fil* file = nullptr; + + for (const auto& tablespace : tddba->tablespaces) + { + if (tablespace.id == page_number.getPageSpaceID()) + { + file = tablespace.file; + break; + } + } + + fb_assert(file); - const FB_UINT64 offset = ((FB_UINT64) page_number) * ((FB_UINT64) tddba->page_size); - if (os_utils::lseek (tddba->file->fil_desc, offset, 0) == -1) + const FB_UINT64 offset = ((FB_UINT64) pageNum) * ((FB_UINT64) tddba->page_size); + if (os_utils::lseek(file->fil_desc, offset, 0) == -1) { tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 30, SafeArg()); // msg 30: Can't read a database page @@ -2125,7 +2229,7 @@ static const pag* db_read( ULONG page_number, bool ok_enc) USHORT length = tddba->page_size; for (SCHAR* p = (SCHAR *) tddba->global_buffer; length > 0;) { - const int l = read(tddba->file->fil_desc, p, length); + const int l = read(file->fil_desc, p, length); if (l < 0) { tddba->uSvc->getStatusAccessor().setServiceStatus(GSTAT_MSG_FAC, 30, SafeArg());