diff --git a/builds/install/misc/replication.conf b/builds/install/misc/replication.conf
index 54bf02da664..f1a1b4e19c6 100644
--- a/builds/install/misc/replication.conf
+++ b/builds/install/misc/replication.conf
@@ -48,7 +48,7 @@ database
# with an ordinal sequential number. If not specified, database filename
# (without path) is used as a prefix.
#
- # journal_file_prefix =
+ # journal_file_prefix =
# Maximum allowed size for a single replication segment.
#
@@ -162,6 +162,11 @@ database
# then reconnects back and tries to re-apply the latest segments from the point of failure.
#
# apply_error_timeout = 60
+
+ # If disabled, tablespaces-related DDL statements and clauses in
+ # CREATE/ALTER TABLE/INDEX will not be applied to the replica.
+ #
+ # apply_tablespaces_ddl = true
}
#
diff --git a/builds/posix/make.shared.targets b/builds/posix/make.shared.targets
index 5fefbf18b05..ff4a673d13d 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.vcxproj b/builds/win32/msvc15/engine.vcxproj
index dac34ca8093..1c8ff3688e4 100644
--- a/builds/win32/msvc15/engine.vcxproj
+++ b/builds/win32/msvc15/engine.vcxproj
@@ -231,4 +231,4 @@
-
\ No newline at end of file
+
diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj
index c2b465b4cce..2b77348508b 100644
--- a/builds/win32/msvc15/engine_static.vcxproj
+++ b/builds/win32/msvc15/engine_static.vcxproj
@@ -22,6 +22,7 @@
+
@@ -166,6 +167,7 @@
+
@@ -216,6 +218,7 @@
+
@@ -352,6 +355,7 @@
+
@@ -380,6 +384,7 @@
+
Document
diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters
index 7aa65c72ea2..5abafbb904b 100644
--- a/builds/win32/msvc15/engine_static.vcxproj.filters
+++ b/builds/win32/msvc15/engine_static.vcxproj.filters
@@ -459,6 +459,9 @@
DSQL\preprocesed
+
+ DSQL\preprocesed
+
Services
@@ -477,6 +480,9 @@
JRD files
+
+ JRD files
+
JRD files
@@ -1100,6 +1106,12 @@
Header files
+
+ Header files
+
+
+ Header files
+
Header files
@@ -1118,6 +1130,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..2a23382ae24
--- /dev/null
+++ b/doc/README.tablespaces
@@ -0,0 +1,189 @@
+-----------------
+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.
+
+-----------------
+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. Add the main database file to the RDB$TABLESPACES table. Designate it as PRIMARY.
+ The RDB$TABLESPACE_NAME field contained in the system tables of tables, indexes will have the value “PRIMARY” instead of NULL.
+ 2. TEMPORARY predefined tablespaces.
+ 3. Grouping page counters by tablespace (For output in trace and monitoring).
+ 4. New header page for tablespaces
+ 5. NBACKUP support for tablespaces
+ 6. Moving blobs to separate tablespaces (The RDB$TABLESPACE_NAME column in RDB$RELATION_FIELDS is reserved for this purpose).
+ 7. Possibility to introduce UNDO tablespace in future versions
+ 8. The ability to set default tablespaces for tables, indexes and BLOBs at the database or schema level.
+
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/OdsDetection.epp b/src/burp/OdsDetection.epp
index 024adfe7762..2725e42aa22 100644
--- a/src/burp/OdsDetection.epp
+++ b/src/burp/OdsDetection.epp
@@ -45,6 +45,7 @@ namespace
{"RDB$ROLES", 0, DB_VERSION_DDL9}, // IB5
{"RDB$PACKAGES", 0, DB_VERSION_DDL12}, // FB3
{"RDB$PUBLICATIONS", 0, DB_VERSION_DDL13}, // FB4
+ {"RDB$TABLESPACES", 0, DB_VERSION_DDL14}, // FB6
{0, 0, 0}
};
diff --git a/src/burp/OdsDetection.h b/src/burp/OdsDetection.h
index 5abaf298520..61f99be03f4 100644
--- a/src/burp/OdsDetection.h
+++ b/src/burp/OdsDetection.h
@@ -69,6 +69,7 @@ const int DB_VERSION_DDL11_2 = 112; // ods11.2 db, FB2.5
const int DB_VERSION_DDL12 = 120; // ods12.0 db, FB3.0
const int DB_VERSION_DDL13 = 130; // ods13.0 db, FB4.0
const int DB_VERSION_DDL13_1 = 131; // ods13.1 db, FB5.0
+const int DB_VERSION_DDL14 = 140; // ods14.0 db, FB6.0
const int DB_VERSION_OLDEST_SUPPORTED = DB_VERSION_DDL8; // IB4.0 is ods8
diff --git a/src/burp/backup.epp b/src/burp/backup.epp
index ebfe71f168d..65aa6891d4e 100644
--- a/src/burp/backup.epp
+++ b/src/burp/backup.epp
@@ -150,6 +150,7 @@ void write_rel_constraints();
void write_relations();
void write_secclasses();
void write_shadow_files();
+void write_tablespaces();
void write_triggers();
void write_trigger_messages();
void write_types();
@@ -402,6 +403,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
{
@@ -763,6 +771,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
@@ -1683,6 +1695,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,
@@ -1760,6 +1776,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;
@@ -2046,6 +2066,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);
}
@@ -3914,6 +3937,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;
@@ -4200,6 +4227,61 @@ 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
+ {
+ 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 9db13af103f..a3fe41cf529 100644
--- a/src/burp/burp.cpp
+++ b/src/burp/burp.cpp
@@ -596,6 +596,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;
@@ -1090,6 +1091,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
@@ -2888,6 +2918,52 @@ 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)
diff --git a/src/burp/burp.h b/src/burp/burp.h
index d174c84f487..6af63656198 100644
--- a/src/burp/burp.h
+++ b/src/burp/burp.h
@@ -47,6 +47,7 @@
#include "../common/status.h"
#include "../common/sha.h"
#include "../common/classes/ImplementHelper.h"
+#include "../common/classes/GenericMap.h"
#ifdef HAVE_UNISTD_H
#include
@@ -120,7 +121,8 @@ enum rec_type {
rec_package, // Package
rec_db_creator, // Database creator
rec_publication, // Publication
- rec_pub_table // Publication table
+ rec_pub_table, // Publication table
+ rec_tablespace // Tablespace
};
@@ -207,7 +209,7 @@ Version 11: FB4.0.
SQL SECURITY feature, tables RDB$PUBLICATIONS/RDB$PUBLICATION_TABLES.
*/
-const int ATT_BACKUP_FORMAT = 11;
+const int ATT_BACKUP_FORMAT = 13;
// max array dimension
@@ -282,6 +284,7 @@ enum att_type {
att_relation_type,
att_relation_sql_security_deprecated, // can be removed later
att_relation_sql_security,
+ att_relation_tablespace_name,
// Field attributes (used for both global and local fields)
@@ -341,6 +344,7 @@ enum att_type {
att_field_owner_name, // FB3.0, ODS12_0,
att_field_generator_name,
att_field_identity_type,
+ att_field_tablespace_name,
// Index attributes
@@ -357,6 +361,7 @@ enum att_type {
att_index_expression_blr,
att_index_condition_source,
att_index_condition_blr,
+ att_index_tablespace_name,
// Data record
@@ -656,7 +661,16 @@ enum att_type {
// Publication tables
att_ptab_pub_name = SERIES,
- att_ptab_table_name
+ att_ptab_table_name,
+
+ // 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
};
@@ -728,6 +742,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 {
@@ -971,7 +986,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,
@@ -1036,6 +1052,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;
@@ -1154,6 +1171,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.
@@ -1177,6 +1196,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();
@@ -1192,6 +1214,7 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool
void setupSkipData(const Firebird::string& regexp);
void setupIncludeData(const Firebird::string& regexp);
bool skipRelation(const char* name);
+ void loadMapping(const char* mapping_file, StringMap& map, bool clearMap = true, bool caseSensitive = true);
char veryEnd;
//starting after this members must be initialized in constructor explicitly
@@ -1213,6 +1236,7 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool
bool stdIoMode; // stdin or stdout is used as backup file
Firebird::AutoPtr skipDataMatcher;
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 609f928614b..e781206a11e 100644
--- a/src/burp/burpswi.h
+++ b/src/burp/burpswi.h
@@ -98,10 +98,13 @@ const int IN_SW_BURP_CRYPT = 51; // name of crypt plugin
const int IN_SW_BURP_INCLUDE_DATA = 52; // backup data from tables
const int IN_SW_BURP_REPLICA = 53; // replica mode
-
const int IN_SW_BURP_PARALLEL_WORKERS = 54; // parallel workers
const int IN_SW_BURP_DIRECT_IO = 55; // direct IO for backup files
+const int IN_SW_BURP_TS_MAPPING_FILE = 56; // mapping file for tablespaces
+const int IN_SW_BURP_TS_ORIGINAL_PATHS = 57; // restore tablespaces to their original paths
+const int IN_SW_BURP_TS_PATH = 58; // set a path for a tablespace
+
/**************************************************************************/
static const char* const BURP_SW_MODE_NONE = "NONE";
@@ -231,6 +234,9 @@ static const 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 1bc95c3df0e..9192f69719e 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"
using MsgFormat::SafeArg;
@@ -144,6 +146,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, GDS_NAME, GDS_NAME);
bool get_publication(BurpGlobals* tdgbl);
@@ -3762,6 +3765,8 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation)
// ODS 12
X.RDB$GENERATOR_NAME.NULL = TRUE;
X.RDB$IDENTITY_TYPE.NULL = TRUE;
+ // ODS 14
+ X.RDB$TABLESPACE_NAME.NULL = TRUE;
skip_init(&scan_next_attr);
while (get_attribute(&attribute, tdgbl) != att_end)
@@ -3931,6 +3936,16 @@ 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:
+ {
+ GET_TEXT(X.RDB$TABLESPACE_NAME);
+ const auto str = tdgbl->tablespace_mapping.get(X.RDB$TABLESPACE_NAME);
+ X.RDB$TABLESPACE_NAME.NULL = str ? str->equals(PRIMARY_TABLESPACE_NAME) : FALSE;
+ break;
+ }
+
default:
bad_attribute(scan_next_attr, attribute, 84);
// msg 84 column
@@ -6533,6 +6548,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;
+ // ODS 14
+ X.RDB$TABLESPACE_NAME.NULL = TRUE;
skip_init(&scan_next_attr);
while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
@@ -6645,6 +6662,16 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation)
GET_TEXT(X.RDB$FOREIGN_KEY);
break;
+ // ODS 14
+
+ case att_index_tablespace_name:
+ {
+ GET_TEXT(X.RDB$TABLESPACE_NAME);
+ const auto str = tdgbl->tablespace_mapping.get(X.RDB$TABLESPACE_NAME);
+ X.RDB$TABLESPACE_NAME.NULL = str ? str->equals(PRIMARY_TABLESPACE_NAME) : FALSE;
+ break;
+ }
+
default:
bad_attribute(scan_next_attr, attribute, 93);
// msg 93 index
@@ -6889,6 +6916,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)
{
/**************************************
@@ -7764,6 +7970,10 @@ 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;
+ tableSpace[0] = '\0';
+ bool tableSpaceNull = true;
+
// 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.
@@ -7895,6 +8105,14 @@ 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:
+ {
+ GET_TEXT(tableSpace);
+ const auto str = tdgbl->tablespace_mapping.get(tableSpace);
+ tableSpaceNull = str ? str->equals(PRIMARY_TABLESPACE_NAME) : FALSE;
+ break;
+ }
+
default:
bad_attribute(scan_next_attr, attribute, 111);
// msg 111 table
@@ -7941,6 +8159,10 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
X.RDB$RELATION_TYPE = (USHORT) type;
X.RDB$SQL_SECURITY = (FB_BOOLEAN) sql_security;
+ // ODS 14
+ X.RDB$TABLESPACE_NAME.NULL = tableSpaceNull;
+ strcpy(X.RDB$TABLESPACE_NAME, tableSpace);
+
END_STORE;
ON_ERROR
general_on_error ();
@@ -10642,13 +10864,36 @@ 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
+ 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:
@@ -10712,6 +10957,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 ad2b6827228..8aeaf8916b4 100644
--- a/src/common/ParserTokens.h
+++ b/src/common/ParserTokens.h
@@ -560,3 +560,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/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp
index 0fa5cce0bb2..e7335f23660 100644
--- a/src/dsql/DdlNodes.epp
+++ b/src/dsql/DdlNodes.epp
@@ -113,6 +113,8 @@ static void updateRdbFields(const TypeClause* type,
SSHORT& collationIdNull, SSHORT& collationId,
SSHORT& segmentLengthNull, SSHORT& segmentLength);
+static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const MetaName& name, int type);
+
static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
DATABASE DB = STATIC "ODS.RDB";
@@ -1455,6 +1457,11 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
SCL_check_package(tdbb, &dscName, SCL_alter);
break;
+ case obj_tablespace:
+ dscName.makeText(objNameStr.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
+ SCL_check_tablespace(tdbb, &dscName, SCL_alter);
+ break;
+
default:
fb_assert(false);
}
@@ -1602,6 +1609,12 @@ void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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;
@@ -6077,7 +6090,8 @@ RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
: DdlNode(p),
dsqlNode(aDsqlNode),
name(p, dsqlNode->dsqlName),
- clauses(p)
+ clauses(p),
+ tableSpace(p)
{
}
@@ -6176,6 +6190,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 = TRUE;
if (collationId.has_value())
{
@@ -6244,6 +6259,14 @@ 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);
}
+
+ if (tableSpace.hasData() && applyTablespacesDdl(tdbb))
+ {
+ if (!checkObjectExist(tdbb, transaction, tableSpace, obj_tablespaces))
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace.c_str());
+ RFR.RDB$TABLESPACE_NAME.NULL = FALSE;
+ strcpy(RFR.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ }
}
END_STORE
}
@@ -6585,6 +6608,9 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
fieldDefinition.defaultValue = defaultValue;
fieldDefinition.collationId = field->collationId;
+ if (field->fld_ts_name.hasData())
+ fieldDefinition.tableSpace = field->fld_ts_name;
+
fieldDefinition.store(tdbb, transaction);
// Define the field constraints.
@@ -6666,6 +6692,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
if (constraint.create->index && constraint.create->index->name.isEmpty())
constraint.create->index->name = constraint.name;
constraint.create->columns = clause->columns;
+ constraint.create->tableSpace = clause->tableSpace;
break;
}
@@ -6784,6 +6811,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
}
}
+ constraint.create->tableSpace = clause->tableSpace;
break;
}
@@ -6862,6 +6890,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;
CreateIndexNode::store(tdbb, transaction, constraint.index->name,
definition, &referredIndexName);
@@ -7553,6 +7582,16 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
REL.RDB$VIEW_SOURCE.NULL = TRUE;
REL.RDB$EXTERNAL_FILE.NULL = TRUE;
+ if (tableSpace.hasData() && tableSpace != PRIMARY_TABLESPACE_NAME && applyTablespacesDdl(tdbb))
+ {
+ if (!checkObjectExist(tdbb, transaction, tableSpace, obj_tablespaces))
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << tableSpace.c_str());
+ REL.RDB$TABLESPACE_NAME.NULL = FALSE;
+ strcpy(REL.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ }
+ else
+ REL.RDB$TABLESPACE_NAME.NULL = TRUE;
+
if (externalFile)
{
if (externalFile->length() >= sizeof(REL.RDB$EXTERNAL_FILE))
@@ -8041,6 +8080,70 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
break;
}
+ case Clause::TYPE_SET_TABLESPACE:
+ {
+ fb_assert(tableSpace.hasData());
+
+ if (!applyTablespacesDdl(tdbb))
+ break;
+
+ const bool primary_ts = (tableSpace == PRIMARY_TABLESPACE_NAME);
+
+ if (!primary_ts && !checkObjectExist(tdbb, transaction, 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$RELATION_NAME EQ name.c_str()
+ {
+ 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_table) << name);
+ }
+
+ // Check if we are trying to change tablespace name to the already set
+ if ( (REL.RDB$TABLESPACE_NAME.NULL && primary_ts) ||
+ (!REL.RDB$TABLESPACE_NAME.NULL && 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
+ {
+ if (!primary_ts)
+ {
+ REL.RDB$TABLESPACE_NAME.NULL = FALSE;
+ strcpy(REL.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ }
+ else
+ {
+ REL.RDB$TABLESPACE_NAME.NULL = TRUE;
+ REL.RDB$TABLESPACE_NAME[0] = 0;
+ }
+ }
+ END_MODIFY
+
+ if (!REL.RDB$RELATION_ID.NULL)
+ DFW_post_work(transaction, dfw_move_relation, name.c_str(), REL.RDB$RELATION_ID);
+ }
+ END_FOR
+
+ break;
+ }
+
default:
fb_assert(false);
break;
@@ -8594,79 +8697,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)
+void DropRelationNode::dropRelation(thread_db* tdbb, jrd_tra* transaction, bool view, jrd_rel* rel_drop, const MetaName& name)
{
- dsc dscName;
- dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
- if (view)
- SCL_check_view(tdbb, &dscName, SCL_drop);
- else
- SCL_check_relation(tdbb, &dscName, 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 (!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);
- }
- }
- 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);
- }
+ 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);
}
- 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$RELATION_NAME EQ name.c_str()
- {
- executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, NULL);
- 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
@@ -8763,12 +8806,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
MetaName triggerName;
@@ -8822,18 +8859,93 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
}
END_FOR
- if (found)
- executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, NULL);
+ METD_drop_relation(transaction, name.c_str());
+ 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";
+}
+
+void DropRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ dsc dscName;
+ dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
+ if (view)
+ SCL_check_view(tdbb, &dscName, SCL_drop);
else
+ SCL_check_relation(tdbb, &dscName, 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->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);
+ }
+ }
+ else
+ {
+ if (!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);
+ }
+ }
+
+ 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$RELATION_NAME EQ name.c_str()
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, NULL);
+ found = true;
+ }
+ END_FOR
+
+ if (!found)
{
// msg 61: "Relation not found"
status_exception::raise(Arg::PrivateDyn(61));
}
- savePoint.release(); // everything is ok
+ dropRelation(tdbb, transaction, view, rel_drop, name);
- METD_drop_relation(transaction, name.c_str());
- MET_dsql_cache_release(tdbb, SYM_relation, name);
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, NULL);
+
+ savePoint.release(); // everything is ok
}
@@ -9688,6 +9800,10 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& nam
IDX.RDB$RELATION_NAME.NULL = FALSE;
IDX.RDB$SYSTEM_FLAG = 0;
IDX.RDB$SYSTEM_FLAG.NULL = FALSE; // Probably redundant.
+ IDX.RDB$TABLESPACE_NAME.NULL = TRUE;
+
+ MetaName relationTablespace;
+ bool temporary = false;
// Check if the table is actually a view.
@@ -9702,6 +9818,15 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& nam
// 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
@@ -9723,6 +9848,31 @@ void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& nam
IDX.RDB$INDEX_TYPE = SSHORT(definition.descending.asBool());
}
+ 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)
+ {
+ if (!checkObjectExist(tdbb, transaction, definition.tableSpace, obj_tablespaces))
+ status_exception::raise(Arg::Gds(isc_dyn_ts_not_found) << definition.tableSpace.c_str());
+
+ IDX.RDB$TABLESPACE_NAME.NULL = FALSE;
+ strcpy(IDX.RDB$TABLESPACE_NAME, definition.tableSpace.c_str());
+ }
+ }
+ else if (relationTablespace.hasData())
+ {
+ /**
+ * 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.
+ */
+ IDX.RDB$TABLESPACE_NAME.NULL = FALSE;
+ 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)
@@ -10087,6 +10237,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();
@@ -10119,7 +10271,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";
}
@@ -10152,10 +10305,62 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX,
name, NULL);
- 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, 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$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);
+ }
+ }
+ END_FOR
+
+ // Check if we are trying to change tablespace name to the already set
+ if ( (IDX.RDB$TABLESPACE_NAME.NULL && primary_ts) ||
+ (!IDX.RDB$TABLESPACE_NAME.NULL && tableSpace == IDX.RDB$TABLESPACE_NAME) )
+ {
+ break;
+ }
+
+ MODIFY IDX
+ if (!primary_ts)
+ {
+ IDX.RDB$TABLESPACE_NAME.NULL = FALSE;
+ strcpy(IDX.RDB$TABLESPACE_NAME, tableSpace.c_str());
+ }
+ else
+ {
+ IDX.RDB$TABLESPACE_NAME.NULL = TRUE;
+ IDX.RDB$TABLESPACE_NAME[0] = 0;
+ }
+ END_MODIFY
+ }
}
END_FOR
@@ -11793,6 +11998,19 @@ static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const MetaNa
END_FOR
break;
}
+
+ case obj_tablespaces:
+ {
+ 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.c_str()
+ {
+ rc = true;
+ }
+ END_FOR
+ break;
+ }
}
return rc;
diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h
index ffa46aa9596..eec1603ae18 100644
--- a/src/dsql/DdlNodes.h
+++ b/src/dsql/DdlNodes.h
@@ -1213,7 +1213,8 @@ class RelationNode : public DdlNode
fieldSource(p),
identitySequence(p),
defaultSource(p),
- baseField(p)
+ baseField(p),
+ tableSpace(p)
{
}
@@ -1233,6 +1234,7 @@ class RelationNode : public DdlNode
Firebird::ByteChunk defaultValue;
std::optional viewContext;
MetaName baseField;
+ MetaName tableSpace;
};
struct IndexConstraintClause
@@ -1287,7 +1289,8 @@ class RelationNode : public DdlNode
refUpdateAction(RI_RESTRICT),
refDeleteAction(RI_RESTRICT),
triggers(p),
- blrWritersHolder(p)
+ blrWritersHolder(p),
+ tableSpace(p)
{
}
@@ -1300,6 +1303,7 @@ class RelationNode : public DdlNode
const char* refDeleteAction;
Firebird::ObjectsArray triggers;
Firebird::ObjectsArray blrWritersHolder;
+ MetaName tableSpace;
};
struct CreateDropConstraint
@@ -1327,7 +1331,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)
@@ -1375,7 +1380,8 @@ class RelationNode : public DdlNode
refRelation(p),
refColumns(p),
refAction(NULL),
- check(NULL)
+ check(NULL),
+ tableSpace(p)
{
}
@@ -1388,6 +1394,7 @@ class RelationNode : public DdlNode
NestConst refAction;
NestConst check;
bool createIfNotExistsOnly = false;
+ MetaName tableSpace;
};
struct IdentityOptions
@@ -1573,6 +1580,7 @@ class RelationNode : public DdlNode
Firebird::Array > clauses;
Firebird::TriState ssDefiner;
Firebird::TriState replicationState;
+ MetaName tableSpace;
};
@@ -1648,6 +1656,8 @@ class DropRelationNode : public DdlNode
static void deleteGlobalField(thread_db* tdbb, jrd_tra* transaction,
const MetaName& globalName);
+ static void dropRelation(thread_db* tdbb, jrd_tra* transaction, bool view, jrd_rel* rel_drop, const MetaName& name);
+
public:
virtual Firebird::string internalPrint(NodePrinter& printer) const;
virtual void checkPermission(thread_db* tdbb, jrd_tra* transaction);
@@ -1756,6 +1766,7 @@ class CreateIndexNode : public DdlNode
bid conditionSource;
MetaName refRelation;
Firebird::ObjectsArray refColumns;
+ MetaName tableSpace;
};
public:
@@ -1790,16 +1801,19 @@ class CreateIndexNode : public DdlNode
NestConst computed;
NestConst partial;
bool createIfNotExistsOnly = false;
+ MetaName tableSpace;
};
class AlterIndexNode : public DdlNode
{
public:
- AlterIndexNode(MemoryPool& p, const MetaName& aName, bool aActive)
+ enum OP {OP_ACTIVE, OP_INACTIVE, OP_SET_TABLESPACE};
+
+ AlterIndexNode(MemoryPool& p, const MetaName& aName, OP aOp)
: DdlNode(p),
name(p, aName),
- active(aActive)
+ op(aOp)
{
}
@@ -1816,7 +1830,8 @@ class AlterIndexNode : public DdlNode
public:
MetaName name;
- bool active;
+ OP op;
+ MetaName tableSpace;
};
diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h
index 5bbb59a4465..5ff5bf3ee70 100644
--- a/src/dsql/Nodes.h
+++ b/src/dsql/Nodes.h
@@ -192,6 +192,12 @@ class DdlNode : public Node
static void deletePrivilegesByRelName(thread_db* tdbb, jrd_tra* transaction,
const MetaName& 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 51cb5195623..af69c2abf75 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..d6df9a782d0
--- /dev/null
+++ b/src/dsql/TablespaceNodes.epp
@@ -0,0 +1,395 @@
+/*
+ * 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)
+{
+ dsc dscName;
+ dscName.makeText(name.length(), ttype_metadata, (UCHAR*) name.c_str());
+ if (alter)
+ SCL_check_tablespace(tdbb, &dscName, 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, name, obj_tablespace))
+ return;
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_CREATE_TABLESPACE, name, NULL);
+
+ DYN_UTIL_check_unique_name(tdbb, transaction, 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,
+ name, NULL);
+}
+
+
+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()
+ {
+ 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, name, NULL);
+
+ 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)
+ {
+ DFW_post_work(transaction, dfw_modify_tablespace, NULL, tableSpaceId);
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction,
+ DTW_AFTER, DDL_TRIGGER_ALTER_TABLESPACE, name, NULL);
+ }
+
+ 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)
+{
+ dsc dscName;
+ dscName.makeText(name.length(), ttype_metadata, (UCHAR*) name.c_str());
+ SCL_check_tablespace(tdbb, &dscName, 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;
+ ULONG tableSpaceId = 0;
+ string tableSpaceFileName;
+ 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()
+ {
+ found = true;
+ tableSpaceId = X.RDB$TABLESPACE_ID;
+ tableSpaceFileName = X.RDB$FILE_NAME;
+
+ // cache it for DFW stage
+ MET_tablespace_id(tdbb, tableSpaceId);
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_DROP_TABLESPACE, name, NULL);
+
+ 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)
+ {
+ MetaName relationName(X.RDB$RELATION_NAME);
+ jrd_rel* rel_drop = MET_lookup_relation(tdbb, relationName);
+ if (rel_drop)
+ MET_scan_relation(tdbb, rel_drop);
+
+ DropRelationNode::dropRelation(tdbb, transaction, false, rel_drop, 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, X.RDB$INDEX_NAME))
+ {
+ // msg 50: "No segments found for index"
+ status_exception::raise(Arg::PrivateDyn(50));
+ }
+
+ ERASE X;
+ }
+ else
+ {
+ total++;
+ }
+ }
+ END_FOR
+
+ if (!found && !silent)
+ {
+ status_exception::raise(
+ Arg::Gds(isc_no_meta_update) <<
+ Arg::Gds(isc_dyn_ts_not_found) << Arg::Str(name));
+ }
+
+ 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)
+ {
+ DFW_post_work(transaction, dfw_drop_tablespace, tableSpaceFileName, tableSpaceId);
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TABLESPACE,
+ name, NULL);
+ }
+
+ 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..ce7f9c1849a
--- /dev/null
+++ b/src/dsql/TablespaceNodes.h
@@ -0,0 +1,109 @@
+/*
+ * 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),
+ silent(false),
+ dropDependencies(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(isc_dsql_drop_ts_failed) << name;
+ }
+
+public:
+ MetaName name;
+ bool silent;
+ bool dropDependencies;
+};
+
+typedef RecreateNode
+RecreateTablespaceNode;
+
+} // namespace
+
+#endif // DSQL_TABLESPACE_NODES_H
diff --git a/src/dsql/btyacc_fb.ske b/src/dsql/btyacc_fb.ske
index 061b91b5ca9..23510cddceb 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 422e36c3961..f4b8ee73201 100644
--- a/src/dsql/dsql.h
+++ b/src/dsql/dsql.h
@@ -250,7 +250,8 @@ class dsql_fld : public TypeClause
public:
explicit dsql_fld(MemoryPool& p)
: TypeClause(p, nullptr),
- fld_name(p)
+ fld_name(p),
+ fld_ts_name(p)
{
}
@@ -265,7 +266,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 612edc3bcf0..5d4c5bae211 100644
--- a/src/dsql/parse-conflicts.txt
+++ b/src/dsql/parse-conflicts.txt
@@ -1 +1 @@
-117 shift/reduce conflicts, 22 reduce/reduce conflicts.
+121 shift/reduce conflicts, 23 reduce/reduce conflicts.
diff --git a/src/dsql/parse.y b/src/dsql/parse.y
index 6e2c5a31fd8..93da09c4da8 100644
--- a/src/dsql/parse.y
+++ b/src/dsql/parse.y
@@ -707,6 +707,13 @@ using namespace Firebird;
%token NAMED_ARG_ASSIGN
%token RTRIM
+// tablespaces
+%token INCLUDING
+%token CONTENTS
+%token TABLESPACE
+%token OFFLINE
+%token ONLINE
+
// precedence declarations for expression evaluation
%left OR
@@ -863,6 +870,8 @@ using namespace Firebird;
Jrd::SetDecFloatTrapsNode* setDecFloatTrapsNode;
Jrd::SetBindNode* setBindNode;
Jrd::SessionResetNode* sessionResetNode;
+ Jrd::CreateAlterTablespaceNode* createAlterTablespaceNode;
+ Jrd::DropTablespaceNode* dropTablespaceNode;
}
%include types.y
@@ -1083,6 +1092,8 @@ object
{ $$ = newNode(obj_collations, getSecurityClassName(obj_collations)); }
| FILTER
{ $$ = newNode(obj_filters, getSecurityClassName(obj_filters)); }
+ | TABLESPACE
+ { $$ = newNode(obj_tablespaces, getSecurityClassName(obj_tablespaces)); }
;
table_noise
@@ -1556,8 +1567,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
@@ -1664,6 +1678,12 @@ create_clause
node->createIfNotExistsOnly = $3;
$$ = node;
}
+ | TABLESPACE if_not_exists_opt tablespace_clause
+ {
+ const auto node = $3;
+ node->createIfNotExistsOnly = $2;
+ $$ = node;
+ }
;
@@ -1696,6 +1716,8 @@ recreate_clause
{ $$ = newNode($2); }
| SEQUENCE generator_clause
{ $$ = newNode($2); }
+ | TABLESPACE tablespace_clause
+ { $$ = newNode($2); }
| USER create_user_clause
{ $$ = newNode($2); }
;
@@ -1719,6 +1741,7 @@ replace_clause
| USER replace_user_clause { $$ = $2; }
| MAPPING replace_map_clause(false) { $$ = $2; }
| GLOBAL MAPPING replace_map_clause(true) { $$ = $3; }
+ | TABLESPACE replace_tablespace_clause { $$ = $2; }
;
@@ -2378,6 +2401,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
@@ -2650,7 +2675,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;
@@ -2669,18 +2694,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;
}
;
@@ -2705,7 +2739,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;
@@ -2718,10 +2752,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;
@@ -2734,11 +2771,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;
@@ -2763,6 +2803,9 @@ table_constraint($relationNode)
constraint.index = $8;
+ if ($9)
+ constraint.tableSpace = *$9;
+
$relationNode->clauses.add(&constraint);
$$ = &constraint;
}
@@ -4247,6 +4290,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; }
;
@@ -4319,6 +4365,7 @@ alter_clause
| MAPPING alter_map_clause(false) { $$ = $2; }
| GLOBAL MAPPING alter_map_clause(true) { $$ = $3; }
| EXTERNAL CONNECTIONS POOL alter_eds_conn_pool_clause { $$ = $4; }
+ | TABLESPACE alter_tablespace_clause { $$ = $2; }
;
%type alter_domain
@@ -4520,6 +4567,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
@@ -4691,9 +4745,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;
}
;
@@ -5046,6 +5104,12 @@ drop_clause
node->silentDrop = $3;
$$ = node;
}
+ | TABLESPACE if_exists_opt drop_tablespace_clause
+ {
+ const auto node = $3;
+ node->silent = $2;
+ $$ = node;
+ }
;
%type if_exists_opt
@@ -5252,7 +5316,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;
@@ -5263,16 +5327,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;
@@ -5280,8 +5350,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;
@@ -5289,6 +5362,9 @@ blob_type
$$->segLength = 80;
$$->subType = (USHORT) $4;
$$->flags |= FLD_has_sub;
+
+ //if ($6)
+ // $$->fld_ts_name = *$6;
}
;
@@ -6103,6 +6179,7 @@ ddl_type1
| CHARACTER SET { $$ = obj_charset; }
| COLLATION { $$ = obj_collation; }
| PACKAGE { $$ = obj_package_header; }
+ | TABLESPACE { $$ = obj_tablespace; }
/***
| SECURITY CLASS { $$ = ddl_sec_class; }
***/
@@ -7884,6 +7961,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
@@ -9760,6 +9933,12 @@ non_reserved_word
| ANY_VALUE
| FORMAT
| OWNER
+ | 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 5ebc4ad4e5a..4cdd9c59d42 100644
--- a/src/include/firebird/impl/msg/dyn.h
+++ b/src/include/firebird/impl/msg/dyn.h
@@ -303,3 +303,7 @@ FB_IMPL_MSG_SYMBOL(DYN, 310, dyn_dup_trigger, "Trigger @1 already exists")
FB_IMPL_MSG_SYMBOL(DYN, 311, dyn_dup_domain, "Domain @1 already exists")
FB_IMPL_MSG_SYMBOL(DYN, 312, dyn_dup_collation, "Collation @1 already exists")
FB_IMPL_MSG_SYMBOL(DYN, 313, dyn_dup_package, "Package @1 already exists")
+FB_IMPL_MSG(DYN, 314, dyn_ts_not_found, -901, "42", "000", "Tablespace @1 not found")
+FB_IMPL_MSG(DYN, 315, dyn_cant_set_ts_table, -901, "42", "000", "Cannot set tablespace for temporary table @1")
+FB_IMPL_MSG(DYN, 316, dyn_cant_set_ts_index, -901, "42", "000", "Cannot set tablespace for temporary index @1")
+FB_IMPL_MSG(DYN, 317, dyn_ts_already_exists, -901, "42", "000", "Tablespace @1 already exists")
diff --git a/src/include/firebird/impl/msg/gbak.h b/src/include/firebird/impl/msg/gbak.h
index ea6291af67d..d58ca4bb1b2 100644
--- a/src/include/firebird/impl/msg/gbak.h
+++ b/src/include/firebird/impl/msg/gbak.h
@@ -406,3 +406,13 @@ FB_IMPL_MSG_SYMBOL(GBAK, 407, gbak_missing_prl_wrks, "parallel workers parameter
FB_IMPL_MSG_SYMBOL(GBAK, 408, gbak_inv_prl_wrks, "expected parallel workers, encountered \"@1\"")
FB_IMPL_MSG_NO_SYMBOL(GBAK, 409, " @1D(IRECT_IO) direct IO for backup file(s)")
FB_IMPL_MSG_NO_SYMBOL(GBAK, 410, "use up to @1 parallel workers")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 411, "writing tablespaces")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 412, "writing tablespace @1")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 413, "restoring tablespace @1")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 414, "tablespace")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 415, " @1TS_MAP(PING_FILE) mapping file for tablespaces")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 416, "path to tablespace @1 is not specified (original path: \"@2\")")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 417, " @1TS_ORIG(INAL_PATHS) restore tablespaces to their original paths")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 418, " @1TS set a path for a tablespace")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 419, "parameter for option -@1 is missing")
+FB_IMPL_MSG_NO_SYMBOL(GBAK, 420, "cannot open mapping file \"@1\"")
diff --git a/src/include/firebird/impl/msg/isql.h b/src/include/firebird/impl/msg/isql.h
index 012c16f40f3..bdf6f00349b 100644
--- a/src/include/firebird/impl/msg/isql.h
+++ b/src/include/firebird/impl/msg/isql.h
@@ -205,3 +205,6 @@ FB_IMPL_MSG_SYMBOL(ISQL, 205, HLP_EXPLAIN, "EXPLAIN -- explai
FB_IMPL_MSG_SYMBOL(ISQL, 206, USAGE_AUTOTERM, " -autot(erm) use auto statement terminator (set autoterm on)")
FB_IMPL_MSG_SYMBOL(ISQL, 207, AUTOTERM_NOT_SUPPORTED, "SET AUTOTERM ON is not supported in engine/server and has been disabled")
FB_IMPL_MSG_SYMBOL(ISQL, 208, HLP_SETAUTOTERM, " SET AUTOTERM -- toggle auto statement terminator")
+FB_IMPL_MSG_SYMBOL(ISQL, 209, NO_TABLESPACE, "There is no tablespace @1 in this database")
+FB_IMPL_MSG_SYMBOL(ISQL, 210, NO_TABLESPACES, "There are no tablespaces in this database")
+FB_IMPL_MSG_SYMBOL(ISQL, 211, MSG_TABLESPACES, "Tablespaces:")
diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h
index 84055568c20..9f58a09c31d 100644
--- a/src/include/firebird/impl/msg/jrd.h
+++ b/src/include/firebird/impl/msg/jrd.h
@@ -987,3 +987,6 @@ FB_IMPL_MSG(JRD, 984, incompatible_format_patterns, -901, "HY", "000", "@1 incom
FB_IMPL_MSG(JRD, 985, only_one_pattern_can_be_used, -901, "HY", "000", "Can use only one of these patterns @1")
FB_IMPL_MSG(JRD, 986, can_not_use_same_pattern_twice, -901, "HY", "000", "Cannot use the same pattern twice: @1")
FB_IMPL_MSG(JRD, 987, sysf_invalid_gen_uuid_version, -833, "42", "000", "Invalid GEN_UUID version (@1). Must be 4 or 7")
+FB_IMPL_MSG(JRD, 988, ts_file_exists, -902, "08", "001", "Tablespace \"@1\" creation error. File \"@2\" exists.")
+FB_IMPL_MSG(JRD, 989, tablespace_name, -901, "42", "000", "TABLESPACE @1")
+FB_IMPL_MSG(JRD, 990, 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 25a7014b0c4..a33e515a46c 100644
--- a/src/include/firebird/impl/msg/sqlerr.h
+++ b/src/include/firebird/impl/msg/sqlerr.h
@@ -283,3 +283,8 @@ FB_IMPL_MSG(SQLERR, 1043, dsql_string_byte_length, -901, "42", "000", "String li
FB_IMPL_MSG(SQLERR, 1044, dsql_string_char_length, -901, "42", "000", "String literal with @1 characters exceeds the maximum length of @2 characters for the @3 character set")
FB_IMPL_MSG(SQLERR, 1045, dsql_max_nesting, -901, "07", "002", "Too many BEGIN...END nesting. Maximum level is @1")
FB_IMPL_MSG(SQLERR, 1046, dsql_recreate_user_failed, -901, "42", "000", "RECREATE USER @1 failed")
+FB_IMPL_MSG(SQLERR, 1047, dsql_create_ts_failed, -901, "42", "000", "CREATE TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1048, dsql_alter_ts_failed, -901, "42", "000", "ALTER TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1049, dsql_create_alter_ts_failed, -901, "42", "000", "CREATE OR ALTER TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1050, dsql_drop_ts_failed, -901, "42", "000", "DROP TABLESPACE @1 failed")
+FB_IMPL_MSG(SQLERR, 1051, 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 9c1be4d79af..10a178655e7 100644
--- a/src/include/gen/Firebird.pas
+++ b/src/include/gen/Firebird.pas
@@ -5740,6 +5740,9 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_only_one_pattern_can_be_used = 335545305;
isc_can_not_use_same_pattern_twice = 335545306;
isc_sysf_invalid_gen_uuid_version = 335545307;
+ isc_ts_file_exists = 335545308;
+ isc_tablespace_name = 335545309;
+ isc_ts_file_not_exists = 335545310;
isc_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;
isc_gfix_incmp_sw = 335740932;
@@ -5896,6 +5899,10 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_dyn_exc_not_exist = 336068915;
isc_dyn_gen_not_exist = 336068916;
isc_dyn_fld_not_exist = 336068917;
+ isc_dyn_ts_not_found = 336068922;
+ isc_dyn_cant_set_ts_table = 336068923;
+ isc_dyn_cant_set_ts_index = 336068924;
+ isc_dyn_ts_already_exists = 336068925;
isc_gbak_unknown_switch = 336330753;
isc_gbak_page_size_missing = 336330754;
isc_gbak_page_size_toobig = 336330755;
@@ -6124,6 +6131,11 @@ IProfilerStatsImpl = class(IProfilerStats)
isc_dsql_string_char_length = 336397332;
isc_dsql_max_nesting = 336397333;
isc_dsql_recreate_user_failed = 336397334;
+ isc_dsql_create_ts_failed = 336397335;
+ isc_dsql_alter_ts_failed = 336397336;
+ isc_dsql_create_alter_ts_failed = 336397337;
+ isc_dsql_drop_ts_failed = 336397338;
+ isc_dsql_recreate_ts_failed = 336397339;
isc_gsec_cant_open_db = 336723983;
isc_gsec_switches_error = 336723984;
isc_gsec_no_op_spec = 336723985;
diff --git a/src/isql/extract.epp b/src/isql/extract.epp
index c281cae6b8f..bc580de11a2 100644
--- a/src/isql/extract.epp
+++ b/src/isql/extract.epp
@@ -94,6 +94,7 @@ static void list_package_headers();
static void list_procedure_bodies(SSHORT default_char_set_id);
static void list_procedure_headers(SSHORT default_char_set_id);
static void list_views();
+static void list_tablespaces();
static const char* const Procterm = "^"; // TXNN: script use only
@@ -177,6 +178,7 @@ int EXTRACT_ddl(LegacyTables flag, const SCHAR* tabname)
list_collations();
list_generators();
list_domains(default_char_set_id);
+ list_tablespaces();
list_all_tables(flag, default_char_set_id);
list_functions_legacy();
list_functions_ods12_headers(default_char_set_id);
@@ -303,6 +305,7 @@ int EXTRACT_list_table(const SCHAR* relation_name,
SCHAR char_sets[CHARSET_COLLATE_SIZE];
rel_t rel_type = rel_persistent;
char ss[28] = "";
+ Firebird::string tableSpaceName;
// Query to obtain relation detail information
@@ -350,6 +353,13 @@ int EXTRACT_list_table(const SCHAR* relation_name,
else
isqlGlob.printf("%s ", new_name ? new_name : relation_name);
+ if (!REL.RDB$TABLESPACE_NAME.NULL)
+ {
+ fb_utils::exact_name(REL.RDB$TABLESPACE_NAME);
+ IUTILS_copy_SQL_id (REL.RDB$TABLESPACE_NAME, SQL_identifier, DBL_QUOTE);
+ tableSpaceName.printf(" TABLESPACE %s", SQL_identifier);
+ }
+
if (!REL.RDB$SQL_SECURITY.NULL)
{
if (REL.RDB$SQL_SECURITY)
@@ -607,9 +617,9 @@ int EXTRACT_list_table(const SCHAR* relation_name,
const char* opt_delim = *gtt_scope && *ss ? ", " : "";
if (*gtt_scope || *ss)
- isqlGlob.printf(")%s%s%s%s", NEWLINE, gtt_scope, opt_delim , ss);
+ isqlGlob.printf(")%s %s%s%s%s", tableSpaceName.c_str(), NEWLINE, gtt_scope, opt_delim , ss);
else
- isqlGlob.printf(")");
+ isqlGlob.printf(")%s", tableSpaceName.c_str());
isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE);
return FINI_OK;
@@ -1436,7 +1446,8 @@ static processing_state list_all_grants2(bool show_role_list, const SCHAR* termi
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);
}
@@ -1464,6 +1475,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);
@@ -3379,6 +3393,12 @@ static void list_indexes()
END_FOR
}
+ if (!IDX.RDB$TABLESPACE_NAME.NULL)
+ {
+ fb_utils::exact_name(IDX.RDB$TABLESPACE_NAME);
+ isqlGlob.printf(" TABLESPACE %s", IDX.RDB$TABLESPACE_NAME);
+ }
+
isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE);
END_FOR
@@ -3596,3 +3616,61 @@ 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 NE 1 OR X.RDB$SYSTEM_FLAG MISSING)
+ SORTED BY X.RDB$TABLESPACE_NAME
+
+ if (first)
+ {
+ isqlGlob.printf("%s/* Tablespaces */%s", NEWLINE, NEWLINE);
+ first = false;
+ }
+
+ fb_utils::exact_name(X.RDB$TABLESPACE_NAME);
+ fb_utils::exact_name(X.RDB$FILE_NAME);
+ fb_utils::exact_name(X.RDB$OWNER_NAME);
+
+ if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION)
+ IUTILS_copy_SQL_id (X.RDB$TABLESPACE_NAME, SQL_identifier, DBL_QUOTE);
+ else
+ strcpy(SQL_identifier, X.RDB$TABLESPACE_NAME);
+
+ isqlGlob.printf("%s/* Tablespace: %s, Owner: %s */%s",
+ NEWLINE,
+ X.RDB$TABLESPACE_NAME,
+ X.RDB$OWNER_NAME,
+ 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";
+
+ isqlGlob.printf("CREATE TABLESPACE %s FILE '%s' %s %s", SQL_identifier, 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 c15a22b422a..db28e2d9584 100644
--- a/src/isql/show.epp
+++ b/src/isql/show.epp
@@ -103,12 +103,13 @@ static processing_state show_functions(const SCHAR* funcname, bool quoted, bool
static processing_state show_func_legacy(const MetaString&);
static processing_state show_func(const MetaString&, const MetaString&);
static processing_state show_generators(const SCHAR*);
-static void show_index(SCHAR*, SCHAR*, const SSHORT, const SSHORT, const SSHORT);
+static void show_index(SCHAR*, SCHAR*, const SSHORT, const SSHORT, const SSHORT, SCHAR*);
static processing_state show_indices(const SCHAR* const*);
static processing_state show_proc(const SCHAR*, bool, bool, const char* msg = nullptr);
static processing_state show_packages(const SCHAR* package_name, bool, const SCHAR* = NULL);
static processing_state show_publications(const SCHAR* pub_name, bool, const SCHAR* = NULL);
static void show_pub_table(const SCHAR* table_name);
+static processing_state show_tablespaces(const SCHAR*);
static processing_state show_role(const SCHAR*, bool, const char* msg = NULL);
static processing_state show_secclass(const char* object, const char* opt);
static processing_state show_table(const SCHAR*, bool);
@@ -2091,7 +2092,7 @@ processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd)
role, table, view, system, index, domain, exception,
filter, function, generator, grant, procedure, trigger,
check, database, comment, dependency, collation, security_class,
- users, package, publication, schema, map, wireStats,
+ users, package, publication, schema, map, tablespace, wireStats,
wrong
};
ShowOptions(const optionsMap* inmap, size_t insize, int wrongval)
@@ -2151,6 +2152,7 @@ processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd)
{ShowOptions::schema, "SCHEMAS", 4},
{ShowOptions::map, "MAPPING", 3},
{ShowOptions::publication, "PUBLICATIONS", 3},
+ {ShowOptions::tablespace, "TABLESPACES", 10},
{ShowOptions::wireStats, "WIRE_STATISTICS", 9},
{ShowOptions::wireStats, "WIRE_STATS", 10}
};
@@ -2769,6 +2771,25 @@ processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd)
}
break;
+ case ShowOptions::tablespace:
+ if (*cmd[2] == '"')
+ {
+ remove_delimited_double_quotes(lcmd[2]);
+ ret = show_tablespaces(lcmd[2]);
+ }
+ else
+ ret = show_tablespaces(cmd[2]);
+
+ switch (ret) {
+ case OBJECT_NOT_FOUND:
+ if (*cmd[2])
+ key = NO_TABLESPACE;
+ else
+ key = NO_TABLESPACES;
+ break;
+ }
+ break;
+
case ShowOptions::map:
if (*cmd[2] == '"')
{
@@ -3573,6 +3594,22 @@ static processing_state show_comments(const commentMode showextract, const char*
return ps_ERR;
END_ERROR
}
+
+ if (isqlGlob.major_ods >= ODS_VERSION14)
+ {
+ FOR TS IN RDB$TABLESPACES
+ WITH TS.RDB$DESCRIPTION NOT MISSING
+ SORTED BY TS.RDB$TABLESPACE_NAME
+
+ show_comment("TABLESPACE", NULL, TS.RDB$TABLESPACE_NAME, NULL, &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;
}
@@ -4803,7 +4840,8 @@ static void show_index(SCHAR* relation_name,
SCHAR* index_name,
const SSHORT unique_flag,
const SSHORT index_type,
- const SSHORT inactive)
+ const SSHORT inactive,
+ SCHAR* tablespace_name)
{
/**************************************
*
@@ -4827,6 +4865,13 @@ static void show_index(SCHAR* relation_name,
(unique_flag ? " UNIQUE" : ""),
(index_type == 1 ? " DESCENDING" : ""), relation_name);
+ if (tablespace_name)
+ {
+ fb_utils::exact_name(tablespace_name);
+ if (tablespace_name[0])
+ isqlGlob.printf(" TABLESPACE %s", tablespace_name);
+ }
+
// Get column names
SCHAR collist[BUFFER_LENGTH512];
@@ -4871,7 +4916,7 @@ static processing_state show_indices(const SCHAR* const* cmd)
IDX.RDB$INDEX_INACTIVE = 0;
show_index(IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME,
- IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE);
+ IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE, IDX.RDB$TABLESPACE_NAME);
if (!IDX.RDB$EXPRESSION_BLR.NULL)
{
@@ -4915,7 +4960,7 @@ static processing_state show_indices(const SCHAR* const* cmd)
first = false;
show_index(IDX.RDB$RELATION_NAME, IDX.RDB$INDEX_NAME,
- IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE);
+ IDX.RDB$UNIQUE_FLAG, IDX.RDB$INDEX_TYPE, IDX.RDB$INDEX_INACTIVE, IDX.RDB$TABLESPACE_NAME);
if (!IDX.RDB$EXPRESSION_BLR.NULL)
{
@@ -5044,6 +5089,80 @@ static processing_state show_packages(const SCHAR* package_name, bool sys, const
}
+static processing_state show_tablespaces(const SCHAR* tablespace_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 (!*tablespace_name)
+ {
+ // List all tablespace names in columns
+ FOR X IN RDB$TABLESPACES WITH
+ (X.RDB$SYSTEM_FLAG NE 1 OR X.RDB$SYSTEM_FLAG MISSING)
+ SORTED BY X.RDB$TABLESPACE_NAME
+ {
+ first = false;
+ isqlGlob.printf("%s%s", fb_utils::exact_name(X.RDB$TABLESPACE_NAME), 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 tablespace_name
+
+ first = false;
+
+ fb_utils::exact_name(X.RDB$TABLESPACE_NAME);
+ isqlGlob.printf("%s", X.RDB$TABLESPACE_NAME);
+
+ 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 printIdent(bool quote, char* ident, const char* format = NULL)
{
fb_utils::exact_name(ident);
@@ -6009,6 +6128,12 @@ static processing_state show_table(const SCHAR* relation_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)
+ {
+ fb_utils::exact_name(REL.RDB$TABLESPACE_NAME);
+ isqlGlob.printf("TABLESPACE: %s%s", REL.RDB$TABLESPACE_NAME, NEWLINE);
+ }
}
first = false;
if ((isView && REL.RDB$VIEW_BLR.NULL) || (!isView && !REL.RDB$VIEW_BLR.NULL))
@@ -6709,4 +6834,4 @@ static processing_state show_wireStats()
return ps_ERR;
return SKIP;
-}
\ No newline at end of file
+}
diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp
index 0638f01d59f..09b6bdc23f1 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"
@@ -228,6 +229,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()),
@@ -278,6 +280,10 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb, JProvider* provider
{
att_internal.grow(irq_MAX);
att_dyn_req.grow(drq_MAX);
+
+ Tablespace* dbTableSpace = FB_NEW_POOL(*pool) Tablespace(*pool);
+ dbTableSpace->id = DB_PAGE_SPACE;
+ att_tablespaces.add(dbTableSpace);
}
@@ -817,6 +823,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 684c8d55339..a17dace3cac 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
#define DEBUG_LCK_LIST
@@ -572,6 +574,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
@@ -806,6 +809,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 c88b61171dd..1983f91b03d 100644
--- a/src/jrd/Database.cpp
+++ b/src/jrd/Database.cpp
@@ -88,27 +88,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();
}
@@ -393,7 +393,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 f379ba4c1ff..8da99eb50bd 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 f2784c2bf64..aa80d35759f 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 3aac47c4d17..190e2bc49c1 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;
@@ -106,6 +107,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)
@@ -114,6 +117,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;
}
@@ -128,6 +132,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;
}
@@ -160,6 +166,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();
@@ -658,6 +673,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)
@@ -666,6 +683,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;
}
@@ -679,6 +697,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;
}
@@ -700,6 +720,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..2277d630fda
--- /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;
+ existenceLock = NULL;
+}
+
+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..cd42442f0f3
--- /dev/null
+++ b/src/jrd/Tablespace.h
@@ -0,0 +1,74 @@
+/*
+ * 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 : public Firebird::PermanentStorage
+ {
+ friend class Attachment;
+ public:
+ explicit Tablespace(MemoryPool& p)
+ : PermanentStorage(p),
+ id(INVALID_PAGE_SPACE),
+ name(p),
+ existenceLock(NULL),
+ modified(false),
+ useCount(0)
+ {
+ }
+
+ ~Tablespace();
+
+ ULONG id; // tablespace id = pagespace id
+ MetaName name; // tablespace name
+ Lock* existenceLock; // existence lock, if any
+ bool modified;
+
+ private:
+ int useCount;
+
+ 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 0cf895dead5..bde284c3522 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*);
@@ -1089,7 +1088,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);
@@ -1136,7 +1135,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);
@@ -2124,7 +2123,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)
{
/**************************************
*
@@ -2191,11 +2190,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)
{
@@ -2473,7 +2472,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 26176b5e695..beba861e095 100644
--- a/src/jrd/blb.h
+++ b/src/jrd/blb.h
@@ -134,7 +134,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*);
@@ -163,7 +163,7 @@ class blb : public pool_alloc
USHORT blb_space_remaining; // Data space left
USHORT blb_max_pages; // Max pages in vector
USHORT blb_level; // Storage type
- USHORT blb_pg_space_id; // page space
+ ULONG blb_pg_space_id; // page space
USHORT blb_fragment_size; // Residual fragment size
USHORT blb_max_segment; // Longest segment
#ifdef CHECK_BLOB_FIELD_ACCESS_FOR_SELECT
diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp
index 71d5e6224cd..78dfb237646 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);
@@ -881,16 +884,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);
@@ -923,18 +930,35 @@ 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 pg_space_id = irt_desc->getRootPageSpaceId();
+
+ 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 PageNumber next(window->win_page.getPageSpaceID(), irt_desc->getRoot());
- tree_exists = (irt_desc->getRoot() != 0);
+ // next is on index page space!!!
+ const PageNumber next(pg_space_id, irt_desc->getRootPage());
+ tree_exists = (irt_desc->getRootPage() != 0);
// remove the pointer to the top-level index page before we delete it
- irt_desc->setRoot(0);
+ irt_desc->setRoot(0, 0);
irt_desc->irt_flags = 0;
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;
@@ -962,11 +986,12 @@ 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];
- if (irt_desc->getRoot() == 0)
+ if (irt_desc->getRootPage() == 0)
return false;
idx->idx_id = id;
- idx->idx_root = irt_desc->getRoot();
+ idx->idx_pg_space_id = irt_desc->getRootPageSpaceId();
+ idx->idx_root = irt_desc->getRootPage();
idx->idx_count = irt_desc->irt_keys;
idx->idx_flags = irt_desc->irt_flags;
idx->idx_runtime_flags = 0;
@@ -1040,6 +1065,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;
}
@@ -1140,6 +1171,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;
@@ -1336,7 +1368,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);
@@ -1347,7 +1381,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
@@ -1423,8 +1457,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;
@@ -1451,7 +1484,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)
@@ -1501,7 +1534,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);
@@ -1515,7 +1548,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,7 +1621,7 @@ void BTR_insert(thread_db* tdbb, WIN* root_window, index_insertion* insertion)
CCH_RELEASE(tdbb, &new_window);
CCH_precedence(tdbb, root_window, new_window.win_page);
CCH_MARK(tdbb, root_window);
- root->irt_rpt[idx->idx_id].setRoot(new_window.win_page.getPageNum());
+ root->irt_rpt[idx->idx_id].setRoot(new_window.win_page.getPageSpaceID(), new_window.win_page.getPageNum());
CCH_RELEASE(tdbb, root_window);
}
@@ -2173,8 +2206,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
@@ -2220,7 +2252,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
@@ -2394,7 +2426,14 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, USHORT id, SelectivityL
return;
ULONG page;
- if (id >= root->irt_count || !(page = root->irt_rpt[id].getRoot()))
+ if (id >= root->irt_count || !(page = root->irt_rpt[id].getRootPage()))
+ {
+ CCH_RELEASE(tdbb, &window);
+ return;
+ }
+
+ index_desc idx;
+ if (!BTR_description(tdbb, relation, root, &idx, id))
{
CCH_RELEASE(tdbb, &window);
return;
@@ -2405,7 +2444,7 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, USHORT id, SelectivityL
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(idx.idx_pg_space_id, 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;
@@ -2564,7 +2603,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);
@@ -2621,6 +2660,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,
@@ -3334,7 +3512,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->getRootPage())
{
const USHORT len = root_idx->irt_keys * sizeof(irtd);
p -= len;
@@ -3659,7 +3837,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)
{
/**************************************
@@ -3726,9 +3904,11 @@ static void delete_tree(thread_db* tdbb,
}
+
+template
static ULONG fast_load(thread_db* tdbb,
IndexCreation& creation,
- SelectivityList& selectivity)
+ SelectivityList& selectivity, RS* scb)
{
/**************************************
*
@@ -3754,7 +3934,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.
@@ -3872,7 +4052,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;
@@ -4510,7 +4690,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)
{
/**************************************
*
@@ -4526,20 +4706,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);
}
@@ -5111,7 +5292,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;
@@ -5769,7 +5950,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 7f1bc99303e..a2f2d491c57 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 c966e4ea7e2..1f30b933220 100644
--- a/src/jrd/cch.cpp
+++ b/src/jrd/cch.cpp
@@ -188,7 +188,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);
@@ -1178,7 +1178,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)
{
/**************************************
*
@@ -1220,7 +1220,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
@@ -1430,7 +1430,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)
{
/**************************************
@@ -1464,8 +1464,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;
@@ -1479,7 +1480,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);
@@ -1488,7 +1489,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)
@@ -1792,7 +1793,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));
@@ -1933,9 +1934,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
@@ -2710,7 +2709,7 @@ static void flushDirty(thread_db* tdbb, SLONG transaction_mask, const bool sys_o
// Collect pages modified by garbage collector or all dirty pages or release page
// locks - depending of flush_flag, and write it to disk.
// See also comments in flushPages.
-static void flushAll(thread_db* tdbb, USHORT flush_flag)
+static void flushAll(thread_db* tdbb, USHORT flush_flag, ULONG page_space_id)
{
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
@@ -2733,28 +2732,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);
+ }
}
}
}
@@ -3200,7 +3204,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;
@@ -3212,6 +3218,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 c04f204b42b..bd1cb7df151 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 ce322ee78d6..34e8ddde5d6 100644
--- a/src/jrd/constants.h
+++ b/src/jrd/constants.h
@@ -391,7 +391,10 @@ static const char* const DDL_TRIGGER_ACTION_NAMES[][2] =
{"DROP", "PACKAGE BODY"},
{"CREATE", "MAPPING"},
{"ALTER", "MAPPING"},
- {"DROP", "MAPPING"}
+ {"DROP", "MAPPING"},
+ {"CREATE", "TABLESPACE"},
+ {"ALTER", "TABLESPACE"},
+ {"DROP", "TABLESPACE"}
};
const int DDL_TRIGGER_BEFORE = 0;
@@ -444,6 +447,9 @@ const int DDL_TRIGGER_DROP_PACKAGE_BODY = 44;
const int DDL_TRIGGER_CREATE_MAPPING = 45;
const int DDL_TRIGGER_ALTER_MAPPING = 46;
const int DDL_TRIGGER_DROP_MAPPING = 47;
+const int DDL_TRIGGER_CREATE_TABLESPACE = 48;
+const int DDL_TRIGGER_ALTER_TABLESPACE = 49;
+const int DDL_TRIGGER_DROP_TABLESPACE = 50;
// that's how database trigger action types are encoded
// (TRIGGER_TYPE_DB | type)
@@ -481,4 +487,7 @@ const int WITH_ADMIN_OPTION = 2;
// Max length of the string returned by ERROR_TEXT context variable
const 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 2609eef97e9..a86f16f37e6 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
@@ -490,6 +492,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 string&, const string&);
+static bool create_tablespace(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
+static bool drop_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*);
// ----------------------------------------------------------------
@@ -1319,6 +1326,11 @@ static const 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_drop_tablespace, drop_tablespace },
+ { dfw_modify_tablespace, modify_tablespace },
+ { dfw_move_relation, move_relation },
+ { dfw_move_index, move_index },
{ dfw_null, NULL }
};
@@ -1626,6 +1638,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_drop_tablespace:
break;
default:
@@ -1641,8 +1656,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)
{
/**************************************
*
@@ -1664,7 +1680,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;)
{
@@ -1680,17 +1697,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_drop_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)
@@ -2756,6 +2818,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();
@@ -2776,6 +2840,9 @@ static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork*
if (relation->rel_name.length() == 0)
relation->rel_name = REL.RDB$RELATION_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());
@@ -2895,6 +2962,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->dfw_name.c_str(), &work->dfw_id,
transaction, selectivity);
@@ -3428,6 +3496,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
@@ -3447,6 +3517,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
@@ -3716,6 +3789,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->dfw_name.c_str(),
&work->dfw_id, transaction, selectivity);
@@ -4863,7 +4937,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;
@@ -6632,6 +6715,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 when restoring
+ *
+ **************************************/
+
+ 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 drop_tablespace(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
+{
+/**************************************
+ *
+ * d r o p _ t a b l e s p a c e
+ *
+ **************************************
+ *
+ * Functional description
+ * Drop tablespace and its file at 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", tablespace->name.c_str());
+ }
+ }
+ 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", tablespace->name.c_str());
+ }
+ }
+ 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, NULL, 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, NULL, 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 1681e9d727b..4791d3893a4 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*, USHORT,
const Jrd::MetaName& package = NULL);
Jrd::DeferredWork* DFW_post_work(Jrd::jrd_tra*, Jrd::dfw_t, const Firebird::string&, USHORT,
diff --git a/src/jrd/dpm.epp b/src/jrd/dpm.epp
index 09eda67cd7f..9f724b499dd 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);
@@ -545,7 +546,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)
{
/**************************************
*
@@ -574,6 +592,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);
}
@@ -826,7 +851,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 (;;)
{
@@ -1362,7 +1387,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)
@@ -2057,7 +2082,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*/)
{
/**************************************
*
@@ -2101,45 +2126,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());
@@ -2621,7 +2701,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)
{
/**************************************
*
@@ -3308,6 +3388,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)
@@ -3327,15 +3426,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
@@ -3926,3 +4045,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 d3b3c5a7fba..78b8f3a47e5 100644
--- a/src/jrd/drq.h
+++ b/src/jrd/drq.h
@@ -240,6 +240,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
@@ -255,6 +260,9 @@ enum drq_type_t
drq_l_pkg_name, // lookup package name
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 4aa1d2569e6..e5797f417c3 100644
--- a/src/jrd/dyn_util.epp
+++ b/src/jrd/dyn_util.epp
@@ -220,6 +220,19 @@ bool DYN_UTIL_check_unique_name_nothrow(thread_db* tdbb, jrd_tra* transaction,
break;
+ case obj_tablespace:
+ request.reset(tdbb, drq_l_ts_name, DYN_REQUESTS);
+
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ TS IN RDB$TABLESPACES
+ WITH TS.RDB$TABLESPACE_NAME EQ object_name.c_str()
+ {
+ *errorCode = 317;
+ }
+ END_FOR
+
+ break;
+
default:
fb_assert(false);
}
diff --git a/src/jrd/fields.h b/src/jrd/fields.h
index c67cfb22342..5ee6636f139 100644
--- a/src/jrd/fields.h
+++ b/src/jrd/fields.h
@@ -234,3 +234,10 @@
FIELD(fld_integer , nam_integer , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_13_1)
FIELD(fld_par_workers , nam_par_workers , dtype_long , sizeof(SLONG) , 0 , NULL , true , ODS_13_1)
+
+ 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 aabe8453cf8..4219c1af35d 100644
--- a/src/jrd/grant.epp
+++ b/src/jrd/grant.epp
@@ -519,6 +519,21 @@ static void get_object_info(thread_db* tdbb,
}
END_FOR
}
+ else if (obj_type == obj_tablespace)
+ {
+ AutoCacheRequest request(tdbb, irq_grant20, IRQ_REQUESTS);
+
+ FOR(REQUEST_HANDLE request)
+ X IN RDB$TABLESPACES WITH
+ X.RDB$TABLESPACE_NAME EQ object_name
+ {
+ s_class = X.RDB$SECURITY_CLASS;
+ default_class = "";
+ owner = X.RDB$OWNER_NAME;
+ view = false;
+ }
+ END_FOR
+ }
else
{
s_class = getSecurityClassName(obj_type);
diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp
index 5c22b9b1862..95feb2cf9c1 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"
@@ -2037,7 +2038,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 3fd62251440..1a6c8a1db83 100644
--- a/src/jrd/idx.h
+++ b/src/jrd/idx.h
@@ -310,6 +310,19 @@ static const struct ini_idx_t indices[] =
INDEX(57, rel_backup_history, idx_descending, 1, ODS_13_1)
SEGMENT(f_backup_time, idx_timestamp_tz) // backup timestamp
}},
+ // define index RDB$INDEX_58 for RDB$TABLESPACES unique RDB$TABLESPACE_NAME;
+ INDEX(58, rel_tablespaces, idx_unique, 1, ODS_13_0)
+ SEGMENT(f_ts_name, idx_metadata) // tablespace name
+ }},
+ // define index RDB$INDEX_59 for RDB$TABLESPACES unique RDB$TABLESPACE_ID;
+ INDEX(59, rel_tablespaces, idx_unique, 1, ODS_13_0)
+ SEGMENT(f_ts_id, idx_numeric) // tablespace id
+ }},
+ // define index RDB$INDEX_60 for RDB$PAGES RDB$PAGE_TYPE, RDB$RELATION_ID;
+ INDEX(60, rel_pages, 0, 2, ODS_13_0)
+ SEGMENT(f_pag_type, idx_numeric), // page type
+ SEGMENT(f_pag_id, idx_numeric), // relation id
+ }}
};
#define SYSTEM_INDEX_COUNT FB_NELEM(indices)
diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp
index d0d6527971b..0a8fd11555f 100644
--- a/src/jrd/ini.epp
+++ b/src/jrd/ini.epp
@@ -1675,6 +1675,7 @@ 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.c_str(), NULL,
diff --git a/src/jrd/irq.h b/src/jrd/irq.h
index 2c6d1a92881..3fc24a749e2 100644
--- a/src/jrd/irq.h
+++ b/src/jrd/irq.h
@@ -177,13 +177,25 @@ 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_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 08f560ddf73..5b2af36c32b 100644
--- a/src/jrd/jrd.cpp
+++ b/src/jrd/jrd.cpp
@@ -1302,6 +1302,7 @@ class TraceFailedConnection :
static void check_database(thread_db* tdbb, bool async = false);
static void commit(thread_db*, jrd_tra*, const bool);
static bool drop_files(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);
@@ -3452,6 +3453,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()");
@@ -3506,6 +3512,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);
@@ -3557,6 +3566,7 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status)
{
err = drop_files(shadow->sdw_file) || err;
}
+ err = drop_files(tsFiles) || err;
tdbb->setDatabase(NULL);
Database::destroy(dbb);
@@ -6831,6 +6841,36 @@ static bool drop_files(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 9bb03c60e8b..18d5bfccafa 100644
--- a/src/jrd/jrd.h
+++ b/src/jrd/jrd.h
@@ -340,7 +340,7 @@ struct win
explicit win(const PageNumber& wp)
: win_page(wp), win_bdb(NULL), win_flags(0)
{}
- win(const USHORT pageSpaceID, const ULONG pageNum)
+ win(const ULONG pageSpaceID, const ULONG pageNum)
: win_page(pageSpaceID, pageNum), win_bdb(NULL), win_flags(0)
{}
};
diff --git a/src/jrd/lck.cpp b/src/jrd/lck.cpp
index aecd56df94d..4a45655e772 100644
--- a/src/jrd/lck.cpp
+++ b/src/jrd/lck.cpp
@@ -586,6 +586,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 dc8f9274984..0af3e68cd6e 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 f16f2190f48..09d44a87a4d 100644
--- a/src/jrd/met.epp
+++ b/src/jrd/met.epp
@@ -93,6 +93,7 @@
#include "../common/classes/Hash.h"
#include "../common/classes/MsgPrint.h"
#include "../jrd/Function.h"
+#include "../jrd/Tablespace.h"
#include "../jrd/trace/TraceJrdHelpers.h"
@@ -3703,6 +3704,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;
@@ -3732,6 +3742,9 @@ jrd_rel* MET_relation(thread_db* tdbb, USHORT id)
}
relation->rel_flags |= (REL_check_existence | REL_check_partners);
+
+ relation->setPageSpace(pageSpaceId);
+
return relation;
}
@@ -5502,3 +5515,358 @@ TriState MET_get_ss_definer(Jrd::thread_db* tdbb)
return r;
}
+
+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
+ {
+ 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$RELATION_NAME EQ relation->rel_name.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));
+
+ Tablespace* tableSpace = NULL;
+
+ try
+ {
+ tableSpace = FB_NEW_POOL(*attachment->att_pool) Tablespace(*attachment->att_pool);
+ tableSpace->id = id;
+ tableSpace->name = 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 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 (...)
+ {
+ if (tableSpace)
+ {
+ if (tableSpace->existenceLock)
+ LCK_release(tdbb, tableSpace->existenceLock);
+
+ delete tableSpace;
+ }
+
+ throw;
+ }
+
+ attachment->setTablespace(id, tableSpace);
+ 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();
+ if (tableSpaceName.isEmpty())
+ 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);
+ tableSpace->id = pageSpaceId;
+ tableSpace->name = 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
+ {
+ 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 1182c814496..3310fc404d9 100644
--- a/src/jrd/met_proto.h
+++ b/src/jrd/met_proto.h
@@ -149,4 +149,11 @@ void MET_store_dependencies(Jrd::thread_db*, Firebird::Array &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 ae061ea7f7d..22d65cda5e8 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)
@@ -463,6 +466,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 12d40717108..fc53ec79f38 100644
--- a/src/jrd/ods.h
+++ b/src/jrd/ods.h
@@ -370,6 +370,7 @@ struct index_root_page
{
private:
friend struct index_root_page; // to allow offset check for private members
+ ULONG irt_page_space_id; // page space of index root
ULONG irt_root; // page number of index root if irt_in_progress is NOT set, or
// highest 32 bit of transaction if irt_in_progress is set
ULONG irt_transaction; // transaction in progress (lowest 32 bits)
@@ -378,8 +379,9 @@ struct index_root_page
UCHAR irt_keys; // number of keys in index
UCHAR irt_flags;
- ULONG getRoot() const;
- void setRoot(ULONG root_page);
+ ULONG getRootPageSpaceId() const;
+ ULONG getRootPage() const;
+ void setRoot(ULONG pageSpaceId, ULONG page);
TraNumber getTransaction() const;
void setTransaction(TraNumber traNumber);
@@ -388,15 +390,16 @@ struct index_root_page
} irt_rpt[1];
- static_assert(sizeof(struct irt_repeat) == 12, "struct irt_repeat size mismatch");
- static_assert(offsetof(struct irt_repeat, irt_root) == 0, "irt_root offset mismatch");
- static_assert(offsetof(struct irt_repeat, irt_transaction) == 4, "irt_transaction offset mismatch");
- static_assert(offsetof(struct irt_repeat, irt_desc) == 8, "irt_desc offset mismatch");
- static_assert(offsetof(struct irt_repeat, irt_keys) == 10, "irt_keys offset mismatch");
- static_assert(offsetof(struct irt_repeat, irt_flags) == 11, "irt_flags offset mismatch");
+ static_assert(sizeof(struct irt_repeat) == 16, "struct irt_repeat size mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_page_space_id) == 0, "irt_page_space_id offset mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_root) == 4, "irt_root offset mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_transaction) == 8, "irt_transaction offset mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_desc) == 12, "irt_desc offset mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_keys) == 14, "irt_keys offset mismatch");
+ static_assert(offsetof(struct irt_repeat, irt_flags) == 15, "irt_flags offset mismatch");
};
-static_assert(sizeof(struct index_root_page) == 32, "struct index_root_page size mismatch");
+static_assert(sizeof(struct index_root_page) == 36, "struct index_root_page size mismatch");
static_assert(offsetof(struct index_root_page, irt_header) == 0, "irt_header offset mismatch");
static_assert(offsetof(struct index_root_page, irt_relation) == 16, "irt_relation offset mismatch");
static_assert(offsetof(struct index_root_page, irt_count) == 18, "irt_count offset mismatch");
@@ -425,14 +428,20 @@ inline constexpr USHORT irt_primary = 16;
inline constexpr USHORT irt_expression = 32;
inline constexpr USHORT irt_condition = 64;
-inline ULONG index_root_page::irt_repeat::getRoot() const
+inline ULONG index_root_page::irt_repeat::getRootPageSpaceId() const
+{
+ return irt_page_space_id;
+}
+
+inline ULONG index_root_page::irt_repeat::getRootPage() const
{
return (irt_flags & irt_in_progress) ? 0 : irt_root;
}
-inline void index_root_page::irt_repeat::setRoot(ULONG root_page)
+inline void index_root_page::irt_repeat::setRoot(ULONG pageSpaceId, ULONG page)
{
- irt_root = root_page;
+ irt_page_space_id = pageSpaceId;
+ irt_root = page;
irt_flags &= ~irt_in_progress;
}
diff --git a/src/jrd/os/pio_proto.h b/src/jrd/os/pio_proto.h
index d936c8aea24..e45cbfed561 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 ba1a3220dcd..d1ccb8c9dc9 100644
--- a/src/jrd/os/posix/unix.cpp
+++ b/src/jrd/os/posix/unix.cpp
@@ -1196,6 +1196,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 71b0a3b50b9..c81e7cdd4aa 100644
--- a/src/jrd/os/win32/winnt.cpp
+++ b/src/jrd/os/win32/winnt.cpp
@@ -813,6 +813,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 jrd_file* seek_file(jrd_file* file,
BufferDesc* bdb,
OVERLAPPED* overlapped)
diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp
index 73df5a3826b..75af2696c49 100644
--- a/src/jrd/pag.cpp
+++ b/src/jrd/pag.cpp
@@ -1494,15 +1494,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)
{
/**************************************
@@ -2243,7 +2244,7 @@ bool PageSpace::extend(thread_db* tdbb, const ULONG pageNum, const bool forceSiz
return true;
}
-ULONG PageSpace::getSCNPageNum(ULONG sequence)
+ULONG PageSpace::getSCNPageNum(ULONG sequence) const
{
/**************************************
*
@@ -2261,26 +2262,47 @@ ULONG PageSpace::getSCNPageNum(ULONG sequence)
return sequence * dbb->dbb_page_manager.pagesPerSCN;
}
-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];
@@ -2289,8 +2311,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))
{
@@ -2324,12 +2348,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);
@@ -2338,7 +2362,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
{
@@ -2348,7 +2372,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)
@@ -2380,6 +2404,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 dc786782fad..8b4adc01b70 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,14 @@ 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 ???
-const USHORT INVALID_PAGE_SPACE = 0;
-const USHORT DB_PAGE_SPACE = 1;
-const USHORT TRANS_PAGE_SPACE = 255;
-const USHORT TEMP_PAGE_SPACE = 256;
+const ULONG INVALID_PAGE_SPACE = 0;
+const ULONG DB_PAGE_SPACE = 1;
+// .. here all tablespace IDs. Keep TRANS_PAGE_SPACE right after DB_PAGE_SPACE.
+// Note that the max tablespace ID should be MAX_USHORT as long as dfw_id is USHORT.
+//const ULONG TRANS_PAGE_SPACE = MAX_USHORT + 1; // is not used for tablespace id
+const ULONG TRANS_PAGE_SPACE = 255;
+const ULONG TEMP_PAGE_SPACE = TRANS_PAGE_SPACE + 1;
+const ULONG MAX_PAGE_SPACE_ID = MAX_ULONG;
const USHORT PAGES_IN_EXTENT = 8;
@@ -75,7 +80,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 +95,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 +103,7 @@ class PageSpace : public pool_alloc
jrd_file* file;
- static inline bool isTemporary(USHORT aPageSpaceID)
+ static inline bool isTemporary(ULONG aPageSpaceID)
{
return (aPageSpaceID >= TEMP_PAGE_SPACE);
}
@@ -108,7 +113,17 @@ class PageSpace : public pool_alloc
return isTemporary(pageSpaceID);
}
- static inline SLONG generate(const PageSpace* Item)
+ 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)
{
return Item->pageSpaceID;
}
@@ -133,8 +148,7 @@ class PageSpace : public pool_alloc
bool extend(thread_db*, const ULONG, const bool);
// get SCN's page number
- ULONG getSCNPageNum(ULONG sequence);
- static ULONG getSCNPageNum(const Database* dbb, ULONG sequence);
+ ULONG getSCNPageNum(ULONG sequence) const;
// is pagespace on raw device
bool onRawDevice() const;
@@ -148,32 +162,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 +190,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 +207,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)
+ inline PageNumber(const ULONG aPageSpace, const ULONG aPageNum)
: pageNum(aPageNum), pageSpaceID(aPageSpace)
{
// Some asserts are commented cause 0 was also used as 'does not matter' pagespace
@@ -224,13 +229,12 @@ class PageNumber
return pageNum;
}
- inline USHORT getPageSpaceID() const
+ inline ULONG getPageSpaceID() const
{
- fb_assert(pageSpaceID != INVALID_PAGE_SPACE);
return pageSpaceID;
}
- inline USHORT setPageSpaceID(const USHORT aPageSpaceID)
+ inline ULONG setPageSpaceID(const ULONG aPageSpaceID)
{
fb_assert(aPageSpaceID != INVALID_PAGE_SPACE);
pageSpaceID = aPageSpaceID;
@@ -319,7 +323,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 dc9a4ae20d9..fc65245c374 100644
--- a/src/jrd/pag_proto.h
+++ b/src/jrd/pag_proto.h
@@ -52,7 +52,7 @@ void PAG_init(Jrd::thread_db*);
void PAG_init2(Jrd::thread_db*, USHORT);
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 7319e4dd832..336da93137a 100644
--- a/src/jrd/recsrc/IndexTableScan.cpp
+++ b/src/jrd/recsrc/IndexTableScan.cpp
@@ -200,7 +200,7 @@ bool IndexTableScan::internalGetRecord(thread_db* tdbb) const
index_desc* const idx = (index_desc*) ((SCHAR*) impure + m_offset);
// 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 b10de39449b..eb3fba992c3 100644
--- a/src/jrd/relations.h
+++ b/src/jrd/relations.h
@@ -99,6 +99,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)
END_RELATION
@@ -126,6 +127,7 @@ RELATION(nam_r_fields, rel_rfr, ODS_8_0, rel_persistent)
FIELD(f_rfr_coll_id, nam_collate_id, fld_collate_id, 1, ODS_8_0)
FIELD(f_rfr_gen_name, nam_gen_name, fld_gen_name, 1, ODS_12_0)
FIELD(f_rfr_identity_type, nam_identity_type, fld_identity_type, 1, ODS_12_0)
+ FIELD(f_rfr_ts_name, nam_ts_name, fld_ts_name, 1, ODS_14_0)
END_RELATION
// Relation 6 (RDB$RELATIONS)
@@ -148,6 +150,9 @@ RELATION(nam_relations, rel_relations, ODS_8_0, rel_persistent)
FIELD(f_rel_flags, nam_flags, fld_flag_nullable, 0, ODS_8_0)
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_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)
@@ -758,3 +763,16 @@ RELATION(nam_mon_compiled_statements, rel_mon_compiled_statements, ODS_13_1, rel
FIELD(f_mon_cmp_stmt_pkg_name, nam_mon_pkg_name, fld_pkg_name, 0, ODS_13_1)
FIELD(f_mon_cmp_stmt_stat_id, nam_mon_stat_id, fld_stat_id, 0, ODS_13_1)
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 c9fae3bf2ea..f80639430e5 100644
--- a/src/jrd/replication/Applier.cpp
+++ b/src/jrd/replication/Applier.cpp
@@ -1005,7 +1005,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 f4e868ca990..56e77dff9e8 100644
--- a/src/jrd/replication/Config.cpp
+++ b/src/jrd/replication/Config.cpp
@@ -126,7 +126,8 @@ Config::Config()
logErrors(true),
reportErrors(false),
disableOnError(true),
- cascadeReplication(false)
+ cascadeReplication(false),
+ applyTablespacesDdl(true)
{
}
@@ -152,7 +153,8 @@ Config::Config(const Config& other)
logErrors(other.logErrors),
reportErrors(other.reportErrors),
disableOnError(other.disableOnError),
- cascadeReplication(other.cascadeReplication)
+ cascadeReplication(other.cascadeReplication),
+ applyTablespacesDdl(other.applyTablespacesDdl)
{
}
@@ -288,6 +290,10 @@ Config* Config::get(const PathName& lookupName)
{
parseBoolean(value, config->cascadeReplication);
}
+ else if (key == "apply_tablespaces_ddl")
+ {
+ parseBoolean(value, config->applyTablespacesDdl);
+ }
}
if (exactMatch)
@@ -299,7 +305,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
@@ -313,9 +319,9 @@ Config* Config::get(const PathName& lookupName)
PathUtils::splitLastComponent(db_directory, db_filename, config->dbName);
config->filePrefix = db_filename;
}
-
- return config.release();
}
+
+ return config.release();
}
catch (const Exception& ex)
{
@@ -405,6 +411,10 @@ void Config::enumerate(ReplicaList& replicas)
{
parseLong(value, config->applyErrorTimeout);
}
+ else if (key == "apply_tablespaces_ddl")
+ {
+ parseBoolean(value, config->applyTablespacesDdl);
+ }
}
if (dbName.hasData() && config->sourceDirectory.hasData())
@@ -427,3 +437,8 @@ void Config::enumerate(ReplicaList& replicas)
logReplicaStatus(dbName, &localStatus);
}
}
+
+bool Config::isMaster() const
+{
+ return journalDirectory.hasData() || syncReplicas.hasData();
+}
diff --git a/src/jrd/replication/Config.h b/src/jrd/replication/Config.h
index 72f29080c3e..24bd44c890c 100644
--- a/src/jrd/replication/Config.h
+++ b/src/jrd/replication/Config.h
@@ -43,6 +43,8 @@ namespace Replication
static Config* get(const Firebird::PathName& dbName);
static void enumerate(ReplicaList& replicas);
+ bool isMaster() const;
+
Firebird::PathName dbName;
ULONG bufferSize;
Firebird::string includeFilter;
@@ -66,6 +68,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 dd141d55dc5..ddfb10e4f2f 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 7b48dd78af6..273f2b7f903 100644
--- a/src/jrd/scl.epp
+++ b/src/jrd/scl.epp
@@ -830,6 +830,44 @@ void SCL_check_role(thread_db* tdbb, const MetaName& name, SecurityClass::flags_
SCL_check_access(tdbb, s_class, 0, NULL, mask, obj_roles, false, name);
}
+
+void SCL_check_tablespace(thread_db* tdbb, const dsc* dsc_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);
+
+ // Get the name in CSTRING format, ending on NULL or SPACE
+ fb_assert(dsc_name->dsc_dtype == dtype_text);
+ const MetaName name(reinterpret_cast(dsc_name->dsc_address),
+ dsc_name->dsc_length);
+
+ 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, name, mask, obj_tablespaces, false, name);
+}
+
+
SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* par_string)
{
/**************************************
@@ -1794,4 +1832,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 ab5fb5429dd..a06993993a5 100644
--- a/src/jrd/scl_proto.h
+++ b/src/jrd/scl_proto.h
@@ -52,6 +52,7 @@ void SCL_check_filter(Jrd::thread_db* tdbb, const Jrd::MetaName &name, Jrd::Secu
void SCL_check_relation(Jrd::thread_db* tdbb, const dsc*, Jrd::SecurityClass::flags_t, bool protectSys = true);
bool SCL_check_view(Jrd::thread_db* tdbb, const dsc*, 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 dsc*, Jrd::SecurityClass::flags_t);
Jrd::SecurityClass* SCL_get_class(Jrd::thread_db*, const TEXT*);
Jrd::SecurityClass::flags_t SCL_get_mask(Jrd::thread_db* tdbb, const TEXT*, const TEXT*);
void SCL_clear_classes(Jrd::thread_db*, const TEXT*);
diff --git a/src/jrd/tpc_proto.h b/src/jrd/tpc_proto.h
index bfa9f4e5eec..c5adc8554b4 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 3acd8489761..65f871ebfff 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"
const int DYN_MSG_FAC = 8;
@@ -543,7 +544,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
@@ -966,6 +967,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 ||
@@ -984,6 +987,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:
@@ -1007,6 +1011,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);
+ }
}
@@ -1263,6 +1276,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++)
@@ -1274,6 +1288,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:
@@ -1289,6 +1304,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)
@@ -2383,7 +2411,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)
@@ -2690,7 +2718,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 dea0202e96c..ac3b0ff191a 100644
--- a/src/jrd/tra.h
+++ b/src/jrd/tra.h
@@ -521,6 +521,13 @@ enum dfw_t {
dfw_store_view_context_type,
dfw_set_generator,
dfw_change_repl_state,
+ dfw_create_tablespace,
+ dfw_drop_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 6b398b4abb6..b8750683c77 100644
--- a/src/jrd/trig.h
+++ b/src/jrd/trig.h
@@ -82,6 +82,7 @@ static const 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 ec0f4981771..ddbe81023d0 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;
@@ -1070,8 +1072,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;
@@ -1167,7 +1174,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)
{
/**************************************
@@ -1202,7 +1209,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;
@@ -1218,7 +1225,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;
}
@@ -1230,12 +1237,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
@@ -1247,22 +1258,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) {
@@ -1271,7 +1281,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)
{
@@ -1288,7 +1298,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;
}
@@ -1296,7 +1306,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
@@ -1326,62 +1336,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
@@ -1477,8 +1495,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;
@@ -1488,7 +1507,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());
}
@@ -1507,7 +1526,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());
@@ -1544,9 +1563,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)
@@ -1558,12 +1579,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++;
@@ -1614,7 +1635,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);
TraNumber next = vdr_max_transaction = Ods::getNT(page);
if (vdr_flags & VDR_online) {
@@ -1629,6 +1650,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++)
{
@@ -1663,7 +1697,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.c_str());
@@ -1700,11 +1734,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)
@@ -1719,7 +1754,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;
@@ -1771,7 +1806,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)
@@ -1856,7 +1891,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)
{
@@ -1949,7 +1984,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);
}
}
@@ -1976,7 +2011,7 @@ 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();
+ const ULONG page_number = root_page.irt_rpt[id].getRootPage();
if (!page_number) {
return rtn_ok;
}
@@ -1986,6 +2021,17 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page& root_
const bool condition = (root_page.irt_rpt[id].irt_flags & irt_condition);
temporary_key nullKey, *null_key = 0;
+
+ // We need to have index description to get index page space
+ const bool isExpression = root_page.irt_rpt[id].irt_flags & irt_expression;
+ if (isExpression)
+ root_page.irt_rpt[id].irt_flags &= ~irt_expression;
+
+ index_desc idx;
+ BTR_description(vdr_tdbb, relation, &root_page, &idx, id);
+ if (isExpression)
+ root_page.irt_rpt[id].irt_flags |= irt_expression;
+
if (unique)
{
index_desc idx;
@@ -2019,11 +2065,11 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page& root_
while (next)
{
- WIN window(DB_PAGE_SPACE, -1);
+ WIN window(idx.idx_pg_space_id, -1);
window.win_flags = WIN_garbage_collector;
btree_page* page = 0;
- fetch_page(true, next, pag_index, &window, &page);
+ fetch_page(true, PageNumber(idx.idx_pg_space_id, next), pag_index, &window, &page);
// remember each page for circular reference detection
visited_pages.set(next);
@@ -2032,7 +2078,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page& root_
// (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, idx.idx_pg_space_id, next, page->btr_level, 0, __FILE__, __LINE__);
//}
if (level == 255) {
@@ -2041,14 +2087,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, idx.idx_pg_space_id, 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, idx.idx_pg_space_id,
next, page->btr_level, 0, __FILE__, __LINE__);
release_page(&window);
return rtn_corrupt;
@@ -2059,7 +2105,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page),
__FILE__, __LINE__);
}
@@ -2076,7 +2122,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (pointer - (UCHAR*) page),
__FILE__, __LINE__);
}
else
@@ -2086,7 +2132,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) jumpNode.offset,
__FILE__, __LINE__);
}
@@ -2094,7 +2140,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) jumpNode.offset,
__FILE__, __LINE__);
}
@@ -2102,7 +2148,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) jumpNode.offset,
__FILE__, __LINE__);
}
@@ -2114,7 +2160,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) page->btr_jump_size + BTR_SIZE,
__FILE__, __LINE__);
}
@@ -2134,7 +2180,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, idx.idx_pg_space_id, next, page->btr_level, node.nodePointer - (UCHAR*) page, __FILE__, __LINE__);
release_page(&window);
return rtn_corrupt;
}
@@ -2155,7 +2201,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (q - (UCHAR*) page),
__FILE__, __LINE__);
}
else if (*p < *q)
@@ -2177,7 +2223,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page),
__FILE__, __LINE__);
}
@@ -2201,7 +2247,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page),
__FILE__, __LINE__);
}
}
@@ -2222,7 +2268,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page),
__FILE__, __LINE__);
}
}
@@ -2261,11 +2307,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(idx.idx_pg_space_id, -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(idx.idx_pg_space_id, 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
@@ -2282,7 +2328,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page),
__FILE__, __LINE__);
}
else if (*p > *q)
@@ -2303,7 +2349,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page),
__FILE__, __LINE__);
}
}
@@ -2312,7 +2358,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, idx.idx_pg_space_id, next, page->btr_level, (ULONG) (node.nodePointer - (UCHAR*) page));
}
downNode.readNode(pointer, leafPage);
@@ -2322,11 +2368,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, idx.idx_pg_space_id, 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, idx.idx_pg_space_id, next);
}
previous_number = down_number;
@@ -2336,7 +2382,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, idx.idx_pg_space_id,
next, page->btr_level, (ULONG) (pointer - (UCHAR*) page), __FILE__, __LINE__);
}
@@ -2367,7 +2413,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, idx.idx_pg_space_id, next);
next = 0;
}
release_page(&window);
@@ -2422,117 +2468,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;
+ }
}
}
@@ -2556,10 +2609,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)
@@ -2574,7 +2628,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"
@@ -2603,7 +2657,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];
}
@@ -2628,9 +2682,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))
{
@@ -2667,7 +2721,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;
@@ -2676,7 +2730,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()) ||
@@ -2688,7 +2742,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);
@@ -2808,11 +2862,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 ||
@@ -2912,9 +2966,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);
@@ -2928,10 +2984,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);
@@ -2954,7 +3011,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);
@@ -2968,7 +3026,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);
}
@@ -2984,20 +3042,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);
@@ -3070,7 +3130,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 = Ods::getNT(page);
release_page(&window);
}
@@ -3089,6 +3149,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;
@@ -3210,20 +3323,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 = 0;
- 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() == 0)
+ if (page->irt_rpt[i].getRootPage() == 0)
continue;
MetaName 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_idx_incl)
{
@@ -3296,7 +3410,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)
@@ -3330,31 +3444,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 971e0617948..880fe2d1ae7 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;
@@ -185,23 +189,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 cdb851df85b..d1e0dba6397 100644
--- a/src/jrd/vio.cpp
+++ b/src/jrd/vio.cpp
@@ -83,6 +83,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"
@@ -175,7 +176,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);
@@ -673,13 +674,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;
}
}
@@ -2339,6 +2342,12 @@ 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");
+ if (EVL_field(0, rpb->rpb_record, f_ts_name, &desc))
+ SCL_check_tablespace(tdbb, &desc, SCL_drop);
+ break;
+
default: // Shut up compiler warnings
break;
}
@@ -3574,15 +3583,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_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, tdbb->getDatabase()->dbb_max_idx);
+ // Only tablespace name was changed. Move index data by pages
+ DFW_post_work(transaction, dfw_move_index, &desc1,
+ tdbb->getDatabase()->dbb_max_idx);
}
else
{
- DFW_post_work(transaction, dfw_create_index, &desc1,
- 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, tdbb->getDatabase()->dbb_max_idx);
+ }
+ else
+ {
+ DFW_post_work(transaction, dfw_create_index, &desc1,
+ tdbb->getDatabase()->dbb_max_idx);
+ }
}
}
break;
@@ -4312,6 +4330,31 @@ 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, object_id);
+ 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, obj_tablespace);
+ break;
+ }
+
default: // Shut up compiler warnings
break;
}
@@ -6306,7 +6349,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;
@@ -6674,7 +6717,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)
{
/**************************************
*
@@ -6692,7 +6735,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);
@@ -6881,7 +6924,7 @@ void VIO_update_in_place(thread_db* tdbb,
if (stack)
{
- const USHORT pageSpaceID = temp2.getWindow(tdbb).win_page.getPageSpaceID();
+ const ULONG pageSpaceID = temp2.getWindow(tdbb).win_page.getPageSpaceID();
stack->push(PageNumber(pageSpaceID, temp2.rpb_page));
}
}
diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp
index 44c0ac4277e..7566cf6104b 100644
--- a/src/utilities/gstat/dba.epp
+++ b/src/utilities/gstat/dba.epp
@@ -1568,7 +1568,7 @@ static void analyze_index( const dba_rel* relation, dba_idx* index)
SLONG page;
if (index_root->irt_count <= index->idx_id ||
- !(page = index_root->irt_rpt[index->idx_id].getRoot()))
+ !(page = index_root->irt_rpt[index->idx_id].getRootPage()))
{
return;
}