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/make.new/config/install-sh~ b/builds/make.new/config/install-sh~
new file mode 100755
index 00000000000..ec298b53740
--- /dev/null
+++ b/builds/make.new/config/install-sh~
@@ -0,0 +1,541 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2020-11-14.01; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+tab=' '
+nl='
+'
+IFS=" $tab$nl"
+
+# Set DOITPROG to "echo" to test this script.
+
+doit=${DOITPROG-}
+doit_exec=${doit:-exec}
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+# Create dirs (including intermediate dirs) using mode 755.
+# This is like GNU 'install' as of coreutils 8.32 (2020).
+mkdir_umask=22
+
+backupsuffix=
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+is_target_a_directory=possibly
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+ or: $0 [OPTION]... SRCFILES... DIRECTORY
+ or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+ or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+ --help display this help and exit.
+ --version display version info and exit.
+
+ -c (ignored)
+ -C install only if different (preserve data modification time)
+ -d create directories instead of installing files.
+ -g GROUP $chgrpprog installed files to GROUP.
+ -m MODE $chmodprog installed files to MODE.
+ -o USER $chownprog installed files to USER.
+ -p pass -p to $cpprog.
+ -s $stripprog installed files.
+ -S SUFFIX attempt to back up existing files, with suffix SUFFIX.
+ -t DIRECTORY install into DIRECTORY.
+ -T report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+ CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+ RMPROG STRIPPROG
+
+By default, rm is invoked with -f; when overridden with RMPROG,
+it's up to you to specify -f if you want it.
+
+If -S is not specified, no backups are attempted.
+
+Email bug reports to bug-automake@gnu.org.
+Automake home page: https://www.gnu.org/software/automake/
+"
+
+while test $# -ne 0; do
+ case $1 in
+ -c) ;;
+
+ -C) copy_on_change=true;;
+
+ -d) dir_arg=true;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift;;
+
+ --help) echo "$usage"; exit $?;;
+
+ -m) mode=$2
+ case $mode in
+ *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
+ echo "$0: invalid mode: $mode" >&2
+ exit 1;;
+ esac
+ shift;;
+
+ -o) chowncmd="$chownprog $2"
+ shift;;
+
+ -p) cpprog="$cpprog -p";;
+
+ -s) stripcmd=$stripprog;;
+
+ -S) backupsuffix="$2"
+ shift;;
+
+ -t)
+ is_target_a_directory=always
+ dst_arg=$2
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ shift;;
+
+ -T) is_target_a_directory=never;;
+
+ --version) echo "$0 $scriptversion"; exit $?;;
+
+ --) shift
+ break;;
+
+ -*) echo "$0: invalid option: $1" >&2
+ exit 1;;
+
+ *) break;;
+ esac
+ shift
+done
+
+# We allow the use of options -d and -T together, by making -d
+# take the precedence; this is for compatibility with GNU install.
+
+if test -n "$dir_arg"; then
+ if test -n "$dst_arg"; then
+ echo "$0: target directory not allowed when installing a directory." >&2
+ exit 1
+ fi
+fi
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+ # When -d is used, all remaining arguments are directories to create.
+ # When -t is used, the destination is already specified.
+ # Otherwise, the last argument is the destination. Remove it from $@.
+ for arg
+ do
+ if test -n "$dst_arg"; then
+ # $@ is not empty: it contains at least $arg.
+ set fnord "$@" "$dst_arg"
+ shift # fnord
+ fi
+ shift # arg
+ dst_arg=$arg
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ done
+fi
+
+if test $# -eq 0; then
+ if test -z "$dir_arg"; then
+ echo "$0: no input file specified." >&2
+ exit 1
+ fi
+ # It's OK to call 'install-sh -d' without argument.
+ # This can happen when creating conditional directories.
+ exit 0
+fi
+
+if test -z "$dir_arg"; then
+ if test $# -gt 1 || test "$is_target_a_directory" = always; then
+ if test ! -d "$dst_arg"; then
+ echo "$0: $dst_arg: Is not a directory." >&2
+ exit 1
+ fi
+ fi
+fi
+
+if test -z "$dir_arg"; then
+ do_exit='(exit $ret); exit $ret'
+ trap "ret=129; $do_exit" 1
+ trap "ret=130; $do_exit" 2
+ trap "ret=141; $do_exit" 13
+ trap "ret=143; $do_exit" 15
+
+ # Set umask so as not to create temps with too-generous modes.
+ # However, 'strip' requires both read and write access to temps.
+ case $mode in
+ # Optimize common cases.
+ *644) cp_umask=133;;
+ *755) cp_umask=22;;
+
+ *[0-7])
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw='% 200'
+ fi
+ cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+ *)
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw=,u+rw
+ fi
+ cp_umask=$mode$u_plus_rw;;
+ esac
+fi
+
+for src
+do
+ # Protect names problematic for 'test' and other utilities.
+ case $src in
+ -* | [=\(\)!]) src=./$src;;
+ esac
+
+ if test -n "$dir_arg"; then
+ dst=$src
+ dstdir=$dst
+ test -d "$dstdir"
+ dstdir_status=$?
+ # Don't chown directories that already exist.
+ if test $dstdir_status = 0; then
+ chowncmd=""
+ fi
+ else
+
+ # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+ # might cause directories to be created, which would be especially bad
+ # if $src (and thus $dsttmp) contains '*'.
+ if test ! -f "$src" && test ! -d "$src"; then
+ echo "$0: $src does not exist." >&2
+ exit 1
+ fi
+
+ if test -z "$dst_arg"; then
+ echo "$0: no destination specified." >&2
+ exit 1
+ fi
+ dst=$dst_arg
+
+ # If destination is a directory, append the input filename.
+ if test -d "$dst"; then
+ if test "$is_target_a_directory" = never; then
+ echo "$0: $dst_arg: Is a directory" >&2
+ exit 1
+ fi
+ dstdir=$dst
+ dstbase=`basename "$src"`
+ case $dst in
+ */) dst=$dst$dstbase;;
+ *) dst=$dst/$dstbase;;
+ esac
+ dstdir_status=0
+ else
+ dstdir=`dirname "$dst"`
+ test -d "$dstdir"
+ dstdir_status=$?
+ fi
+ fi
+
+ case $dstdir in
+ */) dstdirslash=$dstdir;;
+ *) dstdirslash=$dstdir/;;
+ esac
+
+ obsolete_mkdir_used=false
+
+ if test $dstdir_status != 0; then
+ case $posix_mkdir in
+ '')
+ # With -d, create the new directory with the user-specified mode.
+ # Otherwise, rely on $mkdir_umask.
+ if test -n "$dir_arg"; then
+ mkdir_mode=-m$mode
+ else
+ mkdir_mode=
+ fi
+
+ posix_mkdir=false
+ # The $RANDOM variable is not portable (e.g., dash). Use it
+ # here however when possible just to lower collision chance.
+ tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+
+ trap '
+ ret=$?
+ rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null
+ exit $ret
+ ' 0
+
+ # Because "mkdir -p" follows existing symlinks and we likely work
+ # directly in world-writeable /tmp, make sure that the '$tmpdir'
+ # directory is successfully created first before we actually test
+ # 'mkdir -p'.
+ if (umask $mkdir_umask &&
+ $mkdirprog $mkdir_mode "$tmpdir" &&
+ exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
+ then
+ if test -z "$dir_arg" || {
+ # Check for POSIX incompatibilities with -m.
+ # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+ # other-writable bit of parent directory when it shouldn't.
+ # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+ test_tmpdir="$tmpdir/a"
+ ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
+ case $ls_ld_tmpdir in
+ d????-?r-*) different_mode=700;;
+ d????-?--*) different_mode=755;;
+ *) false;;
+ esac &&
+ $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
+ ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
+ test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+ }
+ }
+ then posix_mkdir=:
+ fi
+ rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
+ else
+ # Remove any dirs left behind by ancient mkdir implementations.
+ rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
+ fi
+ trap '' 0;;
+ esac
+
+ if
+ $posix_mkdir && (
+ umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+ )
+ then :
+ else
+
+ # mkdir does not conform to POSIX,
+ # or it failed possibly due to a race condition. Create the
+ # directory the slow way, step by step, checking for races as we go.
+
+ case $dstdir in
+ /*) prefix='/';;
+ [-=\(\)!]*) prefix='./';;
+ *) prefix='';;
+ esac
+
+ oIFS=$IFS
+ IFS=/
+ set -f
+ set fnord $dstdir
+ shift
+ set +f
+ IFS=$oIFS
+
+ prefixes=
+
+ for d
+ do
+ test X"$d" = X && continue
+
+ prefix=$prefix$d
+ if test -d "$prefix"; then
+ prefixes=
+ else
+ if $posix_mkdir; then
+ (umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+ # Don't fail if two instances are running concurrently.
+ test -d "$prefix" || exit 1
+ else
+ case $prefix in
+ *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) qprefix=$prefix;;
+ esac
+ prefixes="$prefixes '$qprefix'"
+ fi
+ fi
+ prefix=$prefix/
+ done
+
+ if test -n "$prefixes"; then
+ # Don't fail if two instances are running concurrently.
+ (umask $mkdir_umask &&
+ eval "\$doit_exec \$mkdirprog $prefixes") ||
+ test -d "$dstdir" || exit 1
+ obsolete_mkdir_used=true
+ fi
+ fi
+ fi
+
+ if test -n "$dir_arg"; then
+ { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+ { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+ else
+
+ # Make a couple of temp file names in the proper directory.
+ dsttmp=${dstdirslash}_inst.$$_
+ rmtmp=${dstdirslash}_rm.$$_
+
+ # Trap to clean up those temp files at exit.
+ trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+ # Copy the file name to the temp name.
+ (umask $cp_umask &&
+ { test -z "$stripcmd" || {
+ # Create $dsttmp read-write so that cp doesn't create it read-only,
+ # which would cause strip to fail.
+ if test -z "$doit"; then
+ : >"$dsttmp" # No need to fork-exec 'touch'.
+ else
+ $doit touch "$dsttmp"
+ fi
+ }
+ } &&
+ $doit_exec $cpprog "$src" "$dsttmp") &&
+
+ # and set any options; do chmod last to preserve setuid bits.
+ #
+ # If any of these fail, we abort the whole thing. If we want to
+ # ignore errors from any of these, just make sure not to ignore
+ # errors from the above "$doit $cpprog $src $dsttmp" command.
+ #
+ { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+ { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+ { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+ # If -C, don't bother to copy if it wouldn't change the file.
+ if $copy_on_change &&
+ old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
+ new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
+ set -f &&
+ set X $old && old=:$2:$4:$5:$6 &&
+ set X $new && new=:$2:$4:$5:$6 &&
+ set +f &&
+ test "$old" = "$new" &&
+ $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+ then
+ rm -f "$dsttmp"
+ else
+ # If $backupsuffix is set, and the file being installed
+ # already exists, attempt a backup. Don't worry if it fails,
+ # e.g., if mv doesn't support -f.
+ if test -n "$backupsuffix" && test -f "$dst"; then
+ $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null
+ fi
+
+ # Rename the file to the real destination.
+ $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+ # The rename failed, perhaps because mv can't rename something else
+ # to itself, or perhaps because mv is so ancient that it does not
+ # support -f.
+ {
+ # Now remove or move aside any old file at destination location.
+ # We try this two ways since rm can't unlink itself on some
+ # systems and the destination file might be busy for other
+ # reasons. In this case, the final cleanup might fail but the new
+ # file should still install successfully.
+ {
+ test ! -f "$dst" ||
+ $doit $rmcmd "$dst" 2>/dev/null ||
+ { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+ { $doit $rmcmd "$rmtmp" 2>/dev/null; :; }
+ } ||
+ { echo "$0: cannot unlink or rename $dst" >&2
+ (exit 1); exit 1
+ }
+ } &&
+
+ # Now rename the file to the real destination.
+ $doit $mvcmd "$dsttmp" "$dst"
+ }
+ fi || exit 1
+
+ trap '' 0
+ fi
+done
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/builds/posix/make.shared.targets b/builds/posix/make.shared.targets
index a838fff588f..5a0a1a4ebee 100644
--- a/builds/posix/make.shared.targets
+++ b/builds/posix/make.shared.targets
@@ -76,6 +76,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/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj
index bc729abfe2a..28ef7b1a344 100644
--- a/builds/win32/msvc15/engine_static.vcxproj
+++ b/builds/win32/msvc15/engine_static.vcxproj
@@ -30,6 +30,7 @@
+
@@ -175,6 +176,7 @@
+
@@ -225,6 +227,7 @@
+
@@ -371,6 +374,7 @@
+
@@ -400,6 +404,7 @@
+
Document
diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters
index f108b854e44..52de8329794 100644
--- a/builds/win32/msvc15/engine_static.vcxproj.filters
+++ b/builds/win32/msvc15/engine_static.vcxproj.filters
@@ -465,6 +465,9 @@
DSQL\preprocesed
+
+ DSQL\preprocesed
+
Services
@@ -483,6 +486,9 @@
JRD files
+
+ JRD files
+
JRD files
@@ -1103,6 +1109,12 @@
Header files
+
+ Header files
+
+
+ Header files
+
Header files
@@ -1157,6 +1169,9 @@
DSQL\GPRE files
+
+ DSQL\GPRE files
+
JRD files\GPRE files
diff --git a/builds/win32/preprocess.bat b/builds/win32/preprocess.bat
index 049959f8ac4..47c672ed452 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) 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) do @call :PREPROCESS jrd %%i -gds_cxx
@for %%i in (extract, isql, show) do @call :PREPROCESS isql %%i -ocxx
diff --git a/doc/README.tablespaces b/doc/README.tablespaces
new file mode 100644
index 00000000000..fdd4fbc596c
--- /dev/null
+++ b/doc/README.tablespaces
@@ -0,0 +1,190 @@
+-----------------
+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'
+ ALTER TABLESPACE SET FILE [TO] '/path/to/file'
+ You can specify either an absolute path or a relative path (relative to the database file)
+
+ DROP TABLESPACE [IF EXISTS]
+ The development of the INCLUDING CONTENTS option has been postponed.
+
+ For an existing tablespace, it is possible to add a comment using the COMMENT ON statement.
+ 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 (CREATE TABLESPACE).
+ Only administrators and users with the “ALTER ANY TABLESPACE” privilege can change tablespaces file paths (ALTER TABLESPACE SET FILE [TO] ).
+ Only administrators, domain owners, or users with the ALTER ANY TABLESPACE privilege can comment (COMMENT ON) tablespaces.
+ Only administrators and users with the “DROP ANY TABLESPACE” privilege can delete tablespaces (DROP TABLESPACE).
+
+-----------------
+ODS CHANGES
+-----------------
+
+ A new table RDB$TABLESPACES:
+ RDB$TABLESPACE_ID - INTEGER # internally it will be pagespaceid.
+ 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
+
+ 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)
+ RDB$POINTER_PAGE - INTEGER # a number of the first pointer page of a relation
+ RDB$ROOT_PAGE - INTEGER # a number of the root page of a relation
+
+ These fields are necessary for reliable implementation of moving data pages to another tablespace.
+ It's a dfw operation with EX database lock. So there are no concurrent changes.
+ 1) copy all data pages
+ 2) switch RDB$POINTER_PAGE and RDB$ROOT_PAGE transactionally
+ 3) Rebuild RDB$PAGES
+ 4) clear old data pages (as post-dfw operation)
+ It can be interrupted but not resumed.
+
+ By default, the RDB$TABLESPACES table stores a record about the PRIMARY tablespace.
+ PRIMARY tablespace has the system flag and FILE_NAME matches the path to the database file.
+
+-----------------
+UTILITIES
+-----------------
+
+ 1. Logical backup
+ gbak -b works as usual for now. It gets data from a database transparently working with tablespaces.
+
+ 2. Logical restore
+ gbak -c
+
+ -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 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. Show
+ 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.
+ If this parameter is disabled, tablespaces-related DDL statements and CREATE/ALTER TABLE/INDEX clauses will not be applied to the replica.
+ This is used if 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.
+
+ An algorithm for moving data to another tablespace:
+ First, you have to move all the pages to another tablespace:
+ 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
+ - walking through the map and copy every DP to the new one by fixing
+ b_page and f_page numbers.
+ At the end of work replace records in RDB$PAGES.
+
+ Then you need to update first pointer page and root page in RDB$RELATIONS transactionally.
+
+ RDB$POINTER_PAGE and RDB$ROOT_PAGE are updated in a transaction because:
+ Moving table pages to another tablespace occurs in DFW. In case of failure, we can get the old values of RDB$POINTER_PAGE and RDB$ROOT_PAGE fields.
+
+ Then we delete all from RDB$PAGES about the relation to have an ability to understand that we need to restore pages
+ if transaction won't be able to finish successfully.
+
+ Then post commit work will clean up old pages. It must be done exactly after commit.
+ If crash is happend the metadata will point to the old page space and new ones will be garbage. Right after commit old pages will be garbage.
+
+
+-----------------
+CONSTRAINTS
+-----------------
+
+ It's possible to create up to 253 tablespaces.
+ Operators to move an index or table to a tablespace require an exclusive database lock.
+
+-----------------
+PLANS
+-----------------
+
+ 1. TEMPORARY predefined tablespaces.
+ 2. Grouping page counters by tablespace (For output in trace and monitoring).
+ 3. New header page for tablespaces
+ 4. NBACKUP support for tablespaces
+ 5. Moving blobs to separate tablespaces (The RDB$TABLESPACE_NAME column in RDB$RELATION_FIELDS is reserved for this purpose).
+ 6. Possibility to introduce UNDO tablespace in future versions
+ 7. 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 664b391d449..343649f1819 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 13059dc5ebe..65d892d8d1c 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
@@ -469,6 +470,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 2cbb866d51d..778068eff27 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();
@@ -411,6 +412,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
{
@@ -784,6 +792,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
@@ -1711,6 +1723,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,
@@ -1787,6 +1803,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;
@@ -2075,6 +2095,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);
}
@@ -4140,6 +4163,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;
@@ -4475,6 +4502,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 a8086d659fe..4137023ebc6 100644
--- a/src/burp/burp.cpp
+++ b/src/burp/burp.cpp
@@ -595,6 +595,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;
@@ -1113,6 +1114,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
@@ -2889,6 +2919,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 d21317e0b18..6e821f901bd 100644
--- a/src/burp/burp.h
+++ b/src/burp/burp.h
@@ -49,6 +49,7 @@
#include "../common/status.h"
#include "../common/sha.h"
#include "../common/classes/ImplementHelper.h"
+#include "../common/classes/GenericMap.h"
#ifdef HAVE_UNISTD_H
#include
@@ -123,7 +124,8 @@ enum rec_type {
rec_db_creator, // Database creator
rec_publication, // Publication
rec_pub_table, // Publication table
- rec_schema // Schema
+ rec_schema, // Schema
+ rec_tablespace // Tablespace
};
@@ -210,7 +212,7 @@ Version 11: FB4.0.
SQL SECURITY feature, tables RDB$PUBLICATIONS/RDB$PUBLICATION_TABLES.
Version 12: FB6.0.
- Schemas.
+ Schemas and tablespaces.
*/
inline constexpr int ATT_BACKUP_FORMAT = 12;
@@ -290,6 +292,7 @@ enum att_type {
att_relation_sql_security_deprecated, // can be removed later
att_relation_sql_security,
att_relation_schema_name,
+ att_relation_tablespace_name,
// Field attributes (used for both global and local fields)
@@ -350,6 +353,7 @@ enum att_type {
att_field_generator_name,
att_field_identity_type,
att_field_schema_name,
+ att_field_tablespace_name,
// Index attributes
@@ -367,6 +371,7 @@ enum att_type {
att_index_condition_source,
att_index_condition_blr,
att_index_foreign_key_schema_name,
+ att_index_tablespace_name,
// Data record
@@ -700,6 +705,15 @@ enum att_type {
att_schema_security_class,
att_schema_owner_name,
att_schema_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
};
@@ -771,6 +785,7 @@ struct burp_fld
SSHORT fld_collation_id;
RCRD_OFFSET fld_sql;
RCRD_OFFSET fld_null;
+ TEXT fld_tablespace[GDS_NAME_LEN];
};
enum fld_flags_vals {
@@ -798,7 +813,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
@@ -1017,7 +1033,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,
@@ -1082,6 +1099,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;
@@ -1201,6 +1219,8 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool
Firebird::IRequest* handles_get_type_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.
@@ -1224,6 +1244,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();
@@ -1236,6 +1259,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);
@@ -1261,6 +1287,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 b9955aa7485..026f79b0891 100644
--- a/src/burp/burpswi.h
+++ b/src/burp/burpswi.h
@@ -105,6 +105,10 @@ inline constexpr int IN_SW_BURP_DIRECT_IO = 55; // direct IO for backup files
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_TS_MAPPING_FILE = 58; // mapping file for tablespaces
+inline constexpr int IN_SW_BURP_TS_ORIGINAL_PATHS = 59; // restore tablespaces to their original paths
+inline constexpr int IN_SW_BURP_TS_PATH = 60; // set a path for a tablespace
+
/**************************************************************************/
static inline constexpr const char* BURP_SW_MODE_NONE = "NONE";
@@ -238,6 +242,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 1aecf4f1cb0..6d33024b257 100644
--- a/src/burp/restore.epp
+++ b/src/burp/restore.epp
@@ -61,8 +61,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"
@@ -145,6 +147,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);
@@ -3871,6 +3874,9 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation)
X.RDB$SCHEMA_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)
{
@@ -4048,6 +4054,20 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation)
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
@@ -6730,6 +6750,8 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation)
X.RDB$CONDITION_BLR.NULL = TRUE;
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())
{
@@ -6859,6 +6881,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
@@ -7122,6 +7158,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(true, &tdgbl->status_vector);
+ return false;
+ END_ERROR
+
+ return true;
+}
+
bool get_procedure(BurpGlobals* tdgbl)
{
/**************************************
@@ -8033,6 +8248,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.
@@ -8144,7 +8362,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;
@@ -8155,6 +8376,20 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
sql_security = get_boolean(tdgbl, attribute == att_relation_sql_security_deprecated);
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
@@ -8187,6 +8422,7 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
X.RDB$EXTERNAL_FILE.NULL = ext_file_name_null;
X.RDB$RELATION_TYPE.NULL = FALSE;
X.RDB$SQL_SECURITY.NULL = sql_security_null;
+ X.RDB$TABLESPACE_NAME.NULL = TRUE;
X.RDB$SYSTEM_FLAG = (USHORT) sys_flag;
X.RDB$FLAGS = (USHORT) rel_flags;
@@ -8207,8 +8443,14 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
X.RDB$RELATION_TYPE = (USHORT) type;
X.RDB$SQL_SECURITY = (FB_BOOLEAN) sql_security;
+
+ 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
@@ -11172,13 +11414,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:
@@ -11242,6 +11508,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 8a44da58a38..355e28404a6 100644
--- a/src/common/ParserTokens.h
+++ b/src/common/ParserTokens.h
@@ -571,3 +571,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/utils_proto.h b/src/common/utils_proto.h
index 5a2f3871a88..e1d1faf9197 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"
#ifdef SFIO
#include
@@ -337,6 +338,13 @@ namespace fb_utils
private:
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;
+ }
+
} // namespace fb_utils
#endif // INCLUDE_UTILS_PROTO_H
diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp
index 4e8c5c9f4cf..64c7eb79efb 100644
--- a/src/dsql/DdlNodes.epp
+++ b/src/dsql/DdlNodes.epp
@@ -89,7 +89,7 @@ static int getGrantorOption(thread_db* tdbb, jrd_tra* transaction, const MetaNam
static QualifiedName getIndexRelationName(thread_db* tdbb, jrd_tra* transaction,
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);
@@ -110,6 +110,7 @@ static void updateRdbFields(const TypeClause* type,
SSHORT& collationIdNull, SSHORT& collationId,
SSHORT& segmentLengthNull, SSHORT& segmentLength);
+static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& name, int type);
static constexpr const char* CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
DATABASE DB = STATIC "ODS.RDB";
@@ -543,10 +544,11 @@ static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& i
}
// 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.
@@ -627,8 +629,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();
}
@@ -639,9 +640,8 @@ static void checkFkPairTypes(const rel_t masterType, const QualifiedName& master
if (masterType != childType &&
!(masterType == rel_global_temp_preserve && childType == rel_global_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);
}
@@ -1398,6 +1398,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;
@@ -1502,6 +1503,10 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
SCL_check_package(tdbb, name, SCL_alter);
break;
+ case obj_tablespace:
+ SCL_check_tablespace(tdbb, name.object, SCL_alter);
+ break;
+
default:
fb_assert(false);
}
@@ -1659,6 +1664,12 @@ void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, j
status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objNameStr);
break;
+ case obj_tablespace:
+ tableClause = "rdb$tablespaces";
+ columnClause = "rdb$tablespace_name";
+ status << Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(objNameStr);
+ break;
+
default:
fb_assert(false);
return;
@@ -6223,7 +6234,8 @@ RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
: DdlNode(p),
dsqlNode(aDsqlNode),
name(p, dsqlNode->dsqlName),
- clauses(p)
+ clauses(p),
+ tableSpace(p)
{
}
@@ -6326,6 +6338,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())
{
@@ -6394,6 +6407,19 @@ 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 ((tableSpace != PRIMARY_TABLESPACE_NAME) &&
+ !checkObjectExist(tdbb, transaction, QualifiedName(tableSpace), obj_tablespaces))
+ {
+ 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
}
@@ -6448,6 +6474,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;
default:
@@ -6827,8 +6854,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;
+
fieldDefinition.store(tdbb, transaction);
// Define the field constraints.
@@ -6913,6 +6945,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;
}
@@ -7032,6 +7065,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
}
}
+ constraint.create->tableSpace = clause->tableSpace;
break;
}
@@ -7115,6 +7149,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);
CreateIndexNode::store(tdbb, transaction, qualifiedIndexName, definition, &referredIndexName);
@@ -7845,6 +7880,9 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
fb_assert(relationType.has_value());
+ if (externalFile)
+ relationType = rel_external;
+
checkRelationTempScope(tdbb, transaction, name, relationType.value());
AutoCacheRequest request(tdbb, drq_s_rels2, DYN_REQUESTS);
@@ -7881,10 +7919,35 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
status_exception::raise(Arg::PrivateDyn(163));
}
+ if (tableSpace.hasData())
+ {
+ const string scope = makeRelationScopeName(name, relationType.value());
+ 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;
}
+
+ const bool nullTsName = !fb_utils::hasTablespaceName(relationType.value());
+ REL.RDB$TABLESPACE_NAME.NULL = nullTsName ? TRUE : FALSE;
+
+ if (!nullTsName)
+ {
+ if (tableSpace.hasData() && applyTablespacesDdl(tdbb))
+ {
+ if ((tableSpace != PRIMARY_TABLESPACE_NAME) &&
+ !checkObjectExist(tdbb, transaction, QualifiedName(tableSpace), obj_tablespaces))
+ {
+ 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
@@ -8366,6 +8429,66 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
break;
}
+ case Clause::TYPE_SET_TABLESPACE:
+ {
+ fb_assert(tableSpace.hasData());
+
+ if (!applyTablespacesDdl(tdbb))
+ break;
+
+ if ((tableSpace != PRIMARY_TABLESPACE_NAME) &&
+ !checkObjectExist(tdbb, transaction, QualifiedName(tableSpace), obj_tablespaces))
+ {
+ 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;
+
+ if (!REL.RDB$RELATION_ID.NULL)
+ {
+ // RS: We need to have relation in the cache with current pageSpaceId
+ // to copy data from it. So check it in assert.
+ jrd_rel* rel = MET_lookup_relation_id(tdbb, REL.RDB$RELATION_ID, false);
+ fb_assert(rel);
+ }
+
+ MODIFY REL
+ {
+ strcpy(REL.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ }
+ END_MODIFY
+
+ if (!REL.RDB$RELATION_ID.NULL)
+ {
+ DFW_post_work(transaction, dfw_move_relation,
+ name.object.c_str(), name.schema, REL.RDB$RELATION_ID);
+ }
+ }
+ END_FOR
+
+ break;
+ }
+
default:
fb_assert(false);
break;
@@ -8947,78 +9070,19 @@ void DropRelationNode::deleteGlobalField(thread_db* tdbb, jrd_tra* transaction,
END_FOR
}
-string DropRelationNode::internalPrint(NodePrinter& printer) const
-{
- DdlNode::internalPrint(printer);
-
- NODE_PRINT(printer, name);
- NODE_PRINT(printer, view);
- NODE_PRINT(printer, silent);
-
- return "DropRelationNode";
-}
-
-void DropRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
-{
- if (view)
- SCL_check_view(tdbb, name, SCL_drop);
- else
- SCL_check_relation(tdbb, name, SCL_drop);
-}
-void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
- jrd_tra* transaction)
+void DropRelationNode::dropRelation(thread_db* tdbb, jrd_tra* transaction, bool view, jrd_rel* rel_drop, const QualifiedName& name)
{
- jrd_rel* rel_drop = MET_lookup_relation(tdbb, name);
- if (rel_drop)
- MET_scan_relation(tdbb, rel_drop);
-
- const dsql_rel* relation = METD_get_relation(transaction, dsqlScratch, name);
-
- if (!relation && silent)
- return;
-
- // Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view.
- if (view)
+ if (!view && rel_drop)
{
- if (!relation || (relation && !(relation->rel_flags & REL_view)))
- {
- status_exception::raise(
- Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
- Arg::Gds(isc_dsql_command_err) <<
- Arg::Gds(isc_dsql_view_not_found) << name.toQuotedString());
- }
+ RelationPages* relPages = rel_drop->getBasePages();
+ if (!relPages->rel_pages || (relPages->rel_pages->count() == 0))
+ DPM_scan_pages(tdbb, pag_pointer, rel_drop->rel_id);
+ if (!relPages->rel_index_root)
+ DPM_scan_pages(tdbb, pag_root, rel_drop->rel_id);
}
- else
- {
- if (!relation || (relation && (relation->rel_flags & REL_view)))
- {
- status_exception::raise(
- Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
- Arg::Gds(isc_dsql_command_err) <<
- Arg::Gds(isc_dsql_table_not_found) << name.toQuotedString());
- }
- }
-
- const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE);
-
- // run all statements under savepoint control
- AutoSavePoint savePoint(tdbb, transaction);
- AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS);
- bool found = false;
-
- FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
- R IN RDB$RELATIONS
- WITH R.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
- R.RDB$RELATION_NAME EQ name.object.c_str()
- {
- executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, {});
- found = true;
- }
- END_FOR
-
- request.reset(tdbb, drq_e_rel_con2, DYN_REQUESTS);
+ AutoCacheRequest request(tdbb, drq_e_rel_con2, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS
@@ -9126,12 +9190,6 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
}
END_FOR
- if (!found)
- {
- // msg 61: "Relation not found"
- status_exception::raise(Arg::PrivateDyn(61));
- }
-
// Triggers must be deleted after check constraints
QualifiedName triggerName;
@@ -9189,18 +9247,92 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
}
END_FOR
- if (found)
- executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, {});
+ METD_drop_relation(transaction, name);
+ MET_dsql_cache_release(tdbb, SYM_relation, name);
+}
+
+void DropRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ if (view)
+ SCL_check_view(tdbb, name, SCL_drop);
+ else
+ SCL_check_relation(tdbb, name, SCL_drop);
+}
+
+void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ jrd_rel* rel_drop = MET_lookup_relation(tdbb, name);
+ if (rel_drop)
+ MET_scan_relation(tdbb, rel_drop);
+
+ const dsql_rel* relation = METD_get_relation(transaction, dsqlScratch, name);
+
+ if (!relation && silent)
+ return;
+
+ // Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view.
+ if (view)
+ {
+ if (!relation || (relation && !(relation->rel_flags & REL_view)))
+ {
+ status_exception::raise(
+ Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
+ Arg::Gds(isc_dsql_command_err) <<
+ Arg::Gds(isc_dsql_view_not_found) << name.toQuotedString());
+ }
+ }
else
+ {
+ if (!relation || (relation && (relation->rel_flags & REL_view)))
+ {
+ status_exception::raise(
+ Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
+ Arg::Gds(isc_dsql_command_err) <<
+ Arg::Gds(isc_dsql_table_not_found) << name.toQuotedString());
+ }
+ }
+
+ const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE);
+
+ // run all statements under savepoint control
+ AutoSavePoint savePoint(tdbb, transaction);
+
+ AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS);
+ bool found = false;
+
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ R IN RDB$RELATIONS
+ WITH R.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
+ R.RDB$RELATION_NAME EQ name.object.c_str()
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, {});
+ found = true;
+ }
+ END_FOR
+
+ if (!found)
{
// msg 61: "Relation not found"
status_exception::raise(Arg::PrivateDyn(61));
}
+ dropRelation(tdbb, transaction, view, rel_drop, name);
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, {});
+
savePoint.release(); // everything is ok
+}
- METD_drop_relation(transaction, name);
- MET_dsql_cache_release(tdbb, SYM_relation, name);
+string DropRelationNode::internalPrint(NodePrinter& printer) const
+{
+ DdlNode::internalPrint(printer);
+
+ NODE_PRINT(printer, name);
+ NODE_PRINT(printer, view);
+ NODE_PRINT(printer, silent);
+
+ return "DropRelationNode";
}
@@ -10090,6 +10222,9 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, QualifiedName
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);
@@ -10104,6 +10239,15 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, QualifiedName
// 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
@@ -10125,6 +10269,32 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, QualifiedName
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 ((definition.tableSpace != PRIMARY_TABLESPACE_NAME) &&
+ !checkObjectExist(tdbb, transaction, QualifiedName(definition.tableSpace), obj_tablespaces))
+ {
+ 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)
@@ -10499,6 +10669,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();
@@ -10550,7 +10722,8 @@ string AlterIndexNode::internalPrint(NodePrinter& printer) const
DdlNode::internalPrint(printer);
NODE_PRINT(printer, name);
- NODE_PRINT(printer, active);
+ NODE_PRINT(printer, op);
+ NODE_PRINT(printer, tableSpace);
return "AlterIndexNode";
}
@@ -10580,10 +10753,53 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name, {});
- MODIFY IDX
- IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
- IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
- END_MODIFY
+ if (op == OP_ACTIVE || op == OP_INACTIVE)
+ {
+ MODIFY IDX
+ IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
+ IDX.RDB$INDEX_INACTIVE = (op == OP_ACTIVE) ? FALSE : TRUE;
+ END_MODIFY
+ }
+ else
+ {
+ fb_assert(op == OP_SET_TABLESPACE);
+ fb_assert(tableSpace.hasData());
+
+ if (!applyTablespacesDdl(tdbb))
+ break;
+
+ const bool primary_ts = (tableSpace == PRIMARY_TABLESPACE_NAME);
+
+ if (!primary_ts && !checkObjectExist(tdbb, transaction, QualifiedName(tableSpace), obj_tablespaces))
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace.c_str());
+
+ 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
+ {
+ if (!REL.RDB$RELATION_TYPE.NULL)
+ {
+ ULONG 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) << name.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)
+ break;
+
+ MODIFY IDX
+ strcpy(IDX.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ END_MODIFY
+ }
}
END_FOR
@@ -12231,6 +12447,27 @@ static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const Qualif
END_FOR
break;
}
+
+ case obj_tablespaces:
+ {
+ fb_assert(name.object.hasData() && name.schema.isEmpty());
+
+ if (name.object == PRIMARY_TABLESPACE_NAME)
+ {
+ rc = true;
+ break;
+ }
+
+ AutoCacheRequest request(tdbb, drq_tablespace_exist, DYN_REQUESTS);
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ 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 ab73673a487..232f211348b 100644
--- a/src/dsql/DdlNodes.h
+++ b/src/dsql/DdlNodes.h
@@ -1294,7 +1294,8 @@ class RelationNode : public DdlNode
fieldSource(p),
identitySequence(p),
defaultSource(p),
- baseField(p)
+ baseField(p),
+ tableSpace(p)
{
}
@@ -1314,6 +1315,7 @@ class RelationNode : public DdlNode
Firebird::ByteChunk defaultValue;
std::optional viewContext;
MetaName baseField;
+ MetaName tableSpace;
};
struct IndexConstraintClause
@@ -1368,7 +1370,8 @@ class RelationNode : public DdlNode
refUpdateAction(RI_RESTRICT),
refDeleteAction(RI_RESTRICT),
triggers(p),
- blrWritersHolder(p)
+ blrWritersHolder(p),
+ tableSpace(p)
{
}
@@ -1381,6 +1384,7 @@ class RelationNode : public DdlNode
const char* refDeleteAction;
Firebird::ObjectsArray triggers;
Firebird::ObjectsArray blrWritersHolder;
+ MetaName tableSpace;
};
struct CreateDropConstraint
@@ -1408,7 +1412,8 @@ class RelationNode : public DdlNode
TYPE_DROP_COLUMN,
TYPE_DROP_CONSTRAINT,
TYPE_ALTER_SQL_SECURITY,
- TYPE_ALTER_PUBLICATION
+ TYPE_ALTER_PUBLICATION,
+ TYPE_SET_TABLESPACE
};
explicit Clause(MemoryPool& p, Type aType) noexcept
@@ -1456,7 +1461,8 @@ class RelationNode : public DdlNode
refRelation(p),
refColumns(p),
refAction(NULL),
- check(NULL)
+ check(NULL),
+ tableSpace(p)
{
}
@@ -1469,6 +1475,7 @@ class RelationNode : public DdlNode
NestConst refAction;
NestConst check;
bool createIfNotExistsOnly = false;
+ MetaName tableSpace;
};
struct IdentityOptions
@@ -1657,6 +1664,7 @@ class RelationNode : public DdlNode
Firebird::Array > clauses;
Firebird::TriState ssDefiner;
Firebird::TriState replicationState;
+ MetaName tableSpace;
};
@@ -1749,6 +1757,8 @@ class DropRelationNode final : public DdlNode
static void deleteGlobalField(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& globalName);
+ static void dropRelation(thread_db* tdbb, jrd_tra* transaction, bool view, jrd_rel* rel_drop, const QualifiedName& name);
+
public:
Firebird::string internalPrint(NodePrinter& printer) const override;
void checkPermission(thread_db* tdbb, jrd_tra* transaction) override;
@@ -1871,6 +1881,7 @@ class CreateIndexNode final : public DdlNode
bid conditionSource;
QualifiedName refRelation;
Firebird::ObjectsArray refColumns;
+ MetaName tableSpace;
};
public:
@@ -1906,16 +1917,19 @@ class CreateIndexNode final : public DdlNode
NestConst computed;
NestConst partial;
bool createIfNotExistsOnly = false;
+ MetaName tableSpace;
};
class AlterIndexNode final : public DdlNode
{
public:
- AlterIndexNode(MemoryPool& p, const QualifiedName& aName, bool aActive)
+ enum OP {OP_ACTIVE, OP_INACTIVE, OP_SET_TABLESPACE};
+
+ AlterIndexNode(MemoryPool& p, const QualifiedName& aName, OP anOp)
: DdlNode(p),
name(p, aName),
- active(aActive)
+ op(anOp)
{
}
@@ -1940,7 +1954,8 @@ class AlterIndexNode final : public DdlNode
public:
QualifiedName name;
- bool active;
+ MetaName tableSpace;
+ OP op;
};
diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h
index b26f2015980..84a1a922f48 100644
--- a/src/dsql/Nodes.h
+++ b/src/dsql/Nodes.h
@@ -200,6 +200,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.h b/src/dsql/Parser.h
index 765fa526a25..df26f817207 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/TriState.h"
diff --git a/src/dsql/TablespaceNodes.epp b/src/dsql/TablespaceNodes.epp
new file mode 100644
index 00000000000..8aaadb3d6b1
--- /dev/null
+++ b/src/dsql/TablespaceNodes.epp
@@ -0,0 +1,389 @@
+/*
+ * 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 "../dsql/TablespaceNodes.h"
+#include "../jrd/dyn.h"
+#include "../jrd/intl.h"
+#include "../jrd/jrd.h"
+#include "../jrd/tra.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 "../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 "../jrd/lck_proto.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, jrd_tra* transaction)
+{
+ 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::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ Attachment* attachment = transaction->getAttachment();
+ Database* dbb = tdbb->getDatabase();
+
+ const MetaName& userName = attachment->att_user->getUserName();
+
+ if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, transaction, QualifiedName(name), obj_tablespace))
+ return;
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_CREATE_TABLESPACE, QualifiedName(name), {});
+
+ DYN_UTIL_check_unique_name(tdbb, transaction, QualifiedName(name), obj_tablespace);
+
+ AutoCacheRequest requestHandle(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") + 1; // +1 to skip DB_PAGE_SPACE
+ id %= TRANS_PAGE_SPACE;
+
+ if (!id || id == 1) // skip DB_PAGE_SPACE
+ continue;
+
+ STORE (REQUEST_HANDLE requestHandle 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 >= TRANS_PAGE_SPACE)
+ throw;
+
+ fb_utils::init_status(tdbb->tdbb_status_vector);
+ }
+ }
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TABLESPACE,
+ QualifiedName(name), {});
+}
+
+
+bool CreateAlterTablespaceNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ Attachment* attachment = transaction->getAttachment();
+ AutoCacheRequest requestHandle(tdbb, drq_m_tablespace, DYN_REQUESTS);
+ bool modified = false;
+ ULONG tableSpaceId = 0;
+
+ FOR (REQUEST_HANDLE requestHandle 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));
+
+ modified = true;
+ tableSpaceId = X.RDB$TABLESPACE_ID;
+
+ fb_assert(PageSpace::isTablespace(tableSpaceId));
+
+ Tablespace* tablespace = MET_tablespace_id(tdbb, tableSpaceId, false);
+ tablespace->modified = true;
+
+ // Get the EX lock here to prevent other attachments from using the tablespace.
+ // It's needed because other attachments see uncommitted changes in RDB$TABLESPACES.
+ // I think this solution is temporary and should be reconsidered.
+ if (tablespace->isUsed() ||
+ !LCK_convert(tdbb, tablespace->existenceLock, LCK_EX, transaction->getLockWait()))
+ {
+ string obj_name;
+ obj_name.printf("TABLESPACE \"%s\"", name.c_str());
+
+ ERR_post(Arg::Gds(isc_obj_in_use) << Arg::Str(obj_name));
+ }
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_ALTER_TABLESPACE, QualifiedName(name), {});
+
+ MODIFY X
+ if (fileName.hasData())
+ {
+ if (fileName.length() >= sizeof(X.RDB$FILE_NAME))
+ status_exception::raise(Arg::Gds(isc_dyn_name_longer));
+
+ fb_assert(X.RDB$FILE_NAME.NULL == FALSE);
+ if (!PIO_file_exists(fileName.c_str()))
+ ERR_post(Arg::Gds(isc_ts_file_not_exists) << Arg::Str(name) << Arg::Str(fileName));
+ 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::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ 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 requestHandle(tdbb, drq_e_tablespace, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle 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;
+
+ // Ensure it's cached to be used in the DFW stage later
+ MET_tablespace_id(tdbb, X.RDB$TABLESPACE_ID);
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_DROP_TABLESPACE, QualifiedName(name), {});
+
+ ERASE X;
+
+ if (!X.RDB$SECURITY_CLASS.NULL)
+ deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS);
+ }
+ END_FOR
+
+ SLONG total = 0;
+
+ // Find all tables
+ requestHandle.reset(tdbb, drq_ts_drop_rel_dfw, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ X IN RDB$RELATIONS
+ WITH X.RDB$TABLESPACE_NAME EQ name.c_str()
+ {
+ if (dropDependencies)
+ {
+ const QualifiedName relationName(X.RDB$RELATION_NAME, X.RDB$SCHEMA_NAME);
+ if (const auto relation = MET_lookup_relation(tdbb, relationName))
+ {
+ MET_scan_relation(tdbb, relation);
+ DropRelationNode::dropRelation(tdbb, transaction, false, relation, relationName);
+ }
+ }
+ else
+ {
+ total++;
+ }
+ }
+ END_FOR
+
+ // Find all indices
+ requestHandle.reset(tdbb, drq_ts_drop_idx_dfw, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ X IN RDB$INDICES
+ WITH X.RDB$TABLESPACE_NAME EQ name.c_str()
+ {
+ if (dropDependencies)
+ {
+ if (X.RDB$EXPRESSION_BLR.NULL &&
+ !DropIndexNode::deleteSegmentRecords(tdbb, transaction, QualifiedName(X.RDB$INDEX_NAME, X.RDB$SCHEMA_NAME)))
+ {
+ // msg 50: "No segments found for index"
+ status_exception::raise(Arg::PrivateDyn(50));
+ }
+
+ ERASE X;
+ }
+ else
+ {
+ total++;
+ }
+ }
+ END_FOR
+
+ if (total)
+ {
+ status_exception::raise(Arg::Gds(isc_no_meta_update) <<
+ Arg::Gds(isc_no_delete) << // Msg353: can not delete
+ Arg::Gds(isc_tablespace_name) << Arg::Str(name) <<
+ Arg::Gds(isc_dependency) << Arg::Num(total)); // Msg310: there are %ld dependencies
+ }
+
+ 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..db88d1ea5ca
--- /dev/null
+++ b/src/dsql/TablespaceNodes.h
@@ -0,0 +1,116 @@
+/*
+ * 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, jrd_tra* transaction);
+ 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 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, jrd_tra* transaction);
+ 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;
+ }
+
+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 594c40ed0c8..41693b31b92 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 1a9c9b90181..b4dddb269e3 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)
{
}
@@ -264,7 +265,8 @@ class dsql_fld : public TypeClause
dsql_rel* fld_relation = nullptr; // Parent relation
dsql_prc* fld_procedure = nullptr; // Parent procedure
USHORT fld_id = 0; // Field in in database
- MetaName fld_name;
+ 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 dea420bc00c..880bdbd038f 100644
--- a/src/dsql/parse-conflicts.txt
+++ b/src/dsql/parse-conflicts.txt
@@ -1 +1 @@
-133 shift/reduce conflicts, 13 reduce/reduce conflicts.
+137 shift/reduce conflicts, 14 reduce/reduce conflicts.
diff --git a/src/dsql/parse.y b/src/dsql/parse.y
index c8f2204af17..52a1450ca5d 100644
--- a/src/dsql/parse.y
+++ b/src/dsql/parse.y
@@ -719,6 +719,13 @@ using namespace Firebird;
%token SEARCH_PATH
%token UNLIST
+// tablespaces
+%token INCLUDING
+%token CONTENTS
+%token TABLESPACE
+%token OFFLINE
+%token ONLINE
+
// precedence declarations for expression evaluation
%left OR
@@ -877,6 +884,8 @@ using namespace Firebird;
Jrd::SetBindNode* setBindNode;
Jrd::SessionResetNode* sessionResetNode;
Jrd::ForRangeNode::Direction forRangeDirection;
+ Jrd::CreateAlterTablespaceNode* createAlterTablespaceNode;
+ Jrd::DropTablespaceNode* dropTablespaceNode;
}
%include types.y
@@ -1114,6 +1123,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
@@ -1628,8 +1639,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
@@ -1742,6 +1756,12 @@ create_clause
node->createIfNotExistsOnly = $2;
$$ = node;
}
+ | TABLESPACE if_not_exists_opt tablespace_clause
+ {
+ const auto node = $3;
+ node->createIfNotExistsOnly = $2;
+ $$ = node;
+ }
;
@@ -1774,6 +1794,8 @@ recreate_clause
{ $$ = newNode($2); }
| SEQUENCE generator_clause
{ $$ = newNode($2); }
+ | TABLESPACE tablespace_clause
+ { $$ = newNode($2); }
| USER create_user_clause
{ $$ = newNode($2); }
| SCHEMA schema_clause
@@ -1800,6 +1822,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; }
;
@@ -2383,6 +2406,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
@@ -2639,7 +2664,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;
@@ -2658,18 +2683,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;
}
;
@@ -2694,7 +2728,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;
@@ -2707,10 +2741,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;
@@ -2723,11 +2760,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;
@@ -2752,6 +2792,9 @@ table_constraint($relationNode)
constraint.index = $8;
+ if ($9)
+ constraint.tableSpace = *$9;
+
$relationNode->clauses.add(&constraint);
$$ = &constraint;
}
@@ -4328,6 +4371,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; }
;
@@ -4401,6 +4447,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
@@ -4607,6 +4654,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
@@ -4781,9 +4835,13 @@ drop_behaviour
%type alter_index_clause
alter_index_clause
- : symbol_index_name index_active
+ : symbol_index_name ACTIVE { $$ = newNode(*$1, AlterIndexNode::OP_ACTIVE); }
+ | symbol_index_name INACTIVE { $$ = newNode(*$1, AlterIndexNode::OP_INACTIVE); }
+ | symbol_index_name SET TABLESPACE to_opt symbol_tablespace_name
{
- $$ = newNode(*$1, $2);
+ AlterIndexNode* node = newNode(*$1, AlterIndexNode::OP_SET_TABLESPACE);
+ node->tableSpace = *$5;
+ $$ = node;
}
;
@@ -5141,6 +5199,12 @@ drop_clause
node->silent = $2;
$$ = node;
}
+ | TABLESPACE if_exists_opt drop_tablespace_clause
+ {
+ const auto node = $3;
+ node->silent = $2;
+ $$ = node;
+ }
;
%type if_exists_opt
@@ -5347,7 +5411,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;
@@ -5358,16 +5422,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;
@@ -5375,8 +5445,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;
@@ -5384,6 +5457,9 @@ blob_type
$$->segLength = 80;
$$->subType = (USHORT) $4;
$$->flags |= FLD_has_sub;
+
+ //if ($6)
+ // $$->fld_ts_name = *$6;
}
;
@@ -6222,6 +6298,7 @@ ddl_type1_schema
| CHARACTER SET { $$ = obj_charset; }
| COLLATION { $$ = obj_collation; }
| PACKAGE { $$ = obj_package_header; }
+ | TABLESPACE { $$ = obj_tablespace; }
;
%type ddl_type1_noschema
@@ -8051,6 +8128,102 @@ 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;
+ }
+// | symbol_tablespace_name INCLUDING CONTENTS
+// {
+// DropTablespaceNode* node = newNode(*$1);
+// node->dropDependencies = true;
+// $$ = node;
+// }
+ ;
// value types
@@ -9971,6 +10144,12 @@ non_reserved_word
| SEARCH_PATH
| SCHEMA
| UNLIST
+ | TABLESPACE
+ | ONLINE
+ | OFFLINE
+ | INCLUDING
+ | CONTENTS
+ | PRIMARY
;
%%
diff --git a/src/include/firebird/impl/msg/dyn.h b/src/include/firebird/impl/msg/dyn.h
index 598b1da1e64..362c60a0dd5 100644
--- a/src/include/firebird/impl/msg/dyn.h
+++ b/src/include/firebird/impl/msg/dyn.h
@@ -312,3 +312,8 @@ FB_IMPL_MSG(DYN, 319, dyn_cannot_mod_obj_sys_schema, -607, "28", "000", "Cannot
FB_IMPL_MSG(DYN, 320, dyn_cannot_create_reserved_schema, -607, "HY", "000", "Schema name @1 is reserved and cannot be created")
FB_IMPL_MSG(DYN, 321, dyn_cannot_infer_schema, -901, "42", "000", "Cannot infer schema name as there is no valid schema in the search path")
FB_IMPL_MSG_SYMBOL(DYN, 322, dyn_dup_blob_filter, "Blob filter @1 already exists")
+FB_IMPL_MSG(DYN, 323, dyn_ts_not_found, -901, "42", "000", "Tablespace @1 not found")
+FB_IMPL_MSG(DYN, 324, dyn_cant_set_ts_table, -901, "42", "000", "Cannot set tablespace for temporary table @1")
+FB_IMPL_MSG(DYN, 325, dyn_cant_set_ts_index, -901, "42", "000", "Cannot set tablespace for temporary index @1")
+FB_IMPL_MSG(DYN, 326, dyn_dup_tablespace, -901, "42", "000", "Tablespace @1 already exists")
+FB_IMPL_MSG(DYN, 327, 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 fb5a173b540..e5142c8862b 100644
--- a/src/include/firebird/impl/msg/gbak.h
+++ b/src/include/firebird/impl/msg/gbak.h
@@ -418,3 +418,13 @@ FB_IMPL_MSG_NO_SYMBOL(GBAK, 419, "regular expression to skip schemas was already
FB_IMPL_MSG_NO_SYMBOL(GBAK, 420, "regular expression to include schemas was already set")
FB_IMPL_MSG_SYMBOL(GBAK, 421, gbak_plugin_schema_migration, "migrating @1 plugin objects to schema @2")
FB_IMPL_MSG_SYMBOL(GBAK, 422, gbak_plugin_schema_migration_err, "error migrating @1 plugin objects to schema @2. Plugin objects will be in inconsistent state:")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 423, "writing tablespaces")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 424, "writing tablespace @1")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 425, "restoring tablespace @1")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 426, "tablespace")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 427, " @1TS_MAP(PING_FILE) mapping file for tablespaces")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 428, "path to tablespace @1 is not specified (original path: \"@2\")")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 429, " @1TS_ORIG(INAL_PATHS) restore tablespaces to their original paths")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 430, " @1TS set a path for a tablespace")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 431, "parameter for option -@1 is missing")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 432, "cannot open mapping file \"@1\"")
diff --git a/src/include/firebird/impl/msg/isql.h b/src/include/firebird/impl/msg/isql.h
index fc4a9619737..02be70f15f6 100644
--- a/src/include/firebird/impl/msg/isql.h
+++ b/src/include/firebird/impl/msg/isql.h
@@ -208,3 +208,6 @@ FB_IMPL_MSG_SYMBOL(ISQL, 208, HLP_SETAUTOTERM, " SET AUTOTERM -- to
FB_IMPL_MSG_SYMBOL(ISQL, 209, HLP_SETWIRESTATS, " SET WIRE_stats -- toggle display of wire (network) statistics")
FB_IMPL_MSG_SYMBOL(ISQL, 210, USAGE_SEARCH_PATH, " -(se)arch_path set schema search path")
FB_IMPL_MSG_SYMBOL(ISQL, 211, MSG_SCHEMAS, "Schemas:")
+FB_IMPL_MSG_SYMBOL(ISQL, 212, NO_TABLESPACE, "There is no tablespace @1 in this database")
+FB_IMPL_MSG_SYMBOL(ISQL, 213, NO_TABLESPACES, "There are no tablespaces in this database")
+FB_IMPL_MSG_SYMBOL(ISQL, 214, MSG_TABLESPACES, "Tablespaces:")
diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h
index cef871febd6..a73de615efd 100644
--- a/src/include/firebird/impl/msg/jrd.h
+++ b/src/include/firebird/impl/msg/jrd.h
@@ -998,3 +998,6 @@ FB_IMPL_MSG(JRD, 995, missing_value_for_format_pattern, -901, "HY", "000", "Cann
FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1")
FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1")
FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored")
+FB_IMPL_MSG(JRD, 999, ts_file_exists, -902, "08", "001", "Tablespace \"@1\" creation error. File \"@2\" exists.")
+FB_IMPL_MSG(JRD, 1000, tablespace_name, -901, "42", "000", "TABLESPACE @1")
+FB_IMPL_MSG(JRD, 1001, ts_file_not_exists, -902, "08", "001", "Tablespace \"@1\" alteration error. File \"@2\" does not exist.")
diff --git a/src/include/firebird/impl/msg/jrd_bugchk.h b/src/include/firebird/impl/msg/jrd_bugchk.h
index 48bee23cd43..f3d0ffe7be8 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 a2687461bcd..6f197660777 100644
--- a/src/include/firebird/impl/msg/sqlerr.h
+++ b/src/include/firebird/impl/msg/sqlerr.h
@@ -289,3 +289,8 @@ FB_IMPL_MSG(SQLERR, 1049, dsql_drop_schema_failed, -901, "42", "000", "DROP SCHE
FB_IMPL_MSG(SQLERR, 1050, dsql_recreate_schema_failed, -901, "42", "000", "RECREATE SCHEMA @1 failed")
FB_IMPL_MSG(SQLERR, 1051, dsql_alter_schema_failed, -901, "42", "000", "ALTER SCHEMA @1 failed")
FB_IMPL_MSG(SQLERR, 1052, dsql_create_alter_schema_failed, -901, "42", "000", "CREATE OR ALTER SCHEMA @1 failed")
+FB_IMPL_MSG(SQLERR, 1053, dsql_create_ts_failed, -901, "42", "000", "CREATE TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1054, dsql_alter_ts_failed, -901, "42", "000", "ALTER TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1055, dsql_create_alter_ts_failed, -901, "42", "000", "CREATE OR ALTER TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1056, dsql_drop_ts_failed, -901, "42", "000", "DROP TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1057, 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 15fb8074f2c..bb79cb21e93 100644
--- a/src/include/gen/Firebird.pas
+++ b/src/include/gen/Firebird.pas
@@ -5844,6 +5844,9 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_invalid_name = 335545316;
isc_invalid_unqualified_name_list = 335545317;
isc_no_user_att_while_restore = 335545318;
+ isc_ts_file_exists = 335545319;
+ isc_tablespace_name = 335545320;
+ isc_ts_file_not_exists = 335545321;
isc_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;
isc_gfix_incmp_sw = 335740932;
@@ -6007,6 +6010,11 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_dyn_cannot_mod_obj_sys_schema = 336068927;
isc_dyn_cannot_create_reserved_schema = 336068928;
isc_dyn_cannot_infer_schema = 336068929;
+ isc_dyn_ts_not_found = 336068931;
+ isc_dyn_cant_set_ts_table = 336068932;
+ isc_dyn_cant_set_ts_index = 336068933;
+ isc_dyn_dup_tablespace = 336068934;
+ isc_dyn_cannot_mod_sys_ts = 336068935;
isc_gbak_unknown_switch = 336330753;
isc_gbak_page_size_missing = 336330754;
isc_gbak_page_size_toobig = 336330755;
@@ -6241,6 +6249,11 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_dsql_recreate_schema_failed = 336397338;
isc_dsql_alter_schema_failed = 336397339;
isc_dsql_create_alter_schema_failed = 336397340;
+ isc_dsql_create_ts_failed = 336397341;
+ isc_dsql_alter_ts_failed = 336397342;
+ isc_dsql_create_alter_ts_failed = 336397343;
+ isc_dsql_drop_ts_failed = 336397344;
+ isc_dsql_recreate_ts_failed = 336397345;
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 64218057132..aae04b5e55f 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");
@@ -635,6 +636,8 @@ FrontendParser::AnyShowNode FrontendParser::parseShow()
}
else if (const auto parsed = parseShowOptQualifiedName(text, TOKEN_TABLES, 5))
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))
return parsed.value();
else if (text == TOKEN_USERS)
diff --git a/src/isql/FrontendParser.h b/src/isql/FrontendParser.h
index 28a1b9cc3ba..19f82c78a6b 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 {};
@@ -179,6 +180,7 @@ class FrontendParser
ShowSqlDialectNode,
ShowSystemNode,
ShowTablesNode,
+ ShowTablespacesNode,
ShowTriggersNode,
ShowUsersNode,
ShowVersionNode,
diff --git a/src/isql/extract.epp b/src/isql/extract.epp
index 52f4876a5a9..5faa4e75b96 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
@@ -378,12 +381,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)
@@ -618,15 +624,20 @@ int EXTRACT_list_table(const QualifiedMetaString& relation_name,
if (first) // we extracted nothing
return FINI_ERROR;
- const char* gtt_scope = (rel_type == rel_global_temp_preserve) ? "ON COMMIT PRESERVE ROWS" :
- ((rel_type == rel_global_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_global_temp_preserve) ? "ON COMMIT PRESERVE ROWS" :
+ ((rel_type == rel_global_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;
@@ -1316,7 +1327,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);
}
@@ -1344,6 +1356,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);
@@ -3156,6 +3171,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
@@ -3414,3 +3436,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 6f190c5bac6..b9aa7b3dcab 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);
@@ -1851,6 +1852,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}
};
@@ -2497,6 +2499,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);
@@ -2576,6 +2596,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)
@@ -3358,6 +3379,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;
@@ -4682,6 +4719,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)
@@ -4703,7 +4741,11 @@ static void show_index(const QualifiedMetaString& relationName,
"%s%s%s INDEX ON %s",
IUTILS_name_to_string(indexName).c_str(),
(unique_flag ? " UNIQUE" : ""),
- (index_type == 1 ? " DESCENDING" : ""), IUTILS_name_to_string(relationName.object).c_str());
+ (index_type == 1 ? " DESCENDING" : ""),
+ IUTILS_name_to_string(relationName.object).c_str());
+
+ if (tablespaceName.hasData() && tablespaceName != PRIMARY_TABLESPACE_NAME)
+ isqlGlob.printf(" TABLESPACE %s", IUTILS_name_to_string(tablespaceName).c_str());
// Get column names
@@ -4748,10 +4790,14 @@ static processing_state show_indices(const std::optional& n
const QualifiedMetaString relationName(IDX.RDB$RELATION_NAME, IDX.RDB$SCHEMA_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)
@@ -4796,7 +4842,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)
@@ -4930,6 +4980,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)
{
@@ -6043,6 +6168,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);
+ }
+
// TODO: indent
FOR RFR IN RDB$RELATION_FIELDS CROSS
diff --git a/src/isql/tests/FrontendParserTest.cpp b/src/isql/tests/FrontendParserTest.cpp
index d4f62903351..cd0841f2d3d 100644
--- a/src/isql/tests/FrontendParserTest.cpp
+++ b/src/isql/tests/FrontendParserTest.cpp
@@ -595,6 +595,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 3222bb1a742..f63bb6b445a 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"
@@ -229,6 +230,7 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb, JProvider* provider
att_ss_user(nullptr),
att_user_ids(*pool),
att_active_snapshots(*pool),
+ att_tablespaces(*pool),
att_statements(*pool),
att_requests(*pool),
att_lock_owner_id(Database::getLockOwnerId()),
@@ -284,6 +286,9 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb, JProvider* provider
att_internal.grow(irq_MAX);
att_dyn_req.grow(drq_MAX);
+ const auto dbTableSpace = FB_NEW_POOL(*pool) Tablespace(*pool);
+ att_tablespaces.add(dbTableSpace);
+
att_system_schema_search_path->push(SYSTEM_SCHEMA);
}
@@ -804,6 +809,19 @@ void Jrd::Attachment::releaseLocks(thread_db* tdbb)
}
}
+ // Release all tablespace existence locks that might have been taken
+
+ for (Tablespace** iter = att_tablespaces.begin(); iter < att_tablespaces.end(); ++iter)
+ {
+ Tablespace* const tablespace = *iter;
+
+ if (tablespace && tablespace->existenceLock)
+ {
+ LCK_release(tdbb, tablespace->existenceLock);
+ tablespace->useCount = 0;
+ }
+ }
+
// Release all procedure existence locks that might have been taken
for (jrd_prc** iter = att_procedures.begin(); iter < att_procedures.end(); ++iter)
diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h
index 26097f0dfe7..26c9fe3660e 100644
--- a/src/jrd/Attachment.h
+++ b/src/jrd/Attachment.h
@@ -49,6 +49,8 @@
#include "../jrd/EngineInterface.h"
#include "../jrd/sbm.h"
+#include "../jrd/Tablespace.h"
+
#include
#include
@@ -578,6 +580,7 @@ class Attachment : public pool_alloc
private:
jrd_tra* att_sys_transaction; // system transaction
StableAttachmentPart* att_stable;
+ Firebird::Array att_tablespaces; // Tablespaces cache ([0] element is DB_PAGE_SPACE)
public:
Firebird::SortedArray att_statements; // Statements belonging to attachment
@@ -818,6 +821,50 @@ class Attachment : public pool_alloc
att_batches.findAndRemove(b);
}
+ Tablespace* getTablespace(ULONG id)
+ {
+ // tablespace id is started from 1
+ if (id <= att_tablespaces.getCount())
+ return att_tablespaces[id - 1];
+
+ return NULL;
+ }
+
+ Tablespace* getTablespaceByName(const MetaName name)
+ {
+ Tablespace** iter = att_tablespaces.begin();
+ ++iter; // skip DB_PAGE_SPACE
+
+ for (; iter < att_tablespaces.end(); ++iter)
+ {
+ Tablespace* const tablespace = *iter;
+
+ if (tablespace && tablespace->name == name)
+ return tablespace;
+ }
+
+ return NULL;
+ }
+
+ void setTablespace(ULONG id, Tablespace* value)
+ {
+ if (id > att_tablespaces.getCount())
+ att_tablespaces.grow(id);
+
+ fb_assert(!att_tablespaces[id - 1]);
+ att_tablespaces[id - 1] = value;
+ }
+
+ void delTablespace(ULONG id)
+ {
+ if (id <= att_tablespaces.getCount())
+ {
+ Tablespace* tablespaceToDelete = att_tablespaces[id - 1];
+ att_tablespaces[id - 1] = NULL;
+ delete tablespaceToDelete;
+ }
+ }
+
UserId* getUserId(const Firebird::MetaString& userName);
const Firebird::MetaString& getUserName(const Firebird::MetaString& emptyName = "") const
diff --git a/src/jrd/Database.cpp b/src/jrd/Database.cpp
index 803bb458a66..67b2155b9d4 100644
--- a/src/jrd/Database.cpp
+++ b/src/jrd/Database.cpp
@@ -96,27 +96,27 @@ 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();
}
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();
}
@@ -400,7 +400,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);
diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp
index 4a40f874761..4823cddf494 100644
--- a/src/jrd/Relation.cpp
+++ b/src/jrd/Relation.cpp
@@ -427,6 +427,11 @@ bool jrd_rel::acquireGCLock(thread_db* tdbb, int wait)
return ret;
}
+void jrd_rel::setPageSpace(ULONG pageSpaceId)
+{
+ rel_pages_base.rel_pg_space_id = pageSpaceId;
+}
+
void jrd_rel::downgradeGCLock(thread_db* tdbb)
{
if (!rel_sweep_count && (rel_flags & REL_gc_blocking))
diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h
index 63dd4256e86..c6676d75f6d 100644
--- a/src/jrd/Relation.h
+++ b/src/jrd/Relation.h
@@ -87,7 +87,7 @@ class RelationPages
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;
+ ULONG rel_pg_space_id;
RelationPages(Firebird::MemoryPool& pool)
: rel_pages(NULL), rel_instance_id(0),
@@ -290,6 +290,16 @@ class jrd_rel : public pool_alloc
return &rel_pages_base;
}
+ RelationPages* getReplacedPages()
+ {
+ return rel_replaced_pages;
+ }
+
+ void setReplacedPages(RelationPages* pages)
+ {
+ rel_replaced_pages = pages;
+ }
+
bool delPages(thread_db* tdbb, TraNumber tran = MAX_TRA_NUMBER, RelationPages* aPages = NULL);
void retainPages(thread_db* tdbb, TraNumber oldNumber, TraNumber newNumber);
@@ -332,6 +342,7 @@ class jrd_rel : public pool_alloc
RelationPagesInstances* rel_pages_inst;
RelationPages rel_pages_base;
RelationPages* rel_pages_free;
+ RelationPages* rel_replaced_pages;
RelationPages* getPagesInternal(thread_db* tdbb, TraNumber tran, bool allocPages);
@@ -348,6 +359,8 @@ class jrd_rel : public pool_alloc
void downgradeGCLock(thread_db* tdbb);
bool acquireGCLock(thread_db* tdbb, int wait);
+ void setPageSpace(ULONG pageSpaceId);
+
// This guard is used by regular code to prevent online validation while
// dead- or back- versions is removed from disk.
class GCShared
diff --git a/src/jrd/Statement.cpp b/src/jrd/Statement.cpp
index 80fd372e971..51eea2cfdb2 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/recsrc/Cursor.h"
using namespace Firebird;
@@ -107,6 +108,8 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb)
// a little complicated since relation locks MUST be taken before
// index locks.
+ USHORT usedTablespaces[TRANS_PAGE_SPACE] = {0};
+
for (Resource* resource = resources.begin(); resource != resources.end(); ++resource)
{
switch (resource->rsc_type)
@@ -115,6 +118,7 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb)
{
jrd_rel* relation = resource->rsc_rel;
MET_post_existence(tdbb, relation);
+ usedTablespaces[relation->getBasePages()->rel_pg_space_id]++;
break;
}
@@ -129,6 +133,8 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb)
LCK_lock(tdbb, index->idl_lock, LCK_SR, LCK_WAIT);
}
}
+ const ULONG pageSpaceId = MET_index_pagespace(tdbb, relation, resource->rsc_id);
+ usedTablespaces[pageSpaceId]++;
break;
}
@@ -161,6 +167,15 @@ Statement::Statement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb)
}
}
+ // Now let's lock all used tablespaces
+ for (ULONG i = DB_PAGE_SPACE + 1; i < TRANS_PAGE_SPACE; i++)
+ if (usedTablespaces[i] > 0)
+ {
+ // Tablespace is locking after the use in relation and indices.
+ // Should we check it here again?
+ MET_tablespace_id(tdbb, i)->addRef(tdbb);
+ }
+
// make a vector of all used RSEs
fors = csb->csb_fors;
csb->csb_fors.clear();
@@ -675,6 +690,8 @@ void Statement::release(thread_db* tdbb)
// Release existence locks on references.
+ USHORT usedTablespaces[TRANS_PAGE_SPACE] = {0};
+
for (Resource* resource = resources.begin(); resource != resources.end(); ++resource)
{
switch (resource->rsc_type)
@@ -683,6 +700,7 @@ void Statement::release(thread_db* tdbb)
{
jrd_rel* relation = resource->rsc_rel;
MET_release_existence(tdbb, relation);
+ usedTablespaces[relation->getBasePages()->rel_pg_space_id]++;
break;
}
@@ -696,6 +714,8 @@ void Statement::release(thread_db* tdbb)
if (!index->idl_count)
LCK_release(tdbb, index->idl_lock);
}
+ const ULONG pageSpaceId = MET_index_pagespace(tdbb, relation, resource->rsc_id);
+ usedTablespaces[pageSpaceId]++;
break;
}
@@ -717,6 +737,19 @@ void Statement::release(thread_db* tdbb)
}
}
+ // Now let's release all used tablespaces
+ for (ULONG i = DB_PAGE_SPACE + 1; i < TRANS_PAGE_SPACE; i++)
+ if (usedTablespaces[i] > 0)
+ {
+ // Tablespace is locking after the use in relation and indices.
+ // Should we check it here again?
+ Tablespace* ts = tdbb->getAttachment()->getTablespace(i);
+ fb_assert(ts);
+
+ if (ts)
+ ts->release(tdbb);
+ }
+
for (Request** instance = requests.begin(); instance != requests.end(); ++instance)
{
if (*instance)
diff --git a/src/jrd/Tablespace.cpp b/src/jrd/Tablespace.cpp
new file mode 100644
index 00000000000..8e70e8c4aa8
--- /dev/null
+++ b/src/jrd/Tablespace.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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/Tablespace.h"
+#include "../jrd/lck_proto.h"
+
+using namespace Firebird;
+
+
+namespace Jrd {
+
+Tablespace::~Tablespace()
+{
+ fb_assert(useCount == 0);
+ delete existenceLock;
+}
+
+
+void Tablespace::addRef(thread_db *tdbb)
+{
+ useCount++;
+ /*if (useCount == 1)
+ {
+ LCK_lock(tdbb, existenceLock, LCK_SR, LCK_WAIT);
+ }*/
+}
+
+void Tablespace::release(thread_db *tdbb)
+{
+ /*if (useCount == 1)
+ {
+ LCK_release(tdbb, existenceLock);
+ }*/
+ useCount--;
+}
+
+
+} // namespace Jrd
diff --git a/src/jrd/Tablespace.h b/src/jrd/Tablespace.h
new file mode 100644
index 00000000000..79bf4c2befa
--- /dev/null
+++ b/src/jrd/Tablespace.h
@@ -0,0 +1,73 @@
+/*
+ * 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"
+
+namespace Jrd
+{
+ class thread_db;
+ class CompilerScratch;
+ class JrdStatement;
+ class Lock;
+ class Format;
+ class Parameter;
+
+ class Tablespace
+ {
+ friend class Attachment;
+ public:
+ explicit Tablespace(MemoryPool& p)
+ : id(DB_PAGE_SPACE), name(p)
+ {}
+
+ Tablespace(MemoryPool& p, ULONG tsId, const MetaName& tsName)
+ : id(tsId), name(p, tsName)
+ {}
+
+ ~Tablespace();
+
+ ULONG id; // tablespace id = pagespace id
+ MetaName name; // tablespace name
+ Lock* existenceLock = nullptr; // existence lock, if any
+ bool modified = false;
+
+ private:
+ int useCount = 0;
+
+ public:
+ void addRef(thread_db* tdbb);
+ void release(thread_db* tdbb);
+
+ bool isUsed() const
+ {
+ return (useCount > 0);
+ }
+
+ };
+}
+
+#endif // JRD_TABLESPACE_H
diff --git a/src/jrd/blb.cpp b/src/jrd/blb.cpp
index 351fd363dc5..abe590098d1 100644
--- a/src/jrd/blb.cpp
+++ b/src/jrd/blb.cpp
@@ -81,7 +81,6 @@ typedef Ods::blob_page blob_page;
static ArrayField* alloc_array(jrd_tra*, Ods::InternalArrayDesc*);
//static blb* allocate_blob(thread_db*, jrd_tra*);
static ISC_STATUS blob_filter(USHORT, BlobControl*);
-//static blb* copy_blob(thread_db*, const bid*, bid*, USHORT, const UCHAR*, USHORT);
//static void delete_blob(thread_db*, blb*, ULONG);
//static void delete_blob_id(thread_db*, const bid*, ULONG, jrd_rel*);
static ArrayField* find_array(jrd_tra*, const bid*);
@@ -1095,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);
@@ -1142,7 +1141,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);
@@ -2139,7 +2138,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)
{
/**************************************
*
@@ -2206,11 +2205,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)
{
@@ -2488,7 +2487,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 1a57843ee60..17f1fb9d0ed 100644
--- a/src/jrd/blb.h
+++ b/src/jrd/blb.h
@@ -133,7 +133,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*);
@@ -162,7 +162,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 bf010642fa1..8f1f5458c54 100644
--- a/src/jrd/btr.cpp
+++ b/src/jrd/btr.cpp
@@ -194,10 +194,13 @@ static void compress(thread_db*, const dsc*, const SSHORT scale, temporary_key*,
static USHORT compress_root(thread_db*, index_root_page*);
static void copy_key(const temporary_key*, temporary_key*);
static contents delete_node(thread_db*, WIN*, UCHAR*);
-static void delete_tree(thread_db*, USHORT, USHORT, PageNumber, PageNumber);
-static ULONG fast_load(thread_db*, IndexCreation&, SelectivityList&);
-static index_root_page* fetch_root(thread_db*, WIN*, const jrd_rel*, const RelationPages*);
+void delete_tree(thread_db* tdbb, USHORT, USHORT, PageNumber, PageNumber); //RS: Should it become a part of BTR_proto.h?
+
+template
+static ULONG fast_load(thread_db*, IndexCreation&, SelectivityList&, RS *scb);
+
+static index_root_page* fetch_root(thread_db*, WIN*, const jrd_rel*, const RelationPages*, bool = false);
static UCHAR* find_node_start_point(btree_page*, temporary_key*, UCHAR*, USHORT*,
bool, int, bool = false, RecordNumber = NO_VALUE);
@@ -882,16 +885,20 @@ void BTR_create(thread_db* tdbb,
jrd_rel* const relation = creation.relation;
index_desc* const idx = creation.index;
+ RelationPages* const relPages = relation->getPages(tdbb);
+
+ if (relation->isTemporary())
+ idx->idx_pg_space_id = relPages->rel_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);
// Index is created. Go back to the index root page and update it to
// point to the index.
- RelationPages* const relPages = relation->getPages(tdbb);
WIN window(relPages->rel_pg_space_id, relPages->rel_index_root);
index_root_page* const root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_root);
CCH_MARK(tdbb, &window);
- root->irt_rpt[idx->idx_id].setRoot(idx->idx_root);
+ root->irt_rpt[idx->idx_id].setRoot(idx->idx_pg_space_id, idx->idx_root);
update_selectivity(root, idx->idx_id, selectivity);
CCH_RELEASE(tdbb, &window);
@@ -924,18 +931,39 @@ bool BTR_delete_index(thread_db* tdbb, WIN* window, USHORT id)
else
{
index_root_page::irt_repeat* irt_desc = root->irt_rpt + id;
+ const ULONG rootPage = irt_desc->getRootPage();
+ const ULONG pg_space_id = irt_desc->getRootPageSpaceId();
+
+ if (rootPage && pg_space_id)
+ {
+ if (PageSpace::isTablespace(pg_space_id))
+ {
+ try
+ {
+ MET_tablespace_id(tdbb, pg_space_id);
+ }
+ catch (...)
+ {
+ CCH_RELEASE(tdbb, window);
+ throw;
+ }
+ }
+ }
+
CCH_MARK(tdbb, window);
- const ULONG rootPage = irt_desc->getRoot();
- const PageNumber next(window->win_page.getPageSpaceID(), rootPage);
+
+ const PageNumber next(pg_space_id, rootPage);
tree_exists = (rootPage != 0);
// 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 (tree_exists)
+ delete_tree(tdbb, relation_id, id, next, prior);
}
return tree_exists;
@@ -963,12 +991,13 @@ bool BTR_description(thread_db* tdbb, jrd_rel* relation, index_root_page* root,
const index_root_page::irt_repeat* irt_desc = &root->irt_rpt[id];
- const ULONG rootPage = irt_desc->getRoot();
+ const ULONG rootPage = irt_desc->getRootPage();
if (!rootPage)
return false;
idx->idx_id = id;
idx->idx_root = rootPage;
+ idx->idx_pg_space_id = irt_desc->getRootPageSpaceId();
idx->idx_count = irt_desc->irt_keys;
idx->idx_flags = irt_desc->irt_flags;
idx->idx_runtime_flags = 0;
@@ -1042,6 +1071,12 @@ bool BTR_description(thread_db* tdbb, jrd_rel* relation, index_root_page* root,
CCH_unwind(tdbb, true);
}
+ if (PageSpace::isTablespace(idx->idx_pg_space_id) &&
+ !MET_tablespace_id(tdbb, idx->idx_pg_space_id))
+ {
+ BUGCHECK(307); // msg 307 Tablespace not found
+ }
+
return true;
}
@@ -1142,6 +1177,7 @@ void BTR_evaluate(thread_db* tdbb, const IndexRetrieval* retrieval, RecordBitmap
SET_TDBB(tdbb);
RelationPages* relPages = retrieval->irb_relation->getPages(tdbb);
+ // In BTR_find_page pagespace can be changed to index's one
WIN window(relPages->rel_pg_space_id, -1);
temporary_key lowerKey, upperKey;
@@ -1338,7 +1374,9 @@ btree_page* BTR_find_page(thread_db* tdbb,
SET_TDBB(tdbb);
RelationPages* relPages = retrieval->irb_relation->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
+ window->win_page.setPageSpaceID(relPages->rel_pg_space_id);
window->win_page = relPages->rel_index_root;
index_root_page* rpage = (index_root_page*) CCH_FETCH(tdbb, window, LCK_read, pag_root);
@@ -1349,7 +1387,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
@@ -1425,8 +1463,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;
@@ -1453,7 +1490,7 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion)
// update the index root page. Oh boy.
index_root_page* root = (index_root_page*) CCH_FETCH(tdbb, root_window, LCK_write, pag_root);
- window.win_page = root->irt_rpt[idx->idx_id].getRoot();
+ window.win_page = root->irt_rpt[idx->idx_id].getRootPage();
bucket = (btree_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_index);
if (window.win_page.getPageNum() != idx->idx_root)
@@ -1503,7 +1540,7 @@ 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();
+ window.win_page = root->irt_rpt[idx->idx_id].getRootPage();
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);
@@ -1517,7 +1554,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)
@@ -1588,9 +1625,11 @@ 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());
+ root->irt_rpt[idx->idx_id].setRoot(newRootPage.getPageSpaceID(), newRootPage.getPageNum());
CCH_RELEASE(tdbb, root_window);
}
@@ -2187,8 +2226,7 @@ void BTR_remove(thread_db* tdbb, WIN* root_window, index_insertion* insertion)
//const Database* dbb = tdbb->getDatabase();
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
@@ -2234,7 +2272,7 @@ 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);
+ root->irt_rpt[idx->idx_id].setRoot(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
@@ -2407,19 +2445,20 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, USHORT id, SelectivityL
if (!root)
return;
- if (id >= root->irt_count || !root->irt_rpt[id].getRoot())
+ if (id >= root->irt_count || !root->irt_rpt[id].getRootPage())
{
CCH_RELEASE(tdbb, &window);
return;
}
- ULONG page = root->irt_rpt[id].getRoot();
+ ULONG page = root->irt_rpt[id].getRootPage();
+ const ULONG pageSpaceId = root->irt_rpt[id].getRootPageSpaceId();
const bool descending = (root->irt_rpt[id].irt_flags & irt_descending);
const ULONG segments = root->irt_rpt[id].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(pageSpaceId, page), LCK_read, pag_index);
// go down the left side of the index to leaf level
UCHAR* pointer = bucket->btr_nodes + bucket->btr_jump_size;
@@ -2578,7 +2617,7 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, USHORT id, SelectivityL
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 = PageNumber(relPages->rel_pg_space_id, relPages->rel_index_root);
window.win_flags = 0;
root = (index_root_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_root);
CCH_MARK(tdbb, &window);
@@ -2635,6 +2674,145 @@ 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)
+ {
+ *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
+ *record = 0;
+ }
+
+private:
+ WIN* window;
+ UCHAR* pointer;
+ UCHAR* end;
+ USHORT key_length;
+ IndexNode node;
+ Array recordData;
+ index_sort_record* isr;
+ int nullIndLen;
+};
+
+bool BTR_move_index(Jrd::thread_db* tdbb, Jrd::jrd_rel* relation, SLONG indexId, ULONG pageSpaceId, PageNumber& oldRootPage)
+{
+/**************************************
+ *
+ * B T R _ m o v e _ i n d e x
+ *
+ **************************************
+ *
+ * Functional description
+ * Move index pages from its tablespace to new one.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+
+ bool result = false;
+
+ // Lets start copy index pages
+ WIN rootWindow(INVALID_PAGE_SPACE, -1);
+ index_root_page* root = fetch_root(tdbb, &rootWindow, relation, relation->getPages(tdbb), true);
+
+ index_desc idx;
+ if (BTR_description(tdbb, relation, root, &idx, indexId))
+ {
+ oldRootPage = PageNumber(idx.idx_pg_space_id, idx.idx_root);
+
+ // Set page space id of the new tablespace
+ idx.idx_pg_space_id = pageSpaceId;
+
+ IndexCreation creation;
+ creation.index = &idx;
+ creation.relation = relation;
+ //creation.sort is unused in this fast_load call
+ //creation.transaction is unused in this fast_load call
+ creation.dup_recno = -1;
+ creation.duplicates = 0;
+
+ const int nullIndLen = !(idx.idx_flags & idx_descending) && (idx.idx_count == 1) ? 1 : 0;
+ creation.key_length = ROUNDUP(BTR_key_length(tdbb, relation, &idx) + nullIndLen, sizeof(SINT64));;
+
+ // 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;
+
+ const ULONG newRootPage = fast_load(tdbb, creation, selectivity, &indexNodes);
+
+ CCH_RELEASE(tdbb, &window);
+
+ CCH_MARK(tdbb, &rootWindow);
+ root->irt_rpt[indexId].setRoot(pageSpaceId, newRootPage);
+ CCH_RELEASE(tdbb, &rootWindow);
+
+ result = true;
+ }
+ else
+ CCH_RELEASE(tdbb, &rootWindow);
+
+ return result;
+}
+
static ULONG add_node(thread_db* tdbb,
WIN* window,
@@ -3348,7 +3526,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;
@@ -3673,7 +3851,7 @@ static contents delete_node(thread_db* tdbb, WIN* window, UCHAR* pointer)
}
-static void delete_tree(thread_db* tdbb,
+void delete_tree(thread_db* tdbb,
USHORT rel_id, USHORT idx_id, PageNumber next, PageNumber prior)
{
/**************************************
@@ -3740,9 +3918,11 @@ static void delete_tree(thread_db* tdbb,
}
+
+template
static ULONG fast_load(thread_db* tdbb,
IndexCreation& creation,
- SelectivityList& selectivity)
+ SelectivityList& selectivity, RS* scb)
{
/**************************************
*
@@ -3768,7 +3948,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.
@@ -3886,7 +4066,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;
@@ -4527,7 +4707,7 @@ static ULONG fast_load(thread_db* tdbb,
static index_root_page* fetch_root(thread_db* tdbb, WIN* window, const jrd_rel* relation,
- const RelationPages* relPages)
+ const RelationPages* relPages, bool writeLock)
{
/**************************************
*
@@ -4543,20 +4723,21 @@ static index_root_page* fetch_root(thread_db* tdbb, WIN* window, const jrd_rel*
**************************************/
SET_TDBB(tdbb);
- if ((window->win_page = relPages->rel_index_root) == 0)
+ if (!relPages->rel_index_root)
{
- if (relation->rel_id == 0)
- return NULL;
+// if (relation->rel_id == 0)
+// return NULL;
- DPM_scan_pages(tdbb);
+ DPM_scan_pages(tdbb, pag_root, relation->rel_id);
if (!relPages->rel_index_root)
return NULL;
- window->win_page = relPages->rel_index_root;
}
- return (index_root_page*) CCH_FETCH(tdbb, window, LCK_read, pag_root);
+ window->win_page = PageNumber(relPages->rel_pg_space_id, relPages->rel_index_root);
+
+ return (index_root_page*) CCH_FETCH(tdbb, window, writeLock ? LCK_write : LCK_read, pag_root);
}
@@ -5128,7 +5309,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;
@@ -5786,7 +5967,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 de47709ae13..d549c4465f9 100644
--- a/src/jrd/btr.h
+++ b/src/jrd/btr.h
@@ -72,6 +72,8 @@ struct index_desc
BoolExprNode* idx_condition; // node tree for index condition
Statement* idx_condition_statement; // stored statement for index condition
float idx_fraction; // fraction of keys included in the index
+
+ ULONG idx_pg_space_id; // PageSpace 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 4eb3f8a8073..c04a6b3e5a8 100644
--- a/src/jrd/btr_proto.h
+++ b/src/jrd/btr_proto.h
@@ -53,5 +53,6 @@ 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::jrd_rel*, USHORT, Jrd::SelectivityList&);
bool BTR_types_comparable(const dsc& target, const dsc& source);
+bool BTR_move_index(Jrd::thread_db*, Jrd::jrd_rel*, SLONG, ULONG, Jrd::PageNumber&);
#endif // JRD_BTR_PROTO_H
diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp
index d660ce17bf1..03908a872b7 100644
--- a/src/jrd/cch.cpp
+++ b/src/jrd/cch.cpp
@@ -190,7 +190,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);
@@ -1187,7 +1187,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)
{
/**************************************
*
@@ -1229,7 +1229,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
@@ -1438,7 +1438,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)
{
/**************************************
@@ -1472,8 +1472,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;
@@ -1487,7 +1488,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);
@@ -1496,7 +1497,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)
@@ -1800,7 +1801,7 @@ void CCH_must_write(thread_db* tdbb, WIN* window)
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));
@@ -1941,9 +1942,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.getPageSpaceID()))
return true;
// Take backup state lock
@@ -2708,7 +2707,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();
@@ -2731,28 +2730,33 @@ 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.getPageSpaceID() == page_space_id)
{
- 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))
+ if (bdb->bdb_flags & (BDB_db_dirty | BDB_dirty))
+ {
+ 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);
+ }
}
}
}
@@ -3201,7 +3205,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;
@@ -3213,6 +3219,8 @@ static void check_precedence(thread_db* tdbb, WIN* window, PageNumber page)
break;
default:
+ if (PageSpace::isTablespace(pageSpaceId))
+ break;
fb_assert(false);
return;
}
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 54102cb4a54..567bfe4f7ce 100644
--- a/src/jrd/constants.h
+++ b/src/jrd/constants.h
@@ -412,7 +412,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;
@@ -468,6 +471,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)
@@ -505,4 +511,7 @@ 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";
+
#endif // JRD_CONSTANTS_H
diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp
index 8c0cf546351..3b8714ef32a 100644
--- a/src/jrd/dfw.epp
+++ b/src/jrd/dfw.epp
@@ -128,6 +128,8 @@
#include "../jrd/CryptoManager.h"
#include "../jrd/Mapping.h"
#include "../jrd/shut_proto.h"
+#include "../jrd/Tablespace.h"
+#include "../jrd/vio_proto.h"
#ifdef HAVE_UNISTD_H
#include
@@ -500,6 +502,11 @@ 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 string remove_icu_info_from_attributes(const QualifiedName&, const string&);
+static bool create_tablespace(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
+static bool delete_tablespace(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
+static bool modify_tablespace(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
+static bool move_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
+static bool move_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
// ----------------------------------------------------------------
@@ -1333,6 +1340,11 @@ 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, create_tablespace },
+ { dfw_delete_tablespace, delete_tablespace },
+ { dfw_modify_tablespace, modify_tablespace },
+ { dfw_move_relation, move_relation },
+ { dfw_move_index, move_index },
{ dfw_null, NULL }
};
@@ -1640,6 +1652,9 @@ void DFW_perform_work(thread_db* tdbb, jrd_tra* transaction)
{
case dfw_post_event:
case dfw_delete_shadow:
+ case dfw_clear_datapages:
+ case dfw_clear_indexpages:
+ case dfw_delete_tablespace:
break;
default:
@@ -1655,8 +1670,9 @@ void DFW_perform_work(thread_db* tdbb, jrd_tra* transaction)
}
}
+void delete_tree(thread_db* tdbb, USHORT, USHORT, PageNumber, PageNumber);
-void DFW_perform_post_commit_work(jrd_tra* transaction)
+void DFW_perform_post_commit_work(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
*
@@ -1678,7 +1694,8 @@ void DFW_perform_post_commit_work(jrd_tra* transaction)
bool pending_events = false;
- Database* dbb = GET_DBB();
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
for (DeferredWork* itr = transaction->tra_deferred_job->work; itr;)
{
@@ -1694,17 +1711,62 @@ void DFW_perform_post_commit_work(jrd_tra* transaction)
work->dfw_name.c_str(),
work->dfw_count);
- delete work;
pending_events = true;
break;
+ case dfw_delete_tablespace:
case dfw_delete_shadow:
if (work->dfw_name.hasData())
unlink(work->dfw_name.c_str());
- delete work;
break;
+ case dfw_clear_datapages:
+ {
+ jrd_rel* relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
+
+ // A relation may already be deleted by dfw_delete_relation in the same transaction
+ if (!relation)
+ {
+ CCH_release_exclusive(tdbb);
+ break;
+ }
+
+ RelationPages* relationPages = relation->getPages(tdbb);
+ DPM_delete_relation_pages(tdbb, relation, relationPages);
+
+ RelationPages* newRelationPages = relation->getReplacedPages();
+ // Regenerate RDB$PAGES for the new relation
+ {
+ AutoRequest handle;
+
+ DPM_pages(tdbb, relation->rel_id, pag_root, 0, newRelationPages->rel_index_root);
+ ULONG* page = &(*newRelationPages->rel_pages)[0];
+ const FB_SIZE_T n = newRelationPages->rel_pages->count();
+ for (FB_SIZE_T seq = 0; seq < n; seq++, page++)
+ DPM_pages(tdbb, relation->rel_id, pag_pointer, seq, *page);
+ }
+
+ CCH_flush(tdbb, FLUSH_SYSTEM, 0);
+
+ *relationPages = *newRelationPages;
+
+ CCH_release_exclusive(tdbb);
+ }
+ break;
+ case dfw_clear_indexpages:
+ {
+ SortedArray& ids = DFW_get_ids(work);
+ fb_assert(ids.getCount() == 3);
+ const SLONG indexId = ids[0];
+ const PageNumber oldRootPage(ids[1], ids[2]);
+ delete_tree(tdbb, work->dfw_id, indexId, oldRootPage, PageNumber());
+ }
+
+ break;
+
default:
break;
}
+
+ delete work;
}
if (pending_events)
@@ -2591,6 +2653,8 @@ static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork*
jrd_rel* relation = nullptr;
CompilerScratch* csb = nullptr;
+ ULONG tableSpaceId = DB_PAGE_SPACE;
+
const auto dbb = tdbb->getDatabase();
const auto attachment = tdbb->getAttachment();
@@ -2612,6 +2676,9 @@ static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork*
if (relation->rel_name.object.isEmpty())
relation->rel_name = QualifiedName(REL.RDB$RELATION_NAME, REL.RDB$SCHEMA_NAME);
+ if (!IDX.RDB$TABLESPACE_NAME.NULL)
+ tableSpaceId = MET_tablespace(tdbb, IDX.RDB$TABLESPACE_NAME)->id;
+
if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0)
{
SelectivityList selectivity(*tdbb->getDefaultPool());
@@ -2731,6 +2798,7 @@ static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork*
{
fb_assert(work->dfw_id <= dbb->dbb_max_idx);
idx.idx_id = work->dfw_id;
+ idx.idx_pg_space_id = tableSpaceId;
IDX_create_index(tdbb, relation, &idx, work->getQualifiedName(), &work->dfw_id,
transaction, selectivity);
@@ -3278,6 +3346,8 @@ static bool create_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_
relation = NULL;
idx.idx_flags = 0;
+ ULONG tableSpaceId = DB_PAGE_SPACE;
+
// Fetch the information necessary to create the index. On the first
// time thru, check to see if the index already exists. If so, delete
// it. If the index inactive flag is set, don't create the index
@@ -3298,6 +3368,9 @@ static bool create_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_
// Msg308: can't create index %s
}
+ if (!IDX.RDB$TABLESPACE_NAME.NULL)
+ tableSpaceId = MET_tablespace(tdbb, IDX.RDB$TABLESPACE_NAME)->id;
+
if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0)
{
// we need to know if this relation is temporary or not
@@ -3573,6 +3646,7 @@ static bool create_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_
fb_assert(work->dfw_id <= dbb->dbb_max_idx);
idx.idx_id = work->dfw_id;
+ idx.idx_pg_space_id = tableSpaceId;
SelectivityList selectivity(*tdbb->getDefaultPool());
IDX_create_index(tdbb, relation, &idx, work->getQualifiedName(),
&work->dfw_id, transaction, selectivity);
@@ -4767,7 +4841,16 @@ static bool delete_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_
index = CMP_get_index_lock(tdbb, relation, id);
if (isTempIndex && index)
index->idl_count++;
- IDX_delete_index(tdbb, relation, id);
+
+ try
+ {
+ IDX_delete_index(tdbb, relation, id);
+ }
+ catch (...)
+ {
+ index->idl_count = 0;
+ throw;
+ }
if (isTempIndex)
return false;
@@ -6564,6 +6647,388 @@ static bool change_repl_state(thread_db* tdbb, SSHORT phase, DeferredWork* work,
}
+static bool create_tablespace(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * c r e a t e _ t a b l e s p a c e
+ *
+ **************************************
+ *
+ * Functional description
+ * Create tablespace file and format it.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+
+ switch (phase)
+ {
+ case 0:
+ {
+ fb_assert(work->dfw_id > DB_PAGE_SPACE);
+
+ AutoCacheRequest request(tdbb, irq_find_ts_dfw0, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$TABLESPACE_ID EQ work->dfw_id
+ {
+ if (dbb->dbb_page_manager.findPageSpace(work->dfw_id))
+ {
+ CCH_flush(tdbb, FLUSH_ALL, 0, work->dfw_id);
+ dbb->dbb_page_manager.delPageSpace(work->dfw_id);
+ unlink(TS.RDB$FILE_NAME);
+ }
+
+ Attachment* attachment = tdbb->getAttachment();
+ Tablespace* tablespace = attachment->getTablespace(work->dfw_id);
+
+ if (tablespace)
+ {
+ LCK_release(tdbb, tablespace->existenceLock);
+ attachment->delTablespace(work->dfw_id);
+ }
+ }
+ END_FOR
+ }
+ return false;
+
+ case 1:
+ return true;
+
+ case 2:
+ {
+ fb_assert(work->dfw_id > DB_PAGE_SPACE);
+
+ AutoCacheRequest request(tdbb, irq_find_ts_dfw, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$TABLESPACE_ID EQ work->dfw_id
+ {
+ PathName file = TS.RDB$FILE_NAME;
+ dbb->dbb_page_manager.allocTableSpace(tdbb, work->dfw_id, true, file);
+ }
+ END_FOR
+ }
+ return true;
+
+ case 3:
+ case 4:
+ break;
+ }
+
+ return false;
+}
+
+
+static bool delete_tablespace(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * d e l e t e _ t a b l e s p a c e
+ *
+ **************************************
+ *
+ * Functional description
+ * Drop tablespace and remove its file at the post-commit stage.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ Attachment* attachment = tdbb->getAttachment();
+
+ Tablespace* tablespace = MET_tablespace_id(tdbb, work->dfw_id);
+
+ // We assume the tablespace must exists if dfw with has added
+ fb_assert(tablespace);
+ fb_assert(tablespace->existenceLock);
+ fb_assert(tablespace->existenceLock->lck_logical != LCK_none);
+
+ switch (phase)
+ {
+ case 0:
+ LCK_convert(tdbb, tablespace->existenceLock, LCK_SR, transaction->getLockWait());
+ return false;
+
+ case 1:
+ {
+ // Make sure that nobody uses the tablespace
+ if (tablespace->isUsed() ||
+ !LCK_convert(tdbb, tablespace->existenceLock, LCK_EX, transaction->getLockWait()))
+ {
+ raiseObjectInUseError("TABLESPACE", QualifiedName(tablespace->name));
+ }
+ }
+ return true;
+
+ case 2:
+ case 3:
+ return true;
+ case 4:
+ CCH_flush(tdbb, FLUSH_ALL, 0, work->dfw_id);
+ dbb->dbb_page_manager.delPageSpace(work->dfw_id);
+
+ LCK_release(tdbb, tablespace->existenceLock);
+
+ attachment->delTablespace(work->dfw_id);
+
+ break;
+ }
+
+ return false;
+}
+
+static bool modify_tablespace(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * m o d i f y _ t a b l e s p a c e
+ *
+ **************************************
+ *
+ * Functional description
+ * Modify tablespace.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ Attachment* attachment = tdbb->getAttachment();
+
+ Tablespace* tablespace = attachment->getTablespace(work->dfw_id);
+
+ // We assume the tablespace must exists if dfw with has added
+ fb_assert(tablespace);
+ fb_assert(tablespace->existenceLock);
+ fb_assert(tablespace->existenceLock->lck_logical != LCK_none);
+
+ switch (phase)
+ {
+ case 0:
+ LCK_convert(tdbb, tablespace->existenceLock, LCK_SR, transaction->getLockWait());
+ return false;
+
+ case 1:
+ {
+ // Make sure that nobody uses the tablespace
+ if (tablespace->isUsed() ||
+ !LCK_convert(tdbb, tablespace->existenceLock, LCK_EX, transaction->getLockWait()))
+ {
+ raiseObjectInUseError("TABLESPACE", QualifiedName(tablespace->name));
+ }
+ }
+ return true;
+
+ case 2:
+ case 3:
+ return true;
+ case 4:
+ CCH_flush(tdbb, FLUSH_ALL, 0, work->dfw_id);
+ dbb->dbb_page_manager.delPageSpace(work->dfw_id);
+
+ LCK_release(tdbb, tablespace->existenceLock);
+
+ attachment->delTablespace(work->dfw_id);
+
+ break;
+ }
+
+ return false;
+}
+
+static bool move_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * m o v e _ r e l a t i o n
+ *
+ **************************************
+ *
+ * Functional description
+ * Move relation from its tablespace to new one.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ Attachment* attachment = tdbb->getAttachment();
+
+ switch (phase)
+ {
+ case 0:
+ // Probably it's good to check here that we have reverted both the first
+ // pointer page and index root or force it.
+ CCH_release_exclusive(tdbb);
+ break;
+
+ case 1:
+ // We ask EX lock on DB to prevent relation changing and keep data consistent.
+ // This is a temporary solution. It's better to migrate data of a tablespace
+ // without locking the whole DB but with locking only relation.
+ if (!CCH_exclusive(tdbb, LCK_EX, WAIT_PERIOD, NULL))
+ raiseDatabaseInUseError(true);
+ return true;
+
+ case 2:
+ {
+ jrd_rel* relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
+ RelationPages* newRelationPages = FB_NEW_POOL(*relation->rel_pool) RelationPages(*relation->rel_pool);
+
+ // Metadata was changed at Ddl stage and now contains new pagespace id
+ newRelationPages->rel_pg_space_id = MET_rel_pagespace(tdbb, relation->rel_id);
+
+ // Scan pages before cleanup relation. Maybe redundant?
+ // Or maybe to scan starting from the last known sequence?
+ DPM_scan_pages(tdbb, pag_pointer, relation->rel_id);
+ DPM_scan_pages(tdbb, pag_root, relation->rel_id);
+
+ RelationPages* relationPages = relation->getPages(tdbb);
+
+ // Copy index root page to a new relation tablespace
+ // And free previous one
+ WIN oldWindow(relationPages->rel_pg_space_id, relationPages->rel_index_root);
+ Ods::pag* oldPage = CCH_FETCH(tdbb, &oldWindow, LCK_read, pag_root);
+
+ WIN newWindow(newRelationPages->rel_pg_space_id, -1);
+ Ods::pag* newPage = DPM_allocate(tdbb, &newWindow);
+ CCH_MARK_MUST_WRITE(tdbb, &newWindow);
+ memcpy(newPage, oldPage, dbb->dbb_page_size);
+ newPage->pag_pageno = newWindow.win_page.getPageNum();
+ newRelationPages->rel_index_root = newWindow.win_page.getPageNum();
+
+ CCH_RELEASE(tdbb, &newWindow);
+ CCH_RELEASE(tdbb, &oldWindow);
+
+ DPM_move_data_pages(tdbb, relation, newRelationPages);
+
+ relation->setReplacedPages(newRelationPages);
+
+ { // We delete all from RDB$PAGES about the relation to have an ability to understand that
+ // we need to restore pages if transaction won't be able to finish successfully.
+ AutoRequest handle;
+
+ FOR(REQUEST_HANDLE handle) X IN RDB$PAGES
+ WITH (X.RDB$RELATION_ID EQ relation->rel_id)
+ SORTED BY DESCENDING X.RDB$PAGE_SEQUENCE
+ {
+ ERASE X;
+ }
+ END_FOR
+ }
+
+ // Post commit work will clean up old pages. It must be done exactly after commit
+ // If crash is happend the metadata will point to the old page space and new ones
+ // will be garbage. Right after commit old pages will be garbage.
+ DFW_post_work(transaction, dfw_clear_datapages, {}, {}, relation->rel_id);
+ }
+ break;
+
+ case 3:
+ case 4:
+ break;
+ }
+
+ return false;
+}
+
+
+static bool move_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * m o v e _ i n d e x
+ *
+ **************************************
+ *
+ * Functional description
+ * Move index pages from its tablespace to new one.
+ *
+ **************************************/
+
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ Attachment* attachment = tdbb->getAttachment();
+
+ switch (phase)
+ {
+ case 0:
+ CCH_release_exclusive(tdbb);
+ break;
+
+ case 1:
+ // We ask EX lock on DB to prevent relation changing and keep data consistent.
+ // This is a temporary solution. It's better to migrate data of a tablespace
+ // without locking the whole DB but with locking only relation.
+ if (!CCH_exclusive(tdbb, LCK_EX, WAIT_PERIOD, NULL))
+ raiseDatabaseInUseError(true);
+ return true;
+
+ case 2:
+ {
+ // Fetch relation and index id by index name
+ PreparedStatement::Builder sql;
+ jrd_rel* relation = NULL;
+ SLONG relationId;
+ SLONG indexId;
+ MetaName tableSpace;
+ sql << "select"
+ << sql("rel.rdb$relation_id,", relationId)
+ << sql("idx.rdb$index_id,", indexId)
+ << sql("idx.rdb$tablespace_name", tableSpace)
+ << "from rdb$indices idx join rdb$relations rel using (rdb$relation_name)"
+ << "where idx.rdb$index_name = " << work->dfw_name
+ << " and rel.rdb$relation_id is not null";
+ AutoPreparedStatement ps(attachment->prepareStatement(tdbb,
+ attachment->getSysTransaction(), sql));
+ AutoResultSet rs(ps->executeQuery(tdbb, attachment->getSysTransaction()));
+
+ while (rs->fetch(tdbb))
+ {
+ relation = MET_lookup_relation_id(tdbb, relationId, false);
+ indexId--; // ID of a index starts from 1 in metadata and from 0 in code :)
+ break;
+ }
+
+ // An index may be deleted in the same transaction.
+ // Don't move index pages in this case.
+ if (!relation)
+ {
+ CCH_release_exclusive(tdbb);
+ break;
+ }
+
+ const ULONG newPageSpaceId = MET_tablespace(tdbb, tableSpace)->id;
+
+ PageNumber oldRootPage;
+ if (BTR_move_index(tdbb, relation, indexId, newPageSpaceId, oldRootPage))
+ {
+ // Futher transaction must finish and flush its pages to the disk.
+ // It actually switched pointer. If server crash before it the database file will have the old data.
+ DeferredWork* work = DFW_post_work(transaction, dfw_clear_indexpages, {}, {}, relation->rel_id);
+ SortedArray& ids = DFW_get_ids(work);
+ ids.resize(3);
+ ids[0] = indexId;
+ ids[1] = oldRootPage.getPageSpaceID();
+ ids[2] = oldRootPage.getPageNum();
+ }
+
+ CCH_release_exclusive(tdbb);
+ }
+ break;
+
+ case 3:
+ case 4:
+ break;
+ }
+
+ return false;
+}
+
+
#ifdef NOT_USED_OR_REPLACED
static bool shadow_defined(thread_db* tdbb)
{
diff --git a/src/jrd/dfw_proto.h b/src/jrd/dfw_proto.h
index d424e9a0351..b0434614a4d 100644
--- a/src/jrd/dfw_proto.h
+++ b/src/jrd/dfw_proto.h
@@ -36,7 +36,7 @@ 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_perform_post_commit_work(Jrd::thread_db*, Jrd::jrd_tra*);
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 94c01d8564f..89a750561d4 100644
--- a/src/jrd/dpm.epp
+++ b/src/jrd/dpm.epp
@@ -62,6 +62,7 @@
#include "../jrd/pag_proto.h"
#include "../jrd/replication/Publisher.h"
#include "../common/StatusArg.h"
+#include "../jrd/ini.h"
DATABASE DB = FILENAME "ODS.RDB";
@@ -76,7 +77,7 @@ using namespace Firebird;
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 void extend_relation(thread_db*, jrd_rel*, WIN*, const Jrd::RecordStorageType type);
static UCHAR* find_space(thread_db*, record_param*, SSHORT, PageStack&, Record*, const Jrd::RecordStorageType type);
@@ -550,7 +551,24 @@ bool DPM_chain( thread_db* tdbb, record_param* org_rpb, record_param* new_rpb)
}
-void DPM_create_relation( thread_db* tdbb, jrd_rel* relation)
+static void update_first_pointer_page(thread_db* tdbb, USHORT rel_id, ULONG page, ULONG root)
+{
+ // Update the first pointer page transactionally.
+ AutoCacheRequest request(tdbb, irq_s_first_pp, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE tdbb->getTransaction())
+ R IN RDB$RELATIONS
+ WITH R.RDB$RELATION_ID EQ rel_id
+ {
+ MODIFY R USING
+ R.RDB$POINTER_PAGE = page;
+ R.RDB$ROOT_PAGE = root;
+ END_MODIFY;
+ }
+ END_FOR
+}
+
+void DPM_create_relation(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
*
@@ -579,6 +597,13 @@ void DPM_create_relation( thread_db* tdbb, jrd_rel* relation)
(*relPages->rel_pages)[0] /*window.win_page*/);
DPM_pages(tdbb, relation->rel_id, pag_root, (ULONG) 0,
relPages->rel_index_root /*root_window.win_page*/);
+
+ // RDB$PAGE_NUMBER is used only for non system tables
+ if (relation->rel_id < rel_MAX)
+ return;
+
+ // Update the first pointer page transactionally.
+ update_first_pointer_page(tdbb, relation->rel_id, (*relPages->rel_pages)[0], relPages->rel_index_root);
}
@@ -831,7 +856,7 @@ 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, -1); // Will be initialized by pagespace of relation later
for (;;)
{
@@ -1367,7 +1392,7 @@ SINT64 DPM_gen_id(thread_db* tdbb, SLONG generator, bool initialize, SINT64 val)
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)
@@ -2062,7 +2087,7 @@ ULONG DPM_pointer_pages(thread_db* tdbb, jrd_rel* relation)
}
-void DPM_scan_pages( thread_db* tdbb)
+void DPM_scan_pages(thread_db* tdbb, SCHAR pagType /*= 0*/, int relId /*= 0*/)
{
/**************************************
*
@@ -2106,45 +2131,100 @@ void DPM_scan_pages( thread_db* tdbb)
CCH_RELEASE(tdbb, &window);
+ if (!relPages->rel_index_root)
+ {
+ // Try to guess IRT number for RDB$PAGES, it should be next page after first PP
+ window.win_page = (*vector)[0] + 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 == 0)
+ relPages->rel_index_root = window.win_page.getPageNum();
+
+ CCH_RELEASE(tdbb, &window);
+ }
+
HalfStaticArray tipSeqList;
HalfStaticArray genSeqList;
- AutoCacheRequest request(tdbb, irq_r_pages, IRQ_REQUESTS);
+ if (pagType == 0 && relId == 0 || !relPages->rel_index_root)
+ {
+ AutoCacheRequest request(tdbb, irq_r_pages, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request) X IN RDB$PAGES
+ {
+ relation = MET_relation(tdbb, X.RDB$RELATION_ID);
+ relPages = relation->getBasePages();
+ 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->rel_pool, relPages->rel_pages, sequence + 1);
+ (*relPages->rel_pages)[sequence] = X.RDB$PAGE_NUMBER;
+ break;
+
+ case pag_transactions:
+ if (sequence >= tipSeqList.getCount())
+ tipSeqList.resize(sequence + 1);
+ tipSeqList[sequence] = X.RDB$PAGE_NUMBER;
+ break;
- FOR(REQUEST_HANDLE request) X IN RDB$PAGES
+ case pag_ids:
+ if (sequence >= genSeqList.getCount())
+ genSeqList.resize(sequence + 1);
+ genSeqList[sequence] = X.RDB$PAGE_NUMBER;
+ break;
+
+ default:
+ CORRUPT(257); // msg 257 bad record in RDB$PAGES
+ }
+ }
+ END_FOR
+ }
+ else
{
- relation = MET_relation(tdbb, X.RDB$RELATION_ID);
- relPages = relation->getBasePages();
- sequence = X.RDB$PAGE_SEQUENCE;
+ AutoCacheRequest request(tdbb, irq_r_pages2, IRQ_REQUESTS);
- switch (X.RDB$PAGE_TYPE)
+ FOR(REQUEST_HANDLE request) X IN RDB$PAGES
+ WITH X.RDB$RELATION_ID = relId
+ AND X.RDB$PAGE_TYPE = pagType
{
- case pag_root:
- relPages->rel_index_root = X.RDB$PAGE_NUMBER;
- break;
+ relation = MET_relation(tdbb, X.RDB$RELATION_ID);
+ relPages = relation->getBasePages();
+ sequence = X.RDB$PAGE_SEQUENCE;
- case pag_pointer:
- relPages->rel_pages = vcl::newVector(*relation->rel_pool, relPages->rel_pages, sequence + 1);
- (*relPages->rel_pages)[sequence] = X.RDB$PAGE_NUMBER;
- break;
+ switch (X.RDB$PAGE_TYPE)
+ {
+ case pag_root:
+ relPages->rel_index_root = X.RDB$PAGE_NUMBER;
+ break;
- case pag_transactions:
- if (sequence >= tipSeqList.getCount())
+ case pag_pointer:
+ relPages->rel_pages = vcl::newVector(*relation->rel_pool, relPages->rel_pages, sequence + 1);
+ (*relPages->rel_pages)[sequence] = X.RDB$PAGE_NUMBER;
+ break;
+
+ case pag_transactions:
tipSeqList.resize(sequence + 1);
- tipSeqList[sequence] = X.RDB$PAGE_NUMBER;
- break;
+ tipSeqList[sequence] = X.RDB$PAGE_NUMBER;
+ break;
- case pag_ids:
- if (sequence >= genSeqList.getCount())
+ case pag_ids:
genSeqList.resize(sequence + 1);
- genSeqList[sequence] = X.RDB$PAGE_NUMBER;
- break;
+ genSeqList[sequence] = X.RDB$PAGE_NUMBER;
+ break;
- default:
- CORRUPT(257); // msg 257 bad record in RDB$PAGES
+ default:
+ CORRUPT(257); // msg 257 bad record in RDB$PAGES
+ }
}
+ END_FOR
}
- END_FOR
if (const auto count = tipSeqList.getCount())
dbb->copyKnownPages(pag_transactions, count, tipSeqList.begin());
@@ -2633,7 +2713,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)
{
/**************************************
*
@@ -3326,6 +3406,25 @@ static bool get_header(WIN* window, USHORT line, record_param* rpb)
}
+static bool restore_pages(thread_db* tdbb, USHORT rel_id)
+{
+ Attachment* attachment = tdbb->getAttachment();
+
+ AutoRequest request;
+
+ bool found = false;
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE tdbb->getTransaction())
+ X IN RDB$RELATIONS WITH X.RDB$RELATION_ID EQ rel_id
+ {
+ DPM_pages(tdbb, rel_id, pag_pointer, 0, X.RDB$POINTER_PAGE);
+ DPM_pages(tdbb, rel_id, pag_root, 0, X.RDB$ROOT_PAGE);
+ found = true;
+ }
+ END_FOR
+ return found;
+}
+
+
static pointer_page* get_pointer_page(thread_db* tdbb,
jrd_rel* relation, RelationPages* relPages,
WIN* window, ULONG sequence, USHORT lock)
@@ -3345,15 +3444,35 @@ static pointer_page* get_pointer_page(thread_db* tdbb,
**************************************/
SET_TDBB(tdbb);
+ if (!relation)
+ return NULL;
+
vcl* vector = relPages->rel_pages;
+ bool restored = false;
if (!vector || sequence >= vector->count())
{
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, pag_pointer, relation->rel_id);
+ vector = relPages->rel_pages;
+
+ // If there is no relation in RDB$PAGES we try to restore it from RDB$RELATIONS
+ // After moving relation and deleting its pages maybe other DFWs
+ // I rely it may not cause cleaning vector of pages. We hold locks. But I leave this comment
+ // describing a potential situation.
+ if (!vector || vector->count() == 0)
+ {
+ if (restored || (relPages->rel_instance_id != 0) || (relation->rel_id < rel_MAX))
+ return NULL;
+
+ if (restore_pages(tdbb, relation->rel_id))
+ {
+ restored = true;
+ continue;
+ }
+ else
+ return NULL;
+ }
if (sequence < vector->count())
break; // we are in business again
@@ -3967,3 +4086,277 @@ static void store_big_record(thread_db* tdbb,
else
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
}
+
+typedef Firebird::GenericMap > > PagesMap;
+
+void DPM_move_data_pages(Jrd::thread_db* tdbb, Jrd::jrd_rel* relation, RelationPages* newRelationPages)
+{
+ /**************************************
+ *
+ * D P M _ m o v e _ d a t a _ p a g e s
+ *
+ **************************************
+ *
+ * Functional description
+ * Copy data and blob pages from the oldRelation pages to the newRelationPages
+ * 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
+ * - walking 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.
+ * At the end of work replace records in RDB$PAGES.
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ Attachment* attachment = tdbb->getAttachment();
+
+ RelationPages* oldRelationPages = relation->getPages(tdbb);
+
+ WIN newWindow(newRelationPages->rel_pg_space_id, -1);
+ WIN oldWindow(oldRelationPages->rel_pg_space_id, -1);
+ WIN dpWindow(newRelationPages->rel_pg_space_id, -1);
+
+ PagesMap pagesMap;
+
+ const ULONG ppCount = oldRelationPages->rel_pages->count();
+ newRelationPages->rel_pages = vcl::newVector(*relation->rel_pool, newRelationPages->rel_pages, ppCount);
+
+ ULONG sequence = 0;
+ // Allocate PPs by extents
+ if (ppCount > PAGES_IN_EXTENT)
+ {
+ for (; sequence < ppCount - PAGES_IN_EXTENT; sequence += PAGES_IN_EXTENT)
+ {
+ PAG_allocate_pages(tdbb, &newWindow, PAGES_IN_EXTENT, true);
+ const ULONG firstPage = newWindow.win_page.getPageNum();
+ CCH_RELEASE(tdbb, &newWindow);
+ for (int i = 0; i < PAGES_IN_EXTENT; i++)
+ (*newRelationPages->rel_pages)[sequence + i] = firstPage + i;
+ }
+ }
+ // Allocate the rest of PPs by pages
+ for (; sequence < ppCount; sequence++)
+ {
+ PAG_allocate_pages(tdbb, &newWindow, 1, false);
+ const ULONG page = newWindow.win_page.getPageNum();
+ CCH_RELEASE(tdbb, &newWindow);
+ (*newRelationPages->rel_pages)[sequence] = page;
+ }
+
+ // Copy and fix every pointer page and allocate and map its datapages
+ for (sequence = 0; sequence < ppCount; sequence++)
+ {
+ ULONG oldPage = oldWindow.win_page = (*oldRelationPages->rel_pages)[sequence];
+ const pointer_page* oldPP = (pointer_page*) CCH_FETCH(tdbb, &oldWindow, LCK_read, pag_pointer);
+
+ ULONG newPage = newWindow.win_page = (*newRelationPages->rel_pages)[sequence];
+ pointer_page* newPP = (pointer_page*) CCH_FETCH(tdbb, &newWindow, LCK_write, pag_undefined);
+
+ // Copy and relpace useful markers from oldRelationPages
+ if (oldRelationPages->rel_slot_space == oldPage)
+ newRelationPages->rel_slot_space = newPage;
+
+ if (oldRelationPages->rel_pri_data_space == oldPage)
+ newRelationPages->rel_pri_data_space = newPage;
+
+ if (oldRelationPages->rel_sec_data_space == oldPage)
+ newRelationPages->rel_sec_data_space = newPage;
+
+ // Now copy the page data
+ CCH_MARK(tdbb, &newWindow);
+ memcpy(newPP, oldPP, dbb->dbb_page_size);
+
+ if (sequence < (ppCount - 1))
+ {
+ // Fix the pointer to the next PP
+ newPP->ppg_next = (*newRelationPages->rel_pages)[sequence + 1];
+ }
+ else
+ {
+ // We rely that rel_pages vector is correct and nobody extend relation when we copy its data!!!
+ fb_assert(oldPP->ppg_header.pag_flags & ppg_eof);
+ }
+
+ CCH_RELEASE(tdbb, &oldWindow);
+
+ // Allocate DPs
+ 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])
+ {
+ PAG_allocate(tdbb, &dpWindow);
+ // Replace slots by the new data page numbers
+ const ULONG newPageNumber = dpWindow.win_page.getPageNum();
+ CCH_RELEASE(tdbb, &dpWindow);
+ pagesMap.put(newPP->ppg_page[slot], newPageNumber);
+ newPP->ppg_page[slot] = newPageNumber;
+ }
+ }
+ }
+ else
+ {
+ // Allocated 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.
+ int count = 0;
+ for (int 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);
+ }
+ // If new extent is empty go to the next
+ if (!count)
+ continue;
+
+ PAG_allocate_pages(tdbb, &dpWindow, PAGES_IN_EXTENT, true);
+ // Replace slots by the new data page numbers
+ const ULONG newPageNumber = dpWindow.win_page.getPageNum();
+ CCH_RELEASE(tdbb, &dpWindow);
+
+ // All non empty slots we replace 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 such slot is we remove it and replace by empty data page.
+ for (int i = 0; i < PAGES_IN_EXTENT; i++)
+ {
+ if (!newPP->ppg_page[slot + i])
+ {
+ // Original PP has empty slot here. We replace it by empty data page
+ 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->rel_id;
+ dpage->dpg_header.pag_type = pag_data;
+ CCH_RELEASE(tdbb, &dpWindow);
+ newPP->ppg_count = MAX(newPP->ppg_count, slot + i);
+ }
+ else
+ pagesMap.put(newPP->ppg_page[slot + i], newPageNumber + i);
+
+ newPP->ppg_page[slot + i] = newPageNumber + i;
+ }
+ }
+ }
+
+ CCH_RELEASE(tdbb, &newWindow);
+ }
+
+ // Now we have copied all PPs, allocated all DPs and got an allocation map
+ // We can copy and fix every DP. We will use sorted order in the map to optimize reads.
+ PagesMap::Accessor dPage(&pagesMap);
+
+ for (bool found = dPage.getFirst(); found; found = dPage.getNext())
+ {
+ const ULONG oldPage = dPage.current()->first;
+ const ULONG newPage = dPage.current()->second;
+
+ if (oldRelationPages->rel_last_free_pri_dp == oldPage)
+ newRelationPages->rel_last_free_pri_dp = newPage;
+
+ // 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);
+
+ CCH_RELEASE(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 blob we need to copy all its pages
+ if (header->rhd_flags & rhd_blob)
+ {
+ blh* blob = (blh*) header;
+ if (blob->blh_level == 0)
+ continue;
+
+ ULONG* page1 = blob->blh_page;
+ const ULONG* const end1 = page1 + (index->dpg_length - BLH_SIZE) / sizeof(ULONG);
+
+ for (; page1 < end1; page1++)
+ {
+ // Copy blob page with blob page numbers
+ WIN oldWindow1(oldRelationPages->rel_pg_space_id, *page1);
+ WIN newWindow1(newRelationPages->rel_pg_space_id, -1);
+ blob_page* oldBlobPage1 = (blob_page*) CCH_FETCH(tdbb, &oldWindow1, LCK_read, pag_blob);
+ blob_page* newBlobPage1 = (blob_page*) PAG_allocate(tdbb, &newWindow1);
+ memcpy(newBlobPage1, oldBlobPage1, dbb->dbb_page_size);
+ *page1 = newWindow1.win_page.getPageNum();
+ CCH_RELEASE_TAIL(tdbb, &oldWindow1);
+
+ if (blob->blh_level == 2)
+ {
+ // Fix blob page numbers and copy other pages
+ ULONG* page2 = newBlobPage1->blp_page;
+ const ULONG* const end2 = page2 + ((newBlobPage1->blp_length) / sizeof(ULONG));
+
+ for (; page2 < end2; page2++)
+ {
+ WIN oldWindow2(oldRelationPages->rel_pg_space_id, *page2);
+ WIN newWindow2(newRelationPages->rel_pg_space_id, -1);
+ blob_page* oldBlobPage2 = (blob_page*) CCH_FETCH(tdbb, &oldWindow2, LCK_read, pag_blob);
+ blob_page* newBlobPage2 = (blob_page*) PAG_allocate(tdbb, &newWindow2);
+ memcpy(newBlobPage2, oldBlobPage2, dbb->dbb_page_size);
+ *page2 = newWindow2.win_page.getPageNum();
+ 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 we don't need mapping
+ if (header->rhd_b_page == oldPage)
+ header->rhd_b_page = newPage;
+ else
+ {
+ const bool r = pagesMap.get(header->rhd_b_page, header->rhd_b_page);
+ fb_assert(r); // Maybe to drop bugcheck?
+ }
+ }
+ if (header->rhd_flags & rhd_incomplete)
+ {
+ Ods::rhdf* fheader = (rhdf*) header;
+ if (fheader->rhdf_f_page)
+ {
+ const bool r = pagesMap.get(fheader->rhdf_f_page, header->rhd_b_page);
+ fb_assert(r); // Maybe to drop bugcheck?
+ }
+ }
+ }
+ }
+ }
+
+ CCH_RELEASE(tdbb, &newWindow);
+ }
+
+ update_first_pointer_page(tdbb, relation->rel_id, (*newRelationPages->rel_pages)[0], newRelationPages->rel_index_root);
+}
diff --git a/src/jrd/dpm_proto.h b/src/jrd/dpm_proto.h
index baa188c5306..fb0c0008dc2 100644
--- a/src/jrd/dpm_proto.h
+++ b/src/jrd/dpm_proto.h
@@ -75,7 +75,7 @@ void DPM_pages(Jrd::thread_db*, SSHORT, int, ULONG, ULONG);
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, int relId = 0);
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::Record*);
void DPM_rewrite_header(Jrd::thread_db*, Jrd::record_param*);
@@ -84,4 +84,6 @@ void DPM_update(Jrd::thread_db*, Jrd::record_param*, Jrd::PageStack*, const Jrd:
void DPM_create_relation_pages(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::RelationPages*);
void DPM_delete_relation_pages(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::RelationPages*);
+void DPM_move_data_pages(Jrd::thread_db* tdbb, Jrd::jrd_rel *relation, Jrd::RelationPages *newRelationPages);
+
#endif // JRD_DPM_PROTO_H
diff --git a/src/jrd/drq.h b/src/jrd/drq.h
index 1f8e344d3b0..58ca0c6b0a9 100644
--- a/src/jrd/drq.h
+++ b/src/jrd/drq.h
@@ -231,6 +231,11 @@ 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_tablespace_exist, // check if tablespace exists
drq_m_coll_attrs, // modify collation attributes
drq_l_pub_mode, // lookup publication auto-enable mode
drq_m_pub_state, // modify publication state
@@ -242,6 +247,9 @@ enum drq_type_t
drq_e_pub_tab_all, // erase relation from all publication
drq_l_rel_con, // lookup relation constraint
drq_l_rel_fld_name, // lookup relation field name
+ drq_l_ts_name, // lookup tablespace name
+ drq_ts_drop_idx_dfw, // find index of tablespace in dfw for drop
+ drq_ts_drop_rel_dfw, // find relation of tablespace in dfw for drop
drq_MAX
};
diff --git a/src/jrd/dyn_util.epp b/src/jrd/dyn_util.epp
index 7c962d9223c..8c68452978a 100644
--- a/src/jrd/dyn_util.epp
+++ b/src/jrd/dyn_util.epp
@@ -301,6 +301,24 @@ bool DYN_UTIL_check_unique_name_nothrow(thread_db* tdbb, jrd_tra* transaction,
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 05e2885456a..c428d8b10f3 100644
--- a/src/jrd/fields.h
+++ b/src/jrd/fields.h
@@ -237,3 +237,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_pp_number , nam_pp_number , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_14_0)
+ FIELD(fld_idx_number , nam_idx_number , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_14_0)
diff --git a/src/jrd/grant.epp b/src/jrd/grant.epp
index 96c2feb1df5..5579cf9c67d 100644
--- a/src/jrd/grant.epp
+++ b/src/jrd/grant.epp
@@ -549,6 +549,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 86f889d2bc2..496f3cdac37 100644
--- a/src/jrd/idx.cpp
+++ b/src/jrd/idx.cpp
@@ -63,6 +63,7 @@
#include "../jrd/vio_proto.h"
#include "../jrd/tra_proto.h"
#include "../jrd/Collation.h"
+#include "../jrd/pag_proto.h"
#include "../common/Task.h"
#include "../jrd/WorkerAttachment.h"
@@ -2046,7 +2047,7 @@ static PageNumber get_root_page(thread_db* tdbb, jrd_rel* relation)
SLONG page = relPages->rel_index_root;
if (!page)
{
- DPM_scan_pages(tdbb);
+ DPM_scan_pages(tdbb, pag_root, relation->rel_id);
page = relPages->rel_index_root;
}
diff --git a/src/jrd/idx.h b/src/jrd/idx.h
index 0e7811a7870..65f7f9062a0 100644
--- a/src/jrd/idx.h
+++ b/src/jrd/idx.h
@@ -524,6 +524,19 @@ static inline constexpr struct ini_idx_t indices[] =
SEGMENT(f_pubtab_tab_schema, idx_metadata), // table schema name
SEGMENT(f_pubtab_tab_name, idx_metadata), // table name
SEGMENT(f_pubtab_pub_name, idx_metadata) // publication name
+ }},
+ // define index RDB$INDEX_98 for RDB$TABLESPACES unique RDB$TABLESPACE_NAME;
+ INDEX(98, 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(99, 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(100, 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/ini.epp b/src/jrd/ini.epp
index 6262e8cd061..8e7baac765d 100644
--- a/src/jrd/ini.epp
+++ b/src/jrd/ini.epp
@@ -604,6 +604,19 @@ namespace
END_MODIFY
}
END_FOR
+
+ handle.reset();
+
+ FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$SYSTEM_FLAG EQ RDB_system
+ {
+ MODIFY TS USING
+ TS.RDB$SECURITY_CLASS.NULL = FALSE;
+ PAD(securityClass, TS.RDB$SECURITY_CLASS);
+ END_MODIFY
+ }
+ END_FOR
}
private:
@@ -713,6 +726,31 @@ 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;
+
+ PAD(dbb->dbb_filename.c_str(), X.RDB$FILE_NAME);
+ X.RDB$FILE_NAME.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();
@@ -1776,12 +1814,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, relation, &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
@@ -1933,6 +1975,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;
@@ -1963,6 +2013,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 d659cc2401a..81388ec1883 100644
--- a/src/jrd/irq.h
+++ b/src/jrd/irq.h
@@ -175,14 +175,26 @@ 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_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_r_pages2,
+ irq_s_first_pp,
irq_MAX
};
diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp
index 700b63d4b94..13c44922085 100644
--- a/src/jrd/jrd.cpp
+++ b/src/jrd/jrd.cpp
@@ -1367,6 +1367,7 @@ 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 bool drop_files(ObjectsArray &tsFiles);
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);
@@ -3529,6 +3530,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()");
@@ -3583,6 +3589,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_ts_files(tdbb, tsFiles);
+
// Forced release of all transactions
purge_transactions(tdbb, attachment, true);
@@ -3635,6 +3644,7 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status)
bool err = drop_file(dbb, file);
for (; shadow; shadow = shadow->sdw_next)
err = drop_file(dbb, shadow->sdw_file) || err;
+ err = drop_files(tsFiles) || err;
tdbb->setDatabase(NULL);
Database::destroy(dbb);
@@ -6970,6 +6980,36 @@ static bool drop_file(const Database* dbb, const jrd_file* file)
return status->getState() & IStatus::STATE_ERRORS ? true : false;
}
+static bool drop_files(ObjectsArray& tsFiles)
+{
+/**************************************
+ *
+ * d r o p _ f i l e s
+ *
+ **************************************
+ *
+ * Functional description
+ * drop files in list
+ *
+ **************************************/
+ FbLocalStatus status;
+
+ while (tsFiles.getCount())
+ {
+ const PathName file(tsFiles.pop());
+ if (unlink(file.c_str()))
+ {
+ ERR_build_status(&status, Arg::Gds(isc_io_error) << Arg::Str("unlink") <<
+ Arg::Str(file) <<
+ Arg::Gds(isc_io_delete_err) << SYS_ERR(errno));
+ Database* dbb = GET_DBB();
+ 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)
{
diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h
index 2e3644c89cc..680b5151e93 100644
--- a/src/jrd/jrd.h
+++ b/src/jrd/jrd.h
@@ -339,7 +339,7 @@ struct win
explicit win(const PageNumber& wp) noexcept
: win_page(wp), win_bdb(NULL), win_flags(0)
{}
- win(const USHORT pageSpaceID, const ULONG pageNum) noexcept
+ win(const ULONG pageSpaceID, const ULONG pageNum) noexcept
: win_page(pageSpaceID, pageNum), win_bdb(NULL), win_flags(0)
{}
};
diff --git a/src/jrd/lck.cpp b/src/jrd/lck.cpp
index c2fa9d04c94..68ee3c10b85 100644
--- a/src/jrd/lck.cpp
+++ b/src/jrd/lck.cpp
@@ -604,6 +604,7 @@ static lck_owner_t get_owner_type(enum lck_t lock_type)
case LCK_repl_tables:
case LCK_dsql_statement_cache:
case LCK_profiler_listener:
+ case LCK_tablespace_exist:
owner_type = LCK_OWNER_attachment;
break;
diff --git a/src/jrd/lck.h b/src/jrd/lck.h
index 08ea8992f0e..9a0dcc6803e 100644
--- a/src/jrd/lck.h
+++ b/src/jrd/lck.h
@@ -76,7 +76,8 @@ enum lck_t {
LCK_repl_state, // Replication state lock
LCK_repl_tables, // Replication set lock
LCK_dsql_statement_cache, // DSQL statement cache lock
- LCK_profiler_listener // Remote profiler listener
+ LCK_profiler_listener, // Remote profiler listener
+ LCK_tablespace_exist // Tablespace existance lock
};
// Lock owner types
diff --git a/src/jrd/met.epp b/src/jrd/met.epp
index 09c38b34d04..efe0d273959 100644
--- a/src/jrd/met.epp
+++ b/src/jrd/met.epp
@@ -92,6 +92,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"
@@ -3764,6 +3765,15 @@ jrd_rel* MET_relation(thread_db* tdbb, USHORT id)
if (relation)
return relation;
+ // From ODS 9 onwards, the first 128 relation IDS have been
+ // reserved for system relations
+ const USHORT max_sys_rel = USER_DEF_REL_INIT_ID - 1;
+
+ ULONG pageSpaceId = DB_PAGE_SPACE;
+
+ if (id > max_sys_rel)
+ pageSpaceId = MET_rel_pagespace(tdbb, id);
+
relation = FB_NEW_POOL(*pool) jrd_rel(*pool);
(*vector)[id] = relation;
relation->rel_id = id;
@@ -3793,6 +3803,9 @@ jrd_rel* MET_relation(thread_db* tdbb, USHORT id)
}
relation->rel_flags |= (REL_check_existence | REL_check_partners);
+
+ relation->setPageSpace(pageSpaceId);
+
return relation;
}
@@ -5898,3 +5911,360 @@ bool MET_check_schema_exists(thread_db* tdbb, const MetaName& name)
return false;
}
+
+ULONG MET_rel_pagespace(Jrd::thread_db* tdbb, USHORT rel_id)
+{
+/**************************************
+ *
+ * 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
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ ULONG pageSpaceId = DB_PAGE_SPACE;
+ Attachment* attachment = tdbb->getAttachment();
+ AutoCacheRequest request(tdbb, irq_find_rel_ts, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request)
+ REL IN RDB$RELATIONS
+ WITH REL.RDB$RELATION_ID EQ rel_id AND
+ REL.RDB$TABLESPACE_NAME NOT MISSING
+ {
+#ifdef DEV_BUILD
+ if (!tdbb->getAttachment()->isGbak() && fb_utils::hasTablespaceName(rel_t(REL.RDB$RELATION_TYPE)))
+ fb_assert(!REL.RDB$TABLESPACE_NAME.NULL);
+#endif
+
+ pageSpaceId = MET_tablespace(tdbb, REL.RDB$TABLESPACE_NAME)->id;
+ }
+ END_FOR
+
+ return pageSpaceId;
+}
+
+ULONG MET_index_pagespace(Jrd::thread_db* tdbb, Jrd::jrd_rel* 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.
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ RelationPages* const relPages = relation->getPages(tdbb);
+ if (relation->isSystem())
+ return relPages->rel_pg_space_id;
+
+ // Relation tablespace name must be in system table after index creation
+ // If it's not so we shoud use the main DB page space.
+ ULONG pageSpaceId = DB_PAGE_SPACE;
+ Attachment* attachment = tdbb->getAttachment();
+ AutoCacheRequest request(tdbb, irq_find_idx_ts, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request)
+ 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 = MET_tablespace(tdbb, IDX.RDB$TABLESPACE_NAME)->id;
+ }
+ END_FOR
+
+ return pageSpaceId;
+}
+
+Tablespace* MET_tablespace_id(Jrd::thread_db* tdbb, ULONG id, bool open)
+{
+/**************************************
+ *
+ * M E T _ t a b l e s p a c e _ i d
+ *
+ **************************************
+ *
+ * Functional description
+ * Find tablespace by its id in attach cache first of all.
+ * If it's not found alloc pagespace, get exist tablespace lock and
+ * add it in attachment cache.
+ *
+ * Return a pointer to the tablespace.
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ // Check that pageSpaceId has reasonable value
+ fb_assert(PageSpace::isTablespace(id));
+
+ Attachment* attachment = tdbb->getAttachment();
+
+ Tablespace* ts = attachment->getTablespace(id);
+
+ if (ts)
+ {
+ fb_assert(ts->id == id);
+
+ // Do not allow to use tablespaces modified by ALTER TABLESPACE in the same transaction
+ if (ts->modified && open)
+ {
+ string name;
+ name.printf("TABLESPACE \"%s\"", ts->name.c_str());
+
+ ERR_post(Arg::Gds(isc_obj_in_use) << Arg::Str(name));
+ }
+
+ return ts;
+ }
+
+ // Now we need to find tablespace in system table
+ AutoCacheRequest request(tdbb, irq_find_ts_id, IRQ_REQUESTS);
+
+ MetaName tableSpaceName;
+ PathName file;
+ bool found = false;
+
+ FOR(REQUEST_HANDLE request)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$TABLESPACE_ID EQ id
+ {
+ found = true;
+ tableSpaceName = TS.RDB$TABLESPACE_NAME;
+ file = TS.RDB$FILE_NAME;
+ }
+ END_FOR
+
+ if (!found)
+ {
+ tableSpaceName.printf("id %d", id);
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(tableSpaceName));
+ }
+
+ // Check that pageSpaceId is either new or is not set (else it should be found in att_tablespaces above)
+ fb_assert(!attachment->getTablespace(id));
+
+ AutoPtr tableSpace;
+
+ try
+ {
+ tableSpace = FB_NEW_POOL(*attachment->att_pool)
+ Tablespace(*attachment->att_pool, id, tableSpaceName);
+
+ Lock* lock = FB_NEW_RPT(*attachment->att_pool, 0)
+ Lock(tdbb, sizeof(ULONG), LCK_tablespace_exist, tableSpace);
+ lock->setKey(tableSpace->id);
+ tableSpace->existenceLock = lock;
+
+ // Get the SR lock to let other attachments know that we use the tablespace
+ if (!LCK_lock(tdbb, lock, LCK_SR, LCK_NO_WAIT))
+ {
+ string name;
+ name.printf("TABLESPACE ID %d", id);
+
+ ERR_post(Arg::Gds(isc_obj_in_use) << Arg::Str(name));
+ }
+
+ if (open)
+ dbb->dbb_page_manager.allocTableSpace(tdbb, id, false, file);
+ }
+ catch (const Exception&)
+ {
+ if (tableSpace && tableSpace->existenceLock)
+ LCK_release(tdbb, tableSpace->existenceLock);
+
+ throw;
+ }
+
+ attachment->setTablespace(id, tableSpace.release());
+ return tableSpace;
+}
+
+
+Tablespace* MET_tablespace(Jrd::thread_db* tdbb, const MetaName& tableSpaceName)
+{
+/**************************************
+ *
+ * M E T _ t a b l e s p a c e
+ *
+ **************************************
+ *
+ * Functional description
+ * Find tablespace by its name in attach cache first of all.
+ * If not found alloc pagespace, get exist tablespace lock and
+ * add it in attachment cache.
+ *
+ * Return a pointer to the tablespace.
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ Attachment* attachment = tdbb->getAttachment();
+
+ // Tablespace name can be empty for view, gtt, virtual tables
+ if (tableSpaceName.isEmpty() || tableSpaceName == PRIMARY_TABLESPACE_NAME)
+ return attachment->getTablespace(DB_PAGE_SPACE);
+
+ Tablespace* ts = attachment->getTablespaceByName(tableSpaceName);
+
+ if (ts)
+ {
+ fb_assert(ts->name == tableSpaceName);
+
+ // Do not allow to use tablespaces modified by ALTER TABLESPACE in the same transaction
+ if (ts->modified)
+ {
+ string name;
+ name.printf("TABLESPACE \"%s\"", ts->name.c_str());
+
+ ERR_post(Arg::Gds(isc_obj_in_use) << Arg::Str(name));
+ }
+
+ return ts;
+ }
+
+ // Now we need to find tablespace in system table
+ AutoCacheRequest request(tdbb, irq_find_ts, IRQ_REQUESTS);
+
+ ULONG pageSpaceId;
+ PathName file;
+ bool found = false;
+
+ FOR(REQUEST_HANDLE request)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$TABLESPACE_NAME EQ tableSpaceName.c_str()
+ {
+ found = true;
+ pageSpaceId = TS.RDB$TABLESPACE_ID;
+ file = TS.RDB$FILE_NAME;
+ }
+ END_FOR
+
+ if (!found)
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(tableSpaceName));
+
+ // Check that pageSpaceId has reasonable value
+ fb_assert(PageSpace::isTablespace(pageSpaceId));
+
+ // Check that pageSpaceId is either new or is not set (else it should be found in att_tablespaces above)
+ fb_assert(!attachment->getTablespace(pageSpaceId));
+
+ Tablespace* tableSpace = NULL;
+
+ try
+ {
+ tableSpace = FB_NEW_POOL(*attachment->att_pool)
+ Tablespace(*attachment->att_pool, pageSpaceId, tableSpaceName);
+
+ Lock* lock = FB_NEW_RPT(*attachment->att_pool, 0)
+ Lock(tdbb, sizeof(ULONG), LCK_tablespace_exist, tableSpace);
+ tableSpace->existenceLock = lock;
+ lock->setKey(tableSpace->id);
+
+ // Get the SR lock to let other attachments know that we use the tablespace
+ if (!LCK_lock(tdbb, lock, LCK_SR, LCK_NO_WAIT))
+ {
+ string name;
+ name.printf("TABLESPACE \"%s\"", tableSpaceName.c_str());
+
+ ERR_post(Arg::Gds(isc_obj_in_use) << Arg::Str(name));
+ }
+
+ dbb->dbb_page_manager.allocTableSpace(tdbb, pageSpaceId, false, file);
+ }
+ catch (...)
+ {
+ if (tableSpace)
+ {
+ if (tableSpace->existenceLock)
+ LCK_release(tdbb, tableSpace->existenceLock);
+
+ delete tableSpace;
+ }
+
+ throw;
+ }
+
+ attachment->setTablespace(pageSpaceId, tableSpace);
+ return tableSpace;
+}
+
+
+void MET_ts_files(Jrd::thread_db* tdbb, ObjectsArray& files)
+{
+/**************************************
+ *
+ * M E T _ t s _ f i l e s
+ *
+ **************************************
+ *
+ * Functional description
+ * List all tablespace file names
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+
+ Attachment* attachment = tdbb->getAttachment();
+ AutoCacheRequest request(tdbb, irq_list_ts_files, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request)
+ 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
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ Attachment* attachment = tdbb->getAttachment();
+
+ // Now we need to find tablespace in system table
+ AutoCacheRequest request(tdbb, irq_scan_ts, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request)
+ TS IN RDB$TABLESPACES
+ {
+ const Tablespace* ts = MET_tablespace(tdbb, TS.RDB$TABLESPACE_NAME);
+ fb_assert(ts);
+ }
+ END_FOR
+}
diff --git a/src/jrd/met_proto.h b/src/jrd/met_proto.h
index fba22677afd..b08953b6b29 100644
--- a/src/jrd/met_proto.h
+++ b/src/jrd/met_proto.h
@@ -158,4 +158,11 @@ std::optional MET_qualify_existing_name(Jrd::thread_db* tdbb, Jrd::Q
const Firebird::ObjectsArray* schemaSearchPath = nullptr);
bool MET_check_schema_exists(Jrd::thread_db* tdbb, const Jrd::MetaName& name);
+ULONG MET_rel_pagespace(Jrd::thread_db* tdbb, USHORT rel_id);
+ULONG MET_index_pagespace(Jrd::thread_db* tdbb, Jrd::jrd_rel* relation, USHORT idx_id);
+Jrd::Tablespace* MET_tablespace_id(Jrd::thread_db* tdbb, ULONG id, bool open = true);
+Jrd::Tablespace* MET_tablespace(Jrd::thread_db* tdbb, const Jrd::MetaName& tableSpaceName);
+void MET_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 661c7974b79..2b3ecb107fd 100644
--- a/src/jrd/names.h
+++ b/src/jrd/names.h
@@ -271,6 +271,9 @@ NAME("RDB$ARGUMENT_NAME", nam_arg_name)
NAME("RDB$IDENTITY_TYPE", nam_identity_type)
NAME("RDB$AUTH_METHOD", nam_auth_method)
+NAME("RDB$POINTER_PAGE", nam_pp_number)
+NAME("RDB$ROOT_PAGE", nam_idx_number)
+
NAME("SEC$USER_NAME", nam_user_name)
NAME("SEC$FIRST_NAME", nam_first_name)
NAME("SEC$MIDDLE_NAME", nam_middle_name)
@@ -464,6 +467,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)
diff --git a/src/jrd/ods.h b/src/jrd/ods.h
index 4c6e67b6e5a..ad1f04dd92e 100644
--- a/src/jrd/ods.h
+++ b/src/jrd/ods.h
@@ -392,8 +392,12 @@ struct index_root_page
TraNumber inProgress() const;
void setInProgress(TraNumber traNumber);
- ULONG getRoot() const;
- void setRoot(ULONG rootPage);
+ TraNumber getTransaction() const;
+ void setTransaction(TraNumber traNumber);
+
+ ULONG getRootPage() const;
+ ULONG getRootPageSpaceId() const;
+ void setRoot(ULONG pageSpaceId, ULONG pageNum);
bool isUsed() const;
void setEmpty();
@@ -476,19 +480,24 @@ inline void index_root_page::irt_repeat::setInProgress(TraNumber traNumber)
irt_state = irt_in_progress;
}
-inline ULONG index_root_page::irt_repeat::getRoot() const
+inline ULONG index_root_page::irt_repeat::getRootPage() const
{
return (irt_state == irt_unused) ? 0 : irt_page_num;
}
-inline void index_root_page::irt_repeat::setRoot(ULONG rootPage)
+inline ULONG index_root_page::irt_repeat::getRootPageSpaceId() const
+{
+ return (irt_state == irt_unused) ? 0 : irt_page_space_id;
+}
+
+inline void index_root_page::irt_repeat::setRoot(ULONG pagespaceId, ULONG pageNum)
{
fb_assert(irt_state == irt_in_progress || irt_state == irt_normal);
- fb_assert(rootPage);
+ fb_assert(pageNum != 0 && pagespaceId != 0);
irt_transaction = 0;
- irt_page_num = rootPage;
- irt_page_space_id = 0;
+ irt_page_num = pageNum;
+ irt_page_space_id = pagespaceId;
irt_state = irt_normal;
}
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 538e7436383..561d1280aee 100644
--- a/src/jrd/os/posix/unix.cpp
+++ b/src/jrd/os/posix/unix.cpp
@@ -1149,6 +1149,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 a7733d59891..1bd5cdd14ea 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 f5b0ee8e2de..5b463b57e41 100644
--- a/src/jrd/pag.cpp
+++ b/src/jrd/pag.cpp
@@ -1278,15 +1278,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)
{
/**************************************
@@ -2018,7 +2019,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
{
/**************************************
*
@@ -2030,32 +2031,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];
@@ -2064,8 +2084,10 @@ PageSpace* PageManager::findPageSpace(const USHORT pageSpace) const
return 0;
}
-void PageManager::delPageSpace(const USHORT pageSpace)
+void PageManager::delPageSpace(const ULONG pageSpace)
{
+ WriteLockGuard guard(pageSpacesLock, FB_FUNCTION);
+
FB_SIZE_T pos;
if (pageSpaces.find(pageSpace, pos))
{
@@ -2099,12 +2121,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);
@@ -2113,7 +2135,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
{
@@ -2123,7 +2145,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)
@@ -2155,6 +2177,75 @@ USHORT PageManager::getTempPageSpaceID(thread_db* tdbb)
return tempPageSpaceID;
}
+
+void PageManager::allocTableSpace(thread_db* tdbb, ULONG tableSpaceID, bool create, const PathName& fileName)
+{
+ /***
+ * NOTE: PageSpaceId of Tablespaces is equal to tablespace id
+ */
+ fb_assert(PageSpace::isTablespace(tableSpaceID));
+
+ if (!findPageSpace(tableSpaceID))
+ {
+ Firebird::MutexLockGuard guard(initTmpMtx, FB_FUNCTION);
+
+ // Double check if someone concurrently have added the tablespaceid
+ if (findPageSpace(tableSpaceID))
+ 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, tableSpaceID);
+
+ 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(tableSpaceID);
+ }
+ 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 db3753e538a..d49f547b5e4 100644
--- a/src/jrd/pag.h
+++ b/src/jrd/pag.h
@@ -37,6 +37,7 @@
#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"
@@ -60,10 +61,11 @@ class PageControl : public pool_alloc
// 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;
+inline constexpr ULONG INVALID_PAGE_SPACE = 0;
+inline constexpr ULONG DB_PAGE_SPACE = 1;
+inline constexpr ULONG TRANS_PAGE_SPACE = 255;
+inline constexpr ULONG TEMP_PAGE_SPACE = 256;
+inline constexpr ULONG MAX_PAGE_SPACE_ID = MAX_ULONG;
inline constexpr USHORT PAGES_IN_EXTENT = 8;
@@ -75,7 +77,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 +92,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,7 +100,7 @@ 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);
}
@@ -108,7 +110,17 @@ class PageSpace : public pool_alloc
return isTemporary(pageSpaceID);
}
- static inline SLONG generate(const PageSpace* Item) noexcept
+ static inline bool isTablespace(ULONG aPageSpaceID)
+ {
+ return (aPageSpaceID > DB_PAGE_SPACE) && (aPageSpaceID < TRANS_PAGE_SPACE);
+ }
+
+ inline bool isTablespace() const
+ {
+ return isTablespace(pageSpaceID);
+ }
+
+ static inline ULONG generate(const PageSpace* Item) noexcept
{
return Item->pageSpaceID;
}
@@ -133,8 +145,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 +159,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 allocTableSpace(thread_db* tdbb, ULONG tableSpaceID, bool create, const Firebird::PathName& fileName);
+ void delPageSpace(const ULONG pageSpaceID);
void closeAll();
@@ -185,13 +187,13 @@ 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;
@@ -202,7 +204,7 @@ 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
+ inline PageNumber(const ULONG aPageSpace, const ULONG aPageNum) noexcept
: pageNum(aPageNum), pageSpaceID(aPageSpace)
{
// Some asserts are commented cause 0 was also used as 'does not matter' pagespace
@@ -224,13 +226,12 @@ class PageNumber
return pageNum;
}
- inline USHORT getPageSpaceID() const noexcept
+ inline ULONG getPageSpaceID() const noexcept
{
- fb_assert(pageSpaceID != INVALID_PAGE_SPACE);
return pageSpaceID;
}
- inline USHORT setPageSpaceID(const USHORT aPageSpaceID) noexcept
+ inline ULONG setPageSpaceID(const ULONG aPageSpaceID) noexcept
{
fb_assert(aPageSpaceID != INVALID_PAGE_SPACE);
pageSpaceID = aPageSpaceID;
@@ -319,7 +320,7 @@ class PageNumber
private:
ULONG pageNum;
- USHORT pageSpaceID;
+ ULONG pageSpaceID;
};
const PageNumber ZERO_PAGE_NUMBER(DB_PAGE_SPACE, 0);
diff --git a/src/jrd/pag_proto.h b/src/jrd/pag_proto.h
index 084b55a2a34..6c0b095df5a 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/IndexTableScan.cpp b/src/jrd/recsrc/IndexTableScan.cpp
index bf44b8bd768..080dd3df4b0 100644
--- a/src/jrd/recsrc/IndexTableScan.cpp
+++ b/src/jrd/recsrc/IndexTableScan.cpp
@@ -201,7 +201,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/relations.h b/src/jrd/relations.h
index 7aaaa4a88e8..fad8c438001 100644
--- a/src/jrd/relations.h
+++ b/src/jrd/relations.h
@@ -102,6 +102,7 @@ RELATION(nam_indices, rel_indices, ODS_8_0, rel_persistent)
FIELD(f_idx_exp_blr, nam_exp_blr, fld_value, 1, ODS_8_0)
FIELD(f_idx_exp_source, nam_exp_source, fld_source, 1, ODS_8_0)
FIELD(f_idx_statistics, nam_statistics, fld_statistics, 1, ODS_8_0)
+ FIELD(f_idx_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0)
FIELD(f_idx_cond_blr, nam_cond_blr, fld_value, 1, ODS_13_1)
FIELD(f_idx_cond_source, nam_cond_source, fld_source, 1, ODS_13_1)
FIELD(f_idx_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0)
@@ -133,6 +134,7 @@ RELATION(nam_r_fields, rel_rfr, ODS_8_0, rel_persistent)
FIELD(f_rfr_identity_type, nam_identity_type, fld_identity_type, 1, ODS_12_0)
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_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0)
END_RELATION
// Relation 6 (RDB$RELATIONS)
@@ -156,6 +158,9 @@ RELATION(nam_relations, rel_relations, ODS_8_0, rel_persistent)
FIELD(f_rel_type, nam_r_type, fld_r_type, 0, ODS_11_1)
FIELD(f_rel_sql_security, nam_sql_security, fld_b_sql_security, 1, ODS_13_0)
FIELD(f_rel_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0)
+ FIELD(f_rel_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0)
+ FIELD(f_rel_first_pp, nam_pp_number, fld_pp_number, 0, ODS_14_0)
+ FIELD(f_rel_idx_root, nam_idx_number, fld_idx_number, 0, ODS_14_0)
END_RELATION
// Relation 7 (RDB$VIEW_RELATIONS)
@@ -811,3 +816,16 @@ RELATION(nam_schemas, rel_schemas, ODS_14_0, rel_persistent)
FIELD(f_sch_sys_flag, nam_sys_flag, fld_flag, 1, ODS_14_0)
FIELD(f_sch_desc, nam_description, fld_description, 1, ODS_14_0)
END_RELATION
+
+// Relation 56 (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
diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp
index c9e9f39419a..c3d8b85b197 100644
--- a/src/jrd/replication/Applier.cpp
+++ b/src/jrd/replication/Applier.cpp
@@ -1057,7 +1057,7 @@ bool Applier::lookupKey(thread_db* tdbb, jrd_rel* relation, index_desc& key)
auto page = relPages->rel_index_root;
if (!page)
{
- DPM_scan_pages(tdbb);
+ DPM_scan_pages(tdbb, pag_root, relation->rel_id);
page = relPages->rel_index_root;
}
diff --git a/src/jrd/replication/Config.cpp b/src/jrd/replication/Config.cpp
index bd35d5c8266..3edaa44029d 100644
--- a/src/jrd/replication/Config.cpp
+++ b/src/jrd/replication/Config.cpp
@@ -198,7 +198,8 @@ Config::Config()
logErrors(true),
reportErrors(false),
disableOnError(true),
- cascadeReplication(false)
+ cascadeReplication(false),
+ applyTablespacesDdl(true)
{
}
@@ -227,7 +228,8 @@ Config::Config(const Config& other)
logErrors(other.logErrors),
reportErrors(other.reportErrors),
disableOnError(other.disableOnError),
- cascadeReplication(other.cascadeReplication)
+ cascadeReplication(other.cascadeReplication),
+ applyTablespacesDdl(other.applyTablespacesDdl)
{
}
@@ -382,6 +384,10 @@ Config* Config::get(const PathName& lookupName)
{
parseBoolean(value, config->cascadeReplication);
}
+ else if (key == "apply_tablespaces_ddl")
+ {
+ parseBoolean(value, config->applyTablespacesDdl);
+ }
}
if (exactMatch)
@@ -393,7 +399,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
@@ -499,6 +505,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;
}
@@ -524,6 +534,11 @@ void Config::enumerate(ReplicaList& replicas)
}
}
+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 3f5aa2b09be..2425dc81175 100644
--- a/src/jrd/replication/Publisher.cpp
+++ b/src/jrd/replication/Publisher.cpp
@@ -398,7 +398,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/scl.epp b/src/jrd/scl.epp
index eeddb594191..f589be0ed57 100644
--- a/src/jrd/scl.epp
+++ b/src/jrd/scl.epp
@@ -850,6 +850,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)
{
/**************************************
@@ -1815,4 +1847,3 @@ static bool check_object(thread_db* tdbb,
}
return found;
}
-
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/tpc_proto.h b/src/jrd/tpc_proto.h
index 1813c29a7f6..286a08429f3 100644
--- a/src/jrd/tpc_proto.h
+++ b/src/jrd/tpc_proto.h
@@ -159,6 +159,11 @@ class TipCache
return m_tpcHeader->getHeader()->monitor_generation++ + 1;
}
+ bool isInitialized()
+ {
+ return m_tpcHeader;
+ }
+
private:
class GlobalTpcHeader : public Firebird::MemoryHeader
{
diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp
index ba658f45dc2..3944d99ce38 100644
--- a/src/jrd/tra.cpp
+++ b/src/jrd/tra.cpp
@@ -76,6 +76,7 @@
#include "../jrd/Mapping.h"
#include "../jrd/DbCreators.h"
#include "../common/os/fbsyslog.h"
+#include "../jrd/Tablespace.h"
#include "firebird/impl/msg_helper.h"
@@ -544,7 +545,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
@@ -967,6 +968,8 @@ void TRA_post_resources(thread_db* tdbb, jrd_tra* transaction, ResourceList& res
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
+ USHORT usedTablespaces[TRANS_PAGE_SPACE] = {0};
+
for (Resource* rsc = resources.begin(); rsc < resources.end(); rsc++)
{
if (rsc->rsc_type == Resource::rsc_relation ||
@@ -985,6 +988,7 @@ void TRA_post_resources(thread_db* tdbb, jrd_tra* transaction, ResourceList& res
if (rsc->rsc_rel->rel_file) {
EXT_tra_attach(rsc->rsc_rel->rel_file, transaction);
}
+ usedTablespaces[rsc->rsc_rel->getBasePages()->rel_pg_space_id]++;
break;
case Resource::rsc_procedure:
case Resource::rsc_function:
@@ -1008,6 +1012,15 @@ void TRA_post_resources(thread_db* tdbb, jrd_tra* transaction, ResourceList& res
}
}
}
+
+ // Now let's lock all used tablespaces
+ for (ULONG i = DB_PAGE_SPACE + 1; i < TRANS_PAGE_SPACE; i++)
+ if (usedTablespaces[i] > 0)
+ {
+ // Tablespace is locking after the use in relation and indices.
+ // Should we check it here again?
+ MET_tablespace_id(tdbb, i)->addRef(tdbb);
+ }
}
@@ -1264,6 +1277,7 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr
}
// Release interest in relation/procedure existence for transaction
+ USHORT usedTablespaces[TRANS_PAGE_SPACE] = {0};
for (Resource* rsc = transaction->tra_resources.begin();
rsc < transaction->tra_resources.end(); rsc++)
@@ -1275,6 +1289,7 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr
if (rsc->rsc_rel->rel_file) {
EXT_tra_detach(rsc->rsc_rel->rel_file, transaction);
}
+ usedTablespaces[rsc->rsc_rel->getBasePages()->rel_pg_space_id]++;
break;
case Resource::rsc_procedure:
case Resource::rsc_function:
@@ -1290,6 +1305,19 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr
release_temp_tables(tdbb, transaction);
+ // Now let's release all used tablespaces
+ for (ULONG i = DB_PAGE_SPACE + 1; i < TRANS_PAGE_SPACE; i++)
+ if (usedTablespaces[i] > 0)
+ {
+ // Tablespace is locking after the use in relation and indices.
+ // Should we check it here again?
+ Tablespace* ts = tdbb->getAttachment()->getTablespace(i);
+ fb_assert(ts);
+
+ if (ts)
+ ts->release(tdbb);
+ }
+
// Release the locks associated with the transaction
if (transaction->tra_alter_db_lock)
@@ -2373,7 +2401,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)
@@ -2680,7 +2708,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);
diff --git a/src/jrd/tra.h b/src/jrd/tra.h
index b55ed364dcf..7ee8293a218 100644
--- a/src/jrd/tra.h
+++ b/src/jrd/tra.h
@@ -520,6 +520,13 @@ enum dfw_t {
dfw_store_view_context_type,
dfw_set_generator,
dfw_change_repl_state,
+ dfw_create_tablespace,
+ dfw_delete_tablespace,
+ dfw_modify_tablespace,
+ dfw_move_relation,
+ dfw_move_index,
+ dfw_clear_datapages,
+ dfw_clear_indexpages,
// deferred works argument types
dfw_arg_index_name, // index name for dfw_delete_index, mandatory
diff --git a/src/jrd/trig.h b/src/jrd/trig.h
index 53bb6bba270..9f8dc5057bd 100644
--- a/src/jrd/trig.h
+++ b/src/jrd/trig.h
@@ -82,6 +82,7 @@ static inline constexpr Jrd::gen generators[] =
{ "RDB$BACKUP_HISTORY", 9, "Nbackup technology", ODS_13_0 },
{ FUNCTIONS_GENERATOR, 10, "Function ID", ODS_13_0 },
{ "RDB$GENERATOR_NAME", 11, "Implicit generator name", ODS_13_0 },
+ { "RDB$TABLESPACES", 12, "Tablespace ID", ODS_14_0 },
{ nullptr, 0, nullptr, 0 }
};
diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp
index b2aa76aa641..d4afd70a884 100644
--- a/src/jrd/validation.cpp
+++ b/src/jrd/validation.cpp
@@ -570,6 +570,8 @@ VI. ADDITIONAL NOTES
#include "../common/db_alias.h"
#include "../jrd/intl_proto.h"
#include "../jrd/lck_proto.h"
+#include "../jrd/PreparedStatement.h"
+#include "../jrd/ResultSet.h"
#ifdef DEBUG_VAL_VERBOSE
#include "../jrd/dmp_proto.h"
@@ -809,20 +811,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"},
@@ -833,22 +835,22 @@ 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)
@@ -856,7 +858,7 @@ Validation::Validation(thread_db* tdbb, UtilSvc* uSvc)
vdr_used_bdbs(*tdbb->getDefaultPool())
{
vdr_tdbb = tdbb;
- vdr_max_page = 0;
+ memset(vdr_max_page, 0, sizeof(vdr_max_page));
vdr_flags = 0;
vdr_errors = 0;
vdr_warns = 0;
@@ -868,7 +870,7 @@ Validation::Validation(thread_db* tdbb, UtilSvc* uSvc)
vdr_chain_pages = NULL;
vdr_rel_records = NULL;
vdr_idx_records = NULL;
- vdr_page_bitmap = NULL;
+ memset(vdr_page_bitmap, 0, sizeof(vdr_page_bitmap));
vdr_service = uSvc;
vdr_lock_tout = -10;
@@ -1080,8 +1082,13 @@ bool Validation::run(thread_db* tdbb, USHORT flags)
void Validation::cleanup()
{
- delete vdr_page_bitmap;
- vdr_page_bitmap = NULL;
+ for (ULONG i = DB_PAGE_SPACE; i < TRANS_PAGE_SPACE; i++)
+ {
+ if (!vdr_page_bitmap[i])
+ continue;
+ delete vdr_page_bitmap[i];
+ vdr_page_bitmap[i] = NULL;
+ }
delete vdr_rel_records;
vdr_rel_records = NULL;
@@ -1177,7 +1184,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)
{
/**************************************
@@ -1212,7 +1219,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;
@@ -1228,7 +1235,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;
}
@@ -1240,12 +1247,16 @@ 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);
+
+ vdr_max_page[pageSpaceId] = MAX(vdr_max_page[pageSpaceId], page_number.getPageNum());
// For walking back versions & record fragments on data pages we
// sometimes will fetch the same page more than once. In that
@@ -1257,22 +1268,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(vdr_page_bitmap[pageSpaceId], 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) {
@@ -1281,7 +1291,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)
{
@@ -1298,7 +1308,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(), &vdr_page_bitmap[page_number.getPageSpaceID()], page_number.getPageNum());
return fetch_ok;
}
@@ -1306,7 +1316,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
@@ -1336,62 +1346,70 @@ 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);
- for (ULONG sequence = 0, number = 0; number < vdr_max_page; sequence++)
+ for (ULONG pageSpaceId = DB_PAGE_SPACE; pageSpaceId < TRANS_PAGE_SPACE; pageSpaceId++)
{
- 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)
+ if (!vdr_max_page[pageSpaceId])
+ continue;
+
+ PageSpace* pageSpace = pageSpaceMgr.findPageSpace(pageSpaceId);
+ fb_assert(pageSpace); // probably here we need to continue if NULL
+ WIN window(pageSpaceId, -1);
+
+ for (ULONG sequence = 0, number = 0; number < vdr_max_page[pageSpaceId]; sequence++)
{
- UCHAR byte = *p++;
- for (int i = 8; i; --i, byte >>= 1, 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 < vdr_max_page[pageSpaceId])
{
- if (PageBitmap::test(vdr_page_bitmap, number))
+ UCHAR byte = *p++;
+ for (int i = 8; i; --i, byte >>= 1, number++)
{
- if (byte & 1)
+ if (PageBitmap::test(vdr_page_bitmap[pageSpaceId], number)) // This page was fetched
{
- corrupt(VAL_PAG_IN_USE, 0, number);
- if (vdr_flags & VDR_update)
+ if (byte & 1) // The page mark as free in PIP
{
- CCH_MARK(vdr_tdbb, &window);
- p[-1] &= ~(1 << (number & 7));
- vdr_fixed++;
+ 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))
- {
- // 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)
+ else if (!(byte & 1) && (vdr_flags & VDR_records) &&
+ (pageSpaceId == DB_PAGE_SPACE || number > HEADER_PAGE))
{
- CCH_MARK(vdr_tdbb, &window);
- p[-1] |= 1 << (number & 7);
- vdr_fixed++;
+ // 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;
+ 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);
+ 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;
}
#ifdef DEBUG_VAL_VERBOSE
@@ -1487,8 +1505,9 @@ Validation::RTN Validation::walk_blob(jrd_rel* relation, const blh* header, USHO
corrupt(VAL_BLOB_UNKNOWN_LEVEL, relation, number.getValue(), header->blh_level);
}
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
// Level 1 blobs are a little more complicated
- WIN window1(DB_PAGE_SPACE, -1), window2(DB_PAGE_SPACE, -1);
+ WIN window1(pageSpaceId, -1), window2(pageSpaceId, -1);
window1.win_flags = window2.win_flags = WIN_garbage_collector;
const ULONG* pages1 = header->blh_page;
@@ -1498,7 +1517,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());
}
@@ -1517,7 +1536,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());
@@ -1554,9 +1573,11 @@ Validation::RTN Validation::walk_chain(jrd_rel* relation, const rhd* header,
USHORT counter = 0;
#endif
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+
ULONG page_number = header->rhd_b_page;
USHORT line_number = header->rhd_b_line;
- WIN window(DB_PAGE_SPACE, -1);
+ WIN window(pageSpaceId, -1);
window.win_flags = WIN_garbage_collector;
while (page_number)
@@ -1568,12 +1589,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->rel_id)
{
- 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++;
@@ -1624,7 +1645,7 @@ void Validation::walk_database()
DPM_scan_pages(vdr_tdbb);
WIN window(DB_PAGE_SPACE, -1);
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)
@@ -1638,6 +1659,19 @@ void Validation::walk_database()
walk_generators();
}
+ // Fill the relations cache with all known relations
+ // Without this we cant report about lost pointer pages
+ PreparedStatement::Builder sql;
+ SLONG rdbRelationID;
+ sql << "select"
+ << sql("rdb$relation_id", rdbRelationID)
+ << "from rdb$relations where (rdb$relation_type = 0) and (rdb$relation_id is not null)";
+ AutoPreparedStatement ps(attachment->prepareStatement(vdr_tdbb, attachment->getSysTransaction(), sql));
+ AutoResultSet rs(ps->executeQuery(vdr_tdbb, attachment->getSysTransaction()));
+
+ while (rs->fetch(vdr_tdbb))
+ MET_relation(vdr_tdbb, rdbRelationID);
+
vec* vector;
for (USHORT i = 0; (vector = attachment->att_relations) && i < vector->count(); i++)
{
@@ -1684,7 +1718,7 @@ void Validation::walk_database()
// We can't realiable 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();
+ vdr_page_bitmap[relation->getBasePages()->rel_pg_space_id]->clear(); // Should all array be cleared?
string relName;
relName.printf("Relation %d (%s)", relation->rel_id, relation->rel_name.toQuotedString().c_str());
@@ -1721,11 +1755,12 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number,
**************************************/
Database* dbb = vdr_tdbb->getDatabase();
- WIN window(DB_PAGE_SPACE, -1);
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+ WIN window(pageSpaceId, -1);
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)
@@ -1740,7 +1775,7 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number,
if (page->dpg_relation != relation->rel_id || 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;
@@ -1792,7 +1827,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)
@@ -1877,7 +1912,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)
{
@@ -1970,7 +2005,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);
}
}
@@ -1997,15 +2032,18 @@ 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 ULONG pageNumber = root_page->irt_rpt[id].getRootPage();
+ if (!pageNumber)
return rtn_ok;
+ const ULONG pageSpaceId = root_page->irt_rpt[id].getRootPageSpaceId();
+
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;
@@ -2021,8 +2059,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;
@@ -2039,20 +2077,20 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_
while (next)
{
- WIN window(DB_PAGE_SPACE, -1);
+ WIN window(pageSpaceId, -1);
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) {
@@ -2061,14 +2099,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->rel_id || 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;
@@ -2079,7 +2117,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__);
}
@@ -2096,7 +2134,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
@@ -2106,7 +2144,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__);
}
@@ -2114,7 +2152,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__);
}
@@ -2122,7 +2160,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__);
}
@@ -2134,7 +2172,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__);
}
@@ -2154,7 +2192,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;
}
@@ -2175,7 +2213,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)
@@ -2197,7 +2235,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__);
}
@@ -2221,7 +2259,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__);
}
}
@@ -2242,7 +2280,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__);
}
}
@@ -2281,11 +2319,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, -1);
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
@@ -2302,7 +2340,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)
@@ -2323,7 +2361,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__);
}
}
@@ -2332,7 +2370,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);
@@ -2342,11 +2380,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;
@@ -2356,7 +2394,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__);
}
@@ -2387,7 +2425,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);
@@ -2442,117 +2480,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 < TRANS_PAGE_SPACE; 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, -1);
+ 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,10 +2621,11 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence)
return corrupt(VAL_P_PAGE_LOST, relation, sequence);
pointer_page* page = 0;
- WIN window(DB_PAGE_SPACE, -1);
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+ WIN window(pageSpaceId, -1);
window.win_flags = WIN_garbage_collector;
- fetch_page(true, (*vector)[sequence], pag_pointer, &window, &page);
+ fetch_page(true, PageNumber(pageSpaceId, (*vector)[sequence]), pag_pointer, &window, &page);
#ifdef DEBUG_VAL_VERBOSE
if (VAL_debug_level)
@@ -2594,7 +2640,7 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence)
if (page->ppg_relation != relation->rel_id || 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, (*vector)[sequence], sequence);
}
// Walk the data pages (someday we may optionally walk pages with "large objects"
@@ -2623,7 +2669,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, PageNumber(pageSpaceId, (*vector)[sequence]), pag_pointer, &window, &page);
bits = (UCHAR*) (page->ppg_page + dbb->dbb_dp_per_pp);
pages = &page->ppg_page[slot];
}
@@ -2648,9 +2694,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))
{
@@ -2687,7 +2733,7 @@ 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, pag_pointer, relation->rel_id);
vector = relation->getBasePages()->rel_pages;
@@ -2696,7 +2742,7 @@ Validation::RTN Validation::walk_pointer_page(jrd_rel* relation, ULONG sequence)
return corrupt(VAL_P_PAGE_LOST, relation, sequence);
}
- fetch_page(false, (*vector)[sequence], pag_pointer, &window, &page);
+ fetch_page(false, PageNumber(pageSpaceId, (*vector)[sequence]), pag_pointer, &window, &page);
++sequence;
const bool error = (sequence >= vector->count()) ||
@@ -2708,7 +2754,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);
@@ -2828,11 +2874,11 @@ Validation::RTN Validation::walk_record(jrd_rel* relation, const rhd* header, US
data_page* page = 0;
while (flags & rhd_incomplete)
{
- WIN window(DB_PAGE_SPACE, -1);
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+ WIN window(pageSpaceId, -1);
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->rel_id ||
@@ -2932,9 +2978,11 @@ 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 ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+
+ 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);
@@ -2948,10 +2996,11 @@ void Validation::checkDPinPP(jrd_rel* relation, ULONG page_number)
pointer_page* ppage = 0;
if (pp_sequence < vector->count())
{
- fetch_page(false, (*vector)[pp_sequence], pag_pointer, &window, &ppage);
+ fetch_page(false, PageNumber(pageSpaceId, (*vector)[pp_sequence]), pag_pointer, &window, &ppage);
if (slot >= ppage->ppg_count)
{
- corrupt(VAL_DATA_PAGE_SLOT_NOT_FOUND, relation, page_number, window.win_page.getPageNum(), slot);
+ 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)
{
CCH_MARK(vdr_tdbb, &window);
@@ -2974,7 +3023,8 @@ void Validation::checkDPinPP(jrd_rel* relation, ULONG page_number)
}
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]);
+ 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);
@@ -2988,7 +3038,7 @@ void Validation::checkDPinPP(jrd_rel* relation, ULONG page_number)
}
}
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);
}
@@ -3004,20 +3054,22 @@ void Validation::checkDPinPIP(jrd_rel* relation, ULONG page_number)
Database* dbb = vdr_tdbb->getDatabase();
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
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);
@@ -3090,7 +3142,7 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation)
WIN window(DB_PAGE_SPACE, -1);
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);
}
@@ -3109,6 +3161,59 @@ Validation::RTN Validation::walk_relation(jrd_rel* relation)
const bool idxRootOk = (vdr_flags & VDR_records) && !relation->isSystem() ?
walk_root(relation, true) == rtn_ok : true;
+ // Check if the first pointer page is lost and try to restore it if VDR_update is set
+ // We use POINTER_PAGE and ROOT_PAGE of RDB$RELATIONS for this purpose
+ if ((vdr_flags & VDR_update) && !relation->getBasePages()->rel_instance_id &&
+ (!relation->getBasePages()->rel_pages || !relation->getBasePages()->rel_pages->count())
+ )
+ {
+ Jrd::Attachment* attachment = vdr_tdbb->getAttachment();
+
+ // Get POINTER_PAGE and ROOT_PAGE from RDB$RELATIONS
+ PreparedStatement::Builder sql;
+ SLONG pointerPage; // Must be ULONG
+ SLONG rootPage; // Must be ULONG
+ SLONG relId;
+ sql << "select"
+ << sql("rdb$pointer_page, " , pointerPage)
+ << sql("rdb$root_page", rootPage)
+ << "from rdb$relations where rdb$relation_id = " << relId;
+ AutoPreparedStatement ps(attachment->prepareStatement(vdr_tdbb, attachment->getSysTransaction(), sql));
+ relId = relation->rel_id;
+ AutoResultSet rs(ps->executeQuery(vdr_tdbb, attachment->getSysTransaction()));
+
+ if (rs->fetch(vdr_tdbb))
+ {
+ // Try to restore pages and check every page that it is a pointer page and belongs to the relation
+ const ULONG pageSpaceId = relation->getBasePages()->rel_pg_space_id;
+ ULONG sequence = 0;
+ while (pointerPage)
+ {
+ pointer_page* page = NULL;
+ WIN window(pageSpaceId, -1);
+ fetch_page(false, PageNumber(pageSpaceId, pointerPage), pag_pointer, &window, &page);
+ if (page->ppg_relation != relation->rel_id)
+ break;
+ const ULONG next_ppg = page->ppg_next;
+ release_page(&window);
+
+ DPM_pages(vdr_tdbb, relation->rel_id, pag_pointer, sequence, pointerPage);
+ pointerPage = next_ppg;
+ sequence++;
+ }
+ index_root_page* root = NULL;
+ WIN window(pageSpaceId, -1);
+ fetch_page(false, PageNumber(pageSpaceId, rootPage), pag_root, &window, &root);
+ const bool correctRoot = (root->irt_relation == relation->rel_id);
+ release_page(&window);
+ if (correctRoot)
+ DPM_pages(vdr_tdbb, relation->rel_id, pag_root, 0, rootPage);
+
+ DPM_scan_pages(vdr_tdbb, pag_pointer, relation->rel_id);
+ DPM_scan_pages(vdr_tdbb, pag_root, relation->rel_id);
+ }
+ }
+
// Walk pointer and selected data pages associated with relation
vdr_rel_backversion_counter = 0;
@@ -3233,20 +3338,21 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo)
if (!relPages->rel_index_root)
return corrupt(VAL_INDEX_ROOT_MISSING, relation);
+ const ULONG pageSpaceId = relPages->rel_pg_space_id;
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, -1);
+ fetch_page(!getInfo, PageNumber(pageSpaceId, relPages->rel_index_root), 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;
QualifiedName index;
release_page(&window);
MET_lookup_index(vdr_tdbb, index, relation->rel_name, i + 1);
- fetch_page(false, relPages->rel_index_root, pag_root, &window, &page);
+ fetch_page(false, PageNumber(pageSpaceId, relPages->rel_index_root), pag_root, &window, &page);
if (vdr_sch_incl)
{
@@ -3331,7 +3437,7 @@ Validation::RTN Validation::walk_tip(TraNumber transaction)
}
WIN window(DB_PAGE_SPACE, -1);
- fetch_page(true, pageNumber, pag_transactions, &window, &page);
+ fetch_page(true, PageNumber(DB_PAGE_SPACE, pageNumber), pag_transactions, &window, &page);
#ifdef DEBUG_VAL_VERBOSE
if (VAL_debug_level)
@@ -3365,31 +3471,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 < TRANS_PAGE_SPACE; 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..9a335b80224 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
@@ -145,20 +145,24 @@ class Validation
static const MSG_ENTRY vdr_msg_table[VAL_MAX_ERROR];
thread_db* vdr_tdbb;
- ULONG vdr_max_page;
+ ULONG vdr_max_page[TRANS_PAGE_SPACE]; // Keep max page in every available tablespace
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;
+ PageBitmap* vdr_page_bitmap[TRANS_PAGE_SPACE]; // fetched pages in every tablespace
ULONG vdr_err_counts[VAL_MAX_ERROR];
Firebird::UtilSvc* vdr_service;
@@ -187,23 +191,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 a750fdab0f2..df622ff6785 100644
--- a/src/jrd/vio.cpp
+++ b/src/jrd/vio.cpp
@@ -84,6 +84,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"
@@ -178,7 +179,7 @@ static void protect_system_table_delupd(thread_db* tdbb, const jrd_rel* relation
static void purge(thread_db*, record_param*);
static void replace_record(thread_db*, record_param*, PageStack*, const jrd_tra*);
static void refresh_fk_fields(thread_db*, Record*, record_param*, record_param*);
-static SSHORT set_metadata_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
+static SSHORT set_metadata_id(thread_db*, Record*, USHORT, drq_type_t, const char*, SLONG shift = 0);
static void set_nbackup_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
static void set_owner_name(thread_db*, Record*, USHORT);
static bool set_security_class(thread_db*, Record*, USHORT);
@@ -676,13 +677,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->rel_id)
{
case rel_segments:
case rel_files:
+ case rel_tablespaces:
return;
// fix_plugins_schemas may also delete these objects:
@@ -2400,6 +2403,14 @@ bool VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
DFW_post_work(transaction, dfw_change_repl_state, {}, {}, 1);
break;
+ case rel_tablespaces:
+ protect_system_table_delupd(tdbb, relation, "DELETE");
+ EVL_field(0, rpb->rpb_record, f_ts_file, &desc);
+ EVL_field(0, rpb->rpb_record, f_ts_id, &desc2);
+ id = MOV_get_long(tdbb, &desc2, 0);
+ DFW_post_work(transaction, dfw_delete_tablespace, &desc, nullptr, id);
+ break;
+
default: // Shut up compiler warnings
break;
}
@@ -3691,15 +3702,24 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
EVL_field(0, new_rpb->rpb_record, f_idx_schema, &schemaDesc);
EVL_field(0, new_rpb->rpb_record, f_idx_name, &desc1);
- if (EVL_field(0, new_rpb->rpb_record, f_idx_exp_blr, &desc2))
+ if (!dfw_should_know(tdbb, org_rpb, new_rpb, f_idx_ts_name, true))
{
- DFW_post_work(transaction, dfw_create_expression_index,
- &desc1, &schemaDesc, tdbb->getDatabase()->dbb_max_idx);
+ // Only tablespace name was changed. Move index data by pages.
+ DFW_post_work(transaction, dfw_move_index, &desc1, &schemaDesc,
+ tdbb->getDatabase()->dbb_max_idx);
}
else
{
- DFW_post_work(transaction, dfw_create_index, &desc1, &schemaDesc,
- tdbb->getDatabase()->dbb_max_idx);
+ if (EVL_field(0, new_rpb->rpb_record, f_idx_exp_blr, &desc2))
+ {
+ DFW_post_work(transaction, dfw_create_expression_index,
+ &desc1, &schemaDesc, tdbb->getDatabase()->dbb_max_idx);
+ }
+ else
+ {
+ DFW_post_work(transaction, dfw_create_index, &desc1, &schemaDesc,
+ tdbb->getDatabase()->dbb_max_idx);
+ }
}
}
break;
@@ -3783,6 +3803,19 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
check_repl_state(tdbb, transaction, org_rpb, new_rpb, f_pub_active_flag);
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);
+ DFW_post_work(transaction, dfw_modify_tablespace, {}, {}, id);
+ }
+ break;
+
default:
break;
}
@@ -4672,6 +4705,32 @@ void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
DFW_post_work(transaction, dfw_change_repl_state, {}, {}, 1);
break;
+ case rel_tablespaces:
+ {
+ protect_system_table_insert(tdbb, request, relation);
+ EVL_field(0, rpb->rpb_record, f_ts_name, &desc);
+ EVL_field(0, rpb->rpb_record, f_ts_file, &desc2);
+
+ // File with tablespace should not exist. Check it in DFW is silent and cause to
+ // clean up of existing files at phase 0. Early checking preserve existing files.
+ const PathName fileName = MOV_make_string2(tdbb, &desc2, ttype_metadata).ToPathName();
+ if (PIO_file_exists(fileName))
+ {
+ string tableSpace = MOV_make_string2(tdbb, &desc, ttype_metadata);
+ tableSpace.trim();
+ ERR_post(Arg::Gds(isc_ts_file_exists) << Arg::Str(tableSpace) << Arg::Str(fileName));
+ }
+
+ object_id = set_metadata_id(tdbb, rpb->rpb_record,
+ f_ts_id, drq_g_nxt_ts_id, "RDB$TABLESPACES", 1);
+ 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;
}
@@ -6753,7 +6812,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;
@@ -7121,7 +7180,7 @@ static void refresh_fk_fields(thread_db* tdbb, Record* old_rec, record_param* cu
static SSHORT set_metadata_id(thread_db* tdbb, Record* record, USHORT field_id, drq_type_t dyn_id,
- const char* name)
+ const char* name, SLONG shift)
{
/**************************************
*
@@ -7139,7 +7198,7 @@ static SSHORT set_metadata_id(thread_db* tdbb, Record* record, USHORT field_id,
if (EVL_field(0, record, field_id, &desc1))
return MOV_get_long(tdbb, &desc1, 0);
- SSHORT value = (SSHORT) DYN_UTIL_gen_unique_id(tdbb, dyn_id, name);
+ SSHORT value = (SSHORT) DYN_UTIL_gen_unique_id(tdbb, dyn_id, name) + shift;
dsc desc2;
desc2.makeShort(0, &value);
MOV_move(tdbb, &desc2, &desc1);
@@ -7329,8 +7388,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/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp
index bd57af75858..a88ace5f838 100644
--- a/src/utilities/gstat/dba.epp
+++ b/src/utilities/gstat/dba.epp
@@ -1595,7 +1595,7 @@ static void analyze_index( const dba_rel* relation, dba_idx* index)
if (index_root->irt_count <= index->idx_id)
return;
- ULONG page = index_root->irt_rpt[index->idx_id].getRoot();
+ ULONG page = index_root->irt_rpt[index->idx_id].getRootPage();
if (!page)
return;