diff --git a/.vscode/launch.json b/.vscode/launch.json
index a5da522a67d..4ac5d323747 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,26 @@
{
"version": "0.2.0",
"configurations": [
+ {
+ "name": "firebird - launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceRoot}/gen/Debug/firebird/bin/firebird",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${workspaceRoot}",
+ "environment": [],
+ "externalConsole": false,
+ "linux": {
+ "MIMode": "gdb"
+ },
+ "osx": {
+ "MIMode": "lldb"
+ },
+ "windows": {
+ "MIMode": "gdb"
+ }
+ },
{
"name": "isql - launch",
"type": "cppdbg",
diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj
index 67bbab0013e..5faaf610fb1 100644
--- a/builds/win32/msvc15/engine_static.vcxproj
+++ b/builds/win32/msvc15/engine_static.vcxproj
@@ -94,6 +94,7 @@
+
@@ -134,6 +135,7 @@
+
@@ -285,6 +287,7 @@
+
diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters
index c5e0ec0fcee..86911bfcad5 100644
--- a/builds/win32/msvc15/engine_static.vcxproj.filters
+++ b/builds/win32/msvc15/engine_static.vcxproj.filters
@@ -72,6 +72,9 @@
JRD files\Data Access
+
+ JRD files\Data Access
+
JRD files\Data Access
@@ -282,6 +285,9 @@
JRD files
+
+ JRD files
+
JRD files
@@ -818,6 +824,9 @@
Header files
+
+ Header files
+
Header files
@@ -1206,4 +1215,4 @@
DSQL
-
\ No newline at end of file
+
diff --git a/doc/sql.extensions/README.MED.md b/doc/sql.extensions/README.MED.md
new file mode 100644
index 00000000000..2cbd04172c9
--- /dev/null
+++ b/doc/sql.extensions/README.MED.md
@@ -0,0 +1,357 @@
+# Management of External Data \(FB 6.0\)
+
+## Concept
+
+Firebird 6.0 allows to managing external data \(distributed or heterogeneous queries\). Distributed queries allow managing data stored in other databases. Those databases can be located on the same or different servers and use the same or different DBMS. Distributed queries allow to integrate and process data from different sources, providing developers with the ability to work with them as a single logical unit.
+
+External data management is defined by ISO/IEC 9075\-9:2023\(E\) \(SQL/MED\) standard. External data management implies both read and write access to external data. Access to foreign data is implemented using 4 objects: foreign-data wrapper, foreign server, user mapping, and foreign table.
+
+## Syntax
+
+### Foreign-data wrapper
+
+In Firebird, the foreign-data wrapper can be implemented as a provider \(plugin\). To use a foreign-data wrapper, it should be defined in `plugins.conf`, for example:
+```
+Plugin = JDBC {
+ Module = $(dir_plugins)/jdbc_provider
+ Config = JDBC_config
+}
+
+Config = JDBC_config {
+ Dir = $(this)/jdbc
+}
+```
+To connect to Firebird databases, the Engine and Remote providers are available by default and require no additional definition in `plugins.conf`.
+
+### Foreign server
+
+A foreign server is a logical representation of a remote data source inside the DBMS. The DBMS connects to the foreign server to execute queries. The foreign server provides metadata and connection information about the remote data source. Usually, the external server stores the connection parameters such as connection string, port, database type, etc.
+
+#### CREATE SERVER definition
+
+```sql
+CREATE SERVER [IF NOT EXISTS]
+ [{FOREIGN DATA WRAPPER | USING PLUGIN} ] [OPTIONS ( [, ...] )]
+ ::= {
+ CONNECTION_STRING = ''
+ | USER = ''
+ | PASSWORD = ''
+ | ROLE = ''
+ | = 'value'
+ | [{FILE | ENV}] 'value' }
+```
+The `SERVER` clause specifies the name of the server.
+The `FOREIGN DATA WRAPPER` or `USING PLUGIN` clause specifies the provider to use to access foreign data. If no clause is specified, the providers specified in the Providers parameter of the `firebird.conf` configuration file will be tried for connection. The clause `USING PLUGIN` is not specified in the standard, but it is familiar to Firebird developers and users, so it is proposed as an extension to the standard definition of a foreign server.
+The optional `OPTIONS` clause defines additional connection parameters to the foreign server. The required connection parameters depend on the provider \(plugin\) used to access the data. The option value can be read from a file or an environment variable. If it is necessary to read the value from a file, then `FILE` is specified after the option name or `ENV` is specified to read from the environment variable. The server should have the permissions to read the specified file. General options are:
+* `CONNECTION_STRING` - connection string to the foreign server \(depends on the foreign server specification\), if the option is not specified, an attempt will be made to connect to the current database;
+* `USER` - the user on whose name the connection will be established;
+* `PASSWORD` - user password;
+* `ROLE` - the role with which the specified user will connect to the foreign server.
+
+The `CREATE SERVER` statement can be executed by administrators or users with the `CREATE SERVER` privilege.
+
+#### ALTER SERVER definition
+
+```sql
+ALTER SERVER
+ [[DROP] {FOREIGN DATA WRAPPER | USING PLUGIN} ]
+ [OPTIONS ( [, ...] )]
+
+ ::= {
+ = 'value'
+ | [{FILE | ENV}] 'value'
+ | DROP
+}
+```
+If existing server options are specified, they will be updated and the others will not be affected.
+The `ALTER SERVER` statement can be executed by administrators, owner or users with the `ALTER SERVER` privilege.
+
+#### DROP SERVER definition
+
+```sql
+DROP SERVER
+```
+The `DROP SERVER` statement can be executed by administrators, owner or users with the `DROP SERVER` privilege. If the server has dependencies, the `DROP SERVER` statement will fail with an execution error.
+
+#### The USAGE Privilege
+To be able to use `SERVER` object it is necessary to grant the USAGE privilege on `SERVER` object:
+```sql
+GRANT USAGE ON FOREIGN SERVER
+TO [WITH GRANT OPTION]
+[{GRANTED BY | AS} [USER] ];
+```
+
+Revoking the USAGE privilege on the `SERVER` object:
+```sql
+REVOKE [GRANT OPTION FOR]
+USAGE ON FOREIGN SERVER
+FROM
+[{GRANTED BY | AS} [USER] ];
+```
+
+### User mappings
+
+User mappings for foreign connections define the connection parameters to be used by the specified user when accessing a foreign server. The user mapping provides security and access control by specifying the credentials to connect to the foreign server. User mapping maps local user accounts to remote server credentials and defines the login and password for the connection.
+
+#### CREATE USER MAPPING FOR definition
+
+```sql
+CREATE USER MAPPING FOR SERVER [OPTIONS ( [, ...] )]
+ ::= {
+ CONNECTION_STRING = ''
+ | USER = ''
+ | PASSWORD = ''
+ | ROLE = ''
+ | = 'value'
+ | [{FILE | ENV}] 'value' }
+```
+The general options are the same as for the `CREATE SERVER` statement. When connecting to a foreign server, the option values specified in the user mapping will be used. The connection parameters specified in the user mapping have a higher priority than those specified for the foreign server.
+
+The `CREATE USER MAPPING FOR` statement can be executed by administrators and database owner.
+
+#### ALTER USER MAPPING FOR definition
+
+```sql
+ALTER USER MAPPING FOR SERVER [OPTIONS ( [, ...] )]
+ ::= {
+ [DROP] [= 'value']
+ | [DROP] [{FILE | ENV}] ['value'] }
+```
+If existing server options are specified, they will be updated and the others will not be affected.
+The `ALTER USER MAPPING FOR` statement can be executed by administrators and database owner.
+
+#### DROP USER MAPPING FOR definition
+
+```sql
+DROP USER MAPPING FOR SERVER
+```
+The `DROP USER MAPPING FOR` statement can be executed by administrators and database owner.
+
+### Access to foreign data
+
+Foreign server data can be managed in two ways: using the `EXECUTE STATEMENT` statement or via a foreign table.
+
+### Foreign table
+
+A foreign table is a table that is physically stored on a foreign server but is accessible by the local DBMS. The foreign table allows executing SQL queries to the data contained in the table as local ones. The foreign table includes the table structure description and column mapping between the foreign and local table.
+
+#### CREATE FOREIGN TABLE definition
+
+```sql
+CREATE FOREIGN TABLE [ IF NOT EXISTS ]
+ ( [, { | }...])
+ SERVER [OPTIONS ( [, ...] )]
+ ::=
+
+ |
+ [OPTIONS ( [, ...] )]
+ ::=
+ { | }
+ [DEFAULT { | NULL | }]
+ [NOT NULL]
+ []
+ [COLLATE ]
+ ::=
+ []
+ GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [( [])]
+ []
+ ::= START WITH
+ | INCREMENT [BY]
+ ::=
+ [CONSTRAINT ] CHECK ()
+ ::=
+ [CONSTRAINT ] CHECK ()
+ ::= {
+ [= 'value']
+ | ['value'] }
+```
+Rules:
+* The name of the foreign table must be unique;
+* The required parameter is the name of the foreign server;
+* If the specified foreign server does not exist, the creation of the foreign table will fail;
+* The table name and column names should be the same as the object names on the foreign server. Can be remapped using the `OPTIONS` clause;
+* A foreign column cannot be `UNIQUE`, `PRIMARY KEY`, or `FOREIGN KEY`;
+* A foreign column cannot be computed \(`COMPUTED [BY] | GENERATED ALWAYS AS`\);
+* Foreign columns can have `NOT NULL` and `CHECK` integrity constraints, but this does not guarantee that the foreign server will check these constraints;
+* Otherwise, the rules for creating a foreign table are the same as for a regular table.
+
+The optional `OPTIONS` clause specifies additional foreign column options and table options. The required foreign column and table options depend on the provider \(plugin\) used to access table data. General table options are:
+* SCHEMA_NAME - the schema name where the table is located on the foreign server;
+* TABLE_NAME - name of the table on the foreign server.
+
+General column options are:
+* COLUMN_NAME - the name of the column in the foreign table;
+* PRIMARY_KEY - an identifier explicitly specifying that the column is used in the primary key;
+* READONLY - defines whether the field is a calculated field (TRUE); modifying a calculated field on the local
+server will not affect the value of the field in the table on the foreign server.
+
+The table name should include the full schema name and the table name on the foreign server \(if not specified in the options\). The table names should match. Column names should match \(if not specified in the options\). The number of defined columns should be less than or equal to the columns in the table on the foreign server.
+
+The `CREATE FOREIGN TABLE` statement can be executed by administrators and users with the `CREATE FOREIGN TABLE` privilege and. The user executing the `CREATE FOREIGN TABLE` statement becomes the owner of the foreign table.
+
+#### ALTER FOREIGN TABLE definition
+
+```sql
+ALTER FOREIGN TABLE [, ...]
+ [OPTIONS ( [, ...] )]
+ ::=
+ ADD
+ | ADD
+ | DROP
+ | DROP CONSTRAINT
+ | ALTER [COLUMN]
+ | [OPTIONS ( [, ...] )]
+ ::=
+
+ |
+ [OPTIONS ( [, ...] )]
+ ::=
+ { | }
+ [DEFAULT { | NULL | }]
+ [NOT NULL]
+ []
+ [COLLATE ]
+ ::=
+ []
+ GENERATED {ALWAYS|BY DEFAULT} AS IDENTITY [( [])]
+ []
+ ::=
+ TO
+ | POSITION
+ |
+ |
+ ::=
+ TYPE { | }
+ | SET DEFAULT { | NULL | }
+ | DROP DEFAULT
+ | SET NOT NULL
+ | DROP NOT NULL
+ ::=
+
+ | SET GENERATED {ALWAYS|BY DEFAULT} [ ...]
+ | DROP IDENTITY
+ ::= {
+ RESTART [ WITH ]
+ | SET INCREMENT [BY] }
+ ::=
+ [DROP] [= 'value']
+ | [DROP] ['value']
+```
+The mandatory parameters are the table name and the modification operation definition. If new values of existing options of a foreign column or foreign table are specified, they will be updated and the others will not be affected. Otherwise, the rules for altering a foreign table are the same as for a regular table.
+
+The `ALTER FOREIGN TABLE` statement can be executed by administrators, the owner of the table and users with the `ALTER FOREIGN TABLE` privilege.
+
+#### DROP FOREIGN TABLE definition
+
+```sql
+DROP FOREIGN TABLE
+```
+
+A foreign table that is referenced in triggers cannot be deleted, except for triggers written by the user specifically for this table. Also, a foreign table that is used in a stored procedure or view cannot be deleted. When a foreign table is dropped, all its triggers and options \(include column options\) will be deleted as well.
+
+The `DROP TABLE` statement can be executed by administrators, the owner of the table and users with the `DROP FOREIGN TABLE` privilege.
+
+### EXECUTE STATEMENT changes
+
+```sql
+ ::= EXECUTE STATEMENT
+ [ ...]
+ [INTO ];
+ ::=
+ | ()
+ | () ()
+ ::= |
+ ::= [, ...]
+ ::= [EXCESS] paramname :=
+ ::= [, ...]
+ ::=
+ WITH {AUTONOMOUS | COMMON} TRANSACTION
+ | WITH CALLER PRIVILEGES
+ | AS USER user
+ | PASSWORD password
+ | ROLE role
+ | ON EXTERNAL { [DATA SOURCE] | SERVER }
+ ::=
+ !! See in the CREATE DATABASE syntax !!
+ ::= [:]varname [, [:]varname ...]
+```
+
+The mandatory option for the `ON EXTERNAL SERVER` clause is the name of the foreign server. If the specified foreign server does not exist, creating an object with this `EXECUTE STATEMENT` will fail. The connection parameters specified in the `EXECUTE STATEMENT` statement have a higher priority than those specified in the user mapping or specified for the foreign server.
+
+## System metadata changes
+
+A new field is added for the `RDB$RELATIONS` table that stores the foreign server name:
+| Field | Type | Description |
+|----------------------------|-------------------|------------------------------------------------------------|
+| RDB$FOREIGN_SERVER_NAME | CHAR \(63\) | The foreign server name |
+
+If the `RDB$FOREIGN_SERVER_NAME` field contains a value other than NULL, the metadata is considered to match the foreign table.
+
+The following system tables have been added:
+
+`RDB$FOREIGN_SERVERS` - contains a description of all foreign servers defined in the database:
+
+| Field | Type | Description |
+|----------------------------|----------------------|------------------------------------------------------------|
+| RDB$FOREIGN_SERVER_NAME | CHAR \(63\) | The foreign server name |
+| RDB$FOREIGN_SERVER_WRAPPER | CHAR \(63\) | Provider (plugin) used to connect to a foreign server |
+| RDB$SECURITY_CLASS | CHAR \(63\) | Security class |
+| RDB$OWNER_NAME | CHAR \(63\) | The username of the user who created the server originally |
+
+`RDB$FOREIGN_SERVER_OPTIONS` - contains a description of all options defined for the foreign servers in the database:
+
+| Field | Type | Description |
+|----------------------------|----------------------|------------------------------------------------------------|
+| RDB$FOREIGN_SERVER_NAME | CHAR \(63\) | The foreign server name |
+| RDB$FOREIGN_OPTION_NAME | CHAR \(63\) | Option name |
+| RDB$FOREIGN_OPTION_VALUE | VARCHAR \(32765\) | Option value |
+| RDB$FOREIGN_OPTION_TYPE | SHORT | Option type |
+
+`RDB$FOREIGN_USER_MAPPINGS` - contains a description of all user mappings defined in the database:
+
+| Field | Type | Description |
+|----------------------------|----------------------|------------------------------------------------------------|
+| RDB$USER | CHAR \(63\) | The user name to map |
+| RDB$FOREIGN_SERVER_NAME | CHAR \(63\) | The foreign server name to map |
+
+`RDB$FOREIGN_MAPPING_OPTIONS` - contains a description of all options defined for user mappings to foreign servers in the database:
+
+| Field | Type | Description |
+|----------------------------|----------------------|------------------------------------------------------------|
+| RDB$USER | CHAR \(63\) | The user name to map |
+| RDB$FOREIGN_SERVER_NAME | CHAR \(63\) | The foreign server name |
+| RDB$FOREIGN_OPTION_NAME | CHAR \(63\) | Option name |
+| RDB$FOREIGN_OPTION_VALUE | VARCHAR \(32765\) | Option value |
+| RDB$FOREIGN_OPTION_TYPE | SHORT | Option type |
+
+This table completely absorbs table `RDB$FOREIGN_USER_MAPPINGS`, but the ISO/IEC 9075\-9:2023\(E\) \(SQL/MED\) standard requires a separate table for user mappings and user mapping options.
+
+`RDB$FOREIGN_TABLE_FIELD_OPTIONS` - contains a description of all options defined for foreign table columns in the database:
+
+| Field | Type | Description |
+|----------------------------|----------------------|------------------------------------------------------------|
+| RDB$SCHEMA_NAME | CHAR \(63\) | The schema name |
+| RDB$TABLE_NAME | CHAR \(63\) | The table name |
+| RDB$FIELD_NAME | CHAR \(63\) | The field name |
+| RDB$FOREIGN_OPTION_NAME | CHAR \(63\) | Option name |
+| RDB$FOREIGN_OPTION_VALUE | VARCHAR \(32765\) | Option value |
+
+`RDB$FOREIGN_TABLE_OPTIONS` - contains a description of all options defined for foreign tables in the database:
+
+| Field | Type | Description |
+|----------------------------|---------------------|------------------------------------------------------------|
+| RDB$SCHEMA_NAME | CHAR \(63\) | The schema name |
+| RDB$TABLE_NAME | CHAR \(63\) | The table name |
+| RDB$FOREIGN_OPTION_NAME | CHAR \(63\) | Option name |
+| RDB$FOREIGN_OPTION_VALUE | VARCHAR \(32765\) | Option value |
+
+For `RDB$FOREIGN_SERVERS` a unique index is created on the field `RDB$FOREIGN_SERVER_NAME`.
+For `RDB$FOREIGN_SERVERS_OPTIONS` creates a unique index on the fields `RDB$FOREIGN_SERVER_NAME` and `RDB$FOREIGN_OPTION_NAME`.
+For `RDB$FOREIGN_TABLE_OPTIONS` a unique index is created on the `RDB$SCHEMA_NAME`, `RDB$TABLE_NAME` and `RDB$FOREIGN_OPTION_NAME` fields.
+For `RDB$FOREIGN_USER_MAPPINGS` a unique index is created for the fields `RDB$USER` and `RDB$FOREIGN_SERVER_NAME`.
+For `RDB$FOREIGN_MAPPING_OPTIONS` a unique index is created on the `RDB$USER`, `RDB$FOREIGN_SERVER_NAME`, and `RDB$FOREIGN_OPTION_NAME` fields.
+For `RDB$FOREIGN_TABLE_FIELD_OPTIONS` a unique index is created for the `RDB$SCHEMA_NAME`, `RDB$TABLE_NAME`, `RDB$FIELD_NAME`, and `RDB$FOREIGN_OPTION_NAME` fields.
+
+Connections to foreign servers are created using the external data source \(EDS\) subsystem.
+
+*This documentation will be updated.*
diff --git a/src/burp/OdsDetection.epp b/src/burp/OdsDetection.epp
index 2ca60361336..79e66f2d596 100644
--- a/src/burp/OdsDetection.epp
+++ b/src/burp/OdsDetection.epp
@@ -46,6 +46,7 @@ namespace
{"RDB$PACKAGES", 0, DB_VERSION_DDL12}, // FB3
{"RDB$PUBLICATIONS", 0, DB_VERSION_DDL13}, // FB4
{"RDB$SCHEMAS", 0, DB_VERSION_DDL14}, // FB6
+ {"RDB$FOREIGN_SERVERS", 0, DB_VERSION_DDL14}, // FB6
{0, 0, 0}
};
diff --git a/src/burp/backup.epp b/src/burp/backup.epp
index 6ab4522498c..2cedd882583 100644
--- a/src/burp/backup.epp
+++ b/src/burp/backup.epp
@@ -134,6 +134,10 @@ void write_database(const TEXT*);
void write_exceptions();
void write_field_dimensions();
void write_filters();
+void write_foreign_mapping_options(const MetaString& userName, const MetaString& serverName);
+void write_foreign_table_options(const QualifiedMetaString& table);
+void write_foreign_table_field_options(const QualifiedMetaString& table, const MetaString& fieldName);
+void write_foreign_user_mappings();
void write_functions();
void write_function_args(const QualifiedMetaString& name);
void write_global_fields();
@@ -151,6 +155,8 @@ void write_rel_constraints();
void write_relations();
void write_schemas();
void write_secclasses();
+void write_foreign_servers();
+void write_foreign_server_options(const MetaString& serverName);
void write_shadow_files();
void write_triggers();
void write_trigger_messages();
@@ -345,6 +351,14 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name)
// Write schemas
BURP_verbose(412); // msg 412 writing schemas
write_schemas();
+
+ // Write foreign servers
+ BURP_verbose(423); // msg 423 writing foreign servers
+ write_foreign_servers();
+
+ // Write foreign user mappings
+ BURP_verbose(436); // msg 436 writing foreign user mappings
+ write_foreign_user_mappings();
}
// Write global fields
@@ -428,7 +442,8 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name)
put(tdgbl, att_end);
- if (!(relation->rel_flags & REL_view) && !(relation->rel_flags & REL_external))
+ if (!(relation->rel_flags & REL_view) && !(relation->rel_flags & REL_external)
+ && !(relation->rel_flags & REL_foreign))
{
put_index(relation);
if (!(tdgbl->gbl_sw_meta || tdgbl->skipRelation(relation->rel_name)))
@@ -2091,6 +2106,9 @@ void put_relation( burp_rel* relation)
}
put(tdgbl, att_end);
+
+ if (relation->rel_flags & REL_foreign)
+ write_foreign_table_field_options(relation->rel_name, field->fld_name);
}
// Write out view relations (if a view, of course)
@@ -2913,6 +2931,189 @@ void write_filters()
}
+void write_foreign_mapping_options(const MetaString& userName, const MetaString& serverName)
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ m a p p i n g _ o p t i o n s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for each foreign user mapping option.
+ *
+ **************************************/
+ IRequest* req_handle = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE req_handle)
+ X IN RDB$FOREIGN_MAPPING_OPTIONS
+ WITH X.RDB$USER EQ userName.c_str() AND
+ X.RDB$FOREIGN_SERVER_NAME EQ serverName.c_str()
+ {
+ put(tdgbl, rec_foreign_mapping_option);
+
+ PUT_TEXT(att_foreign_mapping_option_user, X.RDB$USER);
+
+ PUT_TEXT(att_foreign_mapping_option_server_name, X.RDB$FOREIGN_SERVER_NAME);
+
+ PUT_TEXT(att_foreign_mapping_option_name, X.RDB$FOREIGN_OPTION_NAME);
+
+ // msg 440 writing foreign user mapping option %s
+ BURP_verbose(440, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+
+ PUT_TEXT(att_foreign_mapping_option_value, X.RDB$FOREIGN_OPTION_VALUE);
+
+ if (!X.RDB$FOREIGN_OPTION_TYPE.NULL)
+ put_int32(att_foreign_mapping_option_type, X.RDB$FOREIGN_OPTION_TYPE);
+
+ put(tdgbl, att_end);
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(req_handle);
+}
+
+
+void write_foreign_table_options(const QualifiedMetaString& table)
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ t a b l e _ o p t i o n s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for foreign table option.
+ *
+ **************************************/
+ IRequest* req_handle = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE req_handle)
+ X IN RDB$FOREIGN_TABLE_OPTIONS
+ WITH X.RDB$SCHEMA_NAME EQ table.schema.c_str() AND
+ X.RDB$TABLE_NAME EQ table.object.c_str()
+ {
+ QualifiedMetaString tableName;
+
+ put(tdgbl, rec_foreign_table_option);
+
+ PUT_TEXT(att_foreign_table_option_schema_name, X.RDB$SCHEMA_NAME);
+ tableName.schema = X.RDB$SCHEMA_NAME;
+
+ PUT_TEXT(att_foreign_table_option_table_name, X.RDB$TABLE_NAME);
+ tableName.object = X.RDB$TABLE_NAME;
+
+ PUT_TEXT(att_foreign_table_option_name, X.RDB$FOREIGN_OPTION_NAME);
+
+ PUT_TEXT(att_foreign_table_option_value, X.RDB$FOREIGN_OPTION_VALUE);
+
+ // msg 430 writing foreign table option %s
+ BURP_verbose(430, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+
+ put(tdgbl, att_end);
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(req_handle);
+}
+
+
+void write_foreign_table_field_options(const QualifiedMetaString& table, const MetaString& fieldName)
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ t a b l e _ f i e l d _ o p t i o n s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for each foreign table field option.
+ *
+ **************************************/
+ IRequest* req_handle = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE req_handle)
+ X IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ WITH X.RDB$SCHEMA_NAME EQ table.schema.c_str() AND
+ X.RDB$TABLE_NAME EQ table.object.c_str() AND
+ X.RDB$FIELD_NAME EQ fieldName.c_str()
+ {
+ put(tdgbl, rec_foreign_table_field_option);
+
+ PUT_TEXT(att_foreign_table_field_option_schema_name, X.RDB$SCHEMA_NAME);
+
+ PUT_TEXT(att_foreign_table_field_option_table_name, X.RDB$TABLE_NAME);
+
+ PUT_TEXT(att_foreign_table_field_option_field_name, X.RDB$FIELD_NAME);
+
+ PUT_TEXT(att_foreign_table_field_option_name, X.RDB$FOREIGN_OPTION_NAME);
+
+ PUT_TEXT(att_foreign_table_field_option_value, X.RDB$FOREIGN_OPTION_VALUE);
+
+ // msg 433 writing foreign table column option %s
+ BURP_verbose(433, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+
+ put(tdgbl, att_end);
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(req_handle);
+}
+
+
+void write_foreign_user_mappings()
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ u s e r _ m a p p i n g s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for each user mapping.
+ *
+ **************************************/
+ IRequest* reqForeignUserMappings = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE reqForeignUserMappings)
+ X IN RDB$FOREIGN_USER_MAPPINGS
+ {
+ put(tdgbl, rec_foreign_user_mapping);
+
+ // msg 437 writing foreign user mapping options for user %s and server %s
+ BURP_verbose(437, SafeArg() << MetaString(X.RDB$USER).toQuotedString().c_str() <<
+ MetaString(X.RDB$FOREIGN_SERVER_NAME).toQuotedString().c_str());
+
+ PUT_TEXT(att_foreign_user_mapping_user, X.RDB$USER);
+
+ PUT_TEXT(att_foreign_user_mapping_server_name, X.RDB$FOREIGN_SERVER_NAME);
+
+ put(tdgbl, att_end);
+
+ write_foreign_mapping_options(MetaString(X.RDB$USER), MetaString(X.RDB$FOREIGN_SERVER_NAME));
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(reqForeignUserMappings);
+}
+
+
void write_functions()
{
/**************************************
@@ -4149,6 +4350,12 @@ void write_relations()
}
}
+ if (!X.RDB$FOREIGN_SERVER_NAME.NULL)
+ {
+ PUT_TEXT(att_relation_foreign_server_name, X.RDB$FOREIGN_SERVER_NAME);
+ flags |= REL_foreign;
+ }
+
if (!X.RDB$RELATION_TYPE.NULL)
put_int32 (att_relation_type, X.RDB$RELATION_TYPE);
@@ -4167,6 +4374,9 @@ void write_relations()
relation->rel_name = name;
relation->rel_flags |= flags;
put_relation (relation);
+
+ if (relation->rel_flags & REL_foreign)
+ write_foreign_table_options(relation->rel_name);
}
END_FOR
ON_ERROR
@@ -4304,6 +4514,97 @@ void write_secclasses()
}
+void write_foreign_servers()
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ s e r v e r s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for each foreign server.
+ *
+ **************************************/
+ IRequest* req_handle = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE req_handle)
+ X IN RDB$FOREIGN_SERVERS
+ {
+ put(tdgbl, rec_foreign_server);
+
+ PUT_TEXT(att_foreign_server_name, X.RDB$FOREIGN_SERVER_NAME);
+
+ // msg 424 writing foreign server %s
+ BURP_verbose(424, MetaString(X.RDB$FOREIGN_SERVER_NAME).toQuotedString());
+
+ if (!X.RDB$FOREIGN_SERVER_WRAPPER.NULL)
+ PUT_TEXT(att_foreign_server_data_wrapper_name, X.RDB$FOREIGN_SERVER_WRAPPER);
+
+ if (!X.RDB$SECURITY_CLASS.NULL)
+ PUT_TEXT(att_foreign_server_security_class, X.RDB$SECURITY_CLASS);
+
+ if (!X.RDB$OWNER_NAME.NULL)
+ PUT_TEXT(att_foreign_server_owner_name, X.RDB$OWNER_NAME);
+
+ put(tdgbl, att_end);
+
+ write_foreign_server_options(MetaString(X.RDB$FOREIGN_SERVER_NAME));
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(req_handle);
+}
+
+
+void write_foreign_server_options(const MetaString& serverName)
+{
+/**************************************
+ *
+ * w r i t e _ f o r e i g n _ s e r v e r _ o p t i o n s
+ *
+ **************************************
+ *
+ * Functional description
+ * Write a record in the burp file for each foreign server option.
+ *
+ **************************************/
+ IRequest* req_handle = nullptr;
+ BurpGlobals* tdgbl = BurpGlobals::getSpecific();
+
+ FOR (REQUEST_HANDLE req_handle)
+ X IN RDB$FOREIGN_SERVER_OPTIONS
+ WITH X.RDB$FOREIGN_SERVER_NAME EQ serverName.c_str()
+ {
+ put(tdgbl, rec_foreign_server_option);
+
+ PUT_TEXT(att_foreign_server_option_server_name, X.RDB$FOREIGN_SERVER_NAME);
+
+ PUT_TEXT(att_foreign_server_option_name, X.RDB$FOREIGN_OPTION_NAME);
+
+ // msg 427 writing foreign server option %s
+ BURP_verbose(427, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+
+ PUT_TEXT(att_foreign_server_option_value, X.RDB$FOREIGN_OPTION_VALUE);
+
+ if (!X.RDB$FOREIGN_OPTION_TYPE.NULL)
+ put_int32(att_foreign_server_option_type, X.RDB$FOREIGN_OPTION_TYPE);
+
+ put(tdgbl, att_end);
+ }
+ END_FOR
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ MISC_release_request_silent(req_handle);
+}
+
+
void write_shadow_files()
{
/**************************************
diff --git a/src/burp/burp.h b/src/burp/burp.h
index 3fb58a76b9e..40eb0a53d5b 100644
--- a/src/burp/burp.h
+++ b/src/burp/burp.h
@@ -124,7 +124,13 @@ enum rec_type {
rec_db_creator, // Database creator
rec_publication, // Publication
rec_pub_table, // Publication table
- rec_schema // Schema
+ rec_schema, // Schema
+ rec_foreign_server, // Foreign server
+ rec_foreign_server_option, // Foreign server option
+ rec_foreign_table_option, // Foreign table option
+ rec_foreign_table_field_option, // Foreign table field option
+ rec_foreign_user_mapping, // Foreign user mapping
+ rec_foreign_mapping_option // Foreign user mapping option
};
@@ -291,6 +297,7 @@ enum att_type {
att_relation_sql_security_deprecated, // can be removed later
att_relation_sql_security,
att_relation_schema_name,
+ att_relation_foreign_server_name, // table is foreign
// Field attributes (used for both global and local fields)
@@ -701,6 +708,46 @@ enum att_type {
att_schema_security_class,
att_schema_owner_name,
att_schema_description,
+
+ // Foreign server attributes
+ att_foreign_server_name = SERIES,
+ att_foreign_server_data_wrapper_name,
+ att_foreign_server_security_class,
+ att_foreign_server_owner_name,
+
+ // Foreign server option attributes
+ att_foreign_server_option_server_name = SERIES,
+ att_foreign_server_option_name,
+ att_foreign_server_option_value,
+ att_foreign_server_option_type,
+
+ // Foreign table option attributes
+
+ att_foreign_table_option_schema_name = SERIES,
+ att_foreign_table_option_table_name,
+ att_foreign_table_option_name,
+ att_foreign_table_option_value,
+
+ // Foreign table field option attributes
+
+ att_foreign_table_field_option_schema_name = SERIES,
+ att_foreign_table_field_option_table_name,
+ att_foreign_table_field_option_field_name,
+ att_foreign_table_field_option_name,
+ att_foreign_table_field_option_value,
+
+ // Foreign user mapping attributes
+
+ att_foreign_user_mapping_user = SERIES,
+ att_foreign_user_mapping_server_name,
+
+ // Foreign mapping option attributes
+
+ att_foreign_mapping_option_user = SERIES,
+ att_foreign_mapping_option_server_name,
+ att_foreign_mapping_option_name,
+ att_foreign_mapping_option_value,
+ att_foreign_mapping_option_type,
};
@@ -793,13 +840,15 @@ struct burp_rel
SSHORT rel_flags;
SSHORT rel_id;
Firebird::QualifiedMetaString rel_name;
+ Firebird::QualifiedMetaString rel_foreign_server;
GDS_NAME rel_owner; // relation owner, if not us
ULONG rel_max_pp; // max pointer page sequence number
};
enum burp_rel_flags_vals {
REL_view = 1,
- REL_external = 2
+ REL_external = 2,
+ REL_foreign = 4
};
// package definition
@@ -1174,6 +1223,12 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool
Firebird::IRequest* handles_get_fields_req_handle6;
Firebird::IRequest* handles_get_files_req_handle1;
Firebird::IRequest* handles_get_filter_req_handle1;
+ Firebird::IRequest* handles_get_foreign_mapping_option_req_handle1;
+ Firebird::IRequest* handles_get_foreign_server_req_handle1;
+ Firebird::IRequest* handles_get_foreign_server_option_req_handle1;
+ Firebird::IRequest* handles_get_foreign_table_option_req_handle1;
+ Firebird::IRequest* handles_get_foreign_table_field_option_req_handle1;
+ Firebird::IRequest* handles_get_foreign_user_mapping_req_handle1;
Firebird::IRequest* handles_get_function_arg_req_handle1;
Firebird::IRequest* handles_get_function_req_handle1;
Firebird::IRequest* handles_get_global_field_req_handle1;
diff --git a/src/burp/restore.epp b/src/burp/restore.epp
index 1108459d14d..bea2c8d81c8 100644
--- a/src/burp/restore.epp
+++ b/src/burp/restore.epp
@@ -167,6 +167,12 @@ bool get_trigger_old (BurpGlobals* tdgbl, burp_rel*);
bool get_type(BurpGlobals* tdgbl);
bool get_user_privilege(BurpGlobals* tdgbl);
bool get_view(BurpGlobals* tdgbl, burp_rel*);
+bool get_foreign_server(BurpGlobals* tdgbl);
+bool get_foreign_server_option(BurpGlobals* tdgbl);
+bool get_foreign_table_option(BurpGlobals* tdgbl);
+bool get_foreign_table_field_option(BurpGlobals* tdgbl);
+bool get_foreign_user_mapping(BurpGlobals* tdgbl);
+bool get_foreign_mapping_option(BurpGlobals* tdgbl);
void ignore_array(BurpGlobals* tdgbl, burp_rel*);
void ignore_blob(BurpGlobals* tdgbl);
rec_type ignore_data(BurpGlobals* tdgbl, burp_rel*);
@@ -8084,6 +8090,11 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
relation->rel_name.schema = temp;
break;
+ case att_relation_foreign_server_name:
+ GET_TEXT(temp);
+ relation->rel_foreign_server.object = temp;
+ break;
+
case att_relation_name:
{
GET_TEXT(temp);
@@ -8188,6 +8199,7 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
X IN RDB$RELATIONS
{
X.RDB$SCHEMA_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
X.RDB$SYSTEM_FLAG.NULL = FALSE;
X.RDB$FLAGS.NULL = rel_flags_null;
X.RDB$SECURITY_CLASS.NULL = sec_class_null;
@@ -8213,6 +8225,12 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
X.RDB$SCHEMA_NAME.NULL = FALSE;
}
+ if (relation->rel_foreign_server.object.hasData())
+ {
+ strcpy(X.RDB$FOREIGN_SERVER_NAME, relation->rel_foreign_server.object.c_str());
+ X.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ }
+
strcpy(X.RDB$SECURITY_CLASS, sec_class);
strcpy(X.RDB$RELATION_NAME, relation->rel_name.object.c_str());
strcpy(X.RDB$EXTERNAL_FILE, ext_file_name);
@@ -8300,6 +8318,10 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t
get_view(tdgbl, relation);
break;
+ case rec_foreign_table_field_option:
+ get_foreign_table_field_option(tdgbl);
+ break;
+
default:
BURP_error(43, true, SafeArg() << record);
// msg 43 don't recognize record type %ld
@@ -10534,6 +10556,643 @@ bool get_view(BurpGlobals* tdgbl, burp_rel* relation)
return true;
}
+bool get_foreign_server(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ s e r v e r
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_SERVERS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_server_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_data_wrapper_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_security_class:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_owner_name:
+ eat_text(tdgbl);
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 426); // msg 426 foreign server
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString name;
+ bool securityClass = false;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_server_req_handle1)
+ X IN RDB$FOREIGN_SERVERS
+ {
+ X.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_SERVER_WRAPPER.NULL = TRUE;
+ X.RDB$SECURITY_CLASS.NULL = TRUE;
+ X.RDB$OWNER_NAME.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_server_name:
+ GET_TEXT(X.RDB$FOREIGN_SERVER_NAME);
+ X.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ name.object = X.RDB$FOREIGN_SERVER_NAME;
+ BURP_verbose(425, name.object.toQuotedString()); // msg 425 restoring foreign server %s
+ break;
+
+ case att_foreign_server_data_wrapper_name:
+ GET_TEXT(X.RDB$FOREIGN_SERVER_WRAPPER);
+ X.RDB$FOREIGN_SERVER_WRAPPER.NULL = FALSE;
+ break;
+
+ case att_foreign_server_security_class:
+ GET_TEXT(X.RDB$SECURITY_CLASS);
+ fix_security_class_name(tdgbl, X.RDB$SECURITY_CLASS, false);
+ X.RDB$SECURITY_CLASS.NULL = FALSE;
+ securityClass = true;
+ break;
+
+ case att_foreign_server_owner_name:
+ GET_TEXT(X.RDB$OWNER_NAME);
+ X.RDB$OWNER_NAME.NULL = FALSE;
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 426); // msg 426 foreign server
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ collect_missing_privs(tdgbl, obj_foreign_servers, name, securityClass);
+
+ return true;
+}
+
+bool get_foreign_server_option(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ s e r v e r _ o p t i o n
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_SERVER_OPTIONS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_server_option_server_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_option_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_option_value:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_server_option_type:
+ get_int32(tdgbl);
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 429); // msg 429 foreign server option
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString name;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_server_option_req_handle1)
+ X IN RDB$FOREIGN_SERVER_OPTIONS
+ {
+ X.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_TYPE.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_server_option_server_name:
+ GET_TEXT(X.RDB$FOREIGN_SERVER_NAME);
+ X.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ name.object = X.RDB$FOREIGN_SERVER_NAME;
+ break;
+
+ case att_foreign_server_option_name:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_NAME);
+ X.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ // msg 428 restoring foreign server option %s
+ BURP_verbose(428, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+ break;
+
+ case att_foreign_server_option_value:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_VALUE);
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ break;
+
+ case att_foreign_server_option_type:
+ X.RDB$FOREIGN_OPTION_TYPE = get_int32(tdgbl);
+ X.RDB$FOREIGN_OPTION_TYPE.NULL = FALSE;
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 429); // msg 429 foreign server option
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ return true;
+}
+
+bool get_foreign_table_option(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ t a b l e _ o p t i o n
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_TABLE_OPTIONS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case rec_foreign_table_option:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_option_schema_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_option_table_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_option_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_option_value:
+ eat_text(tdgbl);
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 432); // msg 432 foreign table option
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString name;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_table_option_req_handle1)
+ X IN RDB$FOREIGN_TABLE_OPTIONS
+ {
+ X.RDB$SCHEMA_NAME.NULL = TRUE;
+ X.RDB$TABLE_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_table_option_schema_name:
+ GET_TEXT(X.RDB$SCHEMA_NAME);
+ X.RDB$SCHEMA_NAME.NULL = FALSE;
+ name.schema = X.RDB$SCHEMA_NAME;
+ break;
+
+ case att_foreign_table_option_table_name:
+ GET_TEXT(X.RDB$TABLE_NAME);
+ X.RDB$TABLE_NAME.NULL = FALSE;
+ name.object = X.RDB$TABLE_NAME;
+ break;
+
+ case att_foreign_table_option_name:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_NAME);
+ X.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ // msg 431 restoring foreign table option %s
+ BURP_verbose(431, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+ break;
+
+ case att_foreign_table_option_value:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_VALUE);
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 432); // msg 432 foreign table option
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ return true;
+}
+
+bool get_foreign_table_field_option(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ t a b l e _ f i e l d _ o p t i o n
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_TABLE_FIELD_OPTIONS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_table_field_option_schema_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_field_option_table_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_field_option_field_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_field_option_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_table_field_option_value:
+ eat_text(tdgbl);
+ break;
+
+ default:
+ // msg 435 foreign table column option
+ bad_attribute(scan_next_attr, attribute, 435);
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString tableName;
+ QualifiedMetaString fieldName;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_table_field_option_req_handle1)
+ X IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ {
+ X.RDB$SCHEMA_NAME.NULL = TRUE;
+ X.RDB$TABLE_NAME.NULL = TRUE;
+ X.RDB$FIELD_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_table_field_option_schema_name:
+ GET_TEXT(X.RDB$SCHEMA_NAME);
+ X.RDB$SCHEMA_NAME.NULL = FALSE;
+ tableName.schema = X.RDB$SCHEMA_NAME;
+ break;
+
+ case att_foreign_table_field_option_table_name:
+ GET_TEXT(X.RDB$TABLE_NAME);
+ X.RDB$TABLE_NAME.NULL = FALSE;
+ tableName.object = X.RDB$TABLE_NAME;
+ break;
+
+ case att_foreign_table_field_option_field_name:
+ GET_TEXT(X.RDB$FIELD_NAME);
+ X.RDB$FIELD_NAME.NULL = FALSE;
+ fieldName.object = X.RDB$FIELD_NAME;
+ break;
+
+ case att_foreign_table_field_option_name:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_NAME);
+ X.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ // msg 434 restoring foreign table column option %s
+ BURP_verbose(434, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+ break;
+
+ case att_foreign_table_field_option_value:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_VALUE);
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ break;
+
+ default:
+ // msg 435 foreign table column option
+ bad_attribute(scan_next_attr, attribute, 435);
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ return true;
+}
+
+bool get_foreign_user_mapping(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ u s e r _ m a p p i n g
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_USER_MAPPINGS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_user_mapping_user:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_user_mapping_server_name:
+ eat_text(tdgbl);
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 439); // msg 439 foreign user mapping
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString user;
+ QualifiedMetaString server;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_user_mapping_req_handle1)
+ X IN RDB$FOREIGN_USER_MAPPINGS
+ {
+ X.RDB$USER.NULL = TRUE;
+ X.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_user_mapping_user:
+ GET_TEXT(X.RDB$USER);
+ X.RDB$USER.NULL = FALSE;
+ user.object = X.RDB$USER;
+ break;
+
+ case att_foreign_user_mapping_server_name:
+ GET_TEXT(X.RDB$FOREIGN_SERVER_NAME);
+ X.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ server.object = X.RDB$FOREIGN_SERVER_NAME;
+ // msg 438 restoring foreign user mapping for user %s and server %s
+ BURP_verbose(438, SafeArg() << user.object.toQuotedString().c_str() <<
+ server.object.toQuotedString().c_str());
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 439); // msg 439 foreign user mapping
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ return true;
+}
+
+bool get_foreign_mapping_option(BurpGlobals* tdgbl)
+{
+/**************************************
+ *
+ * g e t _ f o r e i g n _ m a p p i n g _ o p t i o n
+ *
+ **************************************
+ *
+ * Functional description
+ * Store a record in RDB$FOREIGN_MAPPING_OPTIONS.
+ *
+ **************************************/
+
+ att_type attribute;
+ scan_attr_t scan_next_attr;
+
+ if (tdgbl->runtimeODS < DB_VERSION_DDL14)
+ {
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_mapping_option_user:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_mapping_option_server_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_mapping_option_name:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_mapping_option_value:
+ eat_text(tdgbl);
+ break;
+
+ case att_foreign_mapping_option_type:
+ get_int32(tdgbl);
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 442); // msg 442 foreign user mapping option
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ QualifiedMetaString user;
+ QualifiedMetaString server;
+ ITransaction* local_trans = tdgbl->global_trans ? tdgbl->global_trans : gds_trans;
+
+ STORE (TRANSACTION_HANDLE local_trans REQUEST_HANDLE tdgbl->handles_get_foreign_mapping_option_req_handle1)
+ X IN RDB$FOREIGN_MAPPING_OPTIONS
+ {
+ X.RDB$USER.NULL = TRUE;
+ X.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_NAME.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = TRUE;
+ X.RDB$FOREIGN_OPTION_TYPE.NULL = TRUE;
+
+ skip_init(&scan_next_attr);
+
+ while (skip_scan(&scan_next_attr), get_attribute(&attribute, tdgbl) != att_end)
+ {
+ switch (attribute)
+ {
+ case att_foreign_mapping_option_user:
+ GET_TEXT(X.RDB$USER);
+ X.RDB$USER.NULL = FALSE;
+ user.object = X.RDB$USER;
+ break;
+
+ case att_foreign_mapping_option_server_name:
+ GET_TEXT(X.RDB$FOREIGN_SERVER_NAME);
+ X.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ server.object = X.RDB$FOREIGN_SERVER_NAME;
+ break;
+
+ case att_foreign_mapping_option_name:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_NAME);
+ X.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ // msg 441 restoring foreign user mapping option %s
+ BURP_verbose(441, MetaString(X.RDB$FOREIGN_OPTION_NAME).toQuotedString());
+ break;
+
+ case att_foreign_mapping_option_value:
+ GET_TEXT(X.RDB$FOREIGN_OPTION_VALUE);
+ X.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ break;
+
+ case att_foreign_mapping_option_type:
+ X.RDB$FOREIGN_OPTION_TYPE = get_int32(tdgbl);
+ X.RDB$FOREIGN_OPTION_TYPE.NULL = FALSE;
+ break;
+
+ default:
+ bad_attribute(scan_next_attr, attribute, 442); // msg 442 foreign user mapping option
+ break;
+ }
+ }
+ }
+ END_STORE
+ ON_ERROR
+ general_on_error();
+ END_ERROR
+
+ return true;
+}
+
void ignore_array(BurpGlobals* tdgbl, burp_rel* relation)
{
/**************************************
@@ -11367,6 +12026,36 @@ bool restore(BurpGlobals* tdgbl, Firebird::IProvider* provider, const TEXT* file
flag = true;
break;
+ case rec_foreign_server:
+ if (!get_foreign_server(tdgbl))
+ return false;
+ flag = true;
+ break;
+
+ case rec_foreign_server_option:
+ if (!get_foreign_server_option(tdgbl))
+ return false;
+ flag = true;
+ break;
+
+ case rec_foreign_table_option:
+ if (!get_foreign_table_option(tdgbl))
+ return false;
+ flag = true;
+ break;
+
+ case rec_foreign_user_mapping:
+ if (!get_foreign_user_mapping(tdgbl))
+ return false;
+ flag = true;
+ break;
+
+ case rec_foreign_mapping_option:
+ if (!get_foreign_mapping_option(tdgbl))
+ return false;
+ flag = true;
+ break;
+
default:
BURP_error(43, true, SafeArg() << record);
// msg 43 don't recognize record type %ld
diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h
index 588252b429c..894f771ea25 100644
--- a/src/common/ParserTokens.h
+++ b/src/common/ParserTokens.h
@@ -206,6 +206,7 @@ PARSER_TOKEN(TOK_END, "END", false)
PARSER_TOKEN(TOK_ENGINE, "ENGINE", true)
PARSER_TOKEN(TOK_ENTRY_POINT, "ENTRY_POINT", true)
PARSER_TOKEN(TOK_ERROR, "ERROR", true)
+PARSER_TOKEN(TOK_ENV, "ENV", true)
PARSER_TOKEN(TOK_ESCAPE, "ESCAPE", false)
PARSER_TOKEN(TOK_EXCEPTION, "EXCEPTION", true)
PARSER_TOKEN(TOK_EXCESS, "EXCESS", true)
@@ -353,6 +354,7 @@ PARSER_TOKEN(TOK_ONLY, "ONLY", false)
PARSER_TOKEN(TOK_OPEN, "OPEN", false)
PARSER_TOKEN(TOK_OPTIMIZE, "OPTIMIZE", true)
PARSER_TOKEN(TOK_OPTION, "OPTION", true)
+PARSER_TOKEN(TOK_OPTIONS, "OPTIONS", true)
PARSER_TOKEN(TOK_OR, "OR", false)
PARSER_TOKEN(TOK_ORDER, "ORDER", false)
PARSER_TOKEN(TOK_OS_NAME, "OS_NAME", true)
@@ -465,6 +467,7 @@ PARSER_TOKEN(TOK_SEGMENT, "SEGMENT", true)
PARSER_TOKEN(TOK_SELECT, "SELECT", false)
PARSER_TOKEN(TOK_SENSITIVE, "SENSITIVE", false)
PARSER_TOKEN(TOK_SEQUENCE, "SEQUENCE", true)
+PARSER_TOKEN(TOK_SERVER, "SERVER", true)
PARSER_TOKEN(TOK_SERVERWIDE, "SERVERWIDE", true)
PARSER_TOKEN(TOK_SESSION, "SESSION", true)
PARSER_TOKEN(TOK_SET, "SET", false)
@@ -565,6 +568,7 @@ PARSER_TOKEN(TOK_WITH, "WITH", false)
PARSER_TOKEN(TOK_WITHIN, "WITHIN", false)
PARSER_TOKEN(TOK_WITHOUT, "WITHOUT", false)
PARSER_TOKEN(TOK_WORK, "WORK", true)
+PARSER_TOKEN(TOK_WRAPPER, "WRAPPER", true)
PARSER_TOKEN(TOK_WRITE, "WRITE", true)
PARSER_TOKEN(TOK_YEAR, "YEAR", false)
PARSER_TOKEN(TOK_YEARDAY, "YEARDAY", true)
diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp
index 02e4550a85c..4976c341666 100644
--- a/src/dsql/DdlNodes.epp
+++ b/src/dsql/DdlNodes.epp
@@ -80,6 +80,7 @@
#include "../jrd/ini.h"
#include "../jrd/GarbageCollector.h"
#include "../jrd/ProtectRelations.h"
+#include "../jrd/ForeignServer.h"
namespace Jrd {
@@ -142,6 +143,8 @@ static void updateRdbFields(const TypeClause* type,
SSHORT& segmentLengthNull, SSHORT& segmentLength);
static ISC_STATUS getErrorCodeByObjectType(int obj_type);
+static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& name, int type);
+
static constexpr const char* CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
DATABASE DB = STATIC "ODS.RDB";
@@ -684,6 +687,8 @@ static const char* getRelationScopeName(const rel_t type)
return REL_SCOPE_VIEW;
case rel_virtual:
return REL_SCOPE_VIRTUAL;
+ case rel_foreign:
+ return REL_SCOPE_FOREIGN;
case rel_persistent:
return REL_SCOPE_PERSISTENT;
default:
@@ -1590,6 +1595,10 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
SCL_check_package(tdbb, name, SCL_alter);
break;
+ case obj_foreign_server:
+ SCL_check_foreign_server(tdbb, name.object, SCL_alter);
+ break;
+
default:
fb_assert(false);
}
@@ -6411,12 +6420,14 @@ void DropSequenceNode::deleteIdentity(thread_db* tdbb, jrd_tra* transaction, con
//----------------------
-RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
+RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode, bool aForeign)
: DdlNode(p),
dsqlNode(aDsqlNode),
name(p, dsqlNode->dsqlName),
clauses(p),
- indexList(p)
+ indexList(p),
+ options(p),
+ foreign(aForeign)
{
}
@@ -6787,6 +6798,8 @@ DdlNode* RelationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
case Clause::TYPE_DROP_CONSTRAINT:
case Clause::TYPE_ALTER_SQL_SECURITY:
case Clause::TYPE_ALTER_PUBLICATION:
+ case Clause::TYPE_ALTER_FOREIGN_COL:
+ case Clause::TYPE_ALTER_OPTIONS:
break;
default:
@@ -6992,6 +7005,17 @@ bool RelationNode::deleteLocalField(thread_db* tdbb, jrd_tra* transaction,
}
END_FOR
+ request.reset(tdbb, drq_drop_f_tbl_opt, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ WITH FTFOPT.RDB$TABLE_NAME EQ relationName.object.c_str() AND
+ FTFOPT.RDB$FIELD_NAME EQ fieldName.c_str()
+ {
+ ERASE FTFOPT;
+ }
+ END_FOR
+
if (!found && !silent)
{
// msg 176: "column %s does not exist in table/view %s"
@@ -7237,6 +7261,36 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
defineConstraint(tdbb, dsqlScratch, transaction, constraint->name, *constraint->create);
}
}
+
+ // Store foreign field options
+ for (unsigned cnt = 0; cnt < clause->options.getCount(); ++cnt)
+ {
+ const Option* option = &clause->options[cnt];
+ if (option->value.hasData())
+ {
+ AutoCacheRequest request(tdbb, drq_s_f_tbl_f_opt, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ {
+ FTFOPT.RDB$SCHEMA_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$SCHEMA_NAME, relation->rel_name.schema.c_str());
+
+ FTFOPT.RDB$TABLE_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$TABLE_NAME, relation->rel_name.object.c_str());
+
+ FTFOPT.RDB$FIELD_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FIELD_NAME, field->fld_name.c_str());
+
+ FTFOPT.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FOREIGN_OPTION_NAME, option->name.c_str());
+
+ FTFOPT.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FOREIGN_OPTION_VALUE, option->value.c_str());
+ }
+ END_STORE
+ }
+ }
}
catch (const Exception&)
{
@@ -9176,6 +9230,31 @@ Format* RelationNode::makeFormat(thread_db* tdbb, jrd_tra* transaction, Cached::
}
+void RelationNode::storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option)
+{
+ Attachment* const attachment = transaction->getAttachment();
+
+ AutoCacheRequest requestHandle(tdbb, drq_s_f_tbl_opt, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FTOPT IN RDB$FOREIGN_TABLE_OPTIONS
+ {
+ FTOPT.RDB$SCHEMA_NAME.NULL = FALSE;
+ strcpy(FTOPT.RDB$SCHEMA_NAME, name.schema.c_str());
+
+ FTOPT.RDB$TABLE_NAME.NULL = FALSE;
+ strcpy(FTOPT.RDB$TABLE_NAME, name.object.c_str());
+
+ FTOPT.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ strcpy(FTOPT.RDB$FOREIGN_OPTION_NAME, option->name.c_str());
+
+ FTOPT.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ strcpy(FTOPT.RDB$FOREIGN_OPTION_VALUE, option->value.c_str());
+ }
+ END_STORE
+}
+
+
//----------------------
@@ -9186,6 +9265,8 @@ string CreateRelationNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, externalFile);
NODE_PRINT(printer, tempFlag);
NODE_PRINT(printer, tempRowsFlag);
+ NODE_PRINT(printer, relationType);
+ NODE_PRINT(printer, foreignServer);
return "CreateRelationNode";
}
@@ -9193,6 +9274,9 @@ string CreateRelationNode::internalPrint(NodePrinter& printer) const
void CreateRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
SCL_check_create_access(tdbb, obj_relations, name.schema);
+
+ if (foreignServer.hasData())
+ SCL_check_foreign_server(tdbb, foreignServer, SCL_usage);
}
void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
@@ -9217,6 +9301,12 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
dsqlScratch->relation->rel_flags |= REL_external;
}
+ if (foreignServer.hasData())
+ {
+ fb_assert(dsqlScratch->relation);
+ dsqlScratch->relation->rel_flags |= REL_foreign;
+ }
+
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
@@ -9294,11 +9384,32 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
extFile = true;
}
+
+ if (foreignServer.hasData())
+ {
+ if (!checkObjectExist(tdbb, transaction, QualifiedName(foreignServer), obj_foreign_server))
+ status_exception::raise(Arg::Gds(isc_dyn_foreign_server_not_found) << foreignServer.c_str());
+
+ REL.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ strcpy(REL.RDB$FOREIGN_SERVER_NAME, foreignServer.c_str());
+ REL.RDB$RELATION_TYPE = rel_foreign;
+ }
+ else
+ REL.RDB$FOREIGN_SERVER_NAME.NULL = TRUE;
}
END_STORE
lock.release(); // ID generated
+ // Store foreign table options
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ if (options[cnt].value.hasData())
+ {
+ storeOption(tdbb, transaction, &options[cnt]);
+ }
+ }
+
// replication
bool replicationEnabled = false;
@@ -9587,12 +9698,24 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
dsql_rel* relation = saveRelation(tdbb, dsqlScratch, name, false, false);
- if (!relation || (relation->rel_flags & REL_view))
+ ISC_STATUS dsqlError = 0;
+ if (foreign)
+ {
+ if (!relation || !(relation->rel_flags & REL_foreign))
+ dsqlError = isc_dsql_foreign_table_not_found;
+ }
+ else
+ {
+ if (!relation || (relation->rel_flags & (REL_view | REL_foreign)))
+ dsqlError = isc_dsql_table_not_found;
+ }
+
+ if (dsqlError)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
- Arg::Gds(isc_dsql_table_not_found) << name.toQuotedString());
+ Arg::Gds(dsqlError) << name.toQuotedString());
}
if (!dsqlScratch->relation)
@@ -9741,6 +9864,21 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
}
END_FOR
+ // if it's a column of the foreign table, update its name in the column options
+ AutoRequest request3;
+
+ FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ WITH FTFOPT.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
+ FTFOPT.RDB$TABLE_NAME EQ name.object.c_str() AND
+ FTFOPT.RDB$FIELD_NAME EQ clause->fromName.c_str()
+ {
+ MODIFY FTFOPT
+ strcpy(FTFOPT.RDB$FIELD_NAME, clause->toName.c_str());
+ END_MODIFY
+ }
+ END_FOR
+
break;
}
@@ -9832,6 +9970,55 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
break;
}
+ case Clause::TYPE_ALTER_FOREIGN_COL:
+ {
+ const AlterForeignColumnClause* clause =
+ static_cast(i->getObject());
+
+ // Drop old options and insert the new ones
+ for (unsigned cnt = 0; cnt < clause->options.getCount(); ++cnt)
+ {
+ const Option* option = &clause->options[cnt];
+ AutoCacheRequest request(tdbb, drq_drop_f_tbl_opt, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ WITH FTFOPT.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
+ FTFOPT.RDB$TABLE_NAME EQ name.object.c_str() AND
+ FTFOPT.RDB$FIELD_NAME EQ clause->name.c_str() AND
+ FTFOPT.RDB$FOREIGN_OPTION_NAME EQ option->name.c_str()
+ {
+ ERASE FTFOPT;
+ }
+ END_FOR
+
+ if (option->value.hasData())
+ {
+ request.reset(tdbb, drq_s_f_tbl_f_opt, DYN_REQUESTS);
+ STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ {
+ FTFOPT.RDB$SCHEMA_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$SCHEMA_NAME, name.schema.c_str());
+
+ FTFOPT.RDB$TABLE_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$TABLE_NAME, name.object.c_str());
+
+ FTFOPT.RDB$FIELD_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FIELD_NAME, clause->name.c_str());
+
+ FTFOPT.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FOREIGN_OPTION_NAME, option->name.c_str());
+
+ FTFOPT.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ strcpy(FTFOPT.RDB$FOREIGN_OPTION_VALUE, option->value.c_str());
+ }
+ END_STORE
+ }
+ }
+ break;
+ }
+
case Clause::TYPE_DROP_COLUMN:
{
// Fix for bug 8054:
@@ -9965,6 +10152,19 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
break;
}
+ case Clause::TYPE_ALTER_OPTIONS:
+ {
+ // Drop old options and insert the new ones
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ DropRelationNode::dropOption(tdbb, transaction, name, options[cnt].name);
+
+ if (options[cnt].value.hasData())
+ storeOption(tdbb, transaction, &options[cnt]);
+ }
+ break;
+ }
+
default:
fb_assert(false);
break;
@@ -10970,26 +11170,34 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
auto* relation = getPermanent(rel);
- // Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view.
+ // Check that DROP TABLE is dropping a table, that DROP FOREIGN TABLE is dropping a foreign table
+ // and that DROP VIEW is dropping a view.
+ ISC_STATUS dsqlError = 0;
if (view)
{
- if (!relation || (relation && !(relation->isView())))
- {
- status_exception::raise(
- Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
- Arg::Gds(isc_dsql_command_err) <<
- Arg::Gds(isc_dsql_view_not_found) << name.toQuotedString());
- }
+ if (!relation || !relation->isView())
+ dsqlError = isc_dsql_view_not_found;
+ }
+ else if (foreign)
+ {
+ if (!relation || !relation->getForeignAdapter())
+ dsqlError = isc_dsql_foreign_table_not_found;
}
else
{
- if (!relation || (relation && (relation->isView())))
- {
- status_exception::raise(
- Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
- Arg::Gds(isc_dsql_command_err) <<
- Arg::Gds(isc_dsql_table_not_found) << name.toQuotedString());
- }
+ if (!relation || (relation->isView() && relation->getForeignAdapter()))
+ dsqlError = isc_dsql_table_not_found;
+ }
+
+ if (dsqlError)
+ {
+ if (silent)
+ return;
+
+ status_exception::raise(
+ Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
+ Arg::Gds(isc_dsql_command_err) <<
+ Arg::Gds(dsqlError) << name.toQuotedString());
}
const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE);
@@ -11186,6 +11394,32 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
}
END_FOR
+ // Drop foreign table options
+
+ request.reset(tdbb, drq_drop_f_tbl_opts, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTOPT IN RDB$FOREIGN_TABLE_OPTIONS
+ WITH FTOPT.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
+ FTOPT.RDB$TABLE_NAME EQ name.object.c_str()
+ {
+ ERASE FTOPT;
+ }
+ END_FOR
+
+ // Drop foreign field options
+
+ request.reset(tdbb, drq_drop_f_tbl_f_opt, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ FTFOPT IN RDB$FOREIGN_TABLE_FIELD_OPTIONS
+ WITH FTFOPT.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
+ FTFOPT.RDB$TABLE_NAME EQ name.object.c_str()
+ {
+ ERASE FTFOPT;
+ }
+ END_FOR
+
bool rolledBack = false;
try
@@ -11284,6 +11518,28 @@ void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
}
+bool DropRelationNode::dropOption(thread_db* tdbb, jrd_tra* transaction,
+ const QualifiedName& relation, const MetaName& optionName)
+{
+ AutoCacheRequest requestHandle(tdbb, drq_drop_f_tbl_opt, DYN_REQUESTS);
+ bool found = false;
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FTOPT IN RDB$FOREIGN_TABLE_OPTIONS
+ WITH FTOPT.RDB$SCHEMA_NAME EQ relation.schema.c_str() AND
+ FTOPT.RDB$TABLE_NAME EQ relation.object.c_str() AND
+ FTOPT.RDB$FOREIGN_OPTION_NAME EQ optionName.c_str()
+ {
+ found = true;
+
+ ERASE FTOPT;
+ }
+ END_FOR
+
+ return found;
+}
+
+
//----------------------
@@ -15249,106 +15505,671 @@ void DropUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr
//----------------------
-string GrantRevokeNode::internalPrint(NodePrinter& printer) const
+string CreateAlterForeignServerNode::internalPrint(NodePrinter& printer) const
{
DdlNode::internalPrint(printer);
- NODE_PRINT(printer, isGrant);
- NODE_PRINT(printer, privileges);
- NODE_PRINT(printer, roles);
- NODE_PRINT(printer, object);
- NODE_PRINT(printer, users);
- NODE_PRINT(printer, grantAdminOption);
- NODE_PRINT(printer, grantor);
- NODE_PRINT(printer, isDdl);
+ NODE_PRINT(printer, name);
+ NODE_PRINT(printer, create);
+ NODE_PRINT(printer, alter);
+ NODE_PRINT(printer, plugin);
+ // NODE_PRINT(printer, options);
- return "GrantRevokeNode";
+ return "CreateAlterForeignServerNode";
}
-void GrantRevokeNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+
+void CreateAlterForeignServerNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
- // GRANT OPTION will be checked in grantRevoke method
+ if (alter)
+ {
+ if (SCL_check_foreign_server(tdbb, name, SCL_alter) || !create)
+ return;
+ }
+
+ SCL_check_create_access(tdbb, obj_foreign_servers, {});
}
-void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
+
+void CreateAlterForeignServerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
+ fb_assert(create || alter);
+
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
- createDbJobs.clear();
-
- const GranteeClause* usersPtr;
- const GranteeClause* usersEnd;
-
- if (!isGrant && roles.isEmpty() && privileges.isEmpty() && !object) // REVOKE ALL ON ALL
+ if (alter)
{
- usersEnd = users.end();
- for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
- grantRevoke(tdbb, transaction, NULL, usersPtr, NULL, NULL, 0);
+ if (!executeAlter(tdbb, dsqlScratch, transaction))
+ {
+ if (create) // create or alter
+ executeCreate(tdbb, dsqlScratch, transaction);
+ else
+ status_exception::raise(Arg::Gds(isc_dyn_foreign_server_not_found) << Arg::Str(name));
+ }
}
else
+ executeCreate(tdbb, dsqlScratch, transaction);
+
+ savePoint.release(); // everything is ok
+}
+
+
+void CreateAlterForeignServerNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ Attachment* const attachment = transaction->getAttachment();
+ const MetaString& ownerName = attachment->getEffectiveUserName();
+ const QualifiedName qualifiedName(name);
+
+ if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, qualifiedName, obj_foreign_server))
+ return;
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_CREATE_FOREIGN_SERVER, qualifiedName, {});
+
+ DYN_UTIL_check_unique_name(tdbb, qualifiedName, obj_foreign_server);
+
+ AutoCacheRequest requestHandle(tdbb, drq_s_f_server, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSERV IN RDB$FOREIGN_SERVERS
{
- SSHORT option = 0; // no grant/admin option
+ strcpy(FSERV.RDB$FOREIGN_SERVER_NAME, name.c_str());
- if (roles.isEmpty())
+ if (plugin.hasData())
{
- if (grantAdminOption)
- option = 1; // with grant option
-
- usersEnd = users.end();
- for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
- modifyPrivileges(tdbb, transaction, option, usersPtr);
+ FSERV.RDB$FOREIGN_SERVER_WRAPPER.NULL = FALSE;
+ strcpy(FSERV.RDB$FOREIGN_SERVER_WRAPPER, plugin.c_str());
}
else
{
- if (grantAdminOption)
- option = 2; // with admin option
+ FSERV.RDB$FOREIGN_SERVER_WRAPPER.NULL = TRUE;
+ }
- const GranteeClause* rolesPtr = roles.begin();
- const GranteeClause* rolesEnd = roles.end();
- const bool* defaultRolesPtr = defaultRoles.begin();
- for (; rolesPtr != rolesEnd; ++rolesPtr, ++defaultRolesPtr)
+ FSERV.RDB$OWNER_NAME.NULL = FALSE;
+ strcpy(FSERV.RDB$OWNER_NAME, ownerName.c_str());
+ }
+ END_STORE
+
+ if (options.hasData())
+ {
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ if (options[cnt].value.hasData())
{
- usersEnd = users.end();
- const bool defaultRole = *defaultRolesPtr;
- for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
- grantRevoke(tdbb, transaction, rolesPtr, usersPtr, "M", defaultRole ? "D" : NULL, option);
+ storeOption(tdbb, transaction, &options[cnt]);
}
-
- // Invalidate system privileges cache
- DFW_post_work(transaction, dfw_clear_cache, {}, {}, Mapping::SYSTEM_PRIVILEGES_CACHE);
}
+
}
- if (createDbJobs.hasData())
- executeInSecurityDb(transaction);
+ storePrivileges(tdbb, transaction, qualifiedName, obj_foreign_server, USAGE_PRIVILEGES);
- savePoint.release(); // everything is ok
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_FOREIGN_SERVER,
+ qualifiedName, {});
}
-void GrantRevokeNode::runInSecurityDb(SecDbContext* secDbContext)
+
+bool CreateAlterForeignServerNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
{
- for (unsigned n = 0; n < createDbJobs.getCount(); ++n)
- {
- CreateDbJob& j = createDbJobs[n];
- LocalStatus st;
- CheckStatusWrapper statusWrapper(&st);
+ Attachment* const attachment = transaction->getAttachment();
+ QualifiedName qualifiedName(name);
- Message result;
- Field cnt(result);
+ bool found = false;
- if (j.userType == obj_sql_role && (!j.allOnAll))
- {
- Message isRole;
- Field u(isRole, MAX_SQL_IDENTIFIER_LEN);
- u = j.user.c_str();
+ AutoCacheRequest requestHandle(tdbb, drq_m_f_server, DYN_REQUESTS);
- const char* isRoleSql = "select count(*) from SYSTEM.RDB$ROLES where RDB$ROLE_NAME = ?";
- secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, isRoleSql, SQL_DIALECT_V6,
- isRole.getMetadata(), isRole.getBuffer(), result.getMetadata(), result.getBuffer());
- check(&statusWrapper);
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSERV IN RDB$FOREIGN_SERVERS
+ WITH FSERV.RDB$FOREIGN_SERVER_NAME EQ name.c_str()
+ {
+ found = true;
- if (cnt == 0)
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_ALTER_FOREIGN_SERVER, qualifiedName, {});
+
+ MODIFY FSERV
+
+ if (plugin.hasData())
+ {
+ FSERV.RDB$FOREIGN_SERVER_WRAPPER.NULL = FALSE;
+ strcpy(FSERV.RDB$FOREIGN_SERVER_WRAPPER, plugin.c_str());
+ }
+
+ if (dropPlugin)
+ FSERV.RDB$FOREIGN_SERVER_WRAPPER.NULL = TRUE;
+
+ END_MODIFY
+ }
+ END_FOR
+
+
+ if (!found)
+ return found;
+
+ // Drop old options and insert the new ones
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ DropForeignServerNode::dropOption(tdbb, transaction, name, options[cnt].name);
+
+ if (options[cnt].value.hasData())
+ storeOption(tdbb, transaction, &options[cnt]);
+ }
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_FOREIGN_SERVER,
+ qualifiedName, {});
+
+ return found;
+}
+
+
+void CreateAlterForeignServerNode::storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option)
+{
+ Attachment* const attachment = transaction->getAttachment();
+
+ AutoCacheRequest requestHandle(tdbb, drq_s_f_server_opt, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSOPT IN RDB$FOREIGN_SERVER_OPTIONS
+ {
+ FSOPT.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ strcpy(FSOPT.RDB$FOREIGN_SERVER_NAME, name.c_str());
+
+ FSOPT.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ strcpy(FSOPT.RDB$FOREIGN_OPTION_NAME, option->name.c_str());
+
+ FSOPT.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ strcpy(FSOPT.RDB$FOREIGN_OPTION_VALUE, option->value.c_str());
+
+ if (option->type == ExternalValueType::TYPE_STRING)
+ FSOPT.RDB$FOREIGN_OPTION_TYPE.NULL = TRUE;
+ else
+ {
+ FSOPT.RDB$FOREIGN_OPTION_TYPE.NULL = FALSE;
+ FSOPT.RDB$FOREIGN_OPTION_TYPE = option->type;
+ }
+ }
+ END_STORE
+}
+
+//----------------------
+
+
+string DropForeignServerNode::internalPrint(NodePrinter& printer) const
+{
+ DdlNode::internalPrint(printer);
+
+ NODE_PRINT(printer, name);
+ NODE_PRINT(printer, plugin);
+ NODE_PRINT(printer, silent);
+
+ return "DropForeignServerNode";
+}
+
+void DropForeignServerNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ SCL_check_foreign_server(tdbb, name, SCL_drop);
+}
+
+void DropForeignServerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
+{
+ // run all statements under savepoint control
+ AutoSavePoint savePoint(tdbb, transaction);
+ bool found = false;
+ QualifiedName qualifiedName(name);
+
+ dropOptions(tdbb, transaction, name);
+
+ AutoCacheRequest requestHandle(tdbb, drq_drop_f_server, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSERV IN RDB$FOREIGN_SERVERS
+ WITH FSERV.RDB$FOREIGN_SERVER_NAME EQ name.c_str()
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
+ DDL_TRIGGER_DROP_FOREIGN_SERVER, qualifiedName, {});
+
+ ERASE FSERV;
+
+ if (!FSERV.RDB$SECURITY_CLASS.NULL)
+ deleteSecurityClass(tdbb, transaction, FSERV.RDB$SECURITY_CLASS);
+
+ found = true;
+ }
+ END_FOR
+
+ if (found)
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_FOREIGN_SERVER,
+ qualifiedName, {});
+ }
+ else if (!silent)
+ status_exception::raise(Arg::Gds(isc_dyn_foreign_server_not_found) << Arg::Str(name));
+
+ savePoint.release(); // everything is ok
+}
+
+
+bool DropForeignServerNode::dropOption(thread_db* tdbb, jrd_tra* transaction,
+ const MetaName& serverName, const MetaName& optionName)
+{
+ AutoCacheRequest requestHandle(tdbb, drq_drop_f_server_opt, DYN_REQUESTS);
+ bool found = false;
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSOPT IN RDB$FOREIGN_SERVER_OPTIONS
+ WITH FSOPT.RDB$FOREIGN_SERVER_NAME EQ serverName.c_str() AND
+ FSOPT.RDB$FOREIGN_OPTION_NAME EQ optionName.c_str()
+ {
+ found = true;
+
+ ERASE FSOPT;
+ }
+ END_FOR
+
+ return found;
+}
+
+
+void DropForeignServerNode::dropOptions(thread_db* tdbb, jrd_tra* transaction,
+ const MetaName& serverName)
+{
+ AutoCacheRequest requestHandle(tdbb, drq_drop_f_server_opts, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FSOPT IN RDB$FOREIGN_SERVER_OPTIONS
+ WITH FSOPT.RDB$FOREIGN_SERVER_NAME EQ serverName.c_str()
+ {
+ ERASE FSOPT;
+ }
+ END_FOR
+}
+
+
+//----------------------
+
+
+string CreateAlterUserMappingNode::internalPrint(NodePrinter& printer) const
+{
+ DdlNode::internalPrint(printer);
+
+ NODE_PRINT(printer, user);
+ NODE_PRINT(printer, server);
+ NODE_PRINT(printer, create);
+ NODE_PRINT(printer, alter);
+ // NODE_PRINT(printer, options);
+
+ return "CreateAlterUserMappingNode";
+}
+
+
+void CreateAlterUserMappingNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ SCL_check_foreign_server(tdbb, server, SCL_usage);
+}
+
+
+void CreateAlterUserMappingNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
+{
+ fb_assert(create || alter);
+
+ // 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_dyn_user_mapping_not_found) << Arg::Str(user) << Arg::Str(server));
+ }
+ }
+ else
+ executeCreate(tdbb, dsqlScratch, transaction);
+
+ savePoint.release(); // everything is ok
+}
+
+
+void CreateAlterUserMappingNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ Attachment* const attachment = transaction->getAttachment();
+ const MetaString& ownerName = attachment->getEffectiveUserName();
+
+ // User mapping to external servers by MED specification does not have a name,
+ // make a unique name from the user and server.
+ const QualifiedName name(getUniqueName());
+
+ if (!checkObjectExist(tdbb, transaction, QualifiedName(server), obj_foreign_server))
+ status_exception::raise(Arg::Gds(isc_dyn_foreign_server_not_found) << server.c_str());
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_USER_MAPPING,
+ name, {});
+
+ AutoCacheRequest requestHandle(tdbb, drq_s_u_mapping, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ UM IN RDB$FOREIGN_USER_MAPPINGS
+ {
+ strcpy(UM.RDB$USER, user.c_str());
+ strcpy(UM.RDB$FOREIGN_SERVER_NAME, server.c_str());
+ }
+ END_STORE
+
+ if (options.hasData())
+ {
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ if (options[cnt].value.hasData())
+ {
+ storeOption(tdbb, transaction, &options[cnt]);
+ }
+ }
+ }
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_USER_MAPPING,
+ name, {});
+
+ DFW_post_work(transaction, dfw_clear_cache, {}, {}, Mapping::SYSTEM_PRIVILEGES_CACHE);
+}
+
+
+bool CreateAlterUserMappingNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
+ jrd_tra* transaction)
+{
+ Attachment* const attachment = transaction->getAttachment();
+
+ // User mapping to external servers by MED specification does not have a name,
+ // make a unique name from the user and server.
+ const QualifiedName name(getUniqueName());
+
+ bool found = false;
+ bool modified = false;
+
+ AutoCacheRequest requestHandle(tdbb, drq_m_u_mapping, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FUM IN RDB$FOREIGN_USER_MAPPINGS
+ WITH FUM.RDB$USER EQ user.c_str() AND
+ FUM.RDB$FOREIGN_SERVER_NAME EQ server.c_str()
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_USER_MAPPING,
+ name, {});
+
+ found = true;
+ }
+ END_FOR
+
+ if (!found)
+ return found;
+
+ // Drop old options and insert the new ones
+ for (unsigned cnt = 0; cnt < options.getCount(); ++cnt)
+ {
+ if (DropUserMappingNode::dropOption(tdbb, transaction, user, server, options[cnt].name))
+ modified = true;
+
+ if (options[cnt].value.hasData())
+ {
+ storeOption(tdbb, transaction, &options[cnt]);
+
+ modified = true;
+ }
+ }
+
+ if (modified)
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_USER_MAPPING,
+ name, {});
+
+ DFW_post_work(transaction, dfw_clear_cache, {}, {}, Mapping::SYSTEM_PRIVILEGES_CACHE);
+ }
+
+ return found;
+}
+
+
+void CreateAlterUserMappingNode::storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option)
+{
+ Attachment* const attachment = transaction->getAttachment();
+
+ AutoCacheRequest requestHandle(tdbb, drq_s_u_mapping_opt, DYN_REQUESTS);
+
+ STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FMO IN RDB$FOREIGN_MAPPING_OPTIONS
+ {
+ FMO.RDB$USER.NULL = FALSE;
+ strcpy(FMO.RDB$USER, user.c_str());
+
+ FMO.RDB$FOREIGN_SERVER_NAME.NULL = FALSE;
+ strcpy(FMO.RDB$FOREIGN_SERVER_NAME, server.c_str());
+
+ FMO.RDB$FOREIGN_OPTION_NAME.NULL = FALSE;
+ strcpy(FMO.RDB$FOREIGN_OPTION_NAME, option->name.c_str());
+
+ FMO.RDB$FOREIGN_OPTION_VALUE.NULL = FALSE;
+ strcpy(FMO.RDB$FOREIGN_OPTION_VALUE, option->value.c_str());
+
+ if (option->type == ExternalValueType::TYPE_STRING)
+ FMO.RDB$FOREIGN_OPTION_TYPE.NULL = TRUE;
+ else
+ {
+ FMO.RDB$FOREIGN_OPTION_TYPE.NULL = FALSE;
+ FMO.RDB$FOREIGN_OPTION_TYPE = option->type;
+ }
+ }
+ END_STORE
+}
+
+//----------------------
+
+
+string DropUserMappingNode::internalPrint(NodePrinter& printer) const
+{
+ DdlNode::internalPrint(printer);
+
+ NODE_PRINT(printer, user);
+ NODE_PRINT(printer, server);
+ NODE_PRINT(printer, silent);
+
+ return "DropUserMappingNode";
+}
+
+void DropUserMappingNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ SCL_check_foreign_server(tdbb, server, SCL_usage);
+}
+
+void DropUserMappingNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
+{
+ // run all statements under savepoint control
+ AutoSavePoint savePoint(tdbb, transaction);
+ bool found = false;
+
+ // User mapping to external servers by MED specification does not have a name,
+ // make a unique name from the user and server.
+ const QualifiedName name(getUniqueName());
+
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_USER_MAPPING,
+ name, {});
+
+ dropOptions(tdbb, transaction, user, server);
+
+ AutoCacheRequest requestHandle(tdbb, drq_drop_u_mapping, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FUM IN RDB$FOREIGN_USER_MAPPINGS
+ WITH FUM.RDB$USER EQ user.c_str() AND
+ FUM.RDB$FOREIGN_SERVER_NAME EQ server.c_str()
+ {
+
+ ERASE FUM;
+
+ found = true;
+ }
+ END_FOR
+
+ if (found)
+ {
+ executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_USER_MAPPING,
+ QualifiedName(user), {});
+ }
+
+ if (!found && !silent)
+ status_exception::raise(Arg::Gds(isc_dyn_user_mapping_not_found) << Arg::Str(user) << Arg::Str(server));
+
+ DFW_post_work(transaction, dfw_clear_cache, {}, {}, Mapping::SYSTEM_PRIVILEGES_CACHE);
+
+ savePoint.release(); // everything is ok
+}
+
+
+bool DropUserMappingNode::dropOption(thread_db* tdbb, jrd_tra* transaction, const MetaName& user,
+ const MetaName& server, const MetaName& optionName)
+{
+ AutoCacheRequest requestHandle(tdbb, drq_drop_u_mapping_opt, DYN_REQUESTS);
+ bool found = false;
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FMO IN RDB$FOREIGN_MAPPING_OPTIONS
+ WITH FMO.RDB$USER EQ user.c_str() AND
+ FMO.RDB$FOREIGN_SERVER_NAME EQ server.c_str() AND
+ FMO.RDB$FOREIGN_OPTION_NAME EQ optionName.c_str()
+ {
+ found = true;
+
+ ERASE FMO;
+ }
+ END_FOR
+
+ return found;
+}
+
+
+void DropUserMappingNode::dropOptions(thread_db* tdbb, jrd_tra* transaction, const MetaName& user,
+ const MetaName& server)
+{
+ AutoCacheRequest requestHandle(tdbb, drq_drop_u_m_opts, DYN_REQUESTS);
+
+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
+ FMO IN RDB$FOREIGN_MAPPING_OPTIONS
+ WITH FMO.RDB$USER EQ user.c_str() AND
+ FMO.RDB$FOREIGN_SERVER_NAME EQ server.c_str()
+ {
+ ERASE FMO;
+ }
+ END_FOR
+}
+
+
+//----------------------
+
+
+string GrantRevokeNode::internalPrint(NodePrinter& printer) const
+{
+ DdlNode::internalPrint(printer);
+
+ NODE_PRINT(printer, isGrant);
+ NODE_PRINT(printer, privileges);
+ NODE_PRINT(printer, roles);
+ NODE_PRINT(printer, object);
+ NODE_PRINT(printer, users);
+ NODE_PRINT(printer, grantAdminOption);
+ NODE_PRINT(printer, grantor);
+ NODE_PRINT(printer, isDdl);
+
+ return "GrantRevokeNode";
+}
+
+void GrantRevokeNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
+{
+ // GRANT OPTION will be checked in grantRevoke method
+}
+
+void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
+{
+ // run all statements under savepoint control
+ AutoSavePoint savePoint(tdbb, transaction);
+
+ createDbJobs.clear();
+
+ const GranteeClause* usersPtr;
+ const GranteeClause* usersEnd;
+
+ if (!isGrant && roles.isEmpty() && privileges.isEmpty() && !object) // REVOKE ALL ON ALL
+ {
+ usersEnd = users.end();
+ for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
+ grantRevoke(tdbb, transaction, NULL, usersPtr, NULL, NULL, 0);
+ }
+ else
+ {
+ SSHORT option = 0; // no grant/admin option
+
+ if (roles.isEmpty())
+ {
+ if (grantAdminOption)
+ option = 1; // with grant option
+
+ usersEnd = users.end();
+ for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
+ modifyPrivileges(tdbb, transaction, option, usersPtr);
+ }
+ else
+ {
+ if (grantAdminOption)
+ option = 2; // with admin option
+
+ const GranteeClause* rolesPtr = roles.begin();
+ const GranteeClause* rolesEnd = roles.end();
+ const bool* defaultRolesPtr = defaultRoles.begin();
+ for (; rolesPtr != rolesEnd; ++rolesPtr, ++defaultRolesPtr)
+ {
+ usersEnd = users.end();
+ const bool defaultRole = *defaultRolesPtr;
+ for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
+ grantRevoke(tdbb, transaction, rolesPtr, usersPtr, "M", defaultRole ? "D" : NULL, option);
+ }
+
+ // Invalidate system privileges cache
+ DFW_post_work(transaction, dfw_clear_cache, {}, {}, Mapping::SYSTEM_PRIVILEGES_CACHE);
+ }
+ }
+
+ if (createDbJobs.hasData())
+ executeInSecurityDb(transaction);
+
+ savePoint.release(); // everything is ok
+}
+
+void GrantRevokeNode::runInSecurityDb(SecDbContext* secDbContext)
+{
+ for (unsigned n = 0; n < createDbJobs.getCount(); ++n)
+ {
+ CreateDbJob& j = createDbJobs[n];
+ LocalStatus st;
+ CheckStatusWrapper statusWrapper(&st);
+
+ Message result;
+ Field cnt(result);
+
+ if (j.userType == obj_sql_role && (!j.allOnAll))
+ {
+ Message isRole;
+ Field u(isRole, MAX_SQL_IDENTIFIER_LEN);
+ u = j.user.c_str();
+
+ const char* isRoleSql = "select count(*) from SYSTEM.RDB$ROLES where RDB$ROLE_NAME = ?";
+ secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, isRoleSql, SQL_DIALECT_V6,
+ isRole.getMetadata(), isRole.getBuffer(), result.getMetadata(), result.getBuffer());
+ check(&statusWrapper);
+
+ if (cnt == 0)
{
// msg 188: Role doesn't exist.
status_exception::raise(Arg::PrivateDyn(188) << j.user <<
@@ -15564,6 +16385,19 @@ static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const Qualif
END_FOR
break;
}
+
+ case obj_foreign_server:
+ {
+ AutoCacheRequest request(tdbb, drq_f_server_exist, DYN_REQUESTS);
+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
+ X IN RDB$FOREIGN_SERVERS
+ WITH X.RDB$FOREIGN_SERVER_NAME EQ name.object.c_str()
+ {
+ rc = true;
+ }
+ END_FOR
+ break;
+ }
}
return rc;
@@ -15762,6 +16596,11 @@ void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const G
status_exception::raise(Arg::PrivateDyn(188) << objName.toQuotedString()); // Role doesn't exist.
break;
+ case obj_foreign_server:
+ if (!checkObjectExist(tdbb, transaction, objName, objType))
+ status_exception::raise(Arg::PrivateDyn(326) << objName.toQuotedString()); // Foreign server @1 does not exist
+ break;
+
case obj_relations:
case obj_views:
case obj_procedures:
@@ -15982,6 +16821,7 @@ void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const G
case obj_generator:
case obj_package_header:
case obj_schema:
+ case obj_foreign_server:
{
checkGrantorCanGrantObject(tdbb, transaction, currentUser.c_str(), priv, objName, objType);
break;
diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h
index b72ac145e9a..ea6c43970ab 100644
--- a/src/dsql/DdlNodes.h
+++ b/src/dsql/DdlNodes.h
@@ -39,6 +39,7 @@
#include "../jrd/Relation.h"
#include "../jrd/Savepoint.h"
#include "../dsql/errd_proto.h"
+#include "../jrd/ForeignServer.h"
namespace Jrd {
@@ -1364,6 +1365,17 @@ class RelationNode : public DdlNode
MetaName baseField;
};
+ class Option
+ {
+ public:
+ explicit Option(MemoryPool& p)
+ : value(p)
+ {}
+
+ MetaName name;
+ Firebird::string value;
+ };
+
struct IndexConstraintClause
{
explicit IndexConstraintClause(MemoryPool& p)
@@ -1453,10 +1465,12 @@ class RelationNode : public DdlNode
TYPE_ALTER_COL_NULL,
TYPE_ALTER_COL_POS,
TYPE_ALTER_COL_TYPE,
+ TYPE_ALTER_FOREIGN_COL,
TYPE_DROP_COLUMN,
TYPE_DROP_CONSTRAINT,
TYPE_ALTER_SQL_SECURITY,
- TYPE_ALTER_PUBLICATION
+ TYPE_ALTER_PUBLICATION,
+ TYPE_ALTER_OPTIONS
};
explicit Clause(MemoryPool& p, Type aType) noexcept
@@ -1548,8 +1562,21 @@ class RelationNode : public DdlNode
collate(p),
computed(NULL),
identityOptions(NULL),
- notNullSpecified(false)
+ notNullSpecified(false),
+ options(p)
+ {
+ }
+
+ void addOption(MetaName* name, Firebird::string* value = NULL)
{
+ fb_assert(name);
+
+ Option& option = options.add();
+ option.name = *name;
+ if (value)
+ {
+ option.value = *value;
+ }
}
dsql_fld* field;
@@ -1560,6 +1587,32 @@ class RelationNode : public DdlNode
NestConst identityOptions;
bool notNullSpecified;
bool createIfNotExistsOnly = false;
+ Firebird::ObjectsArray options;
+ };
+
+ struct AlterForeignColumnClause : public Clause
+ {
+ explicit AlterForeignColumnClause(MemoryPool& p)
+ : Clause(p, TYPE_ALTER_FOREIGN_COL),
+ name(p),
+ options(p)
+ {
+ }
+
+ void addOption(MetaName* name, Firebird::string* value = NULL)
+ {
+ fb_assert(name);
+
+ Option& option = options.add();
+ option.name = *name;
+ if (value)
+ {
+ option.value = *value;
+ }
+ }
+
+ MetaName name;
+ Firebird::ObjectsArray options;
};
struct AlterColNameClause : public Clause
@@ -1647,7 +1700,7 @@ class RelationNode : public DdlNode
bool silent = false;
};
- RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode);
+ RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode, bool aForeign = false);
static MetaId generateRelId(thread_db* tdbb, MetaName name);
static bool checkDeletedId(thread_db* tdbb, MetaId& relId);
@@ -1706,6 +1759,7 @@ class RelationNode : public DdlNode
void stuffDefaultBlr(const Firebird::ByteChunk& defaultBlr, BlrDebugWriter& blrWriter);
void stuffMatchingBlr(Constraint& constraint, BlrDebugWriter& blrWriter);
void stuffTriggerFiringCondition(const Constraint& constraint, BlrDebugWriter& blrWriter);
+ void storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option);
public:
static void makeVersion(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& relName);
@@ -1713,6 +1767,18 @@ class RelationNode : public DdlNode
static Format* makeFormat(thread_db* tdbb, jrd_tra* transaction, Cached::Relation* relation,
USHORT* version, TemporaryField* stack);
+ void addOption(MetaName* name, Firebird::string* value = NULL)
+ {
+ fb_assert(name);
+
+ Option& option = options.add();
+ option.name = *name;
+ if (value)
+ {
+ option.value = *value;
+ }
+ }
+
private:
static blb* setupTriggers(thread_db* tdbb, jrd_rel* relation, bool null_view,
TrigArray* triggers, blb* blob);
@@ -1733,16 +1799,20 @@ class RelationNode : public DdlNode
Firebird::TriState ssDefiner;
Firebird::TriState replicationState;
ModifyIndexList indexList;
+ Firebird::ObjectsArray options;
+ bool foreign;
};
-class CreateRelationNode final : public RelationNode
+class CreateRelationNode : public RelationNode
{
public:
CreateRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode,
- const Firebird::string* aExternalFile = NULL)
- : RelationNode(p, aDsqlNode),
- externalFile(aExternalFile)
+ const Firebird::string* aExternalFile = NULL,
+ bool aForeign = false)
+ : RelationNode(p, aDsqlNode, aForeign),
+ externalFile(aExternalFile),
+ foreignServer(p)
{
}
@@ -1777,15 +1847,32 @@ class CreateRelationNode final : public RelationNode
public:
const Firebird::string* externalFile;
+ MetaName foreignServer;
bool createIfNotExistsOnly = false;
};
-class AlterRelationNode final : public RelationNode
+class CreateForeignRelationNode final : public CreateRelationNode
+{
+public:
+ CreateForeignRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
+ : CreateRelationNode(p, aDsqlNode, NULL, true)
+ {
+ }
+
+protected:
+ virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
+ {
+ statusVector << Firebird::Arg::Gds(isc_dsql_create_foreign_table_failed) << name.toQuotedString();
+ }
+};
+
+
+class AlterRelationNode : public RelationNode
{
public:
- AlterRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
- : RelationNode(p, aDsqlNode)
+ AlterRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode, bool aForeign = false)
+ : RelationNode(p, aDsqlNode, aForeign)
{
}
@@ -1821,19 +1908,39 @@ class AlterRelationNode final : public RelationNode
};
-class DropRelationNode final : public DdlNode
+class AlterForeignRelationNode final : public AlterRelationNode
+{
+public:
+ AlterForeignRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
+ : AlterRelationNode(p, aDsqlNode, true)
+ {
+ }
+
+protected:
+ virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
+ {
+ statusVector << Firebird::Arg::Gds(isc_dsql_alter_foreign_table_failed) << name.toQuotedString();
+ }
+};
+
+
+class DropRelationNode : public DdlNode
{
public:
- DropRelationNode(MemoryPool& p, const QualifiedName& aName, bool aView = false)
+ DropRelationNode(MemoryPool& p, const QualifiedName& aName, bool aView = false, bool aForeign = false)
: DdlNode(p),
name(p, aName),
view(aView),
- silent(false)
+ silent(false),
+ foreign(aForeign)
{
}
static void deleteGlobalField(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& globalName);
+ static bool dropOption(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& relation,
+ const MetaName& optionName);
+
public:
Firebird::string internalPrint(NodePrinter& printer) const override;
void checkPermission(thread_db* tdbb, jrd_tra* transaction) override;
@@ -1869,12 +1976,43 @@ class DropRelationNode final : public DdlNode
bool view;
bool silent;
bool recreate = false;
+ bool foreign;
+};
+
+
+class DropForeignRelationNode final : public DropRelationNode
+{
+public:
+ DropForeignRelationNode(MemoryPool& p, const QualifiedName& aName)
+ : DropRelationNode(p, aName, false, true)
+ {
+ }
+
+protected:
+ virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
+ {
+ statusVector << Firebird::Arg::Gds(isc_dsql_drop_foreign_table_failed) << name.toQuotedString();
+ }
};
typedef RecreateNode
RecreateTableNode;
+template <>
+inline RecreateNode::
+ RecreateNode(MemoryPool& p, CreateRelationNode* aCreateNode)
+ : DdlNode(p),
+ createNode(aCreateNode),
+ dropNode(p, createNode->name)
+ {
+ createNode->foreign = true;
+ dropNode.silent = true;
+ }
+
+typedef RecreateNode
+ RecreateForeignTableNode;
+
class CreateAlterViewNode final : public RelationNode
{
@@ -2629,6 +2767,235 @@ typedef RecreateNode options;
+ bool dropType;
+ bool dropVersion;
+ bool dropPlugin;
+
+ void addOption(MetaName* name, Firebird::string* value = NULL,
+ SSHORT type = ExternalValueType::TYPE_STRING)
+ {
+ fb_assert(name);
+
+ Option& option = options.add();
+ option.name = *name;
+ option.type = type;
+ if (value)
+ {
+ option.value = *value;
+ }
+ }
+
+private:
+ void executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
+ bool executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
+
+ void storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option);
+};
+
+
+class DropForeignServerNode : public DdlNode
+{
+public:
+ DropForeignServerNode(MemoryPool& p, const MetaName& aName, const MetaName* aPlugin = NULL)
+ : DdlNode(p),
+ name(p, aName),
+ plugin(p),
+ silent(false),
+ recreate(false)
+
+ {
+ if (aPlugin)
+ plugin = *aPlugin;
+ }
+
+public:
+ static bool dropOption(thread_db* tdbb, jrd_tra* transaction, const MetaName& serverName,
+ const MetaName& optionName);
+
+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);
+
+ void dropOptions(thread_db* tdbb, jrd_tra* transaction, const MetaName& serverName);
+
+protected:
+ virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
+ {
+ statusVector << Firebird::Arg::Gds(isc_dsql_drop_foreign_server_failed) << name.toQuotedString();
+ }
+
+public:
+ const MetaName name;
+ MetaName plugin;
+ bool silent;
+ bool recreate;
+};
+
+
+typedef RecreateNode
+ RecreateForeignServerNode;
+
+
+class CreateAlterUserMappingNode : public DdlNode
+{
+public:
+
+ CreateAlterUserMappingNode(MemoryPool& p, const MetaName& aUser, const MetaName& aServer)
+ : DdlNode(p),
+ user(p, aUser),
+ server(p, aServer),
+ create(true),
+ alter(false),
+ options(p)
+ { }
+
+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_user_mapping_failed, isc_dsql_alter_user_mapping_failed,
+ isc_dsql_create_alter_user_mapping_failed)) <<
+ user << server;
+ }
+
+public:
+ const MetaName user;
+ const MetaName server;
+ bool create;
+ bool alter;
+ Firebird::ObjectsArray options;
+
+ void addOption(MetaName* name, Firebird::string* value = NULL,
+ SSHORT type = ExternalValueType::TYPE_STRING)
+ {
+ fb_assert(name);
+
+ Option& option = options.add();
+ option.name = *name;
+ option.type = type;
+ if (value)
+ {
+ option.value = *value;
+ }
+ }
+
+private:
+ void executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
+ bool executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
+
+ void storeOption(thread_db* tdbb, jrd_tra* transaction, Option* option);
+
+ const MetaName getUniqueName() const
+ {
+ MetaName s;
+ s.printf("%s%s", user.c_str(), server.c_str());
+ return s;
+ };
+};
+
+
+class DropUserMappingNode : public DdlNode
+{
+public:
+ DropUserMappingNode(MemoryPool& p, const MetaName& aUser, const MetaName& aServer)
+ : DdlNode(p),
+ user(p, aUser),
+ server(p, aServer),
+ silent(false)
+
+ { }
+
+public:
+ static bool dropOption(thread_db* tdbb, jrd_tra* transaction, const MetaName& user, const MetaName& server,
+ const MetaName& optionName);
+
+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);
+
+ void dropOptions(thread_db* tdbb, jrd_tra* transaction, const MetaName& user, const MetaName& server);
+
+protected:
+ virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
+ {
+ statusVector << Firebird::Arg::Gds(isc_dsql_drop_user_mapping_failed) << user << server;
+ }
+
+public:
+ const MetaName user;
+ const MetaName server;
+ bool silent;
+
+private:
+ const Firebird::string getUniqueName() const
+ {
+ Firebird::string s;
+ s.append(user.c_str());
+ s.append(server.c_str());
+ return s;
+ };
+};
+
+
typedef Firebird::NonPooledPair*> PrivilegeClause;
typedef Firebird::NonPooledPair GranteeClause;
@@ -2695,6 +3062,8 @@ class GrantRevokeNode final : public PrivilegesNode, private ExecInSecurityDb
case obj_jobs:
case obj_tablespaces:
case obj_schemas:
+ case obj_foreign_server:
+ case obj_foreign_servers:
break;
case obj_relations:
diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp
index 29eac015e71..ee7b71839e0 100644
--- a/src/dsql/ExprNodes.cpp
+++ b/src/dsql/ExprNodes.cpp
@@ -10589,8 +10589,11 @@ dsc* RecordKeyNode::execute(thread_db* /*tdbb*/, Request* request) const
const jrd_rel* relation = rpb->rpb_relation;
// If it doesn't point to a valid record, return NULL.
- if (!rpb->rpb_number.isValid() || !relation || relation->isVirtual() || relation->getExtFile())
+ if (!rpb->rpb_number.isValid() || !relation || relation->isVirtual() || relation->getExtFile() ||
+ relation->getForeignAdapter())
+ {
return nullptr;
+ }
impure->vlu_misc.vlu_int64 = rpb->rpb_transaction_nr;
impure->vlu_desc.makeInt64(0, &impure->vlu_misc.vlu_int64);
diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp
index 208e7db07a3..8d0b43b8ad2 100644
--- a/src/dsql/StmtNodes.cpp
+++ b/src/dsql/StmtNodes.cpp
@@ -65,6 +65,8 @@
#include "../dsql/make_proto.h"
#include "../dsql/pass1_proto.h"
#include "../dsql/DsqlStatementCache.h"
+#include "../jrd/ForeignServer.h"
+#include "../jrd/Mapping.h"
using namespace Firebird;
using namespace Jrd;
@@ -2623,7 +2625,7 @@ EraseNode* EraseNode::pass2(thread_db* tdbb, CompilerScratch* csb)
csb->csb_rpt[stream].csb_flags |= csb_update;
- impureOffset = csb->allocImpure();
+ impureOffset = csb->allocImpure();
return this;
}
@@ -2675,7 +2677,8 @@ const StmtNode* EraseNode::execute(thread_db* tdbb, Request* request, ExeState*
// Perform erase operation.
const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger whichTrig) const
{
- impure_state* impure = request->getImpure(impureOffset);
+ Impure* impure = request->getImpure(impureOffset);
+ impure_state* state = &impure->state;
jrd_tra* transaction = request->req_transaction;
record_param* rpb = &request->req_rpb[stream];
@@ -2685,7 +2688,7 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger
{
case Request::req_evaluate:
{
- impure->sta_state = 0;
+ state->sta_state = 0;
if (!(marks & MARK_AVOID_COUNTERS))
request->req_records_affected.bumpModified(false);
@@ -2704,9 +2707,9 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger
}
case Request::req_return:
- if (impure->sta_state == 1)
+ if (state->sta_state == 1)
{
- impure->sta_state = 0;
+ state->sta_state = 0;
rpb->rpb_number.setValid(false);
return parentStmt;
}
@@ -2761,6 +2764,15 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger
extFile->erase(rpb, transaction);
else if (relation->isVirtual())
VirtualTable::erase(tdbb, rpb);
+ else if (auto* foreignAdapter = relation->getForeignAdapter())
+ {
+ if (!impure->statement)
+ {
+ impure->statement = foreignAdapter->createStatement(tdbb, rpb);
+ impure->statement->bindToRequest(request, &impure->statement);
+ }
+ foreignAdapter->execute(tdbb, impure->statement, rpb);
+ }
else if (!relation->isView())
{
// VIO_erase returns false if:
@@ -2809,7 +2821,7 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger
if (!relation->isView())
{
- if (!relation->getExtFile() && !relation->isVirtual())
+ if (!relation->getExtFile() && !relation->isVirtual() && !relation->getForeignAdapter())
IDX_erase(tdbb, rpb, transaction);
// Mark this rpb as already deleted to skip the subsequent attempts
@@ -2827,7 +2839,7 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger
if (returningStatement)
{
- impure->sta_state = 1;
+ state->sta_state = 1;
request->req_operation = Request::req_evaluate;
return returningStatement;
}
@@ -4158,6 +4170,18 @@ DmlNode* ExecStatementNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
node->role = PAR_parse_value(tdbb, csb);
break;
+ case blr_exec_stmt_ext_server:
+ {
+ csb->csb_blr_reader.getMetaName(node->server);
+ if (csb->collectingDependencies())
+ {
+ Dependency dependency(obj_foreign_server);
+ dependency.name = QualifiedName(node->server);
+ csb->addDependency(dependency);
+ }
+ break;
+ }
+
case blr_exec_stmt_tran:
PAR_syntax_error(csb, "external transaction parameters");
break;
@@ -4301,6 +4325,7 @@ StmtNode* ExecStatementNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
node->role = doDsqlPass(dsqlScratch, role);
node->traScope = traScope;
node->useCallerPrivs = useCallerPrivs;
+ node->server = server;
return SavepointEncloseNode::make(dsqlScratch->getPool(), dsqlScratch, node);
}
@@ -4313,6 +4338,7 @@ string ExecStatementNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, dsqlLabelNumber);
NODE_PRINT(printer, sql);
NODE_PRINT(printer, dataSource);
+ NODE_PRINT(printer, server);
NODE_PRINT(printer, userName);
NODE_PRINT(printer, password);
NODE_PRINT(printer, role);
@@ -4336,7 +4362,7 @@ void ExecStatementNode::genBlr(DsqlCompilerScratch* dsqlScratch)
// If no new features of EXECUTE STATEMENT are used, lets generate old BLR.
if (!dataSource && !userName && !password && !role && !useCallerPrivs && !inputs &&
- traScope == EDS::traNotSet)
+ traScope == EDS::traNotSet && server.isEmpty())
{
if (outputs)
{
@@ -4396,6 +4422,13 @@ void ExecStatementNode::genBlr(DsqlCompilerScratch* dsqlScratch)
genOptionalExpr(dsqlScratch, blr_exec_stmt_pwd, password);
genOptionalExpr(dsqlScratch, blr_exec_stmt_role, role);
+ // Server
+ if (server.hasData())
+ {
+ dsqlScratch->appendUChar(blr_exec_stmt_ext_server);
+ dsqlScratch->appendMetaString(server.c_str());
+ }
+
// dsqlScratch's transaction behavior.
if (traScope != EDS::traNotSet)
{
@@ -4470,6 +4503,14 @@ ExecStatementNode* ExecStatementNode::pass1(thread_db* tdbb, CompilerScratch* cs
doPass1(tdbb, csb, innerStmt.getAddress());
doPass1(tdbb, csb, inputs.getAddress());
doPass1(tdbb, csb, outputs.getAddress());
+
+ if (server.hasData())
+ {
+ AutoPtr foreignServer = MET_get_foreign_server(tdbb, server);
+ CMP_post_access(tdbb, csb, foreignServer->getSecurityClass(), 0, SCL_usage, obj_foreign_servers,
+ QualifiedName(server));
+ }
+
return this;
}
@@ -4508,6 +4549,7 @@ const StmtNode* ExecStatementNode::execute(thread_db* tdbb, Request* request, Ex
{
fb_assert(!*stmtPtr);
+ // Explicitly specified connection properties have the highest priority.
string sSql;
getString(tdbb, request, sql, sSql, true);
@@ -4523,7 +4565,53 @@ const StmtNode* ExecStatementNode::execute(thread_db* tdbb, Request* request, Ex
string sRole;
getString(tdbb, request, role, sRole);
- EDS::Connection* conn = EDS::Manager::getConnection(tdbb, sDataSrc, sUser, sPwd, sRole, traScope);
+ PathName sProviders;
+
+ if (server.hasData())
+ {
+ AutoPtr foreignServer = MET_get_foreign_server(tdbb, server);
+
+ Database* const dbb = tdbb->getDatabase();
+ Jrd::Attachment* attachment = tdbb->getAttachment();
+ const string currentUser = attachment->getUserName();
+ const string fServer = server.c_str();
+ Mapping mapping(Mapping::MAP_NO_FLAGS, NULL);
+
+ // If there is mapping for user and foreign server,
+ // get a map of the connection parameters, skipping the explicitly specified properties earlier.
+ GenericMap* foreignMap;
+ mapping.getForeignUserMap(attachment, currentUser, fServer, foreignMap);
+
+ // The connection properties specified in foreign server options have the lowest priority.
+ const auto& options = foreignServer.get()->getOptions();
+
+ auto getOption = [&](const char* optionName) -> string
+ {
+ ForeignOption option(*tdbb->getAttachment()->att_pool);
+ if (foreignMap && foreignMap->get(optionName, option))
+ return option.getActualValue();
+ if (options.get(optionName, option))
+ return option.getActualValue();
+ return "";
+ };
+
+ if (sDataSrc.isEmpty())
+ sDataSrc = getOption(FOREIGN_SERVER_CONNECTION_STRING);
+ if (sUser.isEmpty())
+ sUser = getOption(FOREIGN_SERVER_USER);
+ if (sPwd.isEmpty())
+ sPwd = getOption(FOREIGN_SERVER_PASSWORD);
+ if (sRole.isEmpty())
+ sRole = getOption(FOREIGN_SERVER_ROLE);
+
+ if (!foreignServer.get()->getPluginName().isEmpty())
+ {
+ sProviders = foreignServer.get()->getPluginName().c_str();
+ sProviders.insert(0, "Providers=");
+ }
+ }
+
+ EDS::Connection* conn = EDS::Manager::getConnection(tdbb, sDataSrc, sUser, sPwd, sRole, sProviders, traScope);
stmt = conn->createStatement(sSql);
stmt->bindToRequest(request, stmtPtr);
@@ -6006,8 +6094,8 @@ void ForNode::setWriteLockMode(Request* request) const
void ForNode::checkRecordUpdated(thread_db* tdbb, Request* request, record_param* rpb) const
{
- auto* relation = rpb->rpb_relation->getPermanent();
- if (!(marks & MARK_MERGE) || relation->isVirtual() || relation->isView() || relation->getExtFile())
+ auto* relation = rpb->rpb_relation;
+ if (!(marks & MARK_MERGE) || !relation->isPageBased())
return;
ImpureMerge* impure = request->getImpure(impureOffset);
@@ -6021,8 +6109,8 @@ void ForNode::checkRecordUpdated(thread_db* tdbb, Request* request, record_param
void ForNode::setRecordUpdated(thread_db* tdbb, Request* request, record_param* rpb) const
{
- auto* relation = rpb->rpb_relation->getPermanent();
- if (!(marks & MARK_MERGE) || relation->isVirtual() || relation->isView() || relation->getExtFile())
+ auto* relation = rpb->rpb_relation;
+ if (!(marks & MARK_MERGE) || !relation->isPageBased())
return;
ImpureMerge* impure = request->getImpure(impureOffset);
@@ -8191,20 +8279,21 @@ ModifyNode* ModifyNode::pass2(thread_db* tdbb, CompilerScratch* csb)
if (!(marks & StmtNode::MARK_POSITIONED))
forNode = pass2FindForNode(parentStmt, orgStream);
- impureOffset = csb->allocImpure();
+ impureOffset = csb->allocImpure();
return this;
}
const StmtNode* ModifyNode::execute(thread_db* tdbb, Request* request, ExeState* exeState) const
{
- impure_state* impure = request->getImpure(impureOffset);
+ Impure* impure = request->getImpure(impureOffset);
+ impure_state* state = &impure->state;
const StmtNode* retNode;
if (request->req_operation == Request::req_unwind)
return parentStmt;
- if (request->req_operation == Request::req_return && !impure->sta_state && subMod)
+ if (request->req_operation == Request::req_return && !state->sta_state && subMod)
{
if (!exeState->topNode)
{
@@ -8246,7 +8335,8 @@ const StmtNode* ModifyNode::execute(thread_db* tdbb, Request* request, ExeState*
const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigger whichTrig) const
{
jrd_tra* transaction = request->req_transaction;
- impure_state* impure = request->getImpure(impureOffset);
+ Impure* impure = request->getImpure(impureOffset);
+ impure_state* state = &impure->state;
record_param* orgRpb = &request->req_rpb[orgStream];
jrd_rel* relation = orgRpb->rpb_relation;
@@ -8259,16 +8349,16 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
if (!(marks & MARK_AVOID_COUNTERS))
request->req_records_affected.bumpModified(false);
- if (impure->sta_state == 0 && forNode && forNode->isWriteLockMode(request))
+ if (state->sta_state == 0 && forNode && forNode->isWriteLockMode(request))
request->req_operation = Request::req_return;
else
break;
[[fallthrough]];
case Request::req_return:
- if (impure->sta_state == 1)
+ if (state->sta_state == 1)
{
- impure->sta_state = 0;
+ state->sta_state = 0;
Record* orgRecord = orgRpb->rpb_record;
const Record* newRecord = newRpb->rpb_record;
orgRecord->copyDataFrom(newRecord, true);
@@ -8276,7 +8366,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
return statement;
}
- if (impure->sta_state == 0)
+ if (state->sta_state == 0)
{
if (forNode && forNode->isWriteLockMode(request))
{
@@ -8306,6 +8396,15 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
extFile->modify(orgRpb, newRpb, transaction);
else if (relation->isVirtual())
VirtualTable::modify(tdbb, orgRpb, newRpb);
+ else if (auto* foreignAdapter = relation->getForeignAdapter())
+ {
+ if (!impure->statement)
+ {
+ impure->statement = foreignAdapter->createStatement(tdbb, orgRpb, newRpb);
+ impure->statement->bindToRequest(request, &impure->statement);
+ }
+ foreignAdapter->execute(tdbb, impure->statement, orgRpb, newRpb);
+ }
else if (!relation->isView())
{
// VIO_modify returns false if:
@@ -8350,7 +8449,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
// have fired. This is required for cascading referential integrity,
// which can be implemented as post_erase triggers.
- if (!relation->getExtFile() && !relation->isView() && !relation->isVirtual())
+ if (relation->isPageBased())
IDX_modify_check_constraints(tdbb, orgRpb, newRpb, transaction);
if (!relation->isView() ||
@@ -8365,7 +8464,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
if (statement2)
{
- impure->sta_state = 2;
+ state->sta_state = 2;
request->req_operation = Request::req_evaluate;
return statement2;
}
@@ -8383,7 +8482,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
return parentStmt;
}
- impure->sta_state = 0;
+ state->sta_state = 0;
RLCK_reserve_relation(tdbb, transaction, relation->getPermanent(), true);
if (orgRpb->rpb_runtime_flags & RPB_just_deleted)
@@ -8449,7 +8548,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg
if (mapView)
{
- impure->sta_state = 1;
+ state->sta_state = 1;
return mapView;
}
@@ -9283,17 +9382,18 @@ StoreNode* StoreNode::pass2(thread_db* tdbb, CompilerScratch* csb)
ExprNode::doPass2(tdbb, csb, i->value.getAddress());
}
- impureOffset = csb->allocImpure();
+ impureOffset = csb->allocImpure();
return this;
}
const StmtNode* StoreNode::execute(thread_db* tdbb, Request* request, ExeState* exeState) const
{
- impure_state* impure = request->getImpure(impureOffset);
+ Impure* impure = request->getImpure(impureOffset);
+ impure_state* state = &impure->state;
const StmtNode* retNode;
- if (request->req_operation == Request::req_return && !impure->sta_state && subStore)
+ if (request->req_operation == Request::req_return && !state->sta_state && subStore)
{
if (!exeState->topNode)
{
@@ -9335,7 +9435,8 @@ const StmtNode* StoreNode::execute(thread_db* tdbb, Request* request, ExeState*
const StmtNode* StoreNode::store(thread_db* tdbb, Request* request, WhichTrigger whichTrig) const
{
jrd_tra* transaction = request->req_transaction;
- impure_state* impure = request->getImpure(impureOffset);
+ Impure* impure = request->getImpure(impureOffset);
+ impure_state* state = &impure->state;
const StreamType stream = target->getStream();
record_param* rpb = &request->req_rpb[stream];
@@ -9361,13 +9462,13 @@ const StmtNode* StoreNode::store(thread_db* tdbb, Request* request, WhichTrigger
request->req_records_affected.bumpModified(false);
}
- impure->sta_state = 0;
+ state->sta_state = 0;
if (relation)
RLCK_reserve_relation(tdbb, transaction, relation->getPermanent(), true);
break;
case Request::req_return:
- if (!impure->sta_state)
+ if (!state->sta_state)
{
SavepointChangeMarker scMarker(transaction);
@@ -9395,6 +9496,15 @@ const StmtNode* StoreNode::store(thread_db* tdbb, Request* request, WhichTrigger
extFile->store(tdbb, rpb);
else if (relation->isVirtual())
VirtualTable::store(tdbb, rpb);
+ else if (auto* foreignAdapter = relation->getForeignAdapter())
+ {
+ if (!impure->statement)
+ {
+ impure->statement = foreignAdapter->createStatement(tdbb, NULL, rpb);
+ impure->statement->bindToRequest(request, &impure->statement);
+ }
+ foreignAdapter->execute(tdbb, impure->statement, NULL, rpb);
+ }
else if (!relation->isView())
{
VIO_store(tdbb, rpb, transaction);
@@ -9424,7 +9534,7 @@ const StmtNode* StoreNode::store(thread_db* tdbb, Request* request, WhichTrigger
if (statement2)
{
- impure->sta_state = 1;
+ state->sta_state = 1;
request->req_operation = Request::req_evaluate;
return statement2;
}
diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h
index 901224784ff..9dda91e794c 100644
--- a/src/dsql/StmtNodes.h
+++ b/src/dsql/StmtNodes.h
@@ -516,6 +516,13 @@ class DeclareVariableNode final : public TypedNode
{
+private:
+ struct Impure
+ {
+ impure_state state;
+ EDS::Statement* statement;
+ };
+
public:
explicit EraseNode(MemoryPool& pool)
: TypedNode(pool),
@@ -647,7 +654,8 @@ class ExecStatementNode final : public TypedNode
class ModifyNode final : public TypedNode
{
+private:
+ struct Impure
+ {
+ impure_state state;
+ EDS::Statement* statement;
+ };
+
public:
explicit ModifyNode(MemoryPool& pool)
: TypedNode(pool),
@@ -1342,6 +1358,13 @@ class ReceiveNode final : public TypedNode
class StoreNode final : public TypedNode
{
+private:
+ struct Impure
+ {
+ impure_state state;
+ EDS::Statement* statement;
+ };
+
public:
explicit StoreNode(MemoryPool& pool)
: TypedNode(pool),
diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp
index 2beb3594acb..52bdde364ef 100644
--- a/src/dsql/dsql.cpp
+++ b/src/dsql/dsql.cpp
@@ -1357,7 +1357,8 @@ dsql_rel::dsql_rel(MemoryPool& p, jrd_rel* jrel)
rel_owner(p, jrel->getOwnerName()),
rel_id(jrel->getId()),
rel_dbkey_length(jrel->rel_dbkey_length),
- rel_flags((jrel->getExtFile() ? REL_external : 0) | (jrel->isView() ? REL_view : 0))
+ rel_flags((jrel->getExtFile() ? REL_external : 0) | (jrel->isView() ? REL_view : 0) |
+ (jrel->getForeignAdapter() ? REL_foreign : 0))
{
if (!(jrel->rel_fields))
return;
diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h
index c81cf09dff2..81b31cb49c8 100644
--- a/src/dsql/dsql.h
+++ b/src/dsql/dsql.h
@@ -168,7 +168,8 @@ enum rel_flags_vals {
REL_view = 4, // relation is a view
REL_external = 8, // relation is an external table
REL_creating = 16, // we are creating the bare relation in memory
- REL_ltt_created = 32 // relation is created local temporary table
+ REL_ltt_created = 32, // relation is created local temporary table
+ REL_foreign = 64 // relation is a foreign table
};
class TypeClause
diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt
index 661bcbe76e8..3e64e66f973 100644
--- a/src/dsql/parse-conflicts.txt
+++ b/src/dsql/parse-conflicts.txt
@@ -1 +1 @@
-137 shift/reduce conflicts, 7 reduce/reduce conflicts.
+149 shift/reduce conflicts, 7 reduce/reduce conflicts.
diff --git a/src/dsql/parse.y b/src/dsql/parse.y
index e9318864fdf..5be28779f7a 100644
--- a/src/dsql/parse.y
+++ b/src/dsql/parse.y
@@ -723,6 +723,10 @@ using namespace Firebird;
%token TRUNCATE
%token UNLIST
%token WITHIN
+%token SERVER
+%token OPTIONS
+%token ENV
+%token WRAPPER
// precedence declarations for expression evaluation
@@ -844,6 +848,7 @@ using namespace Firebird;
Jrd::RelationNode::RefActionClause* refActionClause;
Jrd::RelationNode::IndexConstraintClause* indexConstraintClause;
Jrd::RelationNode::IdentityOptions* identityOptions;
+ Jrd::RelationNode::AlterForeignColumnClause* alterForeignColumnClause;
IdentityType identityType;
Jrd::CreateRelationNode* createRelationNode;
Jrd::CreateAlterViewNode* createAlterViewNode;
@@ -883,6 +888,9 @@ using namespace Firebird;
Jrd::SetBindNode* setBindNode;
Jrd::SessionResetNode* sessionResetNode;
Jrd::ForRangeNode::Direction forRangeDirection;
+ Jrd::CreateAlterForeignServerNode* createAlterForeignServerNode;
+ Jrd::CreateAlterUserMappingNode* createAlterUserMappingNode;
+ ExternalValueType extValueType;
}
%include types.y
@@ -1037,6 +1045,13 @@ grant0($node)
$node->grantAdminOption = $7;
$node->grantor = $8;
}
+ | usage_privilege(NOTRIAL(&$node->privileges)) ON FOREIGN SERVER symbol_foreign_server_name
+ TO non_role_grantee_list(NOTRIAL(&$node->users)) grant_option granted_by
+ {
+ $node->object = newNode(obj_foreign_server, QualifiedName(*$5));
+ $node->grantAdminOption = $8;
+ $node->grantor = $9;
+ }
/***
| usage_privilege(NOTRIAL(&$node->privileges)) ON DOMAIN symbol_domain_name
TO non_role_grantee_list(NOTRIAL(&$node->users)) grant_option granted_by
@@ -1121,6 +1136,8 @@ schemaless_object
{ $$ = newNode(obj_filters, QualifiedName(getDdlSecurityName(obj_filters))); }
| SCHEMA
{ $$ = newNode(obj_schemas, QualifiedName(getDdlSecurityName(obj_schemas))); }
+ | SERVER
+ { $$ = newNode(obj_foreign_servers, QualifiedName(getDdlSecurityName(obj_foreign_servers))); }
;
table_noise
@@ -1331,6 +1348,13 @@ revoke0($node)
$node->grantAdminOption = $1;
$node->grantor = $8;
}
+ | rev_grant_option usage_privilege(NOTRIAL(&$node->privileges)) ON FOREIGN SERVER symbol_foreign_server_name
+ FROM non_role_grantee_list(NOTRIAL(&$node->users)) granted_by
+ {
+ $node->object = newNode(obj_foreign_server, QualifiedName(*$6));
+ $node->grantAdminOption = $1;
+ $node->grantor = $9;
+ }
/***
| rev_grant_option usage_privilege(NOTRIAL(&$node->privileges)) ON DOMAIN symbol_domain_name
FROM non_role_grantee_list(NOTRIAL(&$node->users)) granted_by
@@ -1669,6 +1693,12 @@ create_clause
node->createIfNotExistsOnly = $4;
$$ = node;
}
+ | FOREIGN TABLE if_not_exists_opt foreign_table_clause
+ {
+ const auto node = $4;
+ node->createIfNotExistsOnly = $3;
+ $$ = node;
+ }
| TRIGGER if_not_exists_opt trigger_clause
{
const auto node = $3;
@@ -1755,6 +1785,13 @@ create_clause
node->createIfNotExistsOnly = $2;
$$ = node;
}
+ | SERVER if_not_exists_opt create_foreign_server_clause
+ {
+ const auto node = $3;
+ node->createIfNotExistsOnly = $2;
+ $$ = node;
+ }
+ | USER MAPPING FOR create_foreign_user_mapping_clause { $$ = $4; }
;
@@ -1775,6 +1812,8 @@ recreate_clause
{ $$ = newNode($4); }
| LOCAL TEMPORARY TABLE ltt_table_clause
{ $$ = newNode($4); }
+ | FOREIGN TABLE foreign_table_clause
+ { $$ = newNode($3); }
| VIEW view_clause
{ $$ = newNode($2); }
| TRIGGER trigger_clause
@@ -1793,6 +1832,8 @@ recreate_clause
{ $$ = newNode($2); }
| SCHEMA schema_clause
{ $$ = newNode($2); }
+ | SERVER create_foreign_server_clause
+ { $$ = newNode($2); }
;
%type create_or_alter
@@ -2471,6 +2512,23 @@ ltt_subclause_opt($createRelationNode)
| temp_table_rows_type($createRelationNode)
;
+%type foreign_table_clause
+foreign_table_clause
+ : simple_table_name
+ {
+ $$ = newNode($1);
+ }
+ '(' foreign_table_elements($2) ')' SERVER valid_symbol_name
+ {
+ $$ = $2;
+ $$->foreignServer = *$7;
+ }
+ create_foreign_table_fixed_list_opt($8)
+ {
+ $$ = $8;
+ }
+ ;
+
%type external_file
external_file
: /* nothing */ { $$ = NULL; }
@@ -2490,10 +2548,28 @@ table_element($createRelationNode)
| table_constraint_definition($createRelationNode)
;
+%type foreign_table_elements()
+foreign_table_elements($createRelationNode)
+ : foreign_table_element($createRelationNode)
+ | foreign_table_elements ',' foreign_table_element($createRelationNode)
+ ;
+
+%type foreign_table_element()
+foreign_table_element($createRelationNode)
+ : basic_column_def($createRelationNode) create_foreign_column_fixed_list_opt($1)
+ | table_constraint_definition($createRelationNode)
+ ;
+
// column definition
%type column_def()
column_def($relationNode)
+ : basic_column_def($relationNode) { $$ = $1; }
+ | computed_column_def($relationNode) { $$ = $1; }
+ ;
+
+%type basic_column_def()
+basic_column_def($relationNode)
: symbol_column_name data_type_or_domain domain_default_opt
{
RelationNode::AddColumnClause* clause = $$ =
@@ -2522,7 +2598,11 @@ column_def($relationNode)
setCollate($2, $6);
$$ = $4;
}
- | symbol_column_name non_array_type def_computed
+ ;
+
+%type computed_column_def()
+computed_column_def($relationNode)
+ : symbol_column_name non_array_type def_computed
{
RelationNode::AddColumnClause* clause = newNode();
clause->field = $2;
@@ -2709,6 +2789,80 @@ column_constraint($addColumnClause)
}
;
+%type create_foreign_column_fixed_list_opt()
+create_foreign_column_fixed_list_opt($addColumnClause)
+ : // nothing
+ | create_foreign_column_fixed_list($addColumnClause)
+ ;
+
+%type create_foreign_column_fixed_list()
+create_foreign_column_fixed_list($addColumnClause)
+ : create_foreign_column_fixed_option($addColumnClause)
+ | create_foreign_column_fixed_list create_foreign_column_fixed_option($addColumnClause)
+ ;
+
+%type create_foreign_column_fixed_option()
+create_foreign_column_fixed_option($addColumnClause)
+ : OPTIONS '(' create_foreign_column_var_list($addColumnClause) ')'
+ ;
+
+%type create_foreign_column_var_list()
+create_foreign_column_var_list($addColumnClause)
+ : create_foreign_column_var_option($addColumnClause)
+ | create_foreign_column_var_list ',' create_foreign_column_var_option($addColumnClause)
+ ;
+
+%type create_foreign_column_var_option()
+create_foreign_column_var_option($addColumnClause)
+ : symbol_foreign_option_name '=' utf_string
+ {
+ $addColumnClause->addOption($1, $3);
+ }
+ | symbol_foreign_option_name utf_string
+ {
+ $addColumnClause->addOption($1, $2);
+ }
+ ;
+
+%type alter_foreign_column_fixed_list_opt()
+alter_foreign_column_fixed_list_opt($alterForeignColumnClause)
+ : // nothing
+ | alter_foreign_column_fixed_list($alterForeignColumnClause)
+ ;
+
+%type alter_foreign_column_fixed_list()
+alter_foreign_column_fixed_list($alterForeignColumnClause)
+ : alter_foreign_column_fixed_option($alterForeignColumnClause)
+ | alter_foreign_column_fixed_list alter_foreign_column_fixed_option($alterForeignColumnClause)
+ ;
+
+%type alter_foreign_column_fixed_option()
+alter_foreign_column_fixed_option($alterForeignColumnClause)
+ : OPTIONS '(' alter_foreign_column_var_list($alterForeignColumnClause) ')'
+ ;
+
+%type alter_foreign_column_var_list()
+alter_foreign_column_var_list($alterForeignColumnClause)
+ : alter_foreign_column_var_option($alterForeignColumnClause)
+ | alter_foreign_column_var_list ',' alter_foreign_column_var_option($alterForeignColumnClause)
+ ;
+
+%type alter_foreign_column_var_option()
+alter_foreign_column_var_option($alterForeignColumnClause)
+ : symbol_foreign_option_name '=' utf_string
+ {
+ $alterForeignColumnClause->addOption($1, $3);
+ }
+ | symbol_foreign_option_name utf_string
+ {
+ $alterForeignColumnClause->addOption($1, $2);
+ }
+ | DROP symbol_foreign_option_name
+ {
+ $alterForeignColumnClause->addOption($2);
+ }
+ ;
+
// table constraints
@@ -2845,6 +2999,72 @@ referential_action
| NO ACTION { $$ = RelationNode::RefActionClause::ACTION_NONE; }
;
+%type create_foreign_table_fixed_list_opt()
+create_foreign_table_fixed_list_opt($node)
+ : // nothing
+ | create_foreign_table_fixed_list($node)
+ ;
+
+%type create_foreign_table_fixed_list()
+create_foreign_table_fixed_list($node)
+ : create_foreign_table_fixed_option($node)
+ | create_foreign_table_fixed_list create_foreign_table_fixed_option($node)
+ ;
+
+%type create_foreign_table_fixed_option()
+create_foreign_table_fixed_option($node)
+ : OPTIONS '(' create_foreign_table_var_list($node) ')'
+ ;
+
+%type create_foreign_table_var_list()
+create_foreign_table_var_list($node)
+ : foreign_table_var_option($node)
+ | create_foreign_table_var_list ',' foreign_table_var_option($node)
+ ;
+
+%type foreign_table_var_option()
+foreign_table_var_option($node)
+ : symbol_foreign_option_name '=' utf_string
+ {
+ $node->addOption($1, $3);
+ }
+ | symbol_foreign_option_name utf_string
+ {
+ $node->addOption($1, $2);
+ }
+ ;
+
+%type alter_foreign_table_fixed_list_opt()
+alter_foreign_table_fixed_list_opt($node)
+ : // nothing
+ | alter_foreign_table_fixed_list($node)
+ ;
+
+%type alter_foreign_table_fixed_list()
+alter_foreign_table_fixed_list($node)
+ : alter_foreign_table_fixed_option($node)
+ | alter_foreign_table_fixed_list alter_foreign_table_fixed_option($node)
+ ;
+
+%type alter_foreign_table_fixed_option()
+alter_foreign_table_fixed_option($node)
+ : OPTIONS '(' alter_foreign_table_var_list($node) ')'
+ ;
+
+%type alter_foreign_table_var_list()
+alter_foreign_table_var_list($node)
+ : alter_foreign_table_var_option($node)
+ | alter_foreign_table_var_list ',' alter_foreign_table_var_option($node)
+ ;
+
+%type alter_foreign_table_var_option()
+alter_foreign_table_var_option($node)
+ : foreign_table_var_option($node)
+ | DROP symbol_foreign_option_name
+ {
+ $node->addOption($2);
+ }
+ ;
// PROCEDURE
@@ -3832,6 +4052,8 @@ exec_stmt_option($execStatementNode)
{ setClause($execStatementNode->dataSource, "EXTERNAL DATA SOURCE", $5); }
| ON EXTERNAL value
{ setClause($execStatementNode->dataSource, "EXTERNAL DATA SOURCE", $3); }
+ | ON EXTERNAL SERVER symbol_foreign_server_name
+ { setClause($execStatementNode->server, "SERVER", *$4); }
| AS USER value
{ setClause($execStatementNode->userName, "USER", $3); }
| PASSWORD value
@@ -4394,6 +4616,12 @@ 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 SERVER { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_CREATE_FOREIGN_SERVER); }
+ | ALTER SERVER { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_ALTER_FOREIGN_SERVER); }
+ | DROP SERVER { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_DROP_FOREIGN_SERVER); }
+ | CREATE USER MAPPING FOR { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_CREATE_USER_MAPPING); }
+ | ALTER USER MAPPING FOR { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_ALTER_USER_MAPPING); }
+ | DROP USER MAPPING FOR { $$ = TRIGGER_TYPE_DDL | (1LL << DDL_TRIGGER_DROP_USER_MAPPING); }
| trigger_ddl_type OR
trigger_ddl_type { $$ = $1 | $3; }
;
@@ -4444,6 +4672,10 @@ alter_clause
{ $$ = newNode($2); }
alter_ops($3)
{ $$ = $3; }
+ | FOREIGN TABLE simple_table_name
+ { $$ = newNode($3); }
+ foreign_alter_ops($4)
+ { $$ = $4; }
| VIEW alter_view_clause { $$ = $2; }
| TRIGGER alter_trigger_clause { $$ = $2; }
| PROCEDURE alter_procedure_clause { $$ = $2; }
@@ -4467,6 +4699,8 @@ alter_clause
| GLOBAL MAPPING alter_map_clause(true) { $$ = $3; }
| EXTERNAL CONNECTIONS POOL alter_eds_conn_pool_clause { $$ = $4; }
| SCHEMA alter_schema_clause { $$ = $2; }
+ | SERVER alter_foreign_server_clause { $$ = $2; }
+ | USER MAPPING FOR alter_foreign_user_mapping_clause { $$ = $4; }
;
%type alter_domain
@@ -4675,6 +4909,42 @@ alter_op($relationNode)
}
;
+%type foreign_alter_ops()
+foreign_alter_ops($relationNode)
+ : foreign_alter_op($relationNode)
+ | foreign_alter_ops ',' foreign_alter_op($relationNode)
+ ;
+
+%type foreign_alter_op()
+foreign_alter_op($relationNode)
+ : alter_op($relationNode)
+ {
+ $$ = NULL;
+ }
+ | alter_foreign_table_fixed_list_opt($relationNode)
+ {
+ RelationNode::Clause* clause =
+ newNode(RelationNode::Clause::TYPE_ALTER_OPTIONS);
+ $relationNode->clauses.add(clause);
+ }
+ | ADD if_not_exists_opt column_def($relationNode) create_foreign_column_fixed_list_opt($3)
+ {
+ const auto node = $3;
+ node->createIfNotExistsOnly = $2;
+ }
+ | col_opt symbol_column_name
+ {
+ RelationNode::AlterForeignColumnClause* clause = newNode();
+ clause->name = *$2;
+ $relationNode->clauses.add(clause);
+ $$ = clause;
+ }
+ alter_foreign_column_fixed_list_opt(NOTRIAL($3))
+ {
+ $$ = $3;
+ }
+ ;
+
%type alter_column_name
alter_column_name
: keyword_or_column
@@ -5091,6 +5361,12 @@ drop_clause
node->silent = $2;
$$ = node;
}
+ | FOREIGN TABLE if_exists_opt symbol_table_name
+ {
+ const auto node = newNode(*$4);
+ node->silent = $3;
+ $$ = node;
+ }
| TRIGGER if_exists_opt symbol_trigger_name
{
const auto node = newNode