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; }