From 2a2a991fe081777de4b8c5aaff73b25ef3b2bc8e Mon Sep 17 00:00:00 2001 From: hvlad Date: Mon, 3 Apr 2017 11:54:29 +0300 Subject: [PATCH] Merge branch 'master' into read_consistency --- .travis.yml | 34 +- README.md | 2 + .../linux/firebird-classic.socket.in | 1 + builds/install/misc/firebird.conf.in | 30 +- builds/make.new/config/.gitignore | 2 +- builds/posix/firebird.vers | 1 + builds/win32/defs/firebird.def | 1 + doc/README.session_idle_timeouts | 113 +++++ doc/README.statement_timeouts | 159 ++++++ doc/Using_OO_API.html | 36 +- .../README.identity_columns.txt | 30 +- examples/dbcrypt/CryptApplication.cpp | 6 + examples/dbcrypt/DbCrypt.cpp | 2 - examples/dbcrypt/ReadMe.txt | 41 +- lang_helpers/gds_codes.ftn | 24 + lang_helpers/gds_codes.pas | 24 + .../manage/SrpManagement.cpp | 13 +- src/auth/SecurityDatabase/LegacyServer.cpp | 1 + src/burp/mvol.cpp | 33 +- src/common/classes/MsgPrint.cpp | 4 +- src/common/classes/Nullable.h | 27 +- src/common/classes/init.cpp | 2 +- src/common/classes/locks.cpp | 118 ----- src/common/classes/locks.h | 22 +- src/common/common.h | 5 - src/common/config/config.cpp | 20 +- src/common/config/config.h | 9 +- src/common/file_params.h | 11 +- src/common/isc.cpp | 34 -- src/common/isc_file.cpp | 30 +- src/common/isc_proto.h | 4 - src/common/os/mod_loader.h | 8 - src/common/os/posix/mod_loader.cpp | 21 +- src/common/os/win32/mod_loader.cpp | 2 + src/dsql/DdlNodes.epp | 12 +- src/dsql/DdlNodes.h | 8 + src/dsql/ExprNodes.cpp | 191 +++++++- src/dsql/ExprNodes.h | 31 ++ src/dsql/Nodes.h | 23 +- src/dsql/Parser.cpp | 2 +- src/dsql/StmtNodes.cpp | 273 +++++++++-- src/dsql/StmtNodes.h | 11 + src/dsql/dsql.cpp | 147 +++++- src/dsql/dsql.h | 46 +- src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 186 +++++-- src/include/fb_api_proto.h | 6 + src/include/firebird/FirebirdInterface.idl | 15 + src/include/firebird/IdlFbInterfaces.h | 190 +++++++- src/include/gen/codetext.h | 12 + src/include/gen/iberror.h | 28 +- src/include/gen/ids.h | 5 + src/include/gen/msgs.h | 12 + src/include/gen/sql_code.h | 12 + src/include/gen/sql_state.h | 12 + src/isql/extract.epp | 4 +- src/isql/isql.epp | 44 +- src/isql/show.epp | 6 +- src/jrd/Attachment.cpp | 130 ++++- src/jrd/Attachment.h | 77 ++- src/jrd/CryptoManager.cpp | 13 +- src/jrd/EngineInterface.h | 10 +- src/jrd/Mapping.cpp | 5 +- src/jrd/Monitoring.cpp | 18 + src/jrd/Monitoring.h | 5 + src/jrd/Optimizer.cpp | 20 +- src/jrd/RecordSourceNodes.cpp | 17 +- src/jrd/RecordSourceNodes.h | 22 + src/jrd/Relation.h | 10 +- src/jrd/SysFunction.cpp | 6 + src/jrd/blp.h | 2 + src/jrd/blr.h | 3 + src/jrd/btr.cpp | 75 +-- src/jrd/build_no.h | 12 +- src/jrd/dfw.epp | 8 +- src/jrd/extds/ExtDS.cpp | 5 + src/jrd/extds/ExtDS.h | 2 + src/jrd/extds/InternalDS.cpp | 15 + src/jrd/extds/InternalDS.h | 1 + src/jrd/extds/IscDS.cpp | 59 ++- src/jrd/extds/IscDS.h | 5 + src/jrd/fields.h | 5 + src/jrd/filters.cpp | 134 +++++- src/jrd/ibase.h | 4 + src/jrd/inf.cpp | 24 +- src/jrd/inf_pub.h | 9 + src/jrd/jrd.cpp | 341 ++++++++++++- src/jrd/jrd.h | 141 +++++- src/jrd/lck.cpp | 37 ++ src/jrd/met.epp | 18 + src/jrd/met.h | 3 +- src/jrd/mov.cpp | 63 +++ src/jrd/mov_proto.h | 20 + src/jrd/names.h | 5 + src/jrd/os/win32/win9x_nt.h | 196 -------- src/jrd/os/win32/winnt.cpp | 12 +- src/jrd/recsrc/IndexTableScan.cpp | 39 +- src/jrd/relations.h | 5 + src/jrd/req.h | 3 + src/jrd/shut.cpp | 2 +- src/jrd/tra.cpp | 2 +- src/jrd/trace/TraceCmdLine.cpp | 89 ++-- src/jrd/trace/TraceManager.cpp | 9 +- src/jrd/trace/TraceManager.h | 10 + src/lock/print.cpp | 68 ++- src/misc/writeBuildNum.sh | 2 +- src/msgs/facilities2.sql | 6 +- src/msgs/messages2.sql | 13 + src/msgs/msg.sql | 2 +- src/msgs/system_errors2.sql | 12 + src/remote/SockAddr.h | 27 ++ src/remote/client/interface.cpp | 128 +++++ src/remote/inet.cpp | 5 +- src/remote/os/win32/wnet.cpp | 4 +- src/remote/os/win32/xnet.cpp | 4 +- src/remote/protocol.cpp | 5 + src/remote/protocol.h | 11 +- src/remote/remote.h | 3 +- src/remote/server/server.cpp | 37 +- src/utilities/fbtracemgr/traceMgrMain.cpp | 10 + src/utilities/gstat/dba.epp | 12 +- src/utilities/ntrace/TracePluginImpl.cpp | 189 +++++++- src/utilities/ntrace/TracePluginImpl.h | 12 +- src/utilities/ntrace/fbtrace.conf | 36 ++ src/utilities/ntrace/paramtable.h | 4 + src/yvalve/YObjects.h | 19 +- src/yvalve/gds.cpp | 35 +- src/yvalve/keywords.cpp | 3 + src/yvalve/why.cpp | 452 ++++++++++++------ src/yvalve/why_proto.h | 1 + travis.sh | 6 - 131 files changed, 3851 insertions(+), 1094 deletions(-) create mode 100644 doc/README.session_idle_timeouts create mode 100644 doc/README.statement_timeouts delete mode 100644 src/jrd/os/win32/win9x_nt.h mode change 100644 => 100755 travis.sh diff --git a/.travis.yml b/.travis.yml index c15c47278e..990531301c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,24 +11,24 @@ branches: - master install: - - wget --no-check-certificate https://www.cmake.org/files/v3.2/cmake-3.2.3-Linux-x86_64.sh - - sudo sh cmake-3.2.3-Linux-x86_64.sh --skip-license --prefix=/usr + # wget --no-check-certificate https://www.cmake.org/files/v3.2/cmake-3.2.3-Linux-x86_64.sh + # sudo sh cmake-3.2.3-Linux-x86_64.sh --skip-license --prefix=/usr - sudo apt-get update - sudo apt-get install -y libtommath0 libtommath-dev - - sudo python -m pip install Mako fdb - - svn co http://svn.code.sf.net/p/firebird/code/qa/fbtest/trunk/ fbtest - - svn co http://svn.code.sf.net/p/firebird/code/qa/fbt-repository/trunk/ fbtest/fbt + # sudo python -m pip install Mako fdb + # svn co http://svn.code.sf.net/p/firebird/code/qa/fbtest/trunk/ fbtest + # svn co http://svn.code.sf.net/p/firebird/code/qa/fbt-repository/trunk/ fbtest/fbt script: - - ./autogen.sh - - make -j2 - - sudo echo `pwd`/gen/Release/firebird/lib | sudo tee --append /etc/ld.so.conf - - sudo ldconfig - - sudo ./gen/Release/firebird/bin/gsec -add sysdba -pw masterkey - - chmod 777 travis.sh - - sudo ./travis.sh start_server & - - ./travis.sh dummy_output & - - ./travis.sh skip_tests - - sleep 3 - - cd fbtest/fbt - - python ../fbtest.py -d `pwd`/tmp -b `pwd`/../../gen/Release/firebird/bin -v -x -k ../../skip.txt + - ./autogen.sh --enable-binreloc --prefix=/opt/firebird + - make -j4 + - make dist + - tar xzvf gen/Firebird-[0-9]*.tar.gz + - (cd Firebird-[0-9]*; sudo ./install.sh -silent) + - sudo usermod -a -G firebird travis + # sg firebird -c "/opt/firebird/bin/gsec -user sysdba -add sysdba -pw masterkey" + # ./travis.sh dummy_output & + # ./travis.sh skip_tests + # sleep 3 + # cd fbtest/fbt + # sg firebird -c "python ../fbtest.py -d `pwd`/tmp -b /opt/firebird/bin -v -x -k ../../skip.txt" diff --git a/README.md b/README.md index 9ed5c1f652..766c7a87ea 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/FirebirdSQL/firebird.svg?branch=master)](https://travis-ci.org/FirebirdSQL/firebird) + # Firebird README Firebird is a relational database offering many ANSI SQL standard features that runs on Linux, Windows, MacOS and a variety of Unix platforms. Firebird offers excellent concurrency, high performance, and powerful language support for stored procedures and triggers. It has been used in production systems, under a variety of names, since 1981. diff --git a/builds/install/arch-specific/linux/firebird-classic.socket.in b/builds/install/arch-specific/linux/firebird-classic.socket.in index d44c0bea5c..6ce30741a5 100644 --- a/builds/install/arch-specific/linux/firebird-classic.socket.in +++ b/builds/install/arch-specific/linux/firebird-classic.socket.in @@ -5,6 +5,7 @@ Conflicts=firebird-superserver.service [Socket] ListenStream=3050 Accept=true +MaxConnections=2048 [Install] WantedBy=sockets.target diff --git a/builds/install/misc/firebird.conf.in b/builds/install/misc/firebird.conf.in index fe6bf1e878..0ec293e522 100644 --- a/builds/install/misc/firebird.conf.in +++ b/builds/install/misc/firebird.conf.in @@ -483,8 +483,8 @@ # database feature for encrypted database. In that case special care should be # taken to encrypt that key before passing it to server using callback. Make # sure your keys are well encrypted before enabling this parameter. Take into -# account that with CryptSecurityDatabase=TRUE unencrypted by firebird protocol -# key transfer may take place even with not encrypted security database. +# account that with AllowEncryptedSecurityDatabase=TRUE unencrypted by firebird +# protocol key transfer may take place even with not encrypted security database. # This feature is not supported by legacy authentication plugin - if you care # about security please never use legacy authentication. # @@ -496,7 +496,7 @@ # Please understand what are you doing before enabling this feature !!! # ######################################################################### # -#CryptSecurityDatabase = false +#AllowEncryptedSecurityDatabase = false # ---------------------------- # @@ -526,6 +526,30 @@ #DeadlockTimeout = 10 +# ---------------------------- +# +# Set number of seconds after which statement execution will be automatically +# cancelled by the engine. Zero means no timeout is set. +# +# Per-database configurable. +# +# Type: integer +# +#StatementTimeout = 0 + + +# ---------------------------- +# +# Set number of minutes after which idle attachment will be disconnected by the +# engine. Zero means no timeout is set. +# +# Per-database configurable. +# +# Type: integer +# +#ConnectionIdleTimeout = 0 + + # ---------------------------- # # How often the pages are flushed on disk diff --git a/builds/make.new/config/.gitignore b/builds/make.new/config/.gitignore index c6bb7d1535..a98ff4126b 100644 --- a/builds/make.new/config/.gitignore +++ b/builds/make.new/config/.gitignore @@ -1,4 +1,4 @@ config.guess -config.h.in +config.h.in* config.sub ltmain.sh diff --git a/builds/posix/firebird.vers b/builds/posix/firebird.vers index 8ae5b91b86..5137b6f55a 100644 --- a/builds/posix/firebird.vers +++ b/builds/posix/firebird.vers @@ -347,6 +347,7 @@ fb_get_database_handle fb_get_transaction_handle fb_database_crypt_callback +fb_dsql_set_timeout # Other misc functions diff --git a/builds/win32/defs/firebird.def b/builds/win32/defs/firebird.def index 23dee6ed83..be6aa32df5 100644 --- a/builds/win32/defs/firebird.def +++ b/builds/win32/defs/firebird.def @@ -222,6 +222,7 @@ EXPORTS isc_dsql_release @199 isc_dsql_set_cursor_name @200 isc_dsql_sql_info @201 + fb_dsql_set_timeout ; ESQL functions diff --git a/doc/README.session_idle_timeouts b/doc/README.session_idle_timeouts new file mode 100644 index 0000000000..fb3a990e87 --- /dev/null +++ b/doc/README.session_idle_timeouts @@ -0,0 +1,113 @@ + Timeouts for idle database sessions. + +Author: + Vlad Khorsun + + +Description: + + The feature allows to automatically close user connection after period of inactivity. +It could be used by database administrators to forcibly close old inactive connections +and free resources it occupies. Application and tools developers also could find it as +easy replacement of self-made control for the connection life time. + It is recommended (but not required) to set idle timeout to reasonable big value, such +as few hours. By default it is not enabled. + + The feature works as below +- when user API call leaves engine, special idle timer assotiated with current connection + is started +- when user API call enters engine, idle timer is stopped +- when idle time is fired engine immediately closes the connection in the same way as + with asyncronous connection cancellation: + - all active statements and cursors are closed + - all active transactions are rolled back + - network connection is not closed at this moment. It allows client application to get + exact error code on next API call. Network connection will be closed by the server + side after error is reported or due to network timeout if client side disconnects. +- idle session timeout could be set: + - at database level, by setting value in firebird.conf (or databases.conf) by database + administrator + scope - all user connections, except of system connections (garbage collector, cache + writer, etc) + units - minutes + - at connection level, using API and\or new SQL statement (see below) + scope - given connection + units - up to seconds +- effective value of idle timeout is evaluated every time user API call leaves the engine + as: + - if not set at connection level, look at database level + - in any case can't be greater than value set at database level + i.e. value of idle timeout could be overriden by application developer at given + connection but it can't relax limit set by DBA (in config) +- zero timeout means no timeout, i.e. idle timer will not start +- while idle timeout is set in seconds at API level, we can't promise absolute precision. + With high load it could be less precise. The only guarantee is that timeout will not + fire before specified moment. +- if connection was cancelled, next user API call returns error isc_att_shutdown with + secondary error code specifying exact reason: + isc_att_shut_killed: Killed by database administrator + isc_att_shut_idle: Idle timeout expired + isc_att_shut_db_down: Database is shutdown + isc_att_shut_engine: Engine is shutdown + + + Support at configuration level (firebird.conf and\or databases.conf) + + New setting "ConnectionIdleTimeout": set number of minutes after which idle connection +will be disconnected by the engine. Zero means no timeout is set. +Per-database configurable. Type: integer. Default value is 0. + + + Support at API level + +- get\set idle connection timeout, seconds +interface Attachment + uint getIdleTimeout(Status status); + void setIdleTimeout(Status status, uint timeOut); + +- get idle connection timeout at config and\or connection level is possible + using isc_database_info() API with new info tags: + - fb_info_ses_idle_timeout_db value set at config level + - fb_info_ses_idle_timeout_att value set at given connection level + - fb_info_ses_idle_timeout_run actual timeout value for given connection + evaluated considering values set at config and + connection levels, see "effective value of idle + timeout" above + +Remote client implementation notes: + - Attachment::setIdleTimeout() issued "SET SESSION IDLE TIMEOUT" SQL statement + - Attachment::getIdleTimeout() calls isc_database_info() with + fb_info_ses_idle_timeout_att tag + +If remote server doesn't support idle connection timeouts (protocol version less than 16): + - Attachment::setIdleTimeout() will return isc_wish_list error + - Attachment::getIdleTimeout() will return zero and set isc_wish_list error + - isc_database_info() will return isc_info_error tag in info buffer (as usual). + + + Support in SQL + +- New SQL statement allows to set idle connection timeout at connection level: + + SET SESSION IDLE TIMEOUT [HOUR | MINUTE | SECOND] + +if timepart is not set, default is MINUTE. +This statement could run outside of transaction control and immediately effective. + +- Context variables + + Context 'SYSTEM' have new variable: 'SESSION_IDLE_TIMEOUT'. It contains current value +of idle connection timeout that was set at connection level, or zero, if timeout was not +set. + +- Monitoring tables + + MON$ATTACHMENTS + MON$IDLE_TIMEOUT Connection level idle timeout + MON$IDLE_TIMER Idle timer expiration time + + MON$IDLE_TIMEOUT contains timeout value set at connection level, in seconds. Zero, if + timeout is not set. + + MON$IDLE_TIMER contains NULL value if idle timeout was not set or if timer is not + running. diff --git a/doc/README.statement_timeouts b/doc/README.statement_timeouts new file mode 100644 index 0000000000..56f980af31 --- /dev/null +++ b/doc/README.statement_timeouts @@ -0,0 +1,159 @@ + Timeouts for running SQL statements. + +Author: + Vlad Khorsun + + +Description: + + The feature allows to set timeout for SQL statement, i.e. it allows to automatically +stop execution of SQL statement when it running longer than given timeout value. + + The feature could be useful for: +- database administrators get instrument to limit heavy queries from consuming too + much resources +- application developers could use statement timeout when creating\debugging complex + queries with unknown in advance execution time +- testers could use statement timeout to detect long running queries and ensure finite + run time of the test suites +- and so on + + From the end user point of view feature have following details: +- when statement starts execution (or cursor is opened), engine starts special timer +- fetch doesn't reset timer +- timer is stopped when statement execution finished (or last record is fetched) +- when timer is fired + - if statement execution is active, it stops at closest possible moment + - if statement is not active currently (between fetches, for example), it is marked + as cancelled and next fetch will actually break execution and returns with error +- timeout value could be set: + - at database level, by setting value in firebird.conf (or databases.conf) by database + administrator + scope - all statements in all connections + units - seconds + - at connection level, using API and\or new SQL statement (see below) + scope - all statements at given connection + units - up to milliseconds + - at statement level, using API + scope - given statement + units - milliseconds +- effective value of timeout is evaluated every time statement starts execution + (or cursor is opened) as: + - if not set at statement level, look at connection level + - if not set at connection level, look at database level + - in any case can't be greater than value set at database level + i.e. value of statement timeout could be overriden by application developer at lower + scope but it can't relax limit set by DBA (in config) +- zero timeout means no timeout, i.e. timer will not start +- while statement timeout is set in milliseconds at API level, we can't promise + absolute precision. With big load it could be less precise. The only guarantee + is that timeout will not fire before specified moment. +- if statement execution is cancelled due to timeout, then API call returns error + isc_cancelled with secondary error code specifying exact reason: + - isc_cfg_stmt_timeout: Config level timeout expired + - isc_att_stmt_timeout: Attachment level timeout expired + - isc_req_stmt_timeout: Statement level timeout expired +- statement timeout is ignored for all internal queries issued by engine itself +- statement timeout is ignored for DDL statements +- client application could wait more time than set by timeout value if engine + need to undo many actions due to statement cancellation +- when engine run EXECUTE STATEMENT statement, it pass rest of currently active timeout + to the new statement. If external (remote) engine doesn't support statement timeouts, + local engine silently ignores corresponding error +- when engine acquires some lock of lock manager, it could lower value of lock timeout + using rest of the currently active statement timeout, if possible. Due to lock manager + internals rest of statement timeout will be rounded up to the whole seconds. + + + Support at configuration level (firebird.conf and\or databases.conf) + + New setting "StatementTimeout": set number of seconds after which statement execution +will be automatically cancelled by the engine. Zero means no timeout is set. +Per-database configurable. Type: integer. Default value is 0. + + + Support at API level + +- get\set statement execution timeout at connection level, milliseconds: +interface Attachment + uint getStatementTimeout(Status status); + void setStatementTimeout(Status status, uint timeOut); + +- get\set statement execution timeout at statement level, milliseconds: +interface Statement + uint getTimeout(Status status); + void setTimeout(Status status, uint timeOut); + +- set statement execution timeout at statement level using ISC API, milliseconds: + + ISC_STATUS ISC_EXPORT fb_dsql_set_timeout(ISC_STATUS*, isc_stmt_handle*, ISC_ULONG); + +- get statement execution timeout at config and\or connection level is possible + using isc_database_info() API with new info tags: + - fb_info_statement_timeout_db + - fb_info_statement_timeout_att + +- get statement execution timeout at statement level is possible using isc_dsql_info() + API with new info tags: + - isc_info_sql_stmt_timeout_user timeout value of given statement + - isc_info_sql_stmt_timeout_run actual timeout value of given statement + evaluated considering values set at config, connection and statement levels, see + "effective value of timeout" above. Valid only when timeout timer is running, i.e. + for currently executed statements. + +Remote client implementation notes: + - Attachment::setStatementTimeout() issued "SET STATEMENT TIMEOUT" SQL statement + - Attachment::getStatementTimeout() calls isc_database_info() with + fb_info_statement_timeout_att tag + - Statement::setTimeout() save timeout value given and pass it with op_execute + and op_execute2 packets + - Statement::getTimeout() returns saved timeout value + - fb_dsql_set_timeout() is a wrapper over Statement::setTimeout() + +If remote server doesn't support statement timeouts (protocol version less than 16): + - "set" functions will return isc_wish_list error + - "get" functions will return zero and set isc_wish_list error + - "info" functions will return isc_info_error tag in info buffer (as usual). + + + Support in SQL + +- New SQL statement allows to set set statement execution timeout at connection level: + + SET STATEMENT TIMEOUT [HOUR | MINUTE | SECOND | MILLISECOND] + +if timepart is not set, default is SECOND. +This statement could run outside of transaction control and immediately effective. + +- Context variables + + Context 'SYSTEM' have new variable: 'STATEMENT_TIMEOUT'. It contains current value of +statement execution timeout that was set at connection level, or zero, if timeout was +not set. + +- Monitoring tables + + MON$ATTACHMENTS + MON$STATEMENT_TIMEOUT Connection level statement timeout + + MON$STATEMENTS + MON$STATEMENT_TIMEOUT Statement level statement timeout + MON$STATEMENT_TIMER Timeout timer expiration time + + + MON$STATEMENT_TIMEOUT contains timeout values set at connection\statement level, + in milliseconds. Zero, if timeout is not set. + + MON$STATEMENT_TIMER contains NULL value if timeout was not set or if timer is not + running. + + + Support in ISQL tool + + New ISQL command is introduced: + + SET LOCAL_TIMEOUT + + It allows to set statement execution timeout (in milliseconds) for the next statement. +After statement execution it automatically reset to zero. + diff --git a/doc/Using_OO_API.html b/doc/Using_OO_API.html index ce36b2e009..2c01816baf 100644 --- a/doc/Using_OO_API.html +++ b/doc/Using_OO_API.html @@ -5,8 +5,10 @@ - - + + + + @@ -3161,8 +3163,7 @@ or using some own encryption of a key is also very good idea in case when network access to the server is used. All this job should be done in plugin (and application working with it) i.e. database block encryption algorithm by itself may happen to be easiest part of db -crypt plugin, specially -when some standard library is used for it.

+crypt plugin, specially when some standard library is used for it.


@@ -3180,6 +3181,19 @@ plugin or key holder plugin.


+

+

DbCryptInfo +interface is passed to DbCryptPlugin by engine. Plugin may save this +interface and use when needed to obtain additional informatio about +database.

+
    +
  1. +

    const + char* getDatabaseFullPath(StatusType* status) – returns full + (including path) name of primary database file.

    +
+


+

DbCryptPlugin interface is main interface of database crypt plugin.

@@ -3202,6 +3216,10 @@ interface is main interface of database crypt plugin.

void decrypt(StatusType* status, unsigned length, const void* from, void* to) – decrypts data after reading block from database file.

+
  • +

    void + setInfo(StatusType* status, IDbCryptInfo* info) – in this method + crypt plugin typically saves informational interface for future use.


    @@ -3251,11 +3269,11 @@ interface is main interface of database crypt key holder plugin.

    letting it to work with database.

  • ICryptKeyCallback* - chainHandle(StatusType* status) – support of a chain of key holders. In - some cases key has to pass through more than single key holder - before it reaches db crypt plugin. This is needed (for example) to - support execute statement in encrypted database. This is just a - sample – chains are also used in some other cases. Callback + chainHandle(StatusType* status) – support of a chain of key + holders. In some cases key has to pass through more than single key + holder before it reaches db crypt plugin. This is needed (for + example) to support execute statement in encrypted database. This is + just a sample – chains are also used in some other cases. Callback interface, returned by this method, may differ from one returned by keyHandle() function (see above). Typically is should be able to duplicate one-to-one keys, received by KeyHolderPlugin when diff --git a/doc/sql.extensions/README.identity_columns.txt b/doc/sql.extensions/README.identity_columns.txt index 5c5970ef8c..1f68eb1661 100644 --- a/doc/sql.extensions/README.identity_columns.txt +++ b/doc/sql.extensions/README.identity_columns.txt @@ -11,16 +11,20 @@ Description: Syntax: ::= - GENERATED BY DEFAULT AS IDENTITY [ ( ... ) ] + GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( ... ) ] ::= START WITH | INCREMENT [ BY ] ::= + [ ... ] | ... | DROP IDENTITY + ::= + SET GENERATED { ALWAYS | BY DEFAULT } + ::= RESTART [ WITH ] | SET INCREMENT [ BY ] @@ -39,10 +43,8 @@ Notes: Implementation: Two columns have been inserted in RDB$RELATION_FIELDS: RDB$GENERATOR_NAME and RDB$IDENTITY_TYPE. RDB$GENERATOR_NAME stores the automatically created generator for the column. In RDB$GENERATORS, - the value of RDB$SYSTEM_FLAG of that generator will be 6. RDB$IDENTITY_TYPE will currently - always store the value 1 (by default) for identity columns and NULL for non-identity columns. - In the future this column will store the value 0, too (for ALWAYS) when Firebird support this type - of identity column. + the value of RDB$SYSTEM_FLAG of that generator will be 6. RDB$IDENTITY_TYPE stores the value + 0 for GENERATED ALWAYS, 1 for GENERATED BY DEFAULT, and NULL for non-identity columns. Example: @@ -84,3 +86,21 @@ alter table objects alter table objects alter id drop identity; + + +--------------- +Override Clause +--------------- + +BY DEFAULT identity columns can be overriden in INSERT statements (INSERT, UPDATE OR INSERT, MERGE ... WHEN NOT MATCHED) +just specifying the value in the values list. However, for ALWAYS identity columns that is not allowed. + +To use the value passed in the INSERT statement for an ALWAYS column, you should pass OVERRIDING SYSTEM VALUE as +following: + +insert into objects (id, name) overriding system value values (11, 'Laptop'); + +OVERRIDING also supports a subclause to be used with BY DEFAULT columns, to ignore the value passed in INSERT and use +the defined sequence: + +insert into objects (id, name) overriding user value values (12, 'Laptop'); -- 12 is not used diff --git a/examples/dbcrypt/CryptApplication.cpp b/examples/dbcrypt/CryptApplication.cpp index 16fff91c83..ad6d385e65 100644 --- a/examples/dbcrypt/CryptApplication.cpp +++ b/examples/dbcrypt/CryptApplication.cpp @@ -81,6 +81,12 @@ public: } enum Action {NONE, ENC, DEC, EX_LCL, EX_RMT}; + // Switches/actions have the following meanings: + // ENC(-e) - encrypt database + // DEC(-d) - decrypt database + // EX_LCL(-l) - execute some predefined select command (demonstrates that database can respond to select request) + // EX_RMT(-r) - execute select using execute statement in remote datasource (demonstrates that dbcrypt key is + // passed to target database when using execute statement) void execute(const char* dbName, const Action a) { diff --git a/examples/dbcrypt/DbCrypt.cpp b/examples/dbcrypt/DbCrypt.cpp index a8cc249c44..ae20a28380 100644 --- a/examples/dbcrypt/DbCrypt.cpp +++ b/examples/dbcrypt/DbCrypt.cpp @@ -236,9 +236,7 @@ void DbCrypt::setKey(CheckStatusWrapper* status, unsigned int length, IKeyHolder return; if (callback && callback->callback(0, NULL, 1, &key) == 1) - { return; - } } key = 0; diff --git a/examples/dbcrypt/ReadMe.txt b/examples/dbcrypt/ReadMe.txt index c6c3deeade..90ad08f808 100644 --- a/examples/dbcrypt/ReadMe.txt +++ b/examples/dbcrypt/ReadMe.txt @@ -1,2 +1,39 @@ -All files in this directory are trivial samples. -They do not perform any real data encryption and should not be used in production! +************************************************************************************** +* All files in this directory are trivial samples. * +* They do not perform any real data encryption and should not be used in production! * +************************************************************************************** + +Brief description of the sample. + +Sample contains 3 components - DbCrypt plugin, KeyHolder plugin and application, which can pass +crypt key to server. Plugins do not perform any real encryption (XOR with single byte hardly can +be treated as encryption though makes database useless without crypt plugin), key is sent between +components in plain form - they just demonstrate what calls in plugins should be done and what +methods should be implemented in order for plugin to start to work. + +Depending upon settings in configuration file plugins may use different ways to manage encryption +key. DbCrypt's configuration file may contain following parameters: +Auto - boolean value, when FALSE plugin queries KeyHolder plugin for key value (this is default), + when TRUE get key value from "Value" configuration parameter. +Value - integer value (lower byte is actually used), used in "Auto" mode as key value (default 90). + +CryptKeyHolder's configuration file may contain following parameters: +Auto - boolean value, when FALSE plugin queries client application for key value (this is default), + when TRUE get key value from configuration file by name or use default (90) for unnamed key. +Key{Name} - integer value, a key with name "Name" (i.e. when one issues "ALTER DATABASE ENCRYPT ... + KEY Doggy" configuration parameter KeyDoggy should be present). +OnlyOwnKey - boolean value, enables/disables use of a key from another key holder in SuperServer. + Default value is TRUE (i.e. only key, owned by this KeyHolder, can be used by related + attachment). + +Crypt application has a few parameters making it possible to demonstrate different operations. +-e - Encrypt database (use gstat to monitor crypt progress). +-d - Decrypt database. +-l - Locally execute SELECT statement returning name of currently attached user. +-r - Execute same statement using remote datasource 'localhost:employee'. To make it work + user "test" with password "test" should be created in employee database. If employee was + encrypted in advance this demonstrates passing database crypt key through the chain of + key holders. + +cryptDb.pas is a minimum (XOR using fixed key hardcoded in plugin body) sample of database crypt +plugin written on Pascal. Was tested with both FreePascal and Delphi. diff --git a/lang_helpers/gds_codes.ftn b/lang_helpers/gds_codes.ftn index b7419617f1..ca039252b0 100644 --- a/lang_helpers/gds_codes.ftn +++ b/lang_helpers/gds_codes.ftn @@ -1664,6 +1664,28 @@ C -- PARAMETER (GDS__dsql_window_duplicate = 335545125) INTEGER*4 GDS__sql_too_long PARAMETER (GDS__sql_too_long = 335545126) + INTEGER*4 GDS__cfg_stmt_timeout + PARAMETER (GDS__cfg_stmt_timeout = 335545127) + INTEGER*4 GDS__att_stmt_timeout + PARAMETER (GDS__att_stmt_timeout = 335545128) + INTEGER*4 GDS__req_stmt_timeout + PARAMETER (GDS__req_stmt_timeout = 335545129) + INTEGER*4 GDS__att_shut_killed + PARAMETER (GDS__att_shut_killed = 335545130) + INTEGER*4 GDS__att_shut_idle + PARAMETER (GDS__att_shut_idle = 335545131) + INTEGER*4 GDS__att_shut_db_down + PARAMETER (GDS__att_shut_db_down = 335545132) + INTEGER*4 GDS__att_shut_engine + PARAMETER (GDS__att_shut_engine = 335545133) + INTEGER*4 GDS__overriding_without_identity + PARAMETER (GDS__overriding_without_identity = 335545134) + INTEGER*4 GDS__overriding_system_invalid + PARAMETER (GDS__overriding_system_invalid = 335545135) + INTEGER*4 GDS__overriding_user_invalid + PARAMETER (GDS__overriding_user_invalid = 335545136) + INTEGER*4 GDS__overriding_system_missing + PARAMETER (GDS__overriding_system_missing = 335545137) INTEGER*4 GDS__gfix_db_name PARAMETER (GDS__gfix_db_name = 335740929) INTEGER*4 GDS__gfix_invalid_sw @@ -1796,6 +1818,8 @@ C -- PARAMETER (GDS__dsql_wrong_param_num = 336003111) INTEGER*4 GDS__dsql_invalid_drop_ss_clause PARAMETER (GDS__dsql_invalid_drop_ss_clause = 336003112) + INTEGER*4 GDS__upd_ins_cannot_default + PARAMETER (GDS__upd_ins_cannot_default = 336003113) INTEGER*4 GDS__dyn_filter_not_found PARAMETER (GDS__dyn_filter_not_found = 336068645) INTEGER*4 GDS__dyn_func_not_found diff --git a/lang_helpers/gds_codes.pas b/lang_helpers/gds_codes.pas index f428092767..24ffb26fc9 100644 --- a/lang_helpers/gds_codes.pas +++ b/lang_helpers/gds_codes.pas @@ -1659,6 +1659,28 @@ const gds_dsql_window_duplicate = 335545125; isc_sql_too_long = 335545126; gds_sql_too_long = 335545126; + isc_cfg_stmt_timeout = 335545127; + gds_cfg_stmt_timeout = 335545127; + isc_att_stmt_timeout = 335545128; + gds_att_stmt_timeout = 335545128; + isc_req_stmt_timeout = 335545129; + gds_req_stmt_timeout = 335545129; + isc_att_shut_killed = 335545130; + gds_att_shut_killed = 335545130; + isc_att_shut_idle = 335545131; + gds_att_shut_idle = 335545131; + isc_att_shut_db_down = 335545132; + gds_att_shut_db_down = 335545132; + isc_att_shut_engine = 335545133; + gds_att_shut_engine = 335545133; + isc_overriding_without_identity = 335545134; + gds_overriding_without_identity = 335545134; + isc_overriding_system_invalid = 335545135; + gds_overriding_system_invalid = 335545135; + isc_overriding_user_invalid = 335545136; + gds_overriding_user_invalid = 335545136; + isc_overriding_system_missing = 335545137; + gds_overriding_system_missing = 335545137; isc_gfix_db_name = 335740929; gds_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; @@ -1791,6 +1813,8 @@ const gds_dsql_wrong_param_num = 336003111; isc_dsql_invalid_drop_ss_clause = 336003112; gds_dsql_invalid_drop_ss_clause = 336003112; + isc_upd_ins_cannot_default = 336003113; + gds_upd_ins_cannot_default = 336003113; isc_dyn_filter_not_found = 336068645; gds_dyn_filter_not_found = 336068645; isc_dyn_func_not_found = 336068649; diff --git a/src/auth/SecureRemotePassword/manage/SrpManagement.cpp b/src/auth/SecureRemotePassword/manage/SrpManagement.cpp index a79f0e2d32..5b68c6f5dc 100644 --- a/src/auth/SecureRemotePassword/manage/SrpManagement.cpp +++ b/src/auth/SecureRemotePassword/manage/SrpManagement.cpp @@ -539,12 +539,13 @@ public: case Firebird::IUser::OP_USER_DISPLAY: { - Firebird::string disp = "SELECT PLG$USER_NAME, PLG$FIRST, PLG$MIDDLE, PLG$LAST, PLG$COMMENT, PLG$ATTRIBUTES, " - " CASE WHEN RDB$RELATION_NAME IS NULL THEN FALSE ELSE TRUE END, PLG$ACTIVE " - "FROM PLG$SRP_VIEW LEFT JOIN RDB$USER_PRIVILEGES " - " ON PLG$SRP_VIEW.PLG$USER_NAME = RDB$USER_PRIVILEGES.RDB$USER " - " AND RDB$RELATION_NAME = '" ADMIN_ROLE "' " - " AND RDB$PRIVILEGE = 'M' "; + Firebird::string disp = + "WITH ADMINS AS (SELECT RDB$USER FROM RDB$USER_PRIVILEGES " + " WHERE RDB$RELATION_NAME = 'RDB$ADMIN' AND RDB$PRIVILEGE = 'M' GROUP BY RDB$USER) " + "SELECT PLG$USER_NAME, PLG$FIRST, PLG$MIDDLE, PLG$LAST, PLG$COMMENT, PLG$ATTRIBUTES, " + " CASE WHEN RDB$USER IS NULL THEN FALSE ELSE TRUE END, PLG$ACTIVE " + "FROM PLG$SRP_VIEW LEFT JOIN ADMINS " + " ON PLG$SRP_VIEW.PLG$USER_NAME = ADMINS.RDB$USER "; if (user->userName()->entered()) { disp += " WHERE PLG$USER_NAME = ?"; diff --git a/src/auth/SecurityDatabase/LegacyServer.cpp b/src/auth/SecurityDatabase/LegacyServer.cpp index 4898985b3f..1c996f1f92 100644 --- a/src/auth/SecurityDatabase/LegacyServer.cpp +++ b/src/auth/SecurityDatabase/LegacyServer.cpp @@ -353,6 +353,7 @@ int SecurityDatabase::verify(IWriter* authBlock, IServerBlock* sBlock) pw1[MAX_LEGACY_PASSWORD_LENGTH] = 0; string storedHash(pw1, MAX_LEGACY_PASSWORD_LENGTH); storedHash.rtrim(); + storedHash.recalculate_length(); string newHash; LegacyHash::hash(newHash, login, passwordEnc, storedHash); diff --git a/src/burp/mvol.cpp b/src/burp/mvol.cpp index adacb3d529..60c710f748 100644 --- a/src/burp/mvol.cpp +++ b/src/burp/mvol.cpp @@ -585,18 +585,33 @@ UCHAR MVOL_write(const UCHAR c, int* io_cnt, UCHAR** io_ptr) const size_t nBytesToWrite = size_t(longBytesToWrite); + bool error = false; + bool disk_full = false; + cnt = 0; #ifndef WIN_NT - cnt = write(tdgbl->file_desc, ptr, nBytesToWrite); -#else + ssize_t ret = write(tdgbl->file_desc, ptr, nBytesToWrite); - DWORD ret = 0; + if (ret == -1) + { + error = true; + + if (errno == ENOSPC || errno == EIO || errno == ENXIO || errno == EFBIG) + disk_full = true; + } + else + cnt = ret; +#else if (!WriteFile(tdgbl->file_desc, ptr, (DWORD) nBytesToWrite, &cnt, NULL)) { - ret = GetLastError(); + error = true; + DWORD ret = GetLastError(); + + if (ret == ERROR_DISK_FULL || ret == ERROR_HANDLE_DISK_FULL) + disk_full = true; } #endif // !WIN_NT tdgbl->mvol_io_buffer = tdgbl->mvol_io_data; - if (cnt > 0) + if (!error) { tdgbl->mvol_cumul_count += cnt; file_not_empty(); @@ -610,13 +625,7 @@ UCHAR MVOL_write(const UCHAR c, int* io_cnt, UCHAR** io_ptr) } else { - if (!cnt || -#ifndef WIN_NT - errno == ENOSPC || errno == EIO || errno == ENXIO || - errno == EFBIG) -#else - ret == ERROR_DISK_FULL || ret == ERROR_HANDLE_DISK_FULL) -#endif // !WIN_NT + if (disk_full) { if (tdgbl->action->act_action == ACT_backup_split) { diff --git a/src/common/classes/MsgPrint.cpp b/src/common/classes/MsgPrint.cpp index 2bb33471ec..66d6af97f6 100644 --- a/src/common/classes/MsgPrint.cpp +++ b/src/common/classes/MsgPrint.cpp @@ -359,9 +359,9 @@ int MsgPrintErr(const char* format, const SafeArg& arg, bool userFormatting) int fb_msg_format(void* handle, USHORT facility, USHORT number, unsigned int bsize, TEXT* buffer, const MsgFormat::SafeArg& arg) { - // The field MESSAGES.TEXT is 118 bytes long. + // The field MESSAGES.TEXT is 138 bytes long. int total_msg = 0; - char msg[120] = ""; + char msg[138 + 2] = ""; const int n = gds__msg_lookup(handle, facility, number, sizeof(msg), msg, NULL); if (n > 0 && unsigned(n) < sizeof(msg)) diff --git a/src/common/classes/Nullable.h b/src/common/classes/Nullable.h index 54352ec980..727caccc6a 100644 --- a/src/common/classes/Nullable.h +++ b/src/common/classes/Nullable.h @@ -35,7 +35,7 @@ class NullableClear public: static void clear(T& v) { - v = 0; + v = T(); } }; @@ -70,6 +70,11 @@ public: return (!specified && !o.specified) || (specified == o.specified && value == o.value); } + bool operator ==(const T& o) const + { + return specified && value == o; + } + void operator =(const T& v) { this->value = v; @@ -84,26 +89,6 @@ public: // NullableClear specializations. -template <> -class NullableClear // string especialization for NullableClear -{ -public: - static void clear(Firebird::string& v) - { - v = ""; - } -}; - -template <> -class NullableClear // MetaName especialization for NullableClear -{ -public: - static void clear(Firebird::MetaName& v) - { - v = ""; - } -}; - template class NullableClear > { diff --git a/src/common/classes/init.cpp b/src/common/classes/init.cpp index f5759cbd3e..7f67d05f3d 100644 --- a/src/common/classes/init.cpp +++ b/src/common/classes/init.cpp @@ -108,7 +108,7 @@ namespace { fprintf(stderr, "dladdr: %s\n", dlerror()); } -#elif defined(WIN_32) +#elif defined(WIN_NT) HMODULE hmod = 0; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR) &allClean, diff --git a/src/common/classes/locks.cpp b/src/common/classes/locks.cpp index 68b64d6953..daf607d6b1 100644 --- a/src/common/classes/locks.cpp +++ b/src/common/classes/locks.cpp @@ -39,124 +39,6 @@ namespace Firebird { #if defined(WIN_NT) -// Win9X support -#ifdef WIN9X_SUPPORT - -// NS: This code is adapted from from KernelEx project, with the explicit -// permission from the author. KernelEx project aims to provide Windows XP -// compatibility layer for Windows 98 and Windows ME. For further information -// please refer to http://www.sourceforge.net/projects/kernelex/ - -static const K32OBJ_CRITICAL_SECTION = 4; -static const TDBX_WIN98 = 0x50; -static const TDBX_WINME = 0x80; - -typedef struct _CRIT_SECT // Size = 0x20 -{ - BYTE Type; // 00 = 4: K32_OBJECT_CRITICAL_SECTION - int RecursionCount; // 04 initially 0, incremented on lock - void* OwningThread; // 08 pointer to TDBX - DWORD un3; // 0C - int LockCount; // 10 initially 1, decremented on lock - struct _CRIT_SECT* Next; // 14 - void* PLst; // 18 list of processes using it? - struct _WIN_CRITICAL_SECTION* UserCS; // 1C pointer to user defined CRITICAL_SECTION -} CRIT_SECT, *PCRIT_SECT; - -typedef struct _WIN_CRITICAL_SECTION -{ - BYTE Type; //= 4: K32_OBJECT_CRITICAL_SECTION - PCRIT_SECT crit; - DWORD un1; - DWORD un2; - DWORD un3; - DWORD un4; -} WIN_CRITICAL_SECTION, *PWIN_CRITICAL_SECTION; - -static DWORD tdbx_offset; - -__declspec(naked) BOOL WINAPI TryEnterCrst(CRIT_SECT* crit) -{ -__asm { - mov edx, [esp+4] - xor eax, eax - inc eax - xor ecx, ecx - cmpxchg [edx+10h], ecx ;if (OP1==eax) { OP1=OP2; ZF=1; } else { eax=OP1; ZF=0 } - ;mov ecx, ppTDBXCur - mov ecx, fs:[18h] - add ecx, [tdbx_offset] - mov ecx, [ecx] ;ecx will contain TDBX now - cmp eax, 1 - jnz L1 - ;critical section was unowned => successful lock - mov [edx+8], ecx - inc dword ptr [edx+4] - ret 4 -L1: - cmp [edx+8], ecx - jnz L2 - ;critical section owned by this thread - dec dword ptr [edx+10h] - inc dword ptr [edx+4] - xor eax, eax - inc eax - ret 4 -L2: - ;critical section owned by other thread - do nothing - xor eax, eax - ret 4 - } -} - -BOOL WINAPI TryEnterCriticalSection_Win9X(CRITICAL_SECTION* cs) -{ - WIN_CRITICAL_SECTION* mycs = (WIN_CRITICAL_SECTION*) cs; - if (mycs->Type != K32OBJ_CRITICAL_SECTION) - RaiseException(STATUS_ACCESS_VIOLATION, 0, 0, NULL); - - return TryEnterCrst(mycs->crit); -} - -#endif - -// On Win 98 and Win ME TryEnterCriticalSection is defined, but not implemented -// So direct linking to it won't hurt and will signal our incompatibility with Win 95 -TryEnterCS::tTryEnterCriticalSection TryEnterCS::m_funct = - reinterpret_cast(TryEnterCriticalSection); - -static TryEnterCS tryEnterCS; - -TryEnterCS::TryEnterCS() -{ -// Win9X support -#ifdef WIN9X_SUPPORT - OSVERSIONINFO OsVersionInfo; - - OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (GetVersionEx((LPOSVERSIONINFO) &OsVersionInfo)) - { - if (OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && - OsVersionInfo.dwMajorVersion == 4) - { - // Windows 98 - if (OsVersionInfo.dwMinorVersion == 10) - { - tdbx_offset = TDBX_WIN98; - m_funct = TryEnterCriticalSection_Win9X; - } - - // Windows ME - if (OsVersionInfo.dwMinorVersion == 90) - { - tdbx_offset = TDBX_WINME; - m_funct = TryEnterCriticalSection_Win9X; - } - } - } -#endif -} - void Spinlock::init() { SetCriticalSectionSpinCount(&spinlock, 4000); diff --git a/src/common/classes/locks.h b/src/common/classes/locks.h index 74bf4121c3..68a9be9d4b 100644 --- a/src/common/classes/locks.h +++ b/src/common/classes/locks.h @@ -55,22 +55,6 @@ class Exception; // Needed for catch // Windows version of the class -class TryEnterCS -{ -public: - TryEnterCS(); - - static bool tryEnter(LPCRITICAL_SECTION lpCS) - { - return ((m_funct) (lpCS) == TRUE); - } - -private: - typedef BOOL (WINAPI *tTryEnterCriticalSection)(LPCRITICAL_SECTION lpCriticalSection); - - static tTryEnterCriticalSection m_funct; -}; - class Mutex : public Reasons { protected: @@ -97,7 +81,7 @@ public: ~Mutex() { -#if defined DEV_BUILD && !defined WIN9X_SUPPORT +#if defined DEV_BUILD if (spinlock.OwningThread != 0) DebugBreak(); fb_assert(lockCount == 0); @@ -116,7 +100,7 @@ public: bool tryEnter(const char* aReason) { - const bool ret = TryEnterCS::tryEnter(&spinlock); + const bool ret = (TryEnterCriticalSection(&spinlock) == TRUE); if (ret) { reason(aReason); @@ -129,7 +113,7 @@ public: void leave() { -#if defined DEV_BUILD && !defined WIN9X_SUPPORT +#if defined DEV_BUILD // NS: This check is based on internal structure on CRITICAL_SECTION // On 9X it works differently, and future OS versions may break this check as well if ((U_IPTR) spinlock.OwningThread != GetCurrentThreadId()) diff --git a/src/common/common.h b/src/common/common.h index 655cd04d4e..0ee7a33842 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -551,11 +551,6 @@ extern "C" int remove(const char* path); #define INET_ERRNO WSAGetLastError() #define H_ERRNO WSAGetLastError() -// For Visual Studio 2003 and earlier enable Windows 9X support -#if defined _MSC_VER && (_MSC_VER < 1400) -#define WIN9X_SUPPORT -#endif - #endif /* WIN_NT */ diff --git a/src/common/config/config.cpp b/src/common/config/config.cpp index 5c227ed3db..0f9bbb4289 100644 --- a/src/common/config/config.cpp +++ b/src/common/config/config.cpp @@ -79,13 +79,15 @@ public: } } -/* It was a kind of getting ready for changing config remotely... + /*** + It was a kind of getting ready for changing config remotely... void changeDefaultConfig(Config* newConfig) { defaultConfig = newConfig; } - */ + ***/ + Firebird::RefPtr& getDefaultConfig() { return defaultConfig; @@ -207,7 +209,9 @@ const Config::ConfigEntry Config::entries[MAX_CONFIG_KEY] = {TYPE_BOOLEAN, "WireCompression", (ConfigValue) false}, {TYPE_INTEGER, "MaxIdentifierByteLength", (ConfigValue) -1}, {TYPE_INTEGER, "MaxIdentifierCharLength", (ConfigValue) -1}, - {TYPE_BOOLEAN, "CryptSecurityDatabase", (ConfigValue) false}, + {TYPE_BOOLEAN, "AllowEncryptedSecurityDatabase", (ConfigValue) false}, + {TYPE_INTEGER, "StatementTimeout", (ConfigValue) 0}, + {TYPE_INTEGER, "ConnectionIdleTimeout", (ConfigValue) 0}, {TYPE_INTEGER, "SnapshotsMemSize", (ConfigValue) 65536}, // bytes {TYPE_INTEGER, "TpcBlockSize", (ConfigValue) 4194304}, // bytes {TYPE_BOOLEAN, "ReadConsistency", (ConfigValue) true} @@ -852,6 +856,16 @@ bool Config::getCryptSecurityDatabase() const return get(KEY_ENCRYPT_SECURITY_DATABASE); } +unsigned int Config::getStatementTimeout() const +{ + return get(KEY_STMT_TIMEOUT); +} + +unsigned int Config::getConnIdleTimeout() const +{ + return get(KEY_CONN_IDLE_TIMEOUT); +} + bool Config::getReadConsistency() const { return get(KEY_READ_CONSISTENCY); diff --git a/src/common/config/config.h b/src/common/config/config.h index 7bc48d3d1e..8bee5115eb 100644 --- a/src/common/config/config.h +++ b/src/common/config/config.h @@ -143,6 +143,8 @@ public: KEY_MAX_IDENTIFIER_BYTE_LENGTH, KEY_MAX_IDENTIFIER_CHAR_LENGTH, KEY_ENCRYPT_SECURITY_DATABASE, + KEY_STMT_TIMEOUT, + KEY_CONN_IDLE_TIMEOUT, KEY_SNAPSHOTS_MEM_SIZE, KEY_TPC_BLOCK_SIZE, KEY_READ_CONSISTENCY, @@ -355,7 +357,12 @@ public: int getMaxIdentifierCharLength() const; bool getCryptSecurityDatabase() const; - + + // set in seconds + unsigned int getStatementTimeout() const; + // set in minutes + unsigned int getConnIdleTimeout() const; + ULONG getSnapshotsMemSize() const; ULONG getTpcBlockSize() const; diff --git a/src/common/file_params.h b/src/common/file_params.h index 42a56db191..3b3a97b325 100644 --- a/src/common/file_params.h +++ b/src/common/file_params.h @@ -32,13 +32,16 @@ #ifndef COMMON_FILE_PARAMS_H #define COMMON_FILE_PARAMS_H +#define COMMON_FILE_PREFIX "13" + static const char* const EVENT_FILE = "fb_event_%s"; static const char* const LOCK_FILE = "fb_lock_%s"; static const char* const MONITOR_FILE = "fb_monitor_%s"; -static const char* const TRACE_FILE = "fb13_trace"; -static const char* const TPC_HDR_FILE = "fb13_tpc_%s"; -static const char* const TPC_BLOCK_FILE = "fb13_tpc_%s_%d"; -static const char* const SNAPSHOTS_FILE = "fb13_snap_%s"; +static const char* const TRACE_FILE = "fb" COMMON_FILE_PREFIX "_trace"; +static const char* const USER_MAP_FILE = "fb" COMMON_FILE_PREFIX "_user_mapping"; +static const char* const TPC_HDR_FILE = "fb" COMMON_FILE_PREFIX "_tpc_%s"; +static const char* const TPC_BLOCK_FILE = "fb" COMMON_FILE_PREFIX "_tpc_%s_%d"; +static const char* const SNAPSHOTS_FILE = "fb" COMMON_FILE_PREFIX "_snap_%s"; #ifdef UNIX static const char* const INIT_FILE = "fb_init"; diff --git a/src/common/isc.cpp b/src/common/isc.cpp index 88489a0707..acf4c4de79 100644 --- a/src/common/isc.cpp +++ b/src/common/isc.cpp @@ -505,40 +505,6 @@ SLONG ISC_set_prefix(const TEXT* sw, const TEXT* path) } -#ifdef WIN9X_SUPPORT - -static DWORD os_type = 0; - -// Returns the type of OS: true for NT, -// false for the 16-bit based ones (9x/ME, ...). -// -bool ISC_is_WinNT() -{ - // NS: this is thread safe. - // In the worst case initialization will be called more than once - if (!os_type) - { - // The first time this routine is called we use the Windows API - // call GetVersion to determine whether Windows NT or 9X - // is running. - OSVERSIONINFO OsVersionInfo; - - OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (GetVersionEx((LPOSVERSIONINFO) &OsVersionInfo)) - { - os_type = OsVersionInfo.dwPlatformId; - fb_assert(os_type); - } - else { - os_type = VER_PLATFORM_WIN32_NT; // Default to NT - } - } - - return os_type >= VER_PLATFORM_WIN32_NT; // Windows NT, CE and future platforms -} -#endif - - #ifdef WIN_NT LPSECURITY_ATTRIBUTES ISC_get_security_desc() { diff --git a/src/common/isc_file.cpp b/src/common/isc_file.cpp index 6c65352414..00b9cd92aa 100644 --- a/src/common/isc_file.cpp +++ b/src/common/isc_file.cpp @@ -57,6 +57,7 @@ #include "../common/classes/Aligner.h" #include "../common/utils_proto.h" #include "../common/os/os_utils.h" +#include "../common/os/path_utils.h" #include #ifdef HAVE_SYS_IPC_H @@ -84,6 +85,9 @@ #ifdef HAVE_ICONV_H #include #endif +#ifdef LINUX +#include +#endif #include "../common/config/config.h" @@ -224,10 +228,34 @@ bool ISC_analyze_nfs(tstring& expanded_filename, tstring& node_name) // If we are ignoring NFS remote mounts then do not bother checking here // and pretend it's only local. MOD 16-Nov-2002 - if (Config::getRemoteFileOpenAbility()) { + if (Config::getRemoteFileOpenAbility()) + return false; + +#ifdef LINUX + // In order to avoid analyzing mtab in most cases check for non-device mounts first + struct stat fileStat; + unsigned m = 1; // use something that is known to be not non-device major + + if (os_utils::stat(expanded_filename.c_str(), &fileStat) == 0) + m = major(fileStat.st_dev); + else // stat error - let's try with path component + { + tstring path, name; + PathUtils::splitLastComponent(path, name, expanded_filename); + + if (path.hasData() && os_utils::stat(path.c_str(), &fileStat) == 0) + m = major(fileStat.st_dev); + } + + if (m != 0 && m != 144 && m != 145 && m != 146) + { + // device mount or stat for file/path is impossible - definitely not NFS return false; } + // proceed with deeper analysis +#endif + tstring max_node, max_path; size_t len = 0; diff --git a/src/common/isc_proto.h b/src/common/isc_proto.h index d683c7f9d5..f3f52cb19f 100644 --- a/src/common/isc_proto.h +++ b/src/common/isc_proto.h @@ -39,10 +39,6 @@ void iscLogStatus(const TEXT* text, const ISC_STATUS* status_vector); void iscLogStatus(const TEXT* text, const Firebird::IStatus* status); void iscLogException(const TEXT* text, const Firebird::Exception& e); -#ifdef WIN9X_SUPPORT -bool ISC_is_WinNT(); -#endif - #ifdef WIN_NT struct _SECURITY_ATTRIBUTES* ISC_get_security_desc(); #endif diff --git a/src/common/os/mod_loader.h b/src/common/os/mod_loader.h index b27d35630d..b57af4ac9f 100644 --- a/src/common/os/mod_loader.h +++ b/src/common/os/mod_loader.h @@ -70,23 +70,15 @@ public: /// Destructor virtual ~Module() {} -#ifdef WIN_NT const Firebird::PathName fileName; -#endif protected: /// The constructor is protected so normal code can't allocate instances /// of the class, but the class itself is still able to be subclassed. -#ifdef WIN_NT Module(MemoryPool& pool, const Firebird::PathName& aFileName) : fileName(pool, aFileName) { } -#else - Module() - { - } -#endif private: /// Copy construction is not supported, hence the copy constructor is private diff --git a/src/common/os/posix/mod_loader.cpp b/src/common/os/posix/mod_loader.cpp index 7693bc7188..670fdde6e9 100644 --- a/src/common/os/posix/mod_loader.cpp +++ b/src/common/os/posix/mod_loader.cpp @@ -28,6 +28,7 @@ #include "firebird.h" #include "../common/os/mod_loader.h" #include "../common/os/os_utils.h" +#include "../common/os/path_utils.h" #ifdef HAVE_UNISTD_H #include #endif @@ -40,8 +41,9 @@ class DlfcnModule : public ModuleLoader::Module { public: - DlfcnModule(void* m) - : module(m) + DlfcnModule(MemoryPool& pool, const Firebird::PathName& aFileName, void* m) + : ModuleLoader::Module(pool, aFileName), + module(m) {} ~DlfcnModule(); @@ -109,7 +111,7 @@ ModuleLoader::Module* ModuleLoader::loadModule(const Firebird::PathName& modPath system(command.c_str()); #endif - return FB_NEW_POOL(*getDefaultMemoryPool()) DlfcnModule(module); + return FB_NEW_POOL(*getDefaultMemoryPool()) DlfcnModule(*getDefaultMemoryPool(), modPath, module); } DlfcnModule::~DlfcnModule() @@ -127,6 +129,17 @@ void* DlfcnModule::findSymbol(const Firebird::string& symName) result = dlsym(module, newSym.c_str()); } + +#ifdef HAVE_DLADDR + if (!PathUtils::isRelative(fileName)) + { + Dl_info info; + if (!dladdr(result, &info)) + return NULL; + if (fileName != info.dli_fname) + return NULL; + } +#endif + return result; } - diff --git a/src/common/os/win32/mod_loader.cpp b/src/common/os/win32/mod_loader.cpp index 909de85026..bc599c6c37 100644 --- a/src/common/os/win32/mod_loader.cpp +++ b/src/common/os/win32/mod_loader.cpp @@ -103,6 +103,8 @@ public: "msvcr120.dll", #elif _MSC_VER == 1900 "vcruntime140.dll", +#elif _MSC_VER == 1910 + "vcruntime140.dll", #else #error Specify CRT DLL name here ! #endif diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index be233c6bb5..87d6a53476 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -5951,7 +5951,7 @@ void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction) strcpy(RFR.RDB$GENERATOR_NAME, identitySequence.c_str()); RFR.RDB$IDENTITY_TYPE.NULL = FALSE; - RFR.RDB$IDENTITY_TYPE = IDENT_TYPE_BY_DEFAULT; + RFR.RDB$IDENTITY_TYPE = identityType.value; } if (notNullFlag.specified) @@ -6296,6 +6296,7 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence); + fieldDefinition.identityType = clause->identityOptions->type; CreateAlterSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence, fb_sysflag_identity_generator, @@ -6881,6 +6882,8 @@ void RelationNode::defineSetDefaultTrigger(DsqlCompilerScratch* dsqlScratch, // DEFAULT and null flag) and another blr_domain_full with DEFAULT and null flag. We swallow the error when // transfering the value from the second to the first variable. + // This could be better done with blr_default, but let's maintain backward compatible BLR for now. + blrWriter.appendUChar(blr_dcl_variable); blrWriter.appendUShort(index); @@ -7856,6 +7859,13 @@ void AlterRelationNode::modifyField(thread_db* tdbb, DsqlCompilerScratch* dsqlSc transaction->getGenIdCache()->put(id, val); } + if (clause->identityOptions->type.specified) + { + MODIFY RFR + RFR.RDB$IDENTITY_TYPE = clause->identityOptions->type.value; + END_MODIFY + } + if (clause->identityOptions->increment.specified) { if (clause->identityOptions->increment.value == 0) diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index a46b7b6db7..6725b5ee8a 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -1140,6 +1140,7 @@ public: Firebird::MetaName relationName; Firebird::MetaName fieldSource; Firebird::MetaName identitySequence; + Nullable identityType; Nullable collationId; Nullable notNullFlag; // true = NOT NULL / false = NULL Nullable position; @@ -1306,11 +1307,18 @@ public: struct IdentityOptions { + IdentityOptions(MemoryPool&, IdentityType aType) + : type(aType), + restart(false) + { + } + IdentityOptions(MemoryPool&) : restart(false) { } + Nullable type; Nullable startValue; Nullable increment; bool restart; // used in ALTER diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 20552ed03e..7fe34cf928 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -4118,6 +4118,155 @@ dsc* DecodeNode::execute(thread_db* tdbb, jrd_req* request) const //-------------------- +static RegisterNode regDefaultNode(blr_default); + +DefaultNode::DefaultNode(MemoryPool& pool, const Firebird::MetaName& aRelationName, + const Firebird::MetaName& aFieldName) + : DsqlNode(pool), + relationName(aRelationName), + fieldName(aFieldName), + field(NULL) +{ +} + +DmlNode* DefaultNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/) +{ + MetaName relationName, fieldName; + csb->csb_blr_reader.getMetaName(relationName); + csb->csb_blr_reader.getMetaName(fieldName); + + CompilerScratch::Dependency dependency(obj_relation); + dependency.relation = MET_lookup_relation(tdbb, relationName); + dependency.subName = FB_NEW_POOL(pool) MetaName(fieldName); + csb->csb_dependencies.push(dependency); + + jrd_fld* fld = NULL; + + while (true) + { + jrd_rel* relation = MET_lookup_relation(tdbb, relationName); + + if (relation && relation->rel_fields) + { + int fieldId = MET_lookup_field(tdbb, relation, fieldName); + + if (fieldId >= 0) + { + fld = (*relation->rel_fields)[fieldId]; + + if (fld) + { + if (fld->fld_source_rel_field.first.hasData()) + { + relationName = fld->fld_source_rel_field.first; + fieldName = fld->fld_source_rel_field.second; + continue; + } + else + { + DefaultNode* node = FB_NEW_POOL(pool) DefaultNode(pool, relationName, fieldName); + node->field = fld; + return node; + } + } + } + } + + return FB_NEW_POOL(pool) NullNode(pool); + } +} + +ValueExprNode* DefaultNode::createFromField(thread_db* tdbb, CompilerScratch* csb, + StreamType* map, jrd_fld* fld) +{ + if (fld->fld_generator_name.hasData()) + { + // Make a (next value for ) expression. + + GenIdNode* const genNode = FB_NEW_POOL(csb->csb_pool) GenIdNode( + csb->csb_pool, (csb->blrVersion == 4), fld->fld_generator_name, NULL, true, true); + + bool sysGen = false; + if (!MET_load_generator(tdbb, genNode->generator, &sysGen, &genNode->step)) + PAR_error(csb, Arg::Gds(isc_gennotdef) << Arg::Str(fld->fld_generator_name)); + + if (sysGen) + PAR_error(csb, Arg::Gds(isc_cant_modify_sysobj) << "generator" << fld->fld_generator_name); + + return genNode; + } + else if (fld->fld_default_value) + { + StreamMap localMap; + if (!map) + map = localMap.getBuffer(STREAM_MAP_LENGTH); + + return NodeCopier(csb, map).copy(tdbb, fld->fld_default_value); + } + else + return FB_NEW_POOL(csb->csb_pool) NullNode(csb->csb_pool); +} + +string DefaultNode::internalPrint(NodePrinter& printer) const +{ + ValueExprNode::internalPrint(printer); + + NODE_PRINT(printer, relationName); + NODE_PRINT(printer, fieldName); + + return "DefaultNode"; +} + +ValueExprNode* DefaultNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) +{ + DefaultNode* node = FB_NEW_POOL(getPool()) DefaultNode(getPool(), + relationName, fieldName); + return node; +} + +void DefaultNode::setParameterName(dsql_par* parameter) const +{ + parameter->par_name = parameter->par_alias = "DEFAULT"; +} + +bool DefaultNode::setParameterType(DsqlCompilerScratch* /*dsqlScratch*/, const dsc* /*desc*/, bool /*forceVarChar*/) +{ + return false; +} + +void DefaultNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + dsqlScratch->appendUChar(blr_default); + dsqlScratch->appendMetaString(relationName.c_str()); + dsqlScratch->appendMetaString(fieldName.c_str()); +} + +void DefaultNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* /*desc*/) +{ +} + +bool DefaultNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const +{ + if (!ExprNode::dsqlMatch(other, ignoreMapCast)) + return false; + + const DefaultNode* o = other->as(); + fb_assert(o); + + return relationName == o->relationName && fieldName == o->fieldName; +} + +ValueExprNode* DefaultNode::pass1(thread_db* tdbb, CompilerScratch* csb) +{ + ValueExprNode* node = createFromField(tdbb, csb, NULL, field); + doPass1(tdbb, csb, &node); + return node; +} + + +//-------------------- + + static RegisterNode regDerivedExprNode(blr_derived_expr); DmlNode* DerivedExprNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/) @@ -10133,17 +10282,6 @@ void SubstringNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) desc->dsc_flags |= DSC_null; else { - if (offsetNode->is() && desc1.dsc_dtype == dtype_long) - { - SLONG offset = MOV_get_long(&desc1, 0); - - if (decrementNode && decrementNode->is() && desc3.dsc_dtype == dtype_long) - offset -= MOV_get_long(&desc3, 0); - - if (offset < 0) - ERR_post(Arg::Gds(isc_bad_substring_offset) << Arg::Num(offset + 1)); - } - if (length->is() && desc2.dsc_dtype == dtype_long) { const SLONG len = MOV_get_long(&desc2, 0); @@ -10200,20 +10338,24 @@ dsc* SubstringNode::execute(thread_db* tdbb, jrd_req* request) const dsc* SubstringNode::perform(thread_db* tdbb, impure_value* impure, const dsc* valueDsc, const dsc* startDsc, const dsc* lengthDsc) { - const SLONG sStart = MOV_get_long(startDsc, 0); - const SLONG sLength = MOV_get_long(lengthDsc, 0); + SINT64 sStart = MOV_get_long(startDsc, 0); + SINT64 sLength = MOV_get_long(lengthDsc, 0); + + if (sLength < 0) + status_exception::raise(Arg::Gds(isc_bad_substring_length) << Arg::Num(sLength)); if (sStart < 0) - status_exception::raise(Arg::Gds(isc_bad_substring_offset) << Arg::Num(sStart + 1)); - else if (sLength < 0) - status_exception::raise(Arg::Gds(isc_bad_substring_length) << Arg::Num(sLength)); + { + sLength = MAX(sLength + sStart, 0); + sStart = 0; + } + + FB_UINT64 start = FB_UINT64(sStart); + FB_UINT64 length = FB_UINT64(sLength); dsc desc; DataTypeUtil(tdbb).makeSubstr(&desc, valueDsc, startDsc, lengthDsc); - ULONG start = (ULONG) sStart; - ULONG length = (ULONG) sLength; - if (desc.isText() && length > MAX_STR_SIZE) length = MAX_STR_SIZE; @@ -10235,8 +10377,8 @@ dsc* SubstringNode::perform(thread_db* tdbb, impure_value* impure, const dsc* va HalfStaticArray buffer; CharSet* charSet = INTL_charset_lookup(tdbb, valueDsc->getCharSet()); - const FB_UINT64 byte_offset = FB_UINT64(start) * charSet->maxBytesPerChar(); - const FB_UINT64 byte_length = FB_UINT64(length) * charSet->maxBytesPerChar(); + const FB_UINT64 byte_offset = start * charSet->maxBytesPerChar(); + const FB_UINT64 byte_length = length * charSet->maxBytesPerChar(); if (charSet->isMultiByte()) { @@ -11134,7 +11276,8 @@ UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, ValueList name(pool, aName), args(aArgs), function(NULL), - dsqlFunction(NULL) + dsqlFunction(NULL), + isSubRoutine(false) { addChildNode(args, args); } @@ -11197,6 +11340,8 @@ DmlNode* UdfCallNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString())); } + node->isSubRoutine = function->isSubRoutine(); + const UCHAR argCount = csb->csb_blr_reader.getByte(); // Check to see if the argument count matches. @@ -11289,7 +11434,7 @@ ValueExprNode* UdfCallNode::copy(thread_db* tdbb, NodeCopier& copier) const { UdfCallNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) UdfCallNode(*tdbb->getDefaultPool(), name); node->args = copier.copy(tdbb, args); - node->function = function; + node->function = isSubRoutine ? function : Function::lookup(tdbb, name, false); return node; } diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index a9d475ac96..3eea3b08f3 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -476,6 +476,36 @@ public: }; +class DefaultNode : public DsqlNode +{ +public: + explicit DefaultNode(MemoryPool& pool, const Firebird::MetaName& aRelationName, + const Firebird::MetaName& aFieldName); + + static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); + static ValueExprNode* createFromField(thread_db* tdbb, CompilerScratch* csb, StreamType* map, jrd_fld* fld); + + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual ValueExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); + virtual void setParameterName(dsql_par* parameter) const; + virtual bool setParameterType(DsqlCompilerScratch* dsqlScratch, + const dsc* desc, bool forceVarChar); + virtual void genBlr(DsqlCompilerScratch* dsqlScratch); + virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc); + + virtual bool dsqlMatch(const ExprNode* other, bool ignoreMapCast) const; + + virtual ValueExprNode* pass1(thread_db* tdbb, CompilerScratch* csb); + +public: + const Firebird::MetaName relationName; + const Firebird::MetaName fieldName; + +private: + jrd_fld* field; +}; + + class DerivedExprNode : public TypedNode { public: @@ -1720,6 +1750,7 @@ public: private: dsql_udf* dsqlFunction; + bool isSubRoutine; }; diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index bc74526fad..25380047d7 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -281,6 +281,24 @@ public: }; +class SetSessionNode : public Node +{ +public: + enum Type { TYPE_IDLE_TIMEOUT, TYPE_STMT_TIMEOUT }; + + SetSessionNode(MemoryPool& pool, Type aType, ULONG aVal, UCHAR blr_timepart); + +public: + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual SetSessionNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); + virtual void execute(thread_db* tdbb, dsql_req* request) const; + +private: + Type m_type; + ULONG m_value; +}; + + class DmlNode : public Node { public: @@ -408,6 +426,7 @@ public: TYPE_CURRENT_USER, TYPE_DERIVED_EXPR, TYPE_DECODE, + TYPE_DEFAULT, TYPE_DERIVED_FIELD, TYPE_DOMAIN_VALIDATION, TYPE_EXTRACT, @@ -853,13 +872,13 @@ public: fb_assert(false); } - virtual DsqlNode* pass1(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) + virtual ValueExprNode* pass1(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) { fb_assert(false); return NULL; } - virtual DsqlNode* pass2(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) + virtual ValueExprNode* pass2(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) { fb_assert(false); return NULL; diff --git a/src/dsql/Parser.cpp b/src/dsql/Parser.cpp index ceeb0a30db..c650aa5a02 100644 --- a/src/dsql/Parser.cpp +++ b/src/dsql/Parser.cpp @@ -97,7 +97,7 @@ Parser::Parser(thread_db* tdbb, MemoryPool& pool, DsqlCompilerScratch* aScratch, yylexemes = 0; lex.start = string; - lex.line_start = lex.ptr = string; + lex.line_start = lex.last_token = lex.ptr = string; lex.end = string + length; lex.lines = 1; lex.att_charset = characterSet; diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 35bf9f5de6..c9eaac8ff6 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -94,6 +94,8 @@ static void postTriggerAccess(CompilerScratch* csb, jrd_rel* ownerRelation, ExternalAccess::exa_act operation, jrd_rel* view); static void preModifyEraseTriggers(thread_db* tdbb, TrigVector** trigs, StmtNode::WhichTrigger whichTrig, record_param* rpb, record_param* rec, TriggerAction op); +static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb, + StreamType stream, CompoundStmtNode* compoundNode, const Nullable* insertOverride); static void validateExpressions(thread_db* tdbb, const Array& validations); } // namespace Jrd @@ -3448,6 +3450,10 @@ const StmtNode* ExecStatementNode::execute(thread_db* tdbb, jrd_req* request, Ex const MetaName* const* inpNames = inputNames ? inputNames->begin() : NULL; stmt->prepare(tdbb, tran, sSql, inputNames != NULL); + const TimeoutTimer* timer = tdbb->getTimeoutTimer(); + if (timer) + stmt->setTimeout(tdbb, timer->timeToExpire()); + if (stmt->isSelectable()) stmt->open(tdbb, tran, inpNames, inputs, !innerStmt); else @@ -5336,6 +5342,7 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) store->dsqlRelation = relation; store->dsqlFields = notMatched->fields; store->dsqlValues = notMatched->values; + store->overrideClause = notMatched->overrideClause; bool needSavePoint; // unused thisIf->trueAction = store = store->internalDsqlPass(dsqlScratch, false, needSavePoint)->as(); @@ -5877,6 +5884,8 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch) ModifyNode* ModifyNode::pass1(thread_db* tdbb, CompilerScratch* csb) { + preprocessAssignments(tdbb, csb, newStream, statement->as(), NULL); + pass1Modify(tdbb, csb, this); doPass1(tdbb, csb, statement.getAddress()); @@ -6414,6 +6423,7 @@ const StmtNode* ReceiveNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeS static RegisterNode regStoreNode(blr_store); static RegisterNode regStoreNode2(blr_store2); +static RegisterNode regStoreNode3(blr_store3); // Parse a store statement. DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) @@ -6422,6 +6432,21 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs AutoSetRestore autoCurrentDMLNode(&csb->csb_currentDMLNode, node); + if (blrOp == blr_store3) + { + node->overrideClause = static_cast(csb->csb_blr_reader.getByte()); + + switch (node->overrideClause.value) + { + case OverrideClause::USER_VALUE: + case OverrideClause::SYSTEM_VALUE: + break; + + default: + PAR_syntax_error(csb, "invalid blr_store3 override clause"); + } + } + const UCHAR* blrPos = csb->csb_blr_reader.getPos(); node->relationSource = PAR_parseRecordSource(tdbb, csb)->as(); @@ -6436,6 +6461,13 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs if (blrOp == blr_store2) node->statement2 = PAR_parse_stmt(tdbb, csb); + else if (blrOp == blr_store3) + { + if (csb->csb_blr_reader.peekByte() == blr_null) + csb->csb_blr_reader.getByte(); + else + node->statement2 = PAR_parse_stmt(tdbb, csb); + } return node; } @@ -6449,6 +6481,7 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_INSERT); StoreNode* node = FB_NEW_POOL(getPool()) StoreNode(getPool()); + node->overrideClause = overrideClause; // Process SELECT expression, if present @@ -6557,12 +6590,29 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, NestConst* ptr2 = values->items.begin(); for (const NestConst* end = fields.end(); ptr != end; ++ptr, ++ptr2) { - AssignmentNode* temp = FB_NEW_POOL(getPool()) AssignmentNode(getPool()); - temp->asgnFrom = *ptr2; - temp->asgnTo = *ptr; - assignStatements->statements.add(temp); + // *ptr2 is NULL for DEFAULT - PASS1_set_parameter_type(dsqlScratch, *ptr2, temp->asgnTo, false); + if (!*ptr2) + { + const FieldNode* field = (*ptr)->as(); + + if (field && field->dsqlField) + { + *ptr2 = FB_NEW_POOL(getPool()) DefaultNode(getPool(), + relation->rel_name, field->dsqlField->fld_name); + *ptr2 = doDsqlPass(dsqlScratch, *ptr2, false); + } + } + + if (*ptr2) + { + AssignmentNode* temp = FB_NEW_POOL(getPool()) AssignmentNode(getPool()); + temp->asgnFrom = *ptr2; + temp->asgnTo = *ptr; + assignStatements->statements.add(temp); + + PASS1_set_parameter_type(dsqlScratch, *ptr2, temp->asgnTo, false); + } } } @@ -6639,12 +6689,19 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch) { const dsql_msg* message = dsqlGenDmlHeader(dsqlScratch, dsqlRse->as()); - dsqlScratch->appendUChar(statement2 ? blr_store2 : blr_store); + dsqlScratch->appendUChar(overrideClause.specified ? blr_store3 : (statement2 ? blr_store2 : blr_store)); + + if (overrideClause.specified) + dsqlScratch->appendUChar(UCHAR(overrideClause.value)); + GEN_expr(dsqlScratch, dsqlRelation); + statement->genBlr(dsqlScratch); if (statement2) statement2->genBlr(dsqlScratch); + else if (overrideClause.specified) + dsqlScratch->appendUChar(blr_null); if (message) dsqlScratch->appendUChar(blr_end); @@ -6652,6 +6709,8 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch) StoreNode* StoreNode::pass1(thread_db* tdbb, CompilerScratch* csb) { + preprocessAssignments(tdbb, csb, relationSource->getStream(), statement->as(), &overrideClause); + if (pass1Store(tdbb, csb, this)) makeDefaults(tdbb, csb); @@ -6787,7 +6846,6 @@ void StoreNode::makeDefaults(thread_db* tdbb, CompilerScratch* csb) } StmtNodeStack stack; - USHORT fieldId = 0; vec::iterator ptr1 = vector->begin(); @@ -6830,32 +6888,9 @@ void StoreNode::makeDefaults(thread_db* tdbb, CompilerScratch* csb) AssignmentNode* assign = FB_NEW_POOL(*tdbb->getDefaultPool()) AssignmentNode( *tdbb->getDefaultPool()); assign->asgnTo = PAR_gen_field(tdbb, stream, fieldId); + assign->asgnFrom = DefaultNode::createFromField(tdbb, csb, map, *ptr1); stack.push(assign); - - const MetaName& generatorName = (*ptr1)->fld_generator_name; - - if (generatorName.hasData()) - { - // Make a (next value for ) expression. - - GenIdNode* const genNode = FB_NEW_POOL(csb->csb_pool) GenIdNode( - csb->csb_pool, (csb->blrVersion == 4), generatorName, NULL, true, true); - - bool sysGen = false; - if (!MET_load_generator(tdbb, genNode->generator, &sysGen, &genNode->step)) - PAR_error(csb, Arg::Gds(isc_gennotdef) << Arg::Str(generatorName)); - - if (sysGen) - PAR_error(csb, Arg::Gds(isc_cant_modify_sysobj) << "generator" << generatorName); - - assign->asgnFrom = genNode; - } - else //if (value) - { - // Clone the field default value. - assign->asgnFrom = RemapFieldNodeCopier(csb, map, fieldId).copy(tdbb, value); - } } } @@ -7951,6 +7986,80 @@ void SetRoleNode::execute(thread_db* tdbb, dsql_req* request, jrd_tra** transact //-------------------- +SetSessionNode::SetSessionNode(MemoryPool& pool, Type aType, ULONG aVal, UCHAR blr_timepart) + : Node(pool), + m_type(aType), + m_value(0) +{ + // TYPE_IDLE_TIMEOUT should be set in seconds + // TYPE_STMT_TIMEOUT should be set in milliseconds + + ULONG mult = 1; + + switch (blr_timepart) + { + case blr_extract_hour: + mult = (aType == TYPE_IDLE_TIMEOUT) ? 3660 : 3660000; + break; + + case blr_extract_minute: + mult = (aType == TYPE_IDLE_TIMEOUT) ? 60 : 60000; + break; + + case blr_extract_second: + mult = (aType == TYPE_IDLE_TIMEOUT) ? 1 : 1000; + break; + + case blr_extract_millisecond: + if (aType == TYPE_IDLE_TIMEOUT) + Arg::Gds(isc_invalid_extractpart_time).raise(); + mult = 1; + break; + + default: + Arg::Gds(isc_invalid_extractpart_time).raise(); + break; + } + + m_value = aVal * mult; +} + +string SetSessionNode::internalPrint(NodePrinter& printer) const +{ + Node::internalPrint(printer); + + NODE_PRINT(printer, m_type); + NODE_PRINT(printer, m_value); + + return "SetSessionNode"; +} + +SetSessionNode* SetSessionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) +{ + dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_SET_SESSION); + return this; +} + +void SetSessionNode::execute(thread_db* tdbb, dsql_req* request) const +{ + Attachment* att = tdbb->getAttachment(); + + switch (m_type) + { + case TYPE_IDLE_TIMEOUT: + att->setIdleTimeout(m_value); + break; + + case TYPE_STMT_TIMEOUT: + att->setStatementTimeout(m_value); + break; + } +} + + +//-------------------- + + StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { thread_db* tdbb = JRD_get_thread_data(); // necessary? @@ -7970,6 +8079,7 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) insert->dsqlFields = fields; insert->dsqlValues = values; insert->dsqlReturning = returning; + insert->overrideClause = overrideClause; insert = insert->internalDsqlPass(dsqlScratch, true, needSavePoint)->as(); fb_assert(insert); @@ -8047,7 +8157,10 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) for (; fieldPtr != fieldsCopy.end(); ++fieldPtr, ++valuePtr) { AssignmentNode* assign = FB_NEW_POOL(pool) AssignmentNode(pool); - assign->asgnFrom = *valuePtr; + + if (!(assign->asgnFrom = *valuePtr)) // it's NULL for DEFAULT + assign->asgnFrom = FB_NEW_POOL(pool) DefaultNode(pool, relation_name, (*fieldPtr)->dsqlName); + assign->asgnTo = *fieldPtr; assignments->statements.add(assign); @@ -8071,6 +8184,9 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) if (testField == fieldName) { + if (!*valuePtr) // it's NULL for DEFAULT + ERRD_post(Arg::Gds(isc_upd_ins_cannot_default) << fieldName); + ++matchCount; const FB_SIZE_T fieldPos = fieldPtr - fieldsCopy.begin(); @@ -9217,6 +9333,99 @@ static void preModifyEraseTriggers(thread_db* tdbb, TrigVector** trigs, tdbb->getTransaction()->tra_rpblist->PopRpb(rpb, rpblevel); } +// 1. Remove assignments of DEFAULT to computed fields. +// 2. Remove assignments to identity column when OVERRIDING USER VALUE is specified in INSERT. +static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb, + StreamType stream, CompoundStmtNode* compoundNode, const Nullable* insertOverride) +{ + if (!compoundNode) + return; + + jrd_rel* relation = csb->csb_rpt[stream].csb_relation; + + fb_assert(relation); + if (!relation) + return; + + Nullable identityType; + + for (size_t i = compoundNode->statements.getCount(); i--; ) + { + const AssignmentNode* assign = compoundNode->statements[i]->as(); + fb_assert(assign); + if (!assign) + continue; + + const ExprNode* assignFrom = assign->asgnFrom; + const FieldNode* assignToField = assign->asgnTo->as(); + + if (assignToField) + { + int fieldId = assignToField->fieldId; + jrd_fld* fld; + + while (true) + { + if (assignToField->fieldStream == stream && + relation->rel_fields && + (fld = (*relation->rel_fields)[fieldId])) + { + if (insertOverride && fld->fld_identity_type.specified) + { + if (insertOverride->specified || !assignFrom->is()) + identityType = fld->fld_identity_type; + + if (*insertOverride == OverrideClause::USER_VALUE) + { + compoundNode->statements.remove(i); + break; + } + } + + if (fld->fld_computation) + { + if (assignFrom->is()) + compoundNode->statements.remove(i); + } + else if (relation->rel_view_rse && fld->fld_source_rel_field.first.hasData()) + { + relation = MET_lookup_relation(tdbb, fld->fld_source_rel_field.first); + + fb_assert(relation); + if (!relation) + return; + + if ((fieldId = MET_lookup_field(tdbb, relation, fld->fld_source_rel_field.second)) >= 0) + continue; + } + } + + break; + } + } + } + + if (!insertOverride) + return; + + if (insertOverride->specified) + { + if (!identityType.specified) + ERR_post(Arg::Gds(isc_overriding_without_identity) << relation->rel_name); + + if (identityType == IDENT_TYPE_BY_DEFAULT && *insertOverride == OverrideClause::SYSTEM_VALUE) + ERR_post(Arg::Gds(isc_overriding_system_invalid) << relation->rel_name); + + if (identityType == IDENT_TYPE_ALWAYS && *insertOverride == OverrideClause::USER_VALUE) + ERR_post(Arg::Gds(isc_overriding_user_invalid) << relation->rel_name); + } + else + { + if (identityType == IDENT_TYPE_ALWAYS) + ERR_post(Arg::Gds(isc_overriding_system_missing) << relation->rel_name); + } +} + // Execute a list of validation expressions. static void validateExpressions(thread_db* tdbb, const Array& validations) { diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 0dd558bf64..f73f05b66e 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -112,6 +112,14 @@ struct ValidateInfo }; +enum OverrideClause : UCHAR +{ + // Warning: used in BLR + USER_VALUE = 1, + SYSTEM_VALUE +}; + + class AssignmentNode : public TypedNode { public: @@ -1048,6 +1056,7 @@ public: Firebird::Array > fields; NestConst values; NestConst condition; + Nullable overrideClause; }; explicit MergeNode(MemoryPool& pool) @@ -1267,6 +1276,7 @@ public: NestConst subStore; Firebird::Array validations; NestConst relationSource; + Nullable overrideClause; }; @@ -1609,6 +1619,7 @@ public: NestConst values; Firebird::Array > matching; NestConst returning; + Nullable overrideClause; }; diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp index fe20d9966c..2be813f0bb 100644 --- a/src/dsql/dsql.cpp +++ b/src/dsql/dsql.cpp @@ -147,9 +147,11 @@ void DSQL_execute(thread_db* tdbb, Arg::Gds(isc_bad_req_handle)); } - // Only allow NULL trans_handle if we're starting a transaction + // Only allow NULL trans_handle if we're starting a transaction or set session properties - if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS) + if (!*tra_handle && + statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS && + statement->getType() != DsqlCompiledStatement::TYPE_SET_SESSION) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_bad_trans_handle)); @@ -274,6 +276,10 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer) Jrd::Attachment* att = req_dbb->dbb_attachment; TraceDSQLFetch trace(att, this); + thread_db::TimerGuard timerGuard(tdbb, req_timer, false); + if (req_timer && req_timer->expired()) + tdbb->checkCancelState(true); + UCHAR* dsqlMsgBuffer = req_msg_buffers[message->msg_buffer_number]; JRD_receive(tdbb, req_request, message->msg_number, message->msg_length, dsqlMsgBuffer); @@ -283,6 +289,9 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer) if (eofReached) { + if (req_timer) + req_timer->stop(); + delayedFormat = NULL; trace.fetch(true, ITracePlugin::RESULT_SUCCESS); return false; @@ -535,9 +544,11 @@ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tr const DsqlCompiledStatement* statement = request->getStatement(); - // Only allow NULL trans_handle if we're starting a transaction + // Only allow NULL trans_handle if we're starting a transaction or set session properties - if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS) + if (!*tra_handle && + statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS && + statement->getType() != DsqlCompiledStatement::TYPE_SET_SESSION) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) << Arg::Gds(isc_bad_trans_handle)); @@ -676,6 +687,12 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, // manager know statement parameters values TraceDSQLExecute trace(req_dbb->dbb_attachment, this); + // Setup and start timeout timer + const bool have_cursor = reqTypeWithCursor(statement->getType()) && !singleton; + + setupTimer(tdbb); + thread_db::TimerGuard timerGuard(tdbb, req_timer, !have_cursor); + if (!message) JRD_start(tdbb, req_request, req_transaction); else @@ -798,7 +815,6 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle, break; } - const bool have_cursor = reqTypeWithCursor(statement->getType()) && !singleton; trace.finish(have_cursor, ITracePlugin::RESULT_SUCCESS); } @@ -898,14 +914,32 @@ void DsqlTransactionRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scra // Execute a dynamic SQL statement. void DsqlTransactionRequest::execute(thread_db* tdbb, jrd_tra** traHandle, - Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg, - Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg, - bool singleton) + IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/, + IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/, + bool /*singleton*/) { node->execute(tdbb, this, traHandle); } +void SetSessionRequest::execute(thread_db* tdbb, jrd_tra** /*traHandle*/, + IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/, + IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/, + bool /*singleton*/) +{ + node->execute(tdbb, this); +} + +void SetSessionRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, + ntrace_result_t* /*traceResult*/) +{ + node = Node::doDsqlPass(scratch, node); + + // Don't trace pseudo-statements (without requests associated). + req_traced = false; +} + + /** get_request_info @@ -1530,7 +1564,12 @@ dsql_req::dsql_req(MemoryPool& pool) req_cursor_name(req_pool), req_cursor(NULL), req_user_descs(req_pool), - req_traced(false) + req_traced(false), + req_timeout(0) +{ +} + +dsql_req::~dsql_req() { } @@ -1560,11 +1599,91 @@ bool dsql_req::fetch(thread_db* /*tdbb*/, UCHAR* /*msgBuffer*/) return false; // avoid warning } +unsigned int dsql_req::getTimeout() +{ + return req_timeout; +} + +unsigned int dsql_req::getActualTimeout() +{ + if (req_timer) + return req_timer->getValue(); + + return 0; +} + +void dsql_req::setTimeout(unsigned int timeOut) +{ + req_timeout = timeOut; +} + +void dsql_req::setupTimer(thread_db* tdbb) +{ + if (statement->getFlags() & JrdStatement::FLAG_INTERNAL) + return; + + if (req_request) + { + req_request->req_timeout = this->req_timeout; + + fb_assert(!req_request->req_caller); + if (req_request->req_caller) + { + if (req_timer) + req_timer->setup(0, 0); + return; + } + } + + Database* dbb = tdbb->getDatabase(); + Attachment* att = tdbb->getAttachment(); + + ISC_STATUS toutErr = isc_cfg_stmt_timeout; + unsigned int timeOut = dbb->dbb_config->getStatementTimeout() * 1000; + + if (req_timeout) + { + if (!timeOut || req_timeout < timeOut) + { + timeOut = req_timeout; + toutErr = isc_req_stmt_timeout; + } + } + else + { + const unsigned int attTout = att->getStatementTimeout(); + + if (!timeOut || attTout && attTout < timeOut) + { + timeOut = attTout; + toutErr = isc_att_stmt_timeout; + } + } + + if (!req_timer && timeOut) + { + req_timer = FB_NEW TimeoutTimer(); + req_request->req_timer = this->req_timer; + } + + if (req_timer) + { + req_timer->setup(timeOut, toutErr); + req_timer->start(); + } +} + // Release a dynamic request. void dsql_req::destroy(thread_db* tdbb, dsql_req* request, bool drop) { SET_TDBB(tdbb); + if (request->req_timer) + { + request->req_timer->stop(); + request->req_timer = NULL; + } + // If request is parent, orphan the children and release a portion of their requests for (FB_SIZE_T i = 0; i < request->cursors.getCount(); ++i) @@ -1757,6 +1876,7 @@ static void sql_info(thread_db* tdbb, case DsqlCompiledStatement::TYPE_CREATE_DB: case DsqlCompiledStatement::TYPE_DDL: case DsqlCompiledStatement::TYPE_SET_ROLE: + case DsqlCompiledStatement::TYPE_SET_SESSION: number = isc_info_sql_stmt_ddl; break; case DsqlCompiledStatement::TYPE_COMMIT: @@ -1828,6 +1948,15 @@ static void sql_info(thread_db* tdbb, return; break; + case isc_info_sql_stmt_timeout_user: + case isc_info_sql_stmt_timeout_run: + value = (item == isc_info_sql_stmt_timeout_user) ? request->getTimeout() : request->getActualTimeout(); + + length = put_vax_long(buffer, value); + if (!(info = put_item(item, length, buffer, info, end_info))) + return; + break; + case isc_info_sql_get_plan: case isc_info_sql_explain_plan: { diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index 32efb16b42..53c03c9e41 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -80,6 +80,7 @@ namespace Jrd class RseNode; class StmtNode; class TransactionNode; + class SetSessionNode; class ValueExprNode; class ValueListNode; class WindowClause; @@ -93,6 +94,7 @@ namespace Jrd class dsql_par; class dsql_map; class dsql_intlsym; + class TimeoutTimer; typedef Firebird::Stack DsqlContextStack; @@ -431,7 +433,7 @@ public: TYPE_SELECT, TYPE_SELECT_UPD, TYPE_INSERT, TYPE_DELETE, TYPE_UPDATE, TYPE_UPDATE_CURSOR, TYPE_DELETE_CURSOR, TYPE_COMMIT, TYPE_ROLLBACK, TYPE_CREATE_DB, TYPE_DDL, TYPE_START_TRANS, TYPE_EXEC_PROCEDURE, TYPE_COMMIT_RETAIN, TYPE_ROLLBACK_RETAIN, TYPE_SET_GENERATOR, - TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SET_ROLE + TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SET_ROLE, TYPE_SET_SESSION }; // Statement flags. @@ -556,6 +558,19 @@ public: virtual void setDelayedFormat(thread_db* tdbb, Firebird::IMessageMetadata* metadata); + // Get session-level timeout, milliseconds + unsigned int getTimeout(); + + // Set session-level timeout, milliseconds + void setTimeout(unsigned int timeOut); + + // Get actual timeout, milliseconds + unsigned int getActualTimeout(); + + // Evaluate actual timeout value, consider config- and session-level timeout values, + // setup and start timer + void setupTimer(thread_db* tdbb); + static void destroy(thread_db* tdbb, dsql_req* request, bool drop); private: @@ -580,11 +595,12 @@ public: bool req_traced; // request is traced via TraceAPI protected: + unsigned int req_timeout; // query timeout in milliseconds, set by the user + Firebird::RefPtr req_timer; // timeout timer + // Request should never be destroyed using delete. // It dies together with it's pool in release_request(). - ~dsql_req() - { - } + ~dsql_req(); // To avoid posix warning about missing public destructor declare // MemoryPool as friend class. In fact IT releases request memory! @@ -670,6 +686,28 @@ private: NestConst node; }; +class SetSessionRequest : public dsql_req +{ +public: + explicit SetSessionRequest(MemoryPool& pool, SetSessionNode* aNode) + : dsql_req(pool), + node(aNode) + { + req_traced = false; + } + + virtual void dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, + ntrace_result_t* traceResult); + + virtual void execute(thread_db* tdbb, jrd_tra** traHandle, + Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg, + Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg, + bool singleton); + +private: + NestConst node; +}; + //! Implicit (NATURAL and USING) joins class ImplicitJoin : public pool_alloc { diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 20cc527f2d..9a7308c038 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -45 shift/reduce conflicts, 17 reduce/reduce conflicts. +46 shift/reduce conflicts, 17 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index e2750949b8..769f9936bc 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -596,10 +596,12 @@ using namespace Firebird; %token DEFINER %token EXCLUDE %token FOLLOWING +%token IDLE %token INVOKER %token MESSAGE %token NTILE %token OTHERS +%token OVERRIDING %token PERCENT_RANK %token PRECEDING %token PRIVILEGE @@ -608,6 +610,7 @@ using namespace Firebird; %token RDB_ROLE_IN_USE %token RDB_SYSTEM_PRIVILEGE %token SECURITY +%token SESSION %token SQL %token SYSTEM %token TIES @@ -646,6 +649,7 @@ using namespace Firebird; BaseNullable nullableIntVal; BaseNullable nullableBoolVal; BaseNullable nullableSqlSecurityVal; + BaseNullable nullableOverrideClause; bool boolVal; int intVal; unsigned uintVal; @@ -725,6 +729,7 @@ using namespace Firebird; Jrd::RelationNode::RefActionClause* refActionClause; Jrd::RelationNode::IndexConstraintClause* indexConstraintClause; Jrd::RelationNode::IdentityOptions* identityOptions; + IdentityType identityType; Jrd::CreateRelationNode* createRelationNode; Jrd::CreateAlterViewNode* createAlterViewNode; Jrd::CreateIndexNode* createIndexNode; @@ -754,6 +759,7 @@ using namespace Firebird; Jrd::MappingNode* mappingNode; Jrd::MappingNode::OP mappingOp; Jrd::SetRoleNode* setRoleNode; + Jrd::SetSessionNode* setSessionNode; Jrd::CreateAlterRoleNode* createAlterRoleNode; } @@ -773,6 +779,7 @@ statement : dml_statement { $$ = newNode($1); } | ddl_statement { $$ = newNode($1); } | tra_statement { $$ = newNode($1); } + | session_statement { $$ = newNode($1); } ; %type dml_statement @@ -2181,10 +2188,16 @@ column_def($relationNode) %type identity_clause identity_clause - : GENERATED BY DEFAULT AS IDENTITY - { $$ = newNode(); } - identity_clause_options_opt($6) - { $$ = $6; } + : GENERATED identity_clause_type AS IDENTITY + { $$ = newNode($2); } + identity_clause_options_opt($5) + { $$ = $5; } + ; + +%type identity_clause_type +identity_clause_type + : BY DEFAULT { $$ = IDENT_TYPE_BY_DEFAULT; } + | ALWAYS { $$ = IDENT_TYPE_ALWAYS; } ; %type identity_clause_options_opt() @@ -3931,7 +3944,7 @@ alter_op($relationNode) } | col_opt symbol_column_name { $$ = newNode(); } - alter_identity_clause_options($3) + alter_identity_clause_spec($3) { RelationNode::AlterColTypeClause* clause = newNode(); clause->field = newNode(); @@ -4077,6 +4090,24 @@ alter_data_type_or_domain } ; +%type alter_identity_clause_spec() +alter_identity_clause_spec($identityOptions) + : alter_identity_clause_generation($identityOptions) alter_identity_clause_options_opt($identityOptions) + | alter_identity_clause_options($identityOptions) + ; + +%type alter_identity_clause_generation() +alter_identity_clause_generation($identityOptions) + : SET GENERATED ALWAYS { $identityOptions->type = IDENT_TYPE_ALWAYS; } + | SET GENERATED BY DEFAULT { $identityOptions->type = IDENT_TYPE_BY_DEFAULT; } + ; + +%type alter_identity_clause_options_opt() +alter_identity_clause_options_opt($identityOptions) + : // nothing + | alter_identity_clause_options($identityOptions) + ; + %type alter_identity_clause_options() alter_identity_clause_options($identityOptions) : alter_identity_clause_options alter_identity_clause_option($identityOptions) @@ -4985,6 +5016,31 @@ set_role { $$ = newNode(); } ; +%type session_statement +session_statement + : SET SESSION IDLE TIMEOUT long_integer timepart_sesion_idle_tout + { $$ = newNode(SetSessionNode::TYPE_IDLE_TIMEOUT, $5, $6); } + | SET STATEMENT TIMEOUT long_integer timepart_ses_stmt_tout + { $$ = newNode(SetSessionNode::TYPE_STMT_TIMEOUT, $4, $5); } + ; + +%type timepart_sesion_idle_tout +timepart_sesion_idle_tout + : /* nothing */ { $$ = blr_extract_minute; } + | HOUR { $$ = blr_extract_hour; } + | MINUTE { $$ = blr_extract_minute; } + | SECOND { $$ = blr_extract_second; } + ; + +%type timepart_ses_stmt_tout +timepart_ses_stmt_tout + : /* nothing */ { $$ = blr_extract_second; } + | HOUR { $$ = blr_extract_hour; } + | MINUTE { $$ = blr_extract_minute; } + | SECOND { $$ = blr_extract_second; } + | MILLISECOND { $$ = blr_extract_millisecond; } + ; + %type tran_option_list_opt() tran_option_list_opt($setTransactionNode) : // nothing @@ -5913,18 +5969,20 @@ fetch_first_clause // IBO hack: replace column_parens_opt by ins_column_parens_opt. %type insert insert - : insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) VALUES '(' value_list ')' + : insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) override_opt VALUES '(' value_or_default_list ')' returning_clause { StoreNode* node = $$ = $1; - node->dsqlValues = $5; - node->dsqlReturning = $7; + node->overrideClause = $3; + node->dsqlValues = $6; + node->dsqlReturning = $8; } - | insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) select_expr returning_clause + | insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) override_opt select_expr returning_clause { StoreNode* node = $$ = $1; - node->dsqlRse = $3; - node->dsqlReturning = $4; + node->overrideClause = $3; + node->dsqlRse = $4; + node->dsqlReturning = $5; $$ = node; } | insert_start DEFAULT VALUES returning_clause @@ -5945,6 +6003,25 @@ insert_start } ; +%type override_opt +override_opt + : /* nothing */ { $$ = Nullable::empty(); } + | OVERRIDING USER VALUE { $$ = Nullable::val(OverrideClause::USER_VALUE); } + | OVERRIDING SYSTEM VALUE { $$ = Nullable::val(OverrideClause::SYSTEM_VALUE); } + ; + +%type value_or_default_list +value_or_default_list + : value_or_default { $$ = newNode($1); } + | value_or_default_list ',' value_or_default { $$ = $1->add($3); } + ; + +%type value_or_default +value_or_default + : value + | DEFAULT { $$ = NULL; } + ; + // MERGE statement %type merge @@ -5975,7 +6052,7 @@ merge_when_clause($mergeNode) merge_when_matched_clause($mergeNode) : WHEN MATCHED { $$ = &$mergeNode->whenMatched.add(); } - merge_update_specification(NOTRIAL($3)) + merge_update_specification(NOTRIAL($3), NOTRIAL(&$mergeNode->relation->dsqlName)) ; %type merge_when_not_matched_clause() @@ -5985,11 +6062,11 @@ merge_when_not_matched_clause($mergeNode) merge_insert_specification(NOTRIAL($4)) ; -%type merge_update_specification() -merge_update_specification($mergeMatchedClause) - : THEN UPDATE SET assignments +%type merge_update_specification(, ) +merge_update_specification($mergeMatchedClause, $relationName) + : THEN UPDATE SET update_assignments(NOTRIAL($relationName)) { $mergeMatchedClause->assignments = $4; } - | AND search_condition THEN UPDATE SET assignments + | AND search_condition THEN UPDATE SET update_assignments(NOTRIAL($relationName)) { $mergeMatchedClause->condition = $2; $mergeMatchedClause->assignments = $6; @@ -6001,13 +6078,17 @@ merge_update_specification($mergeMatchedClause) %type merge_insert_specification() merge_insert_specification($mergeNotMatchedClause) - : THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) - VALUES '(' value_list ')' - { $mergeNotMatchedClause->values = $6; } - | AND search_condition THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) - VALUES '(' value_list ')' + : THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) override_opt + VALUES '(' value_or_default_list ')' { - $mergeNotMatchedClause->values = $8; + $mergeNotMatchedClause->overrideClause = $4; + $mergeNotMatchedClause->values = $7; + } + | AND search_condition THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) override_opt + VALUES '(' value_or_default_list ')' + { + $mergeNotMatchedClause->overrideClause = $6; + $mergeNotMatchedClause->values = $9; $mergeNotMatchedClause->condition = $2; } ; @@ -6059,7 +6140,7 @@ update %type update_searched update_searched - : UPDATE table_name SET assignments where_clause plan_clause + : UPDATE table_name SET update_assignments(NOTRIAL(&$2->dsqlName)) where_clause plan_clause order_clause_opt rows_clause_optional returning_clause { ModifyNode* node = newNode(); @@ -6076,7 +6157,7 @@ update_searched %type update_positioned update_positioned - : UPDATE table_name SET assignments cursor_clause returning_clause + : UPDATE table_name SET update_assignments(NOTRIAL(&$2->dsqlName)) cursor_clause returning_clause { ModifyNode* node = newNode(); node->dsqlRelation = $2; @@ -6097,12 +6178,13 @@ update_or_insert UpdateOrInsertNode* node = $$ = newNode(); node->relation = $5; } - ins_column_parens_opt(NOTRIAL(&$6->fields)) VALUES '(' value_list ')' + ins_column_parens_opt(NOTRIAL(&$6->fields)) override_opt VALUES '(' value_or_default_list ')' update_or_insert_matching_opt(NOTRIAL(&$6->matching)) returning_clause { UpdateOrInsertNode* node = $$ = $6; - node->values = $10; - node->returning = $13; + node->overrideClause = $8; + node->values = $11; + node->returning = $14; } ; @@ -6144,20 +6226,6 @@ cursor_clause // Assignments -%type assignments -assignments - : assignment - { - $$ = newNode(); - $$->statements.add($1); - } - | assignments ',' assignment - { - $1->statements.add($3); - $$ = $1; - } - ; - %type assignment assignment : update_column_name '=' value @@ -6169,6 +6237,38 @@ assignment } ; +%type update_assignments() +update_assignments($relationName) + : update_assignment($relationName) + { + $$ = newNode(); + $$->statements.add($1); + } + | update_assignments ',' update_assignment($relationName) + { + $1->statements.add($3); + $$ = $1; + } + ; + +%type update_assignment() +update_assignment($relationName) + : update_column_name '=' value + { + AssignmentNode* node = newNode(); + node->asgnTo = $1; + node->asgnFrom = $3; + $$ = node; + } + | update_column_name '=' DEFAULT + { + AssignmentNode* node = newNode(); + node->asgnTo = $1; + node->asgnFrom = newNode(*$relationName, $1->dsqlName); + $$ = node; + } + ; + %type exec_function exec_function : udf @@ -7832,6 +7932,7 @@ symbol_UDF_name %type symbol_blob_subtype_name symbol_blob_subtype_name : valid_symbol_name + | BINARY ; %type symbol_character_set_name @@ -8185,15 +8286,18 @@ non_reserved_word | DEFINER | EXCLUDE | FOLLOWING + | IDLE | INVOKER | MESSAGE | NTILE | OTHERS + | OVERRIDING | PERCENT_RANK | PRECEDING | PRIVILEGE | RANGE | SECURITY + | SESSION | SQL | SYSTEM | TIES diff --git a/src/include/fb_api_proto.h b/src/include/fb_api_proto.h index 7c6b22d471..f891a05e15 100644 --- a/src/include/fb_api_proto.h +++ b/src/include/fb_api_proto.h @@ -442,6 +442,11 @@ typedef ISC_STATUS API_ROUTINE prototype_fb_cancel_operation(ISC_STATUS *, typedef ISC_STATUS API_ROUTINE prototype_fb_database_crypt_callback(ISC_STATUS *, void *); +typedef ISC_STATUS API_ROUTINE prototype_fb_dsql_set_timeout(ISC_STATUS*, + isc_stmt_handle*, + ULONG); + + struct FirebirdApiPointers { prototype_isc_attach_database *isc_attach_database; @@ -523,6 +528,7 @@ struct FirebirdApiPointers prototype_isc_service_start *isc_service_start; prototype_fb_cancel_operation *fb_cancel_operation; prototype_fb_database_crypt_callback *fb_database_crypt_callback; + prototype_fb_dsql_set_timeout* fb_dsql_set_timeout; }; #endif diff --git a/src/include/firebird/FirebirdInterface.idl b/src/include/firebird/FirebirdInterface.idl index 39955fa2b9..7658e410b2 100644 --- a/src/include/firebird/FirebirdInterface.idl +++ b/src/include/firebird/FirebirdInterface.idl @@ -439,6 +439,11 @@ interface Statement : ReferenceCounted void setCursorName(Status status, const string name); void free(Status status); uint getFlags(Status status); + +version: // 3.0 => 4.0 + // Statement execution timeout, milliseconds + uint getTimeout(Status status); + void setTimeout(Status status, uint timeOut); } interface Request : ReferenceCounted @@ -516,6 +521,15 @@ interface Attachment : ReferenceCounted void ping(Status status); void detach(Status status); void dropDatabase(Status status); + +version: // 3.0 => 4.0 + // Idle attachment timeout, seconds + uint getIdleTimeout(Status status); + void setIdleTimeout(Status status, uint timeOut); + + // Statement execution timeout, milliseconds + uint getStatementTimeout(Status status); + void setStatementTimeout(Status status, uint timeOut); } interface Service : ReferenceCounted @@ -599,6 +613,7 @@ interface Server : Auth { [notImplemented(Auth::AUTH_FAILED)] int authenticate(Status status, ServerBlock sBlock, Writer writerInterface); + version: // 3.0.1 => 4.0 void setDbCryptCallback(Status status, CryptKeyCallback cryptCallback); } diff --git a/src/include/firebird/IdlFbInterfaces.h b/src/include/firebird/IdlFbInterfaces.h index fe07b69a87..b5555799f0 100644 --- a/src/include/firebird/IdlFbInterfaces.h +++ b/src/include/firebird/IdlFbInterfaces.h @@ -1556,6 +1556,8 @@ namespace Firebird void (CLOOP_CARG *setCursorName)(IStatement* self, IStatus* status, const char* name) throw(); void (CLOOP_CARG *free)(IStatement* self, IStatus* status) throw(); unsigned (CLOOP_CARG *getFlags)(IStatement* self, IStatus* status) throw(); + unsigned (CLOOP_CARG *getTimeout)(IStatement* self, IStatus* status) throw(); + void (CLOOP_CARG *setTimeout)(IStatement* self, IStatus* status, unsigned timeOut) throw(); }; protected: @@ -1569,7 +1571,7 @@ namespace Firebird } public: - static const unsigned VERSION = 3; + static const unsigned VERSION = 4; static const unsigned PREPARE_PREFETCH_NONE = 0; static const unsigned PREPARE_PREFETCH_TYPE = 1; @@ -1669,6 +1671,33 @@ namespace Firebird StatusType::checkException(status); return ret; } + + template unsigned getTimeout(StatusType* status) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IStatement", cloopVTable->version, 4); + StatusType::checkException(status); + return 0; + } + StatusType::clearException(status); + unsigned ret = static_cast(this->cloopVTable)->getTimeout(this, status); + StatusType::checkException(status); + return ret; + } + + template void setTimeout(StatusType* status, unsigned timeOut) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IStatement", cloopVTable->version, 4); + StatusType::checkException(status); + return; + } + StatusType::clearException(status); + static_cast(this->cloopVTable)->setTimeout(this, status, timeOut); + StatusType::checkException(status); + } }; class IRequest : public IReferenceCounted @@ -1862,6 +1891,10 @@ namespace Firebird void (CLOOP_CARG *ping)(IAttachment* self, IStatus* status) throw(); void (CLOOP_CARG *detach)(IAttachment* self, IStatus* status) throw(); void (CLOOP_CARG *dropDatabase)(IAttachment* self, IStatus* status) throw(); + unsigned (CLOOP_CARG *getIdleTimeout)(IAttachment* self, IStatus* status) throw(); + void (CLOOP_CARG *setIdleTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw(); + unsigned (CLOOP_CARG *getStatementTimeout)(IAttachment* self, IStatus* status) throw(); + void (CLOOP_CARG *setStatementTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw(); }; protected: @@ -1875,7 +1908,7 @@ namespace Firebird } public: - static const unsigned VERSION = 3; + static const unsigned VERSION = 4; template void getInfo(StatusType* status, unsigned itemsLength, const unsigned char* items, unsigned bufferLength, unsigned char* buffer) { @@ -2012,6 +2045,60 @@ namespace Firebird static_cast(this->cloopVTable)->dropDatabase(this, status); StatusType::checkException(status); } + + template unsigned getIdleTimeout(StatusType* status) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4); + StatusType::checkException(status); + return 0; + } + StatusType::clearException(status); + unsigned ret = static_cast(this->cloopVTable)->getIdleTimeout(this, status); + StatusType::checkException(status); + return ret; + } + + template void setIdleTimeout(StatusType* status, unsigned timeOut) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4); + StatusType::checkException(status); + return; + } + StatusType::clearException(status); + static_cast(this->cloopVTable)->setIdleTimeout(this, status, timeOut); + StatusType::checkException(status); + } + + template unsigned getStatementTimeout(StatusType* status) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4); + StatusType::checkException(status); + return 0; + } + StatusType::clearException(status); + unsigned ret = static_cast(this->cloopVTable)->getStatementTimeout(this, status); + StatusType::checkException(status); + return ret; + } + + template void setStatementTimeout(StatusType* status, unsigned timeOut) + { + if (cloopVTable->version < 4) + { + StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4); + StatusType::checkException(status); + return; + } + StatusType::clearException(status); + static_cast(this->cloopVTable)->setStatementTimeout(this, status, timeOut); + StatusType::checkException(status); + } }; class IService : public IReferenceCounted @@ -8183,6 +8270,8 @@ namespace Firebird this->setCursorName = &Name::cloopsetCursorNameDispatcher; this->free = &Name::cloopfreeDispatcher; this->getFlags = &Name::cloopgetFlagsDispatcher; + this->getTimeout = &Name::cloopgetTimeoutDispatcher; + this->setTimeout = &Name::cloopsetTimeoutDispatcher; } } vTable; @@ -8351,6 +8440,35 @@ namespace Firebird } } + static unsigned CLOOP_CARG cloopgetTimeoutDispatcher(IStatement* self, IStatus* status) throw() + { + StatusType status2(status); + + try + { + return static_cast(self)->Name::getTimeout(&status2); + } + catch (...) + { + StatusType::catchException(&status2); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopsetTimeoutDispatcher(IStatement* self, IStatus* status, unsigned timeOut) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::setTimeout(&status2, timeOut); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw() { try @@ -8401,6 +8519,8 @@ namespace Firebird virtual void setCursorName(StatusType* status, const char* name) = 0; virtual void free(StatusType* status) = 0; virtual unsigned getFlags(StatusType* status) = 0; + virtual unsigned getTimeout(StatusType* status) = 0; + virtual void setTimeout(StatusType* status, unsigned timeOut) = 0; }; template @@ -8825,6 +8945,10 @@ namespace Firebird this->ping = &Name::clooppingDispatcher; this->detach = &Name::cloopdetachDispatcher; this->dropDatabase = &Name::cloopdropDatabaseDispatcher; + this->getIdleTimeout = &Name::cloopgetIdleTimeoutDispatcher; + this->setIdleTimeout = &Name::cloopsetIdleTimeoutDispatcher; + this->getStatementTimeout = &Name::cloopgetStatementTimeoutDispatcher; + this->setStatementTimeout = &Name::cloopsetStatementTimeoutDispatcher; } } vTable; @@ -9093,6 +9217,64 @@ namespace Firebird } } + static unsigned CLOOP_CARG cloopgetIdleTimeoutDispatcher(IAttachment* self, IStatus* status) throw() + { + StatusType status2(status); + + try + { + return static_cast(self)->Name::getIdleTimeout(&status2); + } + catch (...) + { + StatusType::catchException(&status2); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopsetIdleTimeoutDispatcher(IAttachment* self, IStatus* status, unsigned timeOut) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::setIdleTimeout(&status2, timeOut); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static unsigned CLOOP_CARG cloopgetStatementTimeoutDispatcher(IAttachment* self, IStatus* status) throw() + { + StatusType status2(status); + + try + { + return static_cast(self)->Name::getStatementTimeout(&status2); + } + catch (...) + { + StatusType::catchException(&status2); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopsetStatementTimeoutDispatcher(IAttachment* self, IStatus* status, unsigned timeOut) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::setStatementTimeout(&status2, timeOut); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw() { try @@ -9150,6 +9332,10 @@ namespace Firebird virtual void ping(StatusType* status) = 0; virtual void detach(StatusType* status) = 0; virtual void dropDatabase(StatusType* status) = 0; + virtual unsigned getIdleTimeout(StatusType* status) = 0; + virtual void setIdleTimeout(StatusType* status, unsigned timeOut) = 0; + virtual unsigned getStatementTimeout(StatusType* status) = 0; + virtual void setStatementTimeout(StatusType* status, unsigned timeOut) = 0; }; template diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index 40409fd74d..9b314540e1 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -828,6 +828,17 @@ static const struct { {"dsql_window_cant_overr_frame", 335545124}, {"dsql_window_duplicate", 335545125}, {"sql_too_long", 335545126}, + {"cfg_stmt_timeout", 335545127}, + {"att_stmt_timeout", 335545128}, + {"req_stmt_timeout", 335545129}, + {"att_shut_killed", 335545130}, + {"att_shut_idle", 335545131}, + {"att_shut_db_down", 335545132}, + {"att_shut_engine", 335545133}, + {"overriding_without_identity", 335545134}, + {"overriding_system_invalid", 335545135}, + {"overriding_user_invalid", 335545136}, + {"overriding_system_missing", 335545137}, {"gfix_db_name", 335740929}, {"gfix_invalid_sw", 335740930}, {"gfix_incmp_sw", 335740932}, @@ -894,6 +905,7 @@ static const struct { {"dsql_no_output_sqlda", 336003110}, {"dsql_wrong_param_num", 336003111}, {"dsql_invalid_drop_ss_clause", 336003112}, + {"upd_ins_cannot_default", 336003113}, {"dyn_filter_not_found", 336068645}, {"dyn_func_not_found", 336068649}, {"dyn_index_not_found", 336068656}, diff --git a/src/include/gen/iberror.h b/src/include/gen/iberror.h index 6ecd4913a3..36794b0a8d 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -862,6 +862,17 @@ const ISC_STATUS isc_dsql_window_cant_overr_order = 335545123L; const ISC_STATUS isc_dsql_window_cant_overr_frame = 335545124L; const ISC_STATUS isc_dsql_window_duplicate = 335545125L; const ISC_STATUS isc_sql_too_long = 335545126L; +const ISC_STATUS isc_cfg_stmt_timeout = 335545127L; +const ISC_STATUS isc_att_stmt_timeout = 335545128L; +const ISC_STATUS isc_req_stmt_timeout = 335545129L; +const ISC_STATUS isc_att_shut_killed = 335545130L; +const ISC_STATUS isc_att_shut_idle = 335545131L; +const ISC_STATUS isc_att_shut_db_down = 335545132L; +const ISC_STATUS isc_att_shut_engine = 335545133L; +const ISC_STATUS isc_overriding_without_identity = 335545134L; +const ISC_STATUS isc_overriding_system_invalid = 335545135L; +const ISC_STATUS isc_overriding_user_invalid = 335545136L; +const ISC_STATUS isc_overriding_system_missing = 335545137L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -928,6 +939,7 @@ const ISC_STATUS isc_dsql_no_input_sqlda = 336003109L; const ISC_STATUS isc_dsql_no_output_sqlda = 336003110L; const ISC_STATUS isc_dsql_wrong_param_num = 336003111L; const ISC_STATUS isc_dsql_invalid_drop_ss_clause = 336003112L; +const ISC_STATUS isc_upd_ins_cannot_default = 336003113L; const ISC_STATUS isc_dyn_filter_not_found = 336068645L; const ISC_STATUS isc_dyn_func_not_found = 336068649L; const ISC_STATUS isc_dyn_index_not_found = 336068656L; @@ -1335,7 +1347,7 @@ const ISC_STATUS isc_trace_switch_user_only = 337182757L; const ISC_STATUS isc_trace_switch_param_miss = 337182758L; const ISC_STATUS isc_trace_param_act_notcompat = 337182759L; const ISC_STATUS isc_trace_mandatory_switch_miss = 337182760L; -const ISC_STATUS isc_err_max = 1279; +const ISC_STATUS isc_err_max = 1291; #else /* c definitions */ @@ -2167,6 +2179,17 @@ const ISC_STATUS isc_err_max = 1279; #define isc_dsql_window_cant_overr_frame 335545124L #define isc_dsql_window_duplicate 335545125L #define isc_sql_too_long 335545126L +#define isc_cfg_stmt_timeout 335545127L +#define isc_att_stmt_timeout 335545128L +#define isc_req_stmt_timeout 335545129L +#define isc_att_shut_killed 335545130L +#define isc_att_shut_idle 335545131L +#define isc_att_shut_db_down 335545132L +#define isc_att_shut_engine 335545133L +#define isc_overriding_without_identity 335545134L +#define isc_overriding_system_invalid 335545135L +#define isc_overriding_user_invalid 335545136L +#define isc_overriding_system_missing 335545137L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2233,6 +2256,7 @@ const ISC_STATUS isc_err_max = 1279; #define isc_dsql_no_output_sqlda 336003110L #define isc_dsql_wrong_param_num 336003111L #define isc_dsql_invalid_drop_ss_clause 336003112L +#define isc_upd_ins_cannot_default 336003113L #define isc_dyn_filter_not_found 336068645L #define isc_dyn_func_not_found 336068649L #define isc_dyn_index_not_found 336068656L @@ -2640,7 +2664,7 @@ const ISC_STATUS isc_err_max = 1279; #define isc_trace_switch_param_miss 337182758L #define isc_trace_param_act_notcompat 337182759L #define isc_trace_mandatory_switch_miss 337182760L -#define isc_err_max 1279 +#define isc_err_max 1291 #endif diff --git a/src/include/gen/ids.h b/src/include/gen/ids.h index 0de44b8932..bbfc2c06f5 100644 --- a/src/include/gen/ids.h +++ b/src/include/gen/ids.h @@ -518,6 +518,9 @@ const USHORT f_mon_att_remote_os_user = 17; const USHORT f_mon_att_auth_method = 18; const USHORT f_mon_att_sys_flag = 19; + const USHORT f_mon_att_idle_timeout = 20; + const USHORT f_mon_att_idle_timer = 21; + const USHORT f_mon_att_stmt_timeout = 22; // Relation 35 (MON$TRANSACTIONS) @@ -547,6 +550,8 @@ const USHORT f_mon_stmt_sql_text = 5; const USHORT f_mon_stmt_stat_id = 6; const USHORT f_mon_stmt_expl_plan = 7; + const USHORT f_mon_stmt_timeout = 8; + const USHORT f_mon_stmt_timer = 9; // Relation 37 (MON$CALL_STACK) diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index d11466ac8c..6da69e955b 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -831,6 +831,17 @@ Data source : @4"}, /* eds_statement */ {335545124, "Cannot override the window @1 because it has a frame clause. Tip: it can be used without parenthesis in OVER"}, /* dsql_window_cant_overr_frame */ {335545125, "Duplicate window definition for @1"}, /* dsql_window_duplicate */ {335545126, "SQL statement is too long. Maximum size is @1 bytes."}, /* sql_too_long */ + {335545127, "Config level timeout expired."}, /* cfg_stmt_timeout */ + {335545128, "Attachment level timeout expired."}, /* att_stmt_timeout */ + {335545129, "Statement level timeout expired."}, /* req_stmt_timeout */ + {335545130, "Killed by database administrator."}, /* att_shut_killed */ + {335545131, "Idle timeout expired."}, /* att_shut_idle */ + {335545132, "Database is shutdown."}, /* att_shut_db_down */ + {335545133, "Engine is shutdown."}, /* att_shut_engine */ + {335545134, "OVERRIDING clause can be used only when an identity column is present in the INSERT's field list for table/view @1"}, /* overriding_without_identity */ + {335545135, "OVERRIDING SYSTEM VALUE can be used only for identity column defined as 'GENERATED ALWAYS' in INSERT for table/view @1"}, /* overriding_system_invalid */ + {335545136, "OVERRIDING USER VALUE can be used only for identity column defined as 'GENERATED BY DEFAULT' in INSERT for table/view @1"}, /* overriding_user_invalid */ + {335545137, "OVERRIDING SYSTEM VALUE should be used to override the value of an identity column defined as 'GENERATED ALWAYS' in table/view @1"}, /* overriding_system_missing */ {335740929, "data base file name (@1) already given"}, /* gfix_db_name */ {335740930, "invalid switch @1"}, /* gfix_invalid_sw */ {335740932, "incompatible switch combination"}, /* gfix_incmp_sw */ @@ -897,6 +908,7 @@ Data source : @4"}, /* eds_statement */ {336003110, "No SQLDA for output values provided"}, /* dsql_no_output_sqlda */ {336003111, "Wrong number of parameters (expected @1, got @2)"}, /* dsql_wrong_param_num */ {336003112, "Invalid DROP SQL SECURITY clause"}, /* dsql_invalid_drop_ss_clause */ + {336003113, "UPDATE OR INSERT value for field @1, part of the implicit or explicit MATCHING clause, cannot be DEFAULT"}, /* upd_ins_cannot_default */ {336068645, "BLOB Filter @1 not found"}, /* dyn_filter_not_found */ {336068649, "Function @1 not found"}, /* dyn_func_not_found */ {336068656, "Index not found"}, /* dyn_index_not_found */ diff --git a/src/include/gen/sql_code.h b/src/include/gen/sql_code.h index ba0b149e91..092c9bbd6d 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -827,6 +827,17 @@ static const struct { {335545124, -833}, /* 804 dsql_window_cant_overr_frame */ {335545125, -833}, /* 805 dsql_window_duplicate */ {335545126, -902}, /* 806 sql_too_long */ + {335545127, -901}, /* 807 cfg_stmt_timeout */ + {335545128, -901}, /* 808 att_stmt_timeout */ + {335545129, -901}, /* 809 req_stmt_timeout */ + {335545130, -902}, /* 810 att_shut_killed */ + {335545131, -902}, /* 811 att_shut_idle */ + {335545132, -902}, /* 812 att_shut_db_down */ + {335545133, -902}, /* 813 att_shut_engine */ + {335545134, -902}, /* 814 overriding_without_identity */ + {335545135, -902}, /* 815 overriding_system_invalid */ + {335545136, -902}, /* 816 overriding_user_invalid */ + {335545137, -902}, /* 817 overriding_system_missing */ {335740929, -901}, /* 1 gfix_db_name */ {335740930, -901}, /* 2 gfix_invalid_sw */ {335740932, -901}, /* 4 gfix_incmp_sw */ @@ -893,6 +904,7 @@ static const struct { {336003110, -802}, /* 38 dsql_no_output_sqlda */ {336003111, -313}, /* 39 dsql_wrong_param_num */ {336003112, -817}, /* 40 dsql_invalid_drop_ss_clause */ + {336003113, -313}, /* 41 upd_ins_cannot_default */ {336068645, -901}, /* 37 dyn_filter_not_found */ {336068649, -901}, /* 41 dyn_func_not_found */ {336068656, -901}, /* 48 dyn_index_not_found */ diff --git a/src/include/gen/sql_state.h b/src/include/gen/sql_state.h index 4436c83cdf..06be5f599a 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -827,6 +827,17 @@ static const struct { {335545124, "42000"}, // 804 dsql_window_cant_overr_frame {335545125, "42000"}, // 805 dsql_window_duplicate {335545126, "54001"}, // 806 sql_too_long + {335545127, "HY008"}, // 807 cfg_stmt_timeout + {335545128, "HY008"}, // 808 att_stmt_timeout + {335545129, "HY008"}, // 809 req_stmt_timeout + {335545130, "08003"}, // 810 att_shut_killed + {335545131, "08003"}, // 811 att_shut_idle + {335545132, "08003"}, // 812 att_shut_db_down + {335545133, "08003"}, // 813 att_shut_engine + {335545134, "42000"}, // 814 overriding_without_identity + {335545135, "42000"}, // 815 overriding_system_invalid + {335545136, "42000"}, // 816 overriding_user_invalid + {335545137, "42000"}, // 817 overriding_system_missing {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw @@ -893,6 +904,7 @@ static const struct { {336003110, "07002"}, // 38 dsql_no_output_sqlda {336003111, "07001"}, // 39 dsql_wrong_param_num {336003112, "42000"}, // 40 dsql_invalid_drop_ss_clause + {336003113, "42000"}, // 41 upd_ins_cannot_default {336068645, "42000"}, // 37 dyn_filter_not_found {336068649, "42000"}, // 41 dyn_func_not_found {336068656, "42000"}, // 48 dyn_index_not_found diff --git a/src/isql/extract.epp b/src/isql/extract.epp index f2ed253afa..bce30e9df8 100644 --- a/src/isql/extract.epp +++ b/src/isql/extract.epp @@ -496,7 +496,9 @@ int EXTRACT_list_table(const SCHAR* relation_name, FOR GEN IN RDB$GENERATORS WITH GEN.RDB$GENERATOR_NAME = RFR.RDB$GENERATOR_NAME { - isqlGlob.printf(" GENERATED BY DEFAULT AS IDENTITY"); + isqlGlob.printf(" GENERATED %s AS IDENTITY", + (RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_BY_DEFAULT ? "BY DEFAULT" : + RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_ALWAYS ? "ALWAYS" : "")); if (!GEN.RDB$INITIAL_VALUE.NULL && GEN.RDB$INITIAL_VALUE != 0) isqlGlob.printf(" (START WITH %" SQUADFORMAT ")", GEN.RDB$INITIAL_VALUE); diff --git a/src/isql/isql.epp b/src/isql/isql.epp index 65a0d0382b..fb08616d68 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -453,6 +453,7 @@ public: ExplainPlan = false; Heading = true; BailOnError = false; + StmtTimeout = 0; ISQL_charset[0] = 0; } @@ -473,6 +474,7 @@ public: bool ExplainPlan; bool Heading; bool BailOnError; + unsigned int StmtTimeout; SCHAR ISQL_charset[MAXCHARSET_SIZE]; }; @@ -4765,7 +4767,8 @@ static processing_state frontend_set(const char* cmd, const char* const* parms, sqlda_display, //#endif sql, warning, sqlCont, heading, bail, - bulk_insert, maxrows, wrong + bulk_insert, maxrows, stmtTimeout, + wrong }; SetOptions(const optionsMap* inmap, size_t insize, int wrongval) : OptionsBase(inmap, insize, wrongval) @@ -4803,6 +4806,7 @@ static processing_state frontend_set(const char* cmd, const char* const* parms, {SetOptions::maxrows, "MAXROWS", 0}, {SetOptions::sqlCont, "ROLE", 0}, {SetOptions::sqlCont, "TRUSTED", 0}, // TRUSTED ROLE, will get DSQL error other case + {SetOptions::stmtTimeout, "LOCAL_TIMEOUT", 0}, }; // Display current set options @@ -4964,14 +4968,28 @@ static processing_state frontend_set(const char* cmd, const char* const* parms, ret = newMaxRows((*lparms[2]) ? lparms[2] : "0"); break; - default: + case SetOptions::stmtTimeout: { - TEXT msg_string[MSG_LENGTH]; - IUTILS_msg_get(VALID_OPTIONS, msg_string); - isqlGlob.printf("%s\n", msg_string); + int val = strtol(parms[2], NULL, 10); + if (val < 0) + ret = ps_ERR; + else + { + setValues.StmtTimeout = val; + ret = SKIP; + } } - setoptions.showCommands(isqlGlob.Out); - ret = ps_ERR; + break; + + default: + //{ + // TEXT msg_string[MSG_LENGTH]; + // IUTILS_msg_get(VALID_OPTIONS, msg_string); + // isqlGlob.printf("%s\n", msg_string); + //} + //setoptions.showCommands(isqlGlob.Out); + //ret = ps_ERR; + ret = CONT; // pass unknown SET command to server as is break; } @@ -5793,6 +5811,7 @@ static processing_state print_sets() print_set("Time:", setValues.Time_display); print_set("Warnings:", setValues.Warnings); print_set("Bail on error:", setValues.BailOnError); + isqlGlob.printf("%-25s%lu%s", "Local statement timeout:", setValues.StmtTimeout, NEWLINE); return SKIP; } @@ -8130,6 +8149,10 @@ static processing_state process_statement(const TEXT* str2) // check for warnings ISQL_warning(fbStatus); + global_Stmt->setTimeout(fbStatus, setValues.StmtTimeout); + if (ISQL_errmsg(fbStatus)) + setValues.StmtTimeout = 0; + // Find out what kind of statement this is const int statement_type = process_request_type(); if (!statement_type) @@ -8193,6 +8216,7 @@ static processing_state process_statement(const TEXT* str2) statement_type == isc_info_sql_stmt_set_generator)) { DB->execute(fbStatus, D__trans, 0, str2, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL); + setValues.StmtTimeout = 0; if (ISQL_errmsg(fbStatus)) { ret = ps_ERR; @@ -8235,6 +8259,7 @@ static processing_state process_statement(const TEXT* str2) // This is a non-select DML statement or trans M__trans = global_Stmt->execute(fbStatus, M__trans, NULL, NULL, NULL, NULL); + setValues.StmtTimeout = 0; if (ISQL_errmsg(fbStatus)) { // CVC: Make this conditional if it causes problems. For example @@ -8337,6 +8362,7 @@ static processing_state process_statement(const TEXT* str2) if (statement_type == isc_info_sql_stmt_exec_procedure) { global_Stmt->execute(fbStatus, M__trans, NULL, NULL, message, buffer); + setValues.StmtTimeout = 0; if (ISQL_errmsg(fbStatus)) { ret = ps_ERR; @@ -8366,6 +8392,7 @@ static processing_state process_statement(const TEXT* str2) Firebird::IResultSet* curs = global_Stmt->openCursor(fbStatus, M__trans, NULL, NULL, message, 0); + setValues.StmtTimeout = 0; if (ISQL_errmsg(fbStatus)) { return ps_ERR; @@ -8527,7 +8554,8 @@ static bool stdin_redirected() { #ifdef WIN_NT HANDLE in = GetStdHandle(STD_INPUT_HANDLE); - if (GetFileType(in) == FILE_TYPE_CHAR) //FILE_TYPE_DISK) + const DWORD file_type = GetFileType(in); + if (file_type == FILE_TYPE_CHAR || file_type == FILE_TYPE_PIPE) return false; #else if (isatty(fileno(stdin))) diff --git a/src/isql/show.epp b/src/isql/show.epp index f631af5581..ef31406b40 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -5838,7 +5838,11 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) } if (!RFR.RDB$GENERATOR_NAME.NULL) - isqlGlob.printf("Identity (by default)"); + { + isqlGlob.printf("Identity (%s)", + (RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_BY_DEFAULT ? "by default" : + RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_ALWAYS ? "always" : "")); + } // Handle defaults for columns diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp index d7103e83d6..22d3ec93c2 100644 --- a/src/jrd/Attachment.cpp +++ b/src/jrd/Attachment.cpp @@ -45,6 +45,7 @@ #include "../common/classes/MetaName.h" #include "../common/StatusArg.h" #include "../common/isc_proto.h" +#include "../common/classes/RefMutex.h" using namespace Jrd; @@ -204,7 +205,9 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb) att_dyn_req(*pool), att_charsets(*pool), att_charset_ids(*pool), - att_pools(*pool) + att_pools(*pool), + att_idle_timeout(0), + att_stmt_timeout(0) { att_internal.grow(irq_MAX); att_dyn_req.grow(drq_MAX); @@ -371,9 +374,11 @@ void Jrd::Attachment::signalCancel() } -void Jrd::Attachment::signalShutdown() +void Jrd::Attachment::signalShutdown(ISC_STATUS code) { att_flags |= ATT_shutdown; + if (getStable()) + getStable()->setShutError(code); if (att_ext_connection && att_ext_connection->isConnected()) att_ext_connection->cancelExecution(); @@ -639,7 +644,7 @@ int Jrd::Attachment::blockingAstShutdown(void* ast_object) AsyncContextHolder tdbb(dbb, FB_FUNCTION, attachment->att_id_lock); - attachment->signalShutdown(); + attachment->signalShutdown(isc_att_shut_killed); JRD_shutdown_attachment(attachment); } @@ -765,3 +770,122 @@ JAttachment* Attachment::getInterface() throw() return att_stable->getInterface(); } +unsigned int Attachment::getActualIdleTimeout() const +{ + unsigned int timeout = att_database->dbb_config->getConnIdleTimeout() * 60; + if (att_idle_timeout && (att_idle_timeout < timeout || !timeout)) + timeout = att_idle_timeout; + + return timeout; +} + +void Attachment::setupIdleTimer(bool clear) +{ + unsigned int timeout = clear ? 0 : getActualIdleTimeout(); + if (!timeout) + { + if (att_idle_timer) + att_idle_timer->reset(0); + } + else + { + if (!att_idle_timer) + att_idle_timer = FB_NEW IdleTimer(getInterface()); + + att_idle_timer->reset(timeout); + } +} + +bool Attachment::getIdleTimerTimestamp(TimeStamp& ts) const +{ + if (!att_idle_timer) + return false; + + time_t value = att_idle_timer->getExpiryTime(); + if (!value) + return false; + + struct tm* times = localtime(&value); + if (!times) + return false; + + ts = TimeStamp::encode_timestamp(times); + return true; +} + +/// Attachment::IdleTimer + +void Attachment::IdleTimer::handler() +{ + m_fireTime = 0; + if (!m_expTime) // Timer was reset to zero, do nothing + return; + + // Ensure attachment is still alive and idle + + StableAttachmentPart* stable = m_attachment->getStable(); + if (!stable) + return; + + MutexEnsureUnlock guard(*stable->getMutex(), FB_FUNCTION); + if (!guard.tryEnter()) + return; + + if (!m_expTime) + return; + + // If timer was reset to fire later, restart ITimer + time_t curTime = time(NULL); + if (curTime < m_expTime) + { + reset(m_expTime - curTime); + return; + } + + Attachment* att = stable->getHandle(); + att->signalShutdown(isc_att_shut_idle); + JRD_shutdown_attachment(att); +} + +int Attachment::IdleTimer::release() +{ + if (--refCounter == 0) + { + delete this; + return 0; + } + + return 1; +} + +void Attachment::IdleTimer::reset(unsigned int timeout) +{ + // Start timer if necessary. If timer was already started, don't restart + // (or stop) it - handler() will take care about it. + + if (!timeout) + { + m_expTime = 0; + return; + } + + const time_t curTime = time(NULL); + m_expTime = curTime + timeout; + + FbLocalStatus s; + ITimerControl* timerCtrl = Firebird::TimerInterfacePtr(); + + if (m_fireTime) + { + if (m_fireTime <= m_expTime) + return; + + timerCtrl->stop(&s, this); + check(&s); + m_fireTime = 0; + } + + timerCtrl->start(&s, this, (m_expTime - curTime) * 1000 * 1000); + check(&s); + m_fireTime = m_expTime; +} diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index b715b3dad4..1989731f37 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -152,7 +152,7 @@ class StableAttachmentPart : public Firebird::RefCounted, public Firebird::Globa { public: explicit StableAttachmentPart(Attachment* handle) - : att(handle), jAtt(NULL) + : att(handle), jAtt(NULL), shutError(0) { } Attachment* getHandle() throw() @@ -171,6 +171,7 @@ public: jAtt->detachEngine(); jAtt = ja; + shutError = 0; } Firebird::Mutex* getMutex(bool useAsync = false, bool forceAsync = false) @@ -208,9 +209,21 @@ public: void manualUnlock(ULONG& flags); void manualAsyncUnlock(ULONG& flags); + void setShutError(ISC_STATUS code) + { + if (!shutError) + shutError = code; + } + + ISC_STATUS getShutError() const + { + return shutError; + } + private: Attachment* att; JAttachment* jAtt; + ISC_STATUS shutError; // These mutexes guarantee attachment existence. After releasing both of them with possibly // zero att_use_count one should check does attachment still exists calling getHandle(). @@ -391,7 +404,7 @@ public: const Firebird::ByteChunk& chunk); void signalCancel(); - void signalShutdown(); + void signalShutdown(ISC_STATUS code); void mergeStats(); @@ -412,9 +425,69 @@ public: JAttachment* getInterface() throw(); + unsigned int getIdleTimeout() const + { + return att_idle_timeout; + } + + void setIdleTimeout(unsigned int timeOut) + { + att_idle_timeout = timeOut; + } + + unsigned int getActualIdleTimeout() const; + + unsigned int getStatementTimeout() const + { + return att_stmt_timeout; + } + + void setStatementTimeout(unsigned int timeOut) + { + att_stmt_timeout = timeOut; + } + + // evaluate new value or clear idle timer + void setupIdleTimer(bool clear); + + // returns time when idle timer will be expired, if set + bool getIdleTimerTimestamp(Firebird::TimeStamp& ts) const; + private: Attachment(MemoryPool* pool, Database* dbb); ~Attachment(); + + class IdleTimer FB_FINAL : + public Firebird::RefCntIface > + { + public: + explicit IdleTimer(JAttachment* jAtt) : + m_attachment(jAtt), + m_fireTime(0), + m_expTime(0) + { } + + // ITimer implementation + void handler(); + int release(); + + // Set timeout, seconds + void reset(unsigned int timeout); + + time_t getExpiryTime() const + { + return m_expTime; + } + + private: + Firebird::RefPtr m_attachment; + time_t m_fireTime; // when ITimer will fire, could be less than m_expTime + time_t m_expTime; // when actual idle timeout will expire + }; + + unsigned int att_idle_timeout; // seconds + unsigned int att_stmt_timeout; // milliseconds + Firebird::RefPtr att_idle_timer; }; diff --git a/src/jrd/CryptoManager.cpp b/src/jrd/CryptoManager.cpp index 7aaca51d78..5a7e70702c 100644 --- a/src/jrd/CryptoManager.cpp +++ b/src/jrd/CryptoManager.cpp @@ -1040,7 +1040,6 @@ namespace Jrd { if (page->pag_flags & Ods::crypted_page) { - fb_assert(cryptPlugin); if (!cryptPlugin) { Arg::Gds(isc_decrypt_error).copyTo(sv); @@ -1351,21 +1350,25 @@ namespace Jrd { SyncLockGuard dsGuard(&mgr->dbb.dbb_sync, SYNC_EXCLUSIVE, FB_FUNCTION); for (Attachment* att = mgr->dbb.dbb_attachments; att; att = att->att_next) { + bool found = false; for (unsigned i = 0; i < knownHolders.getCount(); ++i) { if (knownHolders[i].first == att) - goto found; + { + found = true; + break; + } } - att->signalShutdown(); -found:; + if (!found) + att->signalShutdown(0 /* no special shutdown code */); } // Loop through internal attachments list closing one missing valid holders for (unsigned i = 0; i < knownHolders.getCount(); ++i) { if (!validateHoldersGroup(knownHolders[i], keyName)) - knownHolders[i].first->signalShutdown(); + knownHolders[i].first->signalShutdown(0); } } diff --git a/src/jrd/EngineInterface.h b/src/jrd/EngineInterface.h index 1d4e3390bd..7465a171b2 100644 --- a/src/jrd/EngineInterface.h +++ b/src/jrd/EngineInterface.h @@ -205,6 +205,9 @@ public: void setCursorName(Firebird::CheckStatusWrapper* status, const char* name); unsigned getFlags(Firebird::CheckStatusWrapper* status); + unsigned int getTimeout(Firebird::CheckStatusWrapper* status); + void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + public: JStatement(dsql_req* handle, StableAttachmentPart* sa, Firebird::Array& meta); @@ -345,6 +348,11 @@ public: void detach(Firebird::CheckStatusWrapper* status); void dropDatabase(Firebird::CheckStatusWrapper* status); + unsigned int getIdleTimeout(Firebird::CheckStatusWrapper* status); + void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status); + void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + public: explicit JAttachment(StableAttachmentPart* js); @@ -433,7 +441,7 @@ public: int release(); private: - JAttachment* internalAttach(Firebird::CheckStatusWrapper* status, const char* fileName, + JAttachment* internalAttach(Firebird::CheckStatusWrapper* status, const char* const fileName, unsigned int dpbLength, const unsigned char* dpb, const UserId* existingId); Firebird::ICryptKeyCallback* cryptCallback; Firebird::IPluginConfig* pluginConfig; diff --git a/src/jrd/Mapping.cpp b/src/jrd/Mapping.cpp index f27e0e7bec..4fbca24a92 100644 --- a/src/jrd/Mapping.cpp +++ b/src/jrd/Mapping.cpp @@ -728,7 +728,7 @@ public: try { sharedMemory.reset(FB_NEW_POOL(*getDefaultMemoryPool()) - SharedMemory("fb_user_mapping", DEFAULT_SIZE, this)); + SharedMemory(USER_MAP_FILE, DEFAULT_SIZE, this)); } catch (const Exception& ex) { @@ -947,12 +947,14 @@ public: embeddedSysdba.insertByte(isc_dpb_map_attach, TRUE); embeddedSysdba.insertByte(isc_dpb_no_db_triggers, TRUE); + MAP_DEBUG(fprintf(stderr, "Attach %s\n", aliasDb)); IAttachment* att = prov->attachDatabase(&st, aliasDb, embeddedSysdba.getBufferLength(), embeddedSysdba.getBuffer()); if (st->getState() & IStatus::STATE_ERRORS) { const ISC_STATUS* s = st->getErrors(); + MAP_DEBUG(isc_print_status(s)); bool missing = fb_utils::containsErrorCode(s, isc_io_error); down = fb_utils::containsErrorCode(s, isc_shutdown); if (!(missing || down)) @@ -962,6 +964,7 @@ public: } else reset(att); + MAP_DEBUG(fprintf(stderr, "Att=%p\n", att)); return down; } diff --git a/src/jrd/Monitoring.cpp b/src/jrd/Monitoring.cpp index d697cafcb1..e1c65d47e2 100644 --- a/src/jrd/Monitoring.cpp +++ b/src/jrd/Monitoring.cpp @@ -922,6 +922,15 @@ void Monitoring::putAttachment(SnapshotData::DumpRecord& record, const Jrd::Atta temp = (attachment->att_flags & ATT_system) ? 1 : 0; record.storeInteger(f_mon_att_sys_flag, temp); + // session idle timeout, seconds + record.storeInteger(f_mon_att_idle_timeout, attachment->getIdleTimeout()); + // when idle timer expires, NULL if not running + TimeStamp idleTimer; + if (attachment->getIdleTimerTimestamp(idleTimer)) + record.storeTimestamp(f_mon_att_idle_timer, idleTimer); + // statement timeout, milliseconds + record.storeInteger(f_mon_att_stmt_timeout, attachment->getStatementTimeout()); + record.write(); if (attachment->att_database->dbb_flags & DBB_shared) @@ -1020,6 +1029,13 @@ void Monitoring::putRequest(SnapshotData::DumpRecord& record, const jrd_req* req if (request->req_transaction) record.storeInteger(f_mon_stmt_tra_id, request->req_transaction->tra_number); record.storeTimestamp(f_mon_stmt_timestamp, request->req_timestamp); + + ISC_TIMESTAMP ts; + if (request->req_timer && + request->req_timer->getExpireTimestamp(request->req_timestamp.value(), ts)) + { + record.storeTimestamp(f_mon_stmt_timer, ts); + } } else record.storeInteger(f_mon_stmt_state, mon_state_idle); @@ -1038,6 +1054,8 @@ void Monitoring::putRequest(SnapshotData::DumpRecord& record, const jrd_req* req const int stat_id = fb_utils::genUniqueId(); record.storeGlobalId(f_mon_stmt_stat_id, getGlobalId(stat_id)); + // statement timeout, milliseconds + record.storeInteger(f_mon_stmt_timeout, request->req_timeout); record.write(); putStatistics(record, request->req_stats, stat_id, stat_statement); diff --git a/src/jrd/Monitoring.h b/src/jrd/Monitoring.h index 2ab230b38b..3642054fc2 100644 --- a/src/jrd/Monitoring.h +++ b/src/jrd/Monitoring.h @@ -137,6 +137,11 @@ public: storeField(field_id, VALUE_TIMESTAMP, sizeof(ISC_TIMESTAMP), &value.value()); } + void storeTimestamp(int field_id, const ISC_TIMESTAMP& value) + { + storeField(field_id, VALUE_TIMESTAMP, sizeof(ISC_TIMESTAMP), &value); + } + void storeString(int field_id, const Firebird::string& value) { if (value.length()) diff --git a/src/jrd/Optimizer.cpp b/src/jrd/Optimizer.cpp index 896c18bafa..149932e1c2 100644 --- a/src/jrd/Optimizer.cpp +++ b/src/jrd/Optimizer.cpp @@ -1249,12 +1249,16 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in inversion[i]->used = false; const IndexScratch* const indexScratch = inversion[i]->scratch; - if (indexScratch && (indexScratch->idx->idx_runtime_flags & idx_plan_dont_use)) + if (indexScratch && + (indexScratch == navigationCandidate || + (indexScratch->idx->idx_runtime_flags & idx_plan_dont_use))) + { inversion[i]->used = true; + } } // The matches returned in this inversion are always sorted. - SortedArray matches; + SortedArray matches, navigationMatches; if (navigationCandidate) { @@ -1269,11 +1273,13 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in for (FB_SIZE_T j = 0; j < segment->matches.getCount(); j++) { - if (!matches.exist(segment->matches[j])) - matches.add(segment->matches[j]); + if (!navigationMatches.exist(segment->matches[j])) + navigationMatches.add(segment->matches[j]); } } + matches.join(navigationMatches); + // If the navigational candidate includes any matching segments, // reset the selectivity/cost prerequisites to account these matches if (matchedSegments) @@ -1338,12 +1344,14 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in } // Look if a match is already used by previous matches. - bool anyMatchAlreadyUsed = false; + bool anyMatchAlreadyUsed = false, matchUsedByNavigation = false; for (FB_SIZE_T k = 0; k < currentInv->matches.getCount(); k++) { if (matches.exist(currentInv->matches[k])) { anyMatchAlreadyUsed = true; + if (navigationMatches.exist(currentInv->matches[k])) + matchUsedByNavigation = true; break; } } @@ -1351,6 +1359,8 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in if (anyMatchAlreadyUsed && !customPlan) { currentInv->used = true; + if (matchUsedByNavigation) + continue; // If a match on this index was already used by another // index, add also the other matches from this index. for (FB_SIZE_T j = 0; j < currentInv->matches.getCount(); j++) diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 49dd4ae1ba..50bcd79230 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -940,6 +940,8 @@ ProcedureSourceNode* ProcedureSourceNode::parse(thread_db* tdbb, CompilerScratch *tdbb->getDefaultPool()); node->procedure = procedure; + node->isSubRoutine = procedure->isSubRoutine(); + node->procedureId = node->isSubRoutine ? 0 : procedure->getId(); node->stream = PAR_context(csb, &node->context); csb->csb_rpt[node->stream].csb_procedure = procedure; @@ -1108,16 +1110,17 @@ ProcedureSourceNode* ProcedureSourceNode::copy(thread_db* tdbb, NodeCopier& copi newSource->targetList = copier.copy(tdbb, targetList); } - jrd_prc* const new_procedure = - MET_lookup_procedure_id(tdbb, procedure->getId(), false, false, 0); - newSource->stream = copier.csb->nextStream(); copier.remap[stream] = newSource->stream; newSource->context = context; - newSource->procedure = new_procedure; + newSource->procedure = isSubRoutine ? procedure : + MET_lookup_procedure_id(tdbb, procedureId, false, false, 0); + newSource->isSubRoutine = isSubRoutine; + newSource->procedureId = procedureId; newSource->view = view; + CompilerScratch::csb_repeat* element = CMP_csb_element(copier.csb, newSource->stream); - element->csb_procedure = new_procedure; + element->csb_procedure = newSource->procedure; element->csb_view = newSource->view; element->csb_view_stream = copier.remap[0]; @@ -1148,10 +1151,10 @@ void ProcedureSourceNode::pass1Source(thread_db* tdbb, CompilerScratch* csb, Rse pass1(tdbb, csb); - if (!procedure->isSubRoutine()) + if (!isSubRoutine) { CMP_post_procedure_access(tdbb, csb, procedure); - CMP_post_resource(&csb->csb_resources, procedure, Resource::rsc_procedure, procedure->getId()); + CMP_post_resource(&csb->csb_resources, procedure, Resource::rsc_procedure, procedureId); } jrd_rel* const parentView = csb->csb_view; diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index 5d0d30ad5e..41be872671 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -355,6 +355,8 @@ public: targetList(NULL), in_msg(NULL), procedure(NULL), + isSubRoutine(false), + procedureId(0), view(NULL), context(0) { @@ -416,7 +418,27 @@ public: private: NestConst in_msg; + + /*** + dimitr: Referencing procedures via a pointer is not currently reliable, because + procedures can be removed from the metadata cache after ALTER/DROP. + Usually, this is prevented via the reference counting, but it's incremented + only for compiled requests. Node trees without requests (e.g. computed fields) + are not protected and may end with dead procedure pointers, causing problems + (up to crashing) when they're copied the next time. See CORE-5456 / CORE-5457. + + ExecProcedureNode is a lucky exception because it's never (directly) used in + expressions. Sub-procedures are safe too. In other cases the procedure object + must be refetched from the metadata cache while copying the node. + + A better (IMO) solution would be to add a second-level reference counting for + metadata objects since the parsing stage till either request creation or + explicit unload from the metadata cache. But we don't have clearly established + cache management policies yet, so I leave it for the other day. + ***/ jrd_prc* procedure; + bool isSubRoutine; + USHORT procedureId; jrd_rel* view; SSHORT context; }; diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 6842e63caf..44d46ff1bb 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -484,11 +484,17 @@ public: Firebird::MetaName fld_name; // Field name Firebird::MetaName fld_security_name; // security class name for field Firebird::MetaName fld_generator_name; // identity generator name + Firebird::MetaNamePair fld_source_rel_field; // Relation/field source name + Nullable fld_identity_type; public: explicit jrd_fld(MemoryPool& p) - : fld_name(p), fld_security_name(p), fld_generator_name(p) - { } + : fld_name(p), + fld_security_name(p), + fld_generator_name(p), + fld_source_rel_field(p) + { + } }; } diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index 5757573762..3a8e93e36a 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -218,6 +218,8 @@ const char CLIENT_PROCESS_NAME[] = "CLIENT_PROCESS", CURRENT_USER_NAME[] = "CURRENT_USER", CURRENT_ROLE_NAME[] = "CURRENT_ROLE", + SESSION_IDLE_TIMEOUT[] = "SESSION_IDLE_TIMEOUT", + STATEMENT_TIMEOUT[] = "STATEMENT_TIMEOUT", // SYSTEM namespace: transaction wise items TRANSACTION_ID_NAME[] = "TRANSACTION_ID", ISOLATION_LEVEL_NAME[] = "ISOLATION_LEVEL", @@ -2254,6 +2256,10 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar return NULL; resultStr = role.c_str(); } + else if (nameStr == SESSION_IDLE_TIMEOUT) + resultStr.printf("%" ULONGFORMAT, attachment->getIdleTimeout()); + else if (nameStr == STATEMENT_TIMEOUT) + resultStr.printf("%" ULONGFORMAT, attachment->getStatementTimeout()); else if (nameStr == TRANSACTION_ID_NAME) resultStr.printf("%" SQUADFORMAT, transaction->tra_number); else if (nameStr == ISOLATION_LEVEL_NAME) diff --git a/src/jrd/blp.h b/src/jrd/blp.h index f89bbfe7b8..2d56c1529d 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -242,5 +242,7 @@ static const struct {"record_version2", byte_line}, {"gen_id2", gen_id2}, // 210 {"window_win", window_win}, + {"default", relation_field}, + {"store3", store3}, {0, 0} }; diff --git a/src/jrd/blr.h b/src/jrd/blr.h index 723be2fa46..b586a5e10f 100644 --- a/src/jrd/blr.h +++ b/src/jrd/blr.h @@ -420,4 +420,7 @@ #define blr_window_win_extent_frame_value (unsigned char) 6 #define blr_window_win_exclusion (unsigned char) 7 +#define blr_default (unsigned char) 212 +#define blr_store3 (unsigned char) 213 + #endif // JRD_BLR_H diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 641f5b3936..707e411b2b 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -5911,76 +5911,7 @@ string print_key(thread_db* tdbb, jrd_rel* relation, index_desc* idx, Record* re MET_scan_relation(tdbb, relation); } - class Printer - { - public: - explicit Printer(thread_db* tdbb, const dsc* desc) - { - const int MAX_KEY_STRING_LEN = 250; - const char* const NULL_KEY_STRING = "NULL"; - - if (!desc) - { - value = NULL_KEY_STRING; - return; - } - - fb_assert(!desc->isBlob()); - - value = MOV_make_string2(tdbb, desc, ttype_dynamic); - - const int len = (int) value.length(); - const char* const str = value.c_str(); - - if (desc->isText() || desc->isDateTime()) - { - if (desc->dsc_dtype == dtype_text) - { - const char* const pad = (desc->dsc_sub_type == ttype_binary) ? "\0": " "; - value.rtrim(pad); - } - - if (desc->isText() && desc->getTextType() == ttype_binary) - { - string hex; - char* s = hex.getBuffer(2 * len); - for (int i = 0; i < len; i++) - { - sprintf(s, "%02X", (int) (unsigned char) str[i]); - s += 2; - } - value = "x'" + hex + "'"; - } - else - { - value = "'" + value + "'"; - } - } - - if (value.length() > MAX_KEY_STRING_LEN) - { - fb_assert(desc->isText()); - - value.resize(MAX_KEY_STRING_LEN); - - const CharSet* const cs = INTL_charset_lookup(tdbb, desc->getCharSet()); - - while (value.hasData() && !cs->wellFormed(value.length(), (const UCHAR*) value.c_str())) - value.resize(value.length() - 1); - - value += "..."; - } - } - - const string& get() const - { - return value; - } - - private: - string value; - }; - + const int MAX_KEY_STRING_LEN = 250; string key, value; try @@ -5989,7 +5920,7 @@ string print_key(thread_db* tdbb, jrd_rel* relation, index_desc* idx, Record* re { bool notNull = false; const dsc* const desc = BTR_eval_expression(tdbb, idx, record, notNull); - value = Printer(tdbb, notNull ? desc : NULL).get(); + value = DescPrinter(tdbb, notNull ? desc : NULL, MAX_KEY_STRING_LEN).get(); key += " = " + value; } else @@ -6008,7 +5939,7 @@ string print_key(thread_db* tdbb, jrd_rel* relation, index_desc* idx, Record* re dsc desc; const bool notNull = EVL_field(relation, record, field_id, &desc); - value = Printer(tdbb, notNull ? &desc : NULL).get(); + value = DescPrinter(tdbb, notNull ? &desc : NULL, MAX_KEY_STRING_LEN).get(); key += " = " + value; if (i < idx->idx_count - 1) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index ecb60f9f1e..345837b0ec 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:514 + FORMAL BUILD NUMBER:582 */ -#define PRODUCT_VER_STRING "4.0.0.514" -#define FILE_VER_STRING "WI-T4.0.0.514" -#define LICENSE_VER_STRING "WI-T4.0.0.514" -#define FILE_VER_NUMBER 4, 0, 0, 514 +#define PRODUCT_VER_STRING "4.0.0.582" +#define FILE_VER_STRING "WI-T4.0.0.582" +#define LICENSE_VER_STRING "WI-T4.0.0.582" +#define FILE_VER_NUMBER 4, 0, 0, 582 #define FB_MAJOR_VER "4" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "514" +#define FB_BUILD_NO "582" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 4.0 Unstable" diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 89bfee7074..e9ffc856cd 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -5754,6 +5754,10 @@ static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ (UCHAR*) RFR.RDB$GENERATOR_NAME, n); } + n = RFR.RDB$IDENTITY_TYPE; + if (!RFR.RDB$IDENTITY_TYPE.NULL) + put_summary_record(tdbb, blob, RSR_field_identity_type, (UCHAR*) &n, sizeof(n)); + // Make a temporary field block TemporaryField* tfb = FB_NEW_POOL(*tdbb->getDefaultPool()) TemporaryField; @@ -5827,7 +5831,7 @@ static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID, collation)) { - if (REL.RDB$FORMAT.NULL) + if (null_view && REL.RDB$FORMAT.NULL) DPM_delete_relation(tdbb, relation); ERR_post(Arg::Gds(isc_no_meta_update) << @@ -5837,7 +5841,7 @@ static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ // Make sure the text type specified is implemented if (!validate_text_type(tdbb, tfb)) { - if (REL.RDB$FORMAT.NULL) + if (null_view && REL.RDB$FORMAT.NULL) DPM_delete_relation(tdbb, relation); ERR_post_nothrow(Arg::Gds(isc_no_meta_update) << diff --git a/src/jrd/extds/ExtDS.cpp b/src/jrd/extds/ExtDS.cpp index 81fdbe1fe0..a14593ae89 100644 --- a/src/jrd/extds/ExtDS.cpp +++ b/src/jrd/extds/ExtDS.cpp @@ -849,6 +849,11 @@ void Statement::prepare(thread_db* tdbb, Transaction* tran, const string& sql, b m_preparedByReq = m_callerPrivileges ? tdbb->getRequest() : NULL; } +void Statement::setTimeout(thread_db* tdbb, unsigned int timeout) +{ + doSetTimeout(tdbb, timeout); +} + void Statement::execute(thread_db* tdbb, Transaction* tran, const MetaName* const* in_names, const ValueListNode* in_params, const ValueListNode* out_params) diff --git a/src/jrd/extds/ExtDS.h b/src/jrd/extds/ExtDS.h index 00759a6d55..ef38b596b5 100644 --- a/src/jrd/extds/ExtDS.h +++ b/src/jrd/extds/ExtDS.h @@ -318,6 +318,7 @@ public: Transaction* getTransaction() { return m_transaction; } void prepare(Jrd::thread_db* tdbb, Transaction* tran, const Firebird::string& sql, bool named); + void setTimeout(Jrd::thread_db* tdbb, unsigned int timeout); void execute(Jrd::thread_db* tdbb, Transaction* tran, const Firebird::MetaName* const* in_names, const Jrd::ValueListNode* in_params, const Jrd::ValueListNode* out_params); @@ -352,6 +353,7 @@ public: protected: virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql) = 0; + virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout) = 0; virtual void doExecute(Jrd::thread_db* tdbb) = 0; virtual void doOpen(Jrd::thread_db* tdbb) = 0; virtual bool doFetch(Jrd::thread_db* tdbb) = 0; diff --git a/src/jrd/extds/InternalDS.cpp b/src/jrd/extds/InternalDS.cpp index 7e0e08d35e..e868604f17 100644 --- a/src/jrd/extds/InternalDS.cpp +++ b/src/jrd/extds/InternalDS.cpp @@ -507,6 +507,21 @@ void InternalStatement::doPrepare(thread_db* tdbb, const string& sql) } +void InternalStatement::doSetTimeout(thread_db* tdbb, unsigned int timeout) +{ + FbLocalStatus status; + + { + EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); + + m_request->setTimeout(&status, timeout); + } + + if (status->getState() & IStatus::STATE_ERRORS) + raise(&status, tdbb, "JStatement::setTimeout"); +} + + void InternalStatement::doExecute(thread_db* tdbb) { JTransaction* transaction = getIntTransaction()->getJrdTran(); diff --git a/src/jrd/extds/InternalDS.h b/src/jrd/extds/InternalDS.h index e0a7ba7897..1b47255100 100644 --- a/src/jrd/extds/InternalDS.h +++ b/src/jrd/extds/InternalDS.h @@ -130,6 +130,7 @@ protected: protected: virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql); + virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout); virtual void doExecute(Jrd::thread_db* tdbb); virtual void doOpen(Jrd::thread_db* tdbb); virtual bool doFetch(Jrd::thread_db* tdbb); diff --git a/src/jrd/extds/IscDS.cpp b/src/jrd/extds/IscDS.cpp index b98bc170ac..b11eb163ab 100644 --- a/src/jrd/extds/IscDS.cpp +++ b/src/jrd/extds/IscDS.cpp @@ -126,16 +126,24 @@ void IscConnection::attach(thread_db* tdbb, const PathName& dbName, const MetaNa EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); ICryptKeyCallback* cb = tdbb->getAttachment()->att_crypt_callback; - m_iscProvider.fb_database_crypt_callback(&status, cb); - if (status->getState() & IStatus::STATE_ERRORS) { - raise(&status, tdbb, "crypt_callback"); - } + try + { + m_iscProvider.fb_database_crypt_callback(&status, cb); + if (status->getState() & IStatus::STATE_ERRORS) { + raise(&status, tdbb, "crypt_callback"); + } - m_iscProvider.isc_attach_database(&status, m_dbName.length(), m_dbName.c_str(), - &m_handle, newDpb.getBufferLength(), - reinterpret_cast(newDpb.getBuffer())); - if (status->getState() & IStatus::STATE_ERRORS) { - raise(&status, tdbb, "attach"); + m_iscProvider.isc_attach_database(&status, m_dbName.length(), m_dbName.c_str(), + &m_handle, newDpb.getBufferLength(), + reinterpret_cast(newDpb.getBuffer())); + if (status->getState() & IStatus::STATE_ERRORS) { + raise(&status, tdbb, "attach"); + } + } + catch (const Exception&) + { + m_iscProvider.fb_database_crypt_callback(&status, NULL); + throw; } m_iscProvider.fb_database_crypt_callback(&status, NULL); @@ -485,6 +493,26 @@ void IscStatement::doPrepare(thread_db* tdbb, const string& sql) } } +void IscStatement::doSetTimeout(thread_db* tdbb, unsigned int timeout) +{ + FbLocalStatus status; + + { + EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); + m_iscProvider.fb_dsql_set_timeout(&status, &m_handle, timeout); + } + + if (status->getState() & IStatus::STATE_ERRORS) + { + // silently ignore error if timeouts is not supported by remote server + // or loaded client library + if (status[0] == isc_arg_gds && (status[1] == isc_wish_list || status[1] == isc_unavailable)) + return; + + raise(&status, tdbb, "fb_dsql_set_timeout"); + } +} + void IscStatement::doExecute(thread_db* tdbb) { FB_API_HANDLE& h_tran = getIscTransaction()->getAPIHandle(); @@ -1495,6 +1523,16 @@ ISC_STATUS ISC_EXPORT IscProvider::fb_database_crypt_callback(FbStatusVector* us return notImplemented(user_status); } +ISC_STATUS ISC_EXPORT IscProvider::fb_dsql_set_timeout(Jrd::FbStatusVector* user_status, + isc_stmt_handle* stmt_handle, + ULONG timeout) +{ + if (m_api.fb_dsql_set_timeout) + return m_api.fb_dsql_set_timeout(IscStatus(user_status), stmt_handle, timeout); + + return notImplemented(user_status); +} + void IscProvider::loadAPI() { FbLocalStatus status; @@ -1587,7 +1625,8 @@ static FirebirdApiPointers isc_callbacks = PROTO(isc_service_query), PROTO(isc_service_start), PROTO(fb_cancel_operation), - PROTO(fb_database_crypt_callback) + PROTO(fb_database_crypt_callback), + PROTO(fb_dsql_set_timeout) }; diff --git a/src/jrd/extds/IscDS.h b/src/jrd/extds/IscDS.h index e330170ba7..f90263e9af 100644 --- a/src/jrd/extds/IscDS.h +++ b/src/jrd/extds/IscDS.h @@ -485,6 +485,10 @@ public: virtual ISC_STATUS ISC_EXPORT fb_database_crypt_callback(Jrd::FbStatusVector*, void*); + + virtual ISC_STATUS API_ROUTINE fb_dsql_set_timeout(Jrd::FbStatusVector*, + isc_stmt_handle*, + ULONG); }; @@ -576,6 +580,7 @@ protected: protected: virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql); + virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout); virtual void doExecute(Jrd::thread_db* tdbb); virtual void doOpen(Jrd::thread_db* tdbb); virtual bool doFetch(Jrd::thread_db* tdbb); diff --git a/src/jrd/fields.h b/src/jrd/fields.h index 0189ddc18f..ce88bdaeca 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -195,3 +195,8 @@ FIELD(fld_system_privileges, nam_system_privileges, dtype_text, 8 , dsc_text_type_fixed , dflt_no_privs, true) FIELD(fld_b_sql_security, nam_sql_security , dtype_boolean , 1 , 0 , NULL , true) + + FIELD(fld_idle_timeout , nam_idle_timeout , dtype_long , sizeof(SLONG) , 0 , NULL , false) + FIELD(fld_idle_timer , nam_idle_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true) + FIELD(fld_stmt_timeout , nam_stmt_timeout , dtype_long , sizeof(SLONG) , 0 , NULL , false) + FIELD(fld_stmt_timer , nam_stmt_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true) diff --git a/src/jrd/filters.cpp b/src/jrd/filters.cpp index aa0a10d6ae..79854a01ae 100644 --- a/src/jrd/filters.cpp +++ b/src/jrd/filters.cpp @@ -39,6 +39,7 @@ #include "../yvalve/gds_proto.h" #include "../jrd/intl_proto.h" #include "../jrd/DebugInterface.h" +#include "../jrd/mov_proto.h" using namespace Firebird; using namespace Jrd; @@ -297,38 +298,123 @@ ISC_STATUS filter_format(USHORT action, BlobControl* control) * Get next segment from a record format blob. * **************************************/ - // Unless this is a get segment call, just return success - if (action != isc_blob_filter_get_segment) - return FB_SUCCESS; + if (action != isc_blob_filter_open) + return string_filter(action, control); - // Try to get next descriptor - Ods::Descriptor desc; - memset(&desc, 0, sizeof(desc)); + // 1 2 3 4 5 6 + // 12345678901234567890123456789012345678901234567890123456789012345678 + const char* head1 = " id offset type length sub_type flags"; // descriptors + const char* sep1 = "--- ------ -------------- ------ -------- -----"; - USHORT length; - const ISC_STATUS status = caller(isc_blob_filter_get_segment, control, - sizeof(desc), reinterpret_cast(&desc), &length); + const char* head2 = " id type length default value"; // defaults + const char* sep2 = "--- -------------- ------ -----------------------------------"; + + const char* fmt1 = "%3d %6d %2d %-11s %6d %8d 0x%02x"; // descriptors + const char* fmt2 = "%3d %2d %-11s %6d %s"; // defaults + + const char* name1 = "Fields:"; + const char* name2 = "Defaults:"; + + string str; + USHORT length; + ISC_STATUS status; + + // read number of fields descriptors + + USHORT num; + status = caller(isc_blob_filter_get_segment, control, + sizeof(USHORT), reinterpret_cast(&num), &length); if (status != FB_SUCCESS && status != isc_segment) return status; - char buffer[256]; + if (num) + { + string_put(control, name1); + string_put(control, head1); + string_put(control, sep1); + } - sprintf(buffer, "%5d: type=%d (%s) length=%d sub_type=%d flags=0x%X", - desc.dsc_offset, - desc.dsc_dtype, - desc.dsc_dtype >= DTYPE_TYPE_MAX ? "unknown" : dtypes[desc.dsc_dtype], - desc.dsc_length, - desc.dsc_sub_type, - desc.dsc_flags); + // read fields descriptors + for (int id = 0; num; --num, id++) + { + Ods::Descriptor desc; + memset(&desc, 0, sizeof(desc)); - length = static_cast(strlen(buffer)); - if (length > control->ctl_buffer_length) - length = control->ctl_buffer_length; + status = caller(isc_blob_filter_get_segment, control, + sizeof(desc), reinterpret_cast(&desc), &length); + if (status != FB_SUCCESS && status != isc_segment) + return status; - control->ctl_segment_length = length; - memcpy(control->ctl_buffer, buffer, length); + str.printf(fmt1, + id, + desc.dsc_offset, + desc.dsc_dtype, + desc.dsc_dtype >= DTYPE_TYPE_MAX ? "unknown" : dtypes[desc.dsc_dtype], + desc.dsc_length, + desc.dsc_sub_type, + desc.dsc_flags); + string_put(control, str.c_str()); + } + + // read number of default values + num = 0; + status = caller(isc_blob_filter_get_segment, control, + sizeof(USHORT), reinterpret_cast(&num), &length); + if (status != FB_SUCCESS && status != isc_segment) + return status; + + if (num) + { + string_put(control, ""); + string_put(control, name2); + string_put(control, head2); + string_put(control, sep2); + } + + // defaults descriptors and values + for (; num; --num) + { + USHORT fieldId; + status = caller(isc_blob_filter_get_segment, control, + sizeof(fieldId), reinterpret_cast(&fieldId), &length); + if (status != FB_SUCCESS && status != isc_segment) + return status; + + Ods::Descriptor desc; + memset(&desc, 0, sizeof(desc)); + + status = caller(isc_blob_filter_get_segment, control, + sizeof(desc), reinterpret_cast(&desc), &length); + if (status != FB_SUCCESS && status != isc_segment) + return status; + + UCharBuffer buff; + UCHAR* pBuff = buff.getBuffer(desc.dsc_length); + + status = caller(isc_blob_filter_get_segment, control, + desc.dsc_length, pBuff, &length); + if (status != FB_SUCCESS && status != isc_segment) + return status; + + dsc d; + d = desc; + d.dsc_address = pBuff; + + DescPrinter val(JRD_get_thread_data(), &d, 32); + + str.printf(fmt2, + fieldId, + desc.dsc_dtype, + desc.dsc_dtype >= DTYPE_TYPE_MAX ? "unknown" : dtypes[desc.dsc_dtype], + desc.dsc_length, + val.get().c_str()); + + string_put(control, str.c_str()); + } + + control->ctl_data[1] = control->ctl_data[0]; return FB_SUCCESS; } @@ -449,6 +535,10 @@ ISC_STATUS filter_runtime(USHORT action, BlobControl* control) sprintf(line, " field_generator_name: %s", p); break; + case RSR_field_identity_type: + sprintf(line, "Field identity type: %d", n); + break; + default: sprintf(line, "*** unknown verb %d ***", (int) buff[0]); } diff --git a/src/jrd/ibase.h b/src/jrd/ibase.h index 1b601a7833..76bb1cc842 100644 --- a/src/jrd/ibase.h +++ b/src/jrd/ibase.h @@ -475,6 +475,10 @@ ISC_STATUS ISC_EXPORT isc_dsql_sql_info(ISC_STATUS*, short, ISC_SCHAR*); +ISC_STATUS ISC_EXPORT fb_dsql_set_timeout(ISC_STATUS*, + isc_stmt_handle*, + ISC_ULONG); + void ISC_EXPORT isc_encode_date(const void*, ISC_QUAD*); diff --git a/src/jrd/inf.cpp b/src/jrd/inf.cpp index 003d87b95c..66e3c97636 100644 --- a/src/jrd/inf.cpp +++ b/src/jrd/inf.cpp @@ -238,7 +238,7 @@ void INF_database_info(thread_db* tdbb, const UCHAR* const end_items = items + item_length; const UCHAR* const end = info + output_length; - const Jrd::Attachment* const err_att = tdbb->getAttachment(); + const Jrd::Attachment* const att = tdbb->getAttachment(); while (items < end_items && *items != isc_info_end) { @@ -633,7 +633,7 @@ void INF_database_info(thread_db* tdbb, case fb_info_tpage_warns: case fb_info_pip_errors: case fb_info_pip_warns: - err_val = (err_att->att_validation) ? err_att->att_validation->getInfo(item) : 0; + err_val = (att->att_validation) ? att->att_validation->getInfo(item) : 0; length = INF_convert(err_val, buffer); break; @@ -769,6 +769,26 @@ void INF_database_info(thread_db* tdbb, dbb->dbb_crypto_manager->getCurrentState() : 0, buffer); break; + case fb_info_statement_timeout_db: + length = INF_convert(dbb->dbb_config->getStatementTimeout(), buffer); + break; + + case fb_info_statement_timeout_att: + length = INF_convert(att->getStatementTimeout(), buffer); + break; + + case fb_info_ses_idle_timeout_db: + length = INF_convert(dbb->dbb_config->getConnIdleTimeout() * 60, buffer); + break; + + case fb_info_ses_idle_timeout_att: + length = INF_convert(att->getIdleTimeout(), buffer); + break; + + case fb_info_ses_idle_timeout_run: + length = INF_convert(att->getActualIdleTimeout(), buffer); + break; + default: buffer[0] = item; item = isc_info_error; diff --git a/src/jrd/inf_pub.h b/src/jrd/inf_pub.h index 9c3b0c2e85..9bea487d74 100644 --- a/src/jrd/inf_pub.h +++ b/src/jrd/inf_pub.h @@ -142,6 +142,13 @@ enum db_info_types fb_info_crypt_state = 126, + fb_info_statement_timeout_db, + fb_info_statement_timeout_att, + + fb_info_ses_idle_timeout_db, + fb_info_ses_idle_timeout_att, + fb_info_ses_idle_timeout_run, + isc_info_db_last_value /* Leave this LAST! */ }; @@ -419,6 +426,8 @@ enum info_db_provider #define isc_info_sql_relation_alias 25 #define isc_info_sql_explain_plan 26 #define isc_info_sql_stmt_flags 27 +#define isc_info_sql_stmt_timeout_user 28 +#define isc_info_sql_stmt_timeout_run 29 /*********************************/ /* SQL information return values */ diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 957bbc012a..1643b704af 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -680,14 +680,21 @@ namespace // with the flag set cause shutdownMutex mutex is not locked here. // That's not a danger cause check of att_use_count // in shutdown code makes it anyway safe. - status_exception::raise(Arg::Gds(isc_att_shutdown)); + Arg::Gds err(isc_att_shutdown); + if (sAtt->getShutError()) + err << Arg::Gds(sAtt->getShutError()); + + err.raise(); } tdbb->setAttachment(attachment); tdbb->setDatabase(attachment->att_database); if (!async) + { attachment->att_use_count++; + attachment->setupIdleTimer(true); + } } catch (const Firebird::Exception&) { @@ -709,7 +716,11 @@ namespace Jrd::Attachment* attachment = sAtt->getHandle(); if (attachment && !async) + { attachment->att_use_count--; + if (!attachment->att_use_count) + attachment->setupIdleTimer(false); + } if (!nolock) sAtt->getMutex(async)->leave(); @@ -1322,7 +1333,7 @@ JAttachment* JProvider::attachDatabase(CheckStatusWrapper* user_status, const ch return internalAttach(user_status, filename, dpb_length, dpb, NULL); } -JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const char* filename, +JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const char* const filename, unsigned int dpb_length, const unsigned char* dpb, const UserId* existingId) { /************************************** @@ -1647,7 +1658,7 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch jAtt->getStable()->manualUnlock(attachment->att_flags); try { - getUserInfo(userId, options, org_filename.c_str(), expanded_name.c_str(), + getUserInfo(userId, options, filename, expanded_name.c_str(), &config, false, jAtt, cryptCallback); } catch(const Exception&) @@ -1684,8 +1695,12 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch if (attachment->att_flags & ATT_shutdown) { + const ISC_STATUS err = jAtt->getStable()->getShutError(); + if (dbb->dbb_ast_flags & DBB_shutdown) ERR_post(Arg::Gds(isc_shutdown) << Arg::Str(org_filename)); + else if (err) + ERR_post(Arg::Gds(isc_att_shutdown) << Arg::Gds(err)); else ERR_post(Arg::Gds(isc_att_shutdown)); } @@ -1850,6 +1865,7 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch CCH_release_exclusive(tdbb); + attachment->att_trace_manager->activate(); if (attachment->att_trace_manager->needs(ITraceFactory::TRACE_EVENT_ATTACH)) { TraceConnectionImpl conn(attachment); @@ -2474,7 +2490,7 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch ERR_post(Arg::Gds(isc_unavailable)); // Check for correct credentials supplied - getUserInfo(userId, options, org_filename.c_str(), NULL, &config, true, nullptr, cryptCallback); + getUserInfo(userId, options, filename, NULL, &config, true, nullptr, cryptCallback); #ifdef WIN_NT guardDbInit.enter(); // Required to correctly expand name of just created database @@ -2784,6 +2800,7 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch guardDbInit.leave(); // Report that we created attachment to Trace API + attachment->att_trace_manager->activate(); if (attachment->att_trace_manager->needs(ITraceFactory::TRACE_EVENT_ATTACH)) { TraceConnectionImpl conn(attachment); @@ -2918,7 +2935,15 @@ void JAttachment::freeEngineData(CheckStatusWrapper* user_status, bool forceFree if (forceFree) flags |= PURGE_NOCHECK; - attachment->signalShutdown(); + ISC_STATUS reason = 0; + if (!forceFree) + reason = 0; + else if (engineShutdown) + reason = isc_att_shut_engine; + else if (dbb->dbb_ast_flags & DBB_shutdown) + reason = isc_att_shut_db_down; + + attachment->signalShutdown(reason); purge_attachment(tdbb, getStable(), flags); att->release(); @@ -2988,15 +3013,15 @@ void JAttachment::dropDatabase(CheckStatusWrapper* user_status) if (attachment->att_flags & ATT_shutdown) { + const ISC_STATUS err = getStable()->getShutError(); + if (dbb->dbb_ast_flags & DBB_shutdown) - { ERR_post(Arg::Gds(isc_shutdown) << Arg::Str(file_name)); - } + else if (err) + ERR_post(Arg::Gds(isc_att_shutdown) << Arg::Gds(err)); else - { ERR_post(Arg::Gds(isc_att_shutdown)); } - } if (!CCH_exclusive(tdbb, LCK_PW, WAIT_PERIOD, NULL)) { @@ -4334,6 +4359,82 @@ void JAttachment::transactRequest(CheckStatusWrapper* user_status, ITransaction* successful_completion(user_status); } +unsigned int JAttachment::getIdleTimeout(Firebird::CheckStatusWrapper* user_status) +{ + unsigned int result = 0; + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + result = getHandle()->getIdleTimeout(); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return 0; + } + + successful_completion(user_status); + return result; +} + +void JAttachment::setIdleTimeout(Firebird::CheckStatusWrapper* user_status, unsigned int timeOut) +{ + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + getHandle()->setIdleTimeout(timeOut); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return; + } + + successful_completion(user_status); +} + +unsigned int JAttachment::getStatementTimeout(Firebird::CheckStatusWrapper* user_status) +{ + unsigned int result = 0; + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + result = getHandle()->getStatementTimeout(); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return 0; + } + + successful_completion(user_status); + return result; +} + +void JAttachment::setStatementTimeout(Firebird::CheckStatusWrapper* user_status, unsigned int timeOut) +{ + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + getHandle()->setStatementTimeout(timeOut); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return; + } + + successful_completion(user_status); +} + void JTransaction::getInfo(CheckStatusWrapper* user_status, unsigned int itemsLength, const unsigned char* items, @@ -5290,6 +5391,66 @@ void JStatement::getInfo(CheckStatusWrapper* user_status, successful_completion(user_status); } + +unsigned int JStatement::getTimeout(CheckStatusWrapper* user_status) +{ + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + try + { + Jrd::dsql_req* req = getHandle(); + return req->getTimeout(); + } + catch (const Exception& ex) + { + transliterateException(tdbb, ex, user_status, FB_FUNCTION); + return 0; + } + trace_warning(tdbb, user_status, FB_FUNCTION); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return 0; + } + + successful_completion(user_status); + return 0; +} + + +void JStatement::setTimeout(CheckStatusWrapper* user_status, unsigned int timeOut) +{ + try + { + EngineContextHolder tdbb(user_status, this, FB_FUNCTION); + check_database(tdbb); + + try + { + Jrd::dsql_req* req = getHandle(); + req->setTimeout(timeOut); + } + catch (const Exception& ex) + { + transliterateException(tdbb, ex, user_status, FB_FUNCTION); + return; + } + trace_warning(tdbb, user_status, FB_FUNCTION); + } + catch (const Exception& ex) + { + ex.stuffException(user_status); + return; + } + + successful_completion(user_status); +} + + void JAttachment::ping(CheckStatusWrapper* user_status) { /************************************** @@ -5445,7 +5606,11 @@ static void check_database(thread_db* tdbb, bool async) } else { - status_exception::raise(Arg::Gds(isc_att_shutdown)); + Arg::Gds err(isc_att_shutdown); + if (attachment->getStable() && attachment->getStable()->getShutError()) + err << Arg::Gds(attachment->getStable()->getShutError()); + + err.raise(); } } @@ -7343,7 +7508,7 @@ namespace Attachment* attachment = sAtt->getHandle(); if (attachment) - attachment->signalShutdown(); + attachment->signalShutdown(isc_att_shut_engine); } } @@ -7483,6 +7648,139 @@ static THREAD_ENTRY_DECLARE shutdown_thread(THREAD_ENTRY_PARAM arg) } +/// TimeoutTimer +#ifdef USE_ITIMER +void TimeoutTimer::handler() +{ + m_expired = true; + m_started = 0; +} + +int TimeoutTimer::release() +{ + if (--refCounter == 0) + { + delete this; + return 0; + } + + return 1; +} + +unsigned int TimeoutTimer::timeToExpire() const +{ + if (!m_started || m_expired) + return 0; + + const SINT64 t = fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency(); + const SINT64 r = m_started + m_value - t; + return r > 0 ? r : 0; +} + +bool TimeoutTimer::getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const +{ + if (!m_started || m_expired) + return false; + + static const SINT64 ISC_TICKS_PER_DAY = 24 * 60 * 60 * ISC_TIME_SECONDS_PRECISION; + + SINT64 ticks = start.timestamp_date * ISC_TICKS_PER_DAY + start.timestamp_time; + ticks += m_value * ISC_TIME_SECONDS_PRECISION / 1000; + + exp.timestamp_date = ticks / ISC_TICKS_PER_DAY; + exp.timestamp_time = ticks % ISC_TICKS_PER_DAY; + + return true; +} + +void TimeoutTimer::start() +{ + FbLocalStatus s; + ITimerControl* timerCtrl = Firebird::TimerInterfacePtr(); + + m_expired = false; + + // todo: timerCtrl->restart to avoid 2 times acquire timerCtrl mutex + + if (m_started) + { + timerCtrl->stop(&s, this); + m_started = 0; + } + + if (m_value != 0) + { + timerCtrl->start(&s, this, m_value * 1000); + check(&s); // ?? todo + m_started = fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency(); + } + + fb_assert(m_value && m_started || !m_value && !m_started); +} + +void TimeoutTimer::stop() +{ + if (m_started) + { + m_started = 0; + + FbLocalStatus s; + ITimerControl* timerCtrl = Firebird::TimerInterfacePtr(); + timerCtrl->stop(&s, this); + } +} +#else +bool TimeoutTimer::expired() const +{ + if (!m_start) + return false; + + const SINT64 t = currTime(); + return t > m_start + m_value; +} + +unsigned int TimeoutTimer::timeToExpire() const +{ + if (!m_start) + return 0; + + const SINT64 t = currTime(); + const SINT64 r = m_start + m_value - t; + return r > 0 ? r : 0; +} + +bool TimeoutTimer::getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const +{ + if (!m_start) + return false; + + static const SINT64 ISC_TICKS_PER_DAY = 24 * 60 * 60 * ISC_TIME_SECONDS_PRECISION; + + SINT64 ticks = start.timestamp_date * ISC_TICKS_PER_DAY + start.timestamp_time; + ticks += m_value * ISC_TIME_SECONDS_PRECISION / 1000; + + exp.timestamp_date = ticks / ISC_TICKS_PER_DAY; + exp.timestamp_time = ticks % ISC_TICKS_PER_DAY; + + return true; +} + +void TimeoutTimer::start() +{ + m_start = 0; + + if (m_value != 0) + m_start = currTime(); +} + +void TimeoutTimer::stop() +{ + m_start = 0; +} + + +#endif // USE_ITIMER + // begin thread_db methods void thread_db::setDatabase(Database* val) @@ -7520,7 +7818,7 @@ SSHORT thread_db::getCharSet() const return attachment->att_charset; } -ISC_STATUS thread_db::checkCancelState() +ISC_STATUS thread_db::checkCancelState(ISC_STATUS* secondary) { // Test for asynchronous shutdown/cancellation requests. // But do that only if we're neither in the verb cleanup state @@ -7540,7 +7838,12 @@ ISC_STATUS thread_db::checkCancelState() if (database->dbb_ast_flags & DBB_shutdown) return isc_shutdown; else if (!(tdbb_flags & TDBB_shutdown_manager)) + { + if (secondary) + *secondary = attachment->getStable() ? attachment->getStable()->getShutError() : 0; + return isc_att_shutdown; + } } // If a cancel has been raised, defer its acknowledgement @@ -7561,6 +7864,14 @@ ISC_STATUS thread_db::checkCancelState() } } + if (tdbb_reqTimer && tdbb_reqTimer->expired()) + { + if (secondary) + *secondary = tdbb_reqTimer->getErrCode(); + + return isc_cancelled; + } + // Check the thread state for already posted system errors. If any still persists, // then someone tries to ignore our attempts to interrupt him. Let's insist. @@ -7572,7 +7883,8 @@ ISC_STATUS thread_db::checkCancelState() bool thread_db::checkCancelState(bool punt) { - const ISC_STATUS error = checkCancelState(); + ISC_STATUS secondary = 0; + const ISC_STATUS error = checkCancelState(&secondary); if (!error) return false; @@ -7582,6 +7894,9 @@ bool thread_db::checkCancelState(bool punt) if (error == isc_shutdown) status << Arg::Str(attachment->att_filename); + if (secondary) + status << Arg::Gds(secondary); + if (attachment) attachment->att_flags &= ~ATT_cancel_raise; diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index ddfcadfa95..d07c209fe1 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -349,6 +349,110 @@ const USHORT WIN_garbage_collector = 4; // garbage collector's window const USHORT WIN_garbage_collect = 8; // scan left a page for garbage collector +#ifdef USE_ITIMER +class TimeoutTimer FB_FINAL : + public Firebird::RefCntIface > +{ +public: + explicit TimeoutTimer() + : m_started(0), + m_expired(false), + m_value(0), + m_error(0) + { } + + // ITimer implementation + void handler(); + int release(); + + bool expired() const + { + return m_expired; + } + + unsigned int getValue() const + { + return m_value; + } + + unsigned int getErrCode() const + { + return m_error; + } + + // milliseconds left before timer expiration + unsigned int timeToExpire() const; + + // evaluate expire timestamp using start timestamp + bool getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const; + + // set timeout value in milliseconds and secondary error code + void setup(unsigned int value, ISC_STATUS error) + { + m_value = value; + m_error = error; + } + + void start(); + void stop(); + +private: + SINT64 m_started; + bool m_expired; + unsigned int m_value; // milliseconds + ISC_STATUS m_error; +}; +#else +class TimeoutTimer : public Firebird::RefCounted +{ +public: + explicit TimeoutTimer() + : m_start(0), + m_value(0), + m_error(0) + { } + + bool expired() const; + + unsigned int getValue() const + { + return m_value; + } + + unsigned int getErrCode() const + { + return m_error; + } + + // milliseconds left before timer expiration + unsigned int timeToExpire() const; + + // evaluate expire timestamp using start timestamp + bool getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const; + + // set timeout value in milliseconds and secondary error code + void setup(unsigned int value, ISC_STATUS error) + { + m_start = 0; + m_value = value; + m_error = error; + } + + void start(); + void stop(); + +private: + SINT64 currTime() const + { + return fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency(); + } + + SINT64 m_start; + unsigned int m_value; // milliseconds + ISC_STATUS m_error; +}; +#endif // USE_ITIMER + // Thread specific database block // tdbb_flags @@ -516,9 +620,13 @@ public: attStat->bumpRelValue(index, relation_id, delta); } - ISC_STATUS checkCancelState(); + ISC_STATUS checkCancelState(ISC_STATUS* secondary = NULL); bool checkCancelState(bool punt); bool reschedule(SLONG quantum, bool punt); + const TimeoutTimer* getTimeoutTimer() const + { + return tdbb_reqTimer; + } void registerBdb(BufferDesc* bdb) { @@ -587,6 +695,37 @@ public: #endif } } + + class TimerGuard + { + public: + TimerGuard(thread_db* tdbb, TimeoutTimer* timer, bool autoStop) + : m_tdbb(tdbb), + m_autoStop(autoStop && timer) + { + fb_assert(m_tdbb->tdbb_reqTimer == NULL); + + m_tdbb->tdbb_reqTimer = timer; + if (timer && timer->expired()) + m_tdbb->tdbb_quantum = 0; + } + + ~TimerGuard() + { + if (m_autoStop) + m_tdbb->tdbb_reqTimer->stop(); + + m_tdbb->tdbb_reqTimer = NULL; + } + + private: + thread_db* m_tdbb; + bool m_autoStop; + }; + +private: + Firebird::RefPtr tdbb_reqTimer; + }; class ThreadContextHolder diff --git a/src/jrd/lck.cpp b/src/jrd/lck.cpp index 4deecf1d66..707ff8bc2a 100644 --- a/src/jrd/lck.cpp +++ b/src/jrd/lck.cpp @@ -56,6 +56,7 @@ using namespace Jrd; using namespace Firebird; +static SSHORT adjust_wait(thread_db* tdbb, SSHORT wait); static void bug_lck(const TEXT*); static bool compatible(const Lock*, const Lock*, USHORT); static void enqueue(thread_db*, CheckStatusWrapper*, Lock*, USHORT, SSHORT); @@ -340,6 +341,7 @@ bool LCK_convert(thread_db* tdbb, Lock* lock, USHORT level, SSHORT wait) WaitCancelGuard guard(tdbb, lock, wait); FbLocalStatus statusVector; + wait = adjust_wait(tdbb, wait); const bool result = CONVERT(tdbb, &statusVector, lock, level, wait); if (!result) @@ -656,6 +658,7 @@ bool LCK_lock(thread_db* tdbb, Lock* lock, USHORT level, SSHORT wait) WaitCancelGuard guard(tdbb, lock, wait); FbLocalStatus statusVector; + wait = adjust_wait(tdbb, wait); ENQUEUE(tdbb, &statusVector, lock, level, wait); fb_assert(LCK_CHECK_LOCK(lock)); @@ -855,6 +858,40 @@ void LCK_write_data(thread_db* tdbb, Lock* lock, LOCK_DATA_T data) } +static SSHORT adjust_wait(thread_db* tdbb, SSHORT wait) +{ +/************************************** + * + * a d j u s t _ w a i t + * + ************************************** + * + * Functional description + * If wait is cancellable and if statement timer was started - calc new wait + * time to ensure it will not take longer than rest of timeout. + * + **************************************/ + if ((wait == LCK_NO_WAIT) || (tdbb->tdbb_flags & TDBB_wait_cancel_disable) || !tdbb->getTimeoutTimer()) + return wait; + + unsigned int tout = tdbb->getTimeoutTimer()->timeToExpire(); + if (tout > 0) + { + SSHORT t; + if (tout < 1000) + t = 1; + else if (tout < MAX_SSHORT * 1000) + t = (tout + 999) / 1000; + else + t = MAX_SSHORT; + + if ((wait == LCK_WAIT) || (-wait > t)) + return -t; + } + return wait; +} + + static void bug_lck(const TEXT* string) { /************************************** diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 743902f45e..cfa96ae4f6 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -3972,6 +3972,18 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) } else field->fld_source = PAR_make_field(tdbb, csb, view_context, (TEXT*) p); + + { // scope + const ViewContexts& ctx = relation->rel_view_contexts; + FB_SIZE_T pos; + + if (ctx.find(view_context, pos) && + (ctx[pos]->vcx_type == VCT_TABLE || ctx[pos]->vcx_type == VCT_VIEW)) + { + field->fld_source_rel_field = MetaNamePair(ctx[pos]->vcx_relation_name, (TEXT*) p); + } + } + break; case RSR_computed_blr: @@ -4034,6 +4046,12 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) case RSR_field_generator_name: field->fld_generator_name = (const TEXT*) p; + if (!field->fld_identity_type.specified) + field->fld_identity_type = IDENT_TYPE_BY_DEFAULT; + break; + + case RSR_field_identity_type: + field->fld_identity_type = static_cast(n); break; default: // Shut up compiler warning diff --git a/src/jrd/met.h b/src/jrd/met.h index 4896177efa..944d20b553 100644 --- a/src/jrd/met.h +++ b/src/jrd/met.h @@ -50,7 +50,8 @@ enum rsr_t { RSR_field_length, RSR_field_sub_type, RSR_field_not_null, - RSR_field_generator_name + RSR_field_generator_name, + RSR_field_identity_type }; // Temporary field block diff --git a/src/jrd/mov.cpp b/src/jrd/mov.cpp index 4d587647c6..816daa1409 100644 --- a/src/jrd/mov.cpp +++ b/src/jrd/mov.cpp @@ -412,3 +412,66 @@ void MOV_move(Jrd::thread_db* tdbb, /*const*/ dsc* from, dsc* to) else CVT_move(from, to); } + +namespace Jrd +{ + +DescPrinter::DescPrinter(thread_db* tdbb, const dsc* desc, int mLen) + : maxLen(mLen) +{ + const char* const NULL_KEY_STRING = "NULL"; + + if (!desc) + { + value = NULL_KEY_STRING; + return; + } + + fb_assert(!desc->isBlob()); + + value = MOV_make_string2(tdbb, desc, ttype_dynamic); + + const int len = (int) value.length(); + const char* const str = value.c_str(); + + if (desc->isText() || desc->isDateTime()) + { + if (desc->dsc_dtype == dtype_text) + { + const char* const pad = (desc->dsc_sub_type == ttype_binary) ? "\0" : " "; + value.rtrim(pad); + } + + if (desc->isText() && desc->getTextType() == ttype_binary) + { + Firebird::string hex; + char* s = hex.getBuffer(2 * len); + + for (int i = 0; i < len; i++) + { + sprintf(s, "%02X", (int)(unsigned char) str[i]); + s += 2; + } + + value = "x'" + hex + "'"; + } + else + value = "'" + value + "'"; + } + + if (value.length() > maxLen) + { + fb_assert(desc->isText()); + + value.resize(maxLen); + + const CharSet* const cs = INTL_charset_lookup(tdbb, desc->getCharSet()); + + while (value.hasData() && !cs->wellFormed(value.length(), (const UCHAR*) value.c_str())) + value.resize(value.length() - 1); + + value += "..."; + } +} + +} // namespace Jrd diff --git a/src/jrd/mov_proto.h b/src/jrd/mov_proto.h index bb90272221..7d97076ec7 100644 --- a/src/jrd/mov_proto.h +++ b/src/jrd/mov_proto.h @@ -52,4 +52,24 @@ Firebird::string MOV_make_string2(Jrd::thread_db* tdbb, const dsc* desc, USHORT bool limit = true); void MOV_move(Jrd::thread_db*, /*const*/ dsc*, dsc*); +namespace Jrd +{ + +class DescPrinter +{ +public: + DescPrinter(thread_db* tdbb, const dsc* desc, int mLen); + + const Firebird::string& get() const + { + return value; + } + +private: + Firebird::string value; + int maxLen; +}; + +} // namespace Jrd + #endif // JRD_MOV_PROTO_H diff --git a/src/jrd/names.h b/src/jrd/names.h index 3110dc470c..9f74061f0a 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -408,3 +408,8 @@ NAME("SEC$USER_TYPE", nam_sec_user_type) NAME("RDB$SYSTEM_PRIVILEGES", nam_system_privileges) NAME("RDB$SQL_SECURITY", nam_sql_security) + +NAME("MON$IDLE_TIMEOUT", nam_idle_timeout) +NAME("MON$IDLE_TIMER", nam_idle_timer) +NAME("MON$STATEMENT_TIMEOUT", nam_stmt_timeout) +NAME("MON$STATEMENT_TIMER", nam_stmt_timer) diff --git a/src/jrd/os/win32/win9x_nt.h b/src/jrd/os/win32/win9x_nt.h deleted file mode 100644 index 0262326693..0000000000 --- a/src/jrd/os/win32/win9x_nt.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * PROGRAM: JRD Access Method - * MODULE: win9x_nt.h - * DESCRIPTION: Windows 9X IO support module - * - * This file needs to be included in winnt.cpp. It is designed to - * mimic Windows NT overlapped API when application runs on Win9X. - * - * The contents of this file are subject to the Initial - * Developer's Public License Version 1.0 (the "License"); - * you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. - * - * Software distributed under the License is distributed AS IS, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. - * See the License for the specific language governing rights - * and limitations under the License. - * - * The Original Code was created by Alexander Peshkoff - * for the Firebird Open Source RDBMS project. - * - * Copyright (c) 2009 Nikolay Samofatov - * and all contributors signed below. - * - * All Rights Reserved. - * Contributor(s): ______________________________________. - */ - -// This file needs to be explicitly included in winnt.cpp - - -#include "../common/classes/fb_pair.h" - -namespace Jrd { - -using Firebird::MutexLockGuard; - -Firebird::Mutex file_locks_mutex; - -struct LocksArrayItem { - HANDLE first; - Firebird::Mutex* second; - - LocksArrayItem() {} - LocksArrayItem(HANDLE handle, Firebird::Mutex* mutex) : first(handle), second(mutex) { } - static HANDLE generate(LocksArrayItem value) { - return value.first; - } -}; - -Firebird::SortedArray< - LocksArrayItem, - Firebird::InlineStorage, - HANDLE, - LocksArrayItem > file_locks_9X(*getDefaultMemoryPool()); - -// This header can be included in winnt.h -// It converts Win32 overlapped API calls to synchronized regular API calls -HANDLE CreateFile_9X( - LPCSTR lpFileName, - DWORD dwDesiredAccess, - DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, - DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, - HANDLE hTemplateFile) -{ - if (!ISC_is_WinNT()) - dwShareMode &= ~FILE_SHARE_DELETE; - - HANDLE file = CreateFileA( - lpFileName, - dwDesiredAccess, - dwShareMode, - lpSecurityAttributes, - dwCreationDisposition, - dwFlagsAndAttributes, - hTemplateFile); - - DWORD dwLastError = GetLastError(); - - if (!ISC_is_WinNT() && file != INVALID_HANDLE_VALUE) { - MutexLockGuard sync(file_locks_mutex); - file_locks_9X.add( - LocksArrayItem( - file, - FB_NEW Firebird::Mutex())); - } - - SetLastError(dwLastError); - return file; -} - -BOOL CloseHandle_9X(HANDLE hObject) -{ - if (!ISC_is_WinNT()) { - MutexLockGuard sync(file_locks_mutex); - size_t pos; - if (file_locks_9X.find(hObject, pos)) { - delete file_locks_9X[pos].second; - file_locks_9X.remove(pos); - } - } - - return CloseHandle(hObject); -} - -BOOL ReadFile_9X( - HANDLE hFile, - LPVOID lpBuffer, - DWORD nNumberOfBytesToRead, - LPDWORD lpNumberOfBytesRead, - LPOVERLAPPED lpOverlapped) -{ - if (ISC_is_WinNT()) - return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped); - - Firebird::Mutex* fileMutex = NULL; - { - MutexLockGuard sync(file_locks_mutex); - size_t pos; - if (file_locks_9X.find(hFile, pos)) - fileMutex = file_locks_9X[pos].second; - } - - BOOL result; - DWORD dwLastError; - { - MutexLockGuard sync(*fileMutex); - - if (lpOverlapped != NULL) { - const DWORD ret = SetFilePointer(hFile, lpOverlapped->Offset, - (PLONG)&lpOverlapped->OffsetHigh, FILE_BEGIN); - if (ret == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) - return 0; - } - - result = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, NULL); - dwLastError = GetLastError(); - } - - SetLastError(dwLastError); - return result; -} - -BOOL WriteFile_9X( - HANDLE hFile, - LPCVOID lpBuffer, - DWORD nNumberOfBytesToWrite, - LPDWORD lpNumberOfBytesWritten, - LPOVERLAPPED lpOverlapped -) -{ - if (ISC_is_WinNT()) - return WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); - - Firebird::Mutex* fileMutex = NULL; - { - MutexLockGuard sync(file_locks_mutex); - size_t pos; - if (file_locks_9X.find(hFile, pos)) - fileMutex = file_locks_9X[pos].second; - } - - BOOL result; - DWORD dwLastError; - { - MutexLockGuard sync(*fileMutex); - - if (lpOverlapped != NULL) { - const DWORD ret = SetFilePointer(hFile, lpOverlapped->Offset, - (PLONG)&lpOverlapped->OffsetHigh, FILE_BEGIN); - if (ret == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) - return 0; - lpOverlapped = NULL; - } - result = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, NULL); - dwLastError = GetLastError(); - } - - SetLastError(dwLastError); - return result; -} - - -} - -#if defined CreateFile -#undef CreateFile -#endif - -#define CreateFile CreateFile_9X -#define CloseHandle CloseHandle_9X -#define ReadFile ReadFile_9X -#define WriteFile WriteFile_9X diff --git a/src/jrd/os/win32/winnt.cpp b/src/jrd/os/win32/winnt.cpp index 69f43ce946..cad785530a 100644 --- a/src/jrd/os/win32/winnt.cpp +++ b/src/jrd/os/win32/winnt.cpp @@ -54,10 +54,6 @@ #include -#ifdef WIN9X_SUPPORT -#include "win9x_nt.h" -#endif - namespace Jrd { class FileExtendLockGuard @@ -997,13 +993,7 @@ static jrd_file* setup_file(Database* dbb, if (pageSpace && pageSpace->file) return file; -#ifdef WIN9X_SUPPORT - // Disable sophisticated file extension when running on 9X - if (ISC_is_WinNT()) -#endif - { - file->fil_ext_lock = FB_NEW_POOL(*dbb->dbb_permanent) Firebird::RWLock(); - } + file->fil_ext_lock = FB_NEW_POOL(*dbb->dbb_permanent) Firebird::RWLock(); } catch (const Firebird::Exception&) { diff --git a/src/jrd/recsrc/IndexTableScan.cpp b/src/jrd/recsrc/IndexTableScan.cpp index 97746985f3..979692dac8 100644 --- a/src/jrd/recsrc/IndexTableScan.cpp +++ b/src/jrd/recsrc/IndexTableScan.cpp @@ -318,44 +318,37 @@ int IndexTableScan::compareKeys(const index_desc* idx, // figure out what segment we're on; if it's a // character segment we've matched the partial string const UCHAR* segment = NULL; - const index_desc::idx_repeat* tail; + USHORT segnum = 0; + if (idx->idx_count > 1) { segment = key_string1 + ((length2 - 1) / (Ods::STUFF_COUNT + 1)) * (Ods::STUFF_COUNT + 1); - tail = idx->idx_rpt + (idx->idx_count - *segment); - } - else - { - tail = &idx->idx_rpt[0]; + segnum = idx->idx_count - (UCHAR) ((flags & irb_descending) ? ((*segment) ^ -1) : *segment); } // If it's a string type key, and we're allowing "starting with" fuzzy // type matching, we're done - if ((flags & irb_starting) && - (tail->idx_itype == idx_string || - tail->idx_itype == idx_byte_array || - tail->idx_itype == idx_metadata || - tail->idx_itype >= idx_first_intl_string)) + if (flags & irb_starting) { - return 0; + const index_desc::idx_repeat* const tail = idx->idx_rpt + segnum; + + if (tail->idx_itype == idx_string || + tail->idx_itype == idx_byte_array || + tail->idx_itype == idx_metadata || + tail->idx_itype >= idx_first_intl_string) + { + return 0; + } } if (idx->idx_count > 1) { // If we search for NULLs at the beginning then we're done if the first - // segment isn't the first one possible (0 for ASC, 255 for DESC). + // segment isn't the first one possible if (length2 == 0) { - if (flags & irb_descending) - { - if (*segment != 255) - return 0; - } - else - { - if (*segment != 0) - return 0; - } + if (segnum != 0) + return 0; } // if we've exhausted the segment, we've found a match diff --git a/src/jrd/relations.h b/src/jrd/relations.h index 1f65369e5f..524116d27a 100644 --- a/src/jrd/relations.h +++ b/src/jrd/relations.h @@ -517,6 +517,9 @@ RELATION(nam_mon_attachments, rel_mon_attachments, ODS_11_1, rel_virtual) FIELD(f_mon_att_remote_os_user, nam_mon_remote_os_user, fld_os_user, 0, ODS_12_0) FIELD(f_mon_att_auth_method, nam_mon_auth_method, fld_auth_method, 0, ODS_12_0) FIELD(f_mon_att_sys_flag, nam_mon_sys_flag, fld_flag, 0, ODS_12_0) + FIELD(f_mon_att_idle_timeout, nam_idle_timeout, fld_idle_timeout, 0, ODS_13_0) + FIELD(f_mon_att_idle_timer, nam_idle_timer, fld_idle_timer, 0, ODS_13_0) + FIELD(f_mon_att_stmt_timeout, nam_stmt_timeout, fld_stmt_timeout, 0, ODS_13_0) END_RELATION // Relation 35 (MON$TRANSACTIONS) @@ -546,6 +549,8 @@ RELATION(nam_mon_statements, rel_mon_statements, ODS_11_1, rel_virtual) FIELD(f_mon_stmt_sql_text, nam_mon_sql_text, fld_source, 0, ODS_11_1) FIELD(f_mon_stmt_stat_id, nam_mon_stat_id, fld_stat_id, 0, ODS_11_1) FIELD(f_mon_stmt_expl_plan, nam_mon_expl_plan, fld_source, 0, ODS_11_1) + FIELD(f_mon_stmt_timeout, nam_stmt_timeout, fld_stmt_timeout, 0, ODS_13_0) + FIELD(f_mon_stmt_timer, nam_stmt_timer, fld_stmt_timer, 0, ODS_13_0) END_RELATION // Relation 37 (MON$CALL_STACK) diff --git a/src/jrd/req.h b/src/jrd/req.h index daec1cb09e..112cc75586 100644 --- a/src/jrd/req.h +++ b/src/jrd/req.h @@ -175,6 +175,7 @@ public: req_ext_stmt(NULL), req_cursors(*req_pool), req_ext_resultset(NULL), + req_timeout(0), req_domain_validation(NULL), req_auto_trans(*req_pool), req_sorts(*req_pool), @@ -267,6 +268,8 @@ public: Savepoint* req_savepoints; // Looper savepoint list Savepoint* req_proc_sav_point; // procedure savepoint list Firebird::TimeStamp req_timestamp; // Start time of request + unsigned int req_timeout; // query timeout in milliseconds, set by the dsql_req::setupTimer + Firebird::RefPtr req_timer; // timeout timer, shared with dsql_req Firebird::AutoPtr req_fetch_baseline; // State of request performance counters when we reported it last time SINT64 req_fetch_elapsed; // Number of clock ticks spent while fetching rows for this request since we reported it last time diff --git a/src/jrd/shut.cpp b/src/jrd/shut.cpp index d38a1106d5..be701e3d2e 100644 --- a/src/jrd/shut.cpp +++ b/src/jrd/shut.cpp @@ -532,7 +532,7 @@ static bool shutdown(thread_db* tdbb, SSHORT flag, bool force) { if (!(attachment->att_flags & ATT_shutdown)) { - attachment->signalShutdown(); + attachment->signalShutdown(isc_att_shut_db_down); found = true; } } diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 65ffe8d1b9..ff8cc76b77 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -349,7 +349,7 @@ void TRA_cleanup(thread_db* tdbb) window.win_page = inventory_page(tdbb, sequence); tx_inv_page* tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_transactions); TraNumber max = ceiling - (TraNumber) sequence * trans_per_tip; - if (max > trans_per_tip) + if (max >= trans_per_tip) max = trans_per_tip - 1; for (; number <= max; number++) { diff --git a/src/jrd/trace/TraceCmdLine.cpp b/src/jrd/trace/TraceCmdLine.cpp index ac6b902c39..40044e8a51 100644 --- a/src/jrd/trace/TraceCmdLine.cpp +++ b/src/jrd/trace/TraceCmdLine.cpp @@ -134,7 +134,8 @@ namespace Firebird void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) { - const char* const* end = uSvc->argv.end(); + UtilSvc::ArgvType& argv = uSvc->argv; + const int argc = argv.getCount(); bool version = false, help = false; // search for "action" switch, set NULL into recognized argv @@ -143,23 +144,22 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) false, true); const Switches::in_sw_tab_t* action_sw = NULL; - const char** argv = uSvc->argv.begin(); - for (++argv; argv < end; argv++) + for (int itr = 1; itr < argc; ++itr) { - if (!uSvc->isService() && strcmp(argv[0], "-?") == 0) + if (!uSvc->isService() && strcmp(argv[itr], "-?") == 0) { help = true; - *argv = NULL; + argv[itr] = NULL; break; } - const Switches::in_sw_tab_t* sw = actSwitches.findSwitch(*argv); + const Switches::in_sw_tab_t* sw = actSwitches.findSwitch(argv[itr]); if (sw) { if (sw->in_sw == IN_SW_TRACE_VERSION) { version = true; - *argv = NULL; + argv[itr] = NULL; continue; } if (action_sw) @@ -167,7 +167,7 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) else action_sw = sw; - *argv = NULL; + argv[itr] = NULL; } } @@ -190,17 +190,16 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) const Switches optSwitches(trace_option_in_sw_table, FB_NELEM(trace_option_in_sw_table), false, true); TraceSession session(*getDefaultMemoryPool()); - argv = uSvc->argv.begin(); - for (++argv; argv < end; argv++) + for (int itr = 1; itr < argc; ++itr) { - if (!*argv) + if (!argv[itr]) continue; - const Switches::in_sw_tab_t* sw = optSwitches.findSwitch(*argv); + const Switches::in_sw_tab_t* sw = optSwitches.findSwitch(argv[itr]); if (!sw) continue; - *argv = NULL; + argv[itr] = NULL; switch (sw->in_sw) { @@ -218,9 +217,9 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!session.ses_config.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - session.ses_config = *argv; + itr++; + if (itr < argc && argv[itr]) + session.ses_config = argv[itr]; else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; @@ -239,9 +238,9 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!session.ses_name.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - session.ses_name = *argv; + itr++; + if (itr < argc && argv[itr]) + session.ses_name = argv[itr]; else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; @@ -258,12 +257,12 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (session.ses_id) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) + itr++; + if (itr < argc && argv[itr]) { - session.ses_id = atol(*argv); + session.ses_id = atol(argv[itr]); if (!session.ses_id) - usage(uSvc, isc_trace_param_invalid, *argv, sw->in_sw_name); + usage(uSvc, isc_trace_param_invalid, argv[itr], sw->in_sw_name); } else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); @@ -272,7 +271,7 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) default: fb_assert(false); } - *argv = NULL; + argv[itr] = NULL; } // search for authentication parameters @@ -280,15 +279,14 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) false, true); string svc_name, user, role, pwd; bool adminRole = false; - argv = uSvc->argv.begin(); - for (++argv; argv < end; argv++) + for (int itr = 1; itr < argc; ++itr) { - if (!*argv) + if (!argv[itr]) continue; - const Switches::in_sw_tab_t* sw = authSwitches.findSwitch(*argv); + const Switches::in_sw_tab_t* sw = authSwitches.findSwitch(argv[itr]); if (!sw) { - usage(uSvc, isc_trace_switch_unknown, *argv); + usage(uSvc, isc_trace_switch_unknown, argv[itr]); } switch (sw->in_sw) @@ -297,9 +295,9 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!user.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - user = *argv; + itr++; + if (itr < argc && argv[itr]) + user = argv[itr]; else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; @@ -308,9 +306,9 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!role.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - role = *argv; + itr++; + if (itr < argc && argv[itr]) + role = argv[itr]; else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; @@ -319,9 +317,12 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!pwd.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - pwd = *argv; + itr++; + if (itr < argc && argv[itr]) + { + pwd = argv[itr]; + uSvc->hidePasswd(argv, itr); + } else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; @@ -333,10 +334,10 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!pwd.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) + itr++; + if (itr < argc && argv[itr]) { - const PathName fileName(*argv); + const PathName fileName(argv[itr]); const char *s = NULL; switch (fb_utils::fetchPassword(fileName, s)) { @@ -374,9 +375,9 @@ void fbtrace(UtilSvc* uSvc, TraceSvcIntf* traceSvc) if (!svc_name.empty()) usage(uSvc, isc_trace_switch_once, sw->in_sw_name); - argv++; - if (argv < end && *argv) - svc_name = *argv; + itr++; + if (itr < argc && argv[itr]) + svc_name = argv[itr]; else usage(uSvc, isc_trace_param_val_miss, sw->in_sw_name); break; diff --git a/src/jrd/trace/TraceManager.cpp b/src/jrd/trace/TraceManager.cpp index 5de23e9740..00f7b915c7 100644 --- a/src/jrd/trace/TraceManager.cpp +++ b/src/jrd/trace/TraceManager.cpp @@ -88,7 +88,8 @@ TraceManager::TraceManager(Attachment* in_att) : attachment(in_att), service(NULL), filename(NULL), - trace_sessions(*in_att->att_pool) + trace_sessions(*in_att->att_pool), + active(false) { init(); } @@ -97,7 +98,8 @@ TraceManager::TraceManager(Service* in_svc) : attachment(NULL), service(in_svc), filename(NULL), - trace_sessions(in_svc->getPool()) + trace_sessions(in_svc->getPool()), + active(true) { init(); } @@ -106,7 +108,8 @@ TraceManager::TraceManager(const char* in_filename) : attachment(NULL), service(NULL), filename(in_filename), - trace_sessions(*getDefaultMemoryPool()) + trace_sessions(*getDefaultMemoryPool()), + active(true) { init(); } diff --git a/src/jrd/trace/TraceManager.h b/src/jrd/trace/TraceManager.h index b46d44bd15..3d8ad72c12 100644 --- a/src/jrd/trace/TraceManager.h +++ b/src/jrd/trace/TraceManager.h @@ -121,11 +121,20 @@ public: inline bool needs(unsigned e) { + if (!active) + return 0; + if (changeNumber != getStorage()->getChangeNumber()) update_sessions(); return trace_needs & (FB_CONST64(1) << e); } + // should be called after attachment user is authenticated + void activate() + { + active = true; + } + /* DSQL-friendly routines to call Trace API hooks. Needed because DSQL cannot include JRD for the current engine */ static bool need_dsql_prepare(Attachment* att); @@ -229,6 +238,7 @@ private: static Firebird::GlobalPtr storageInstance; ULONG changeNumber; + bool active; }; } diff --git a/src/lock/print.cpp b/src/lock/print.cpp index 17f1263d19..e654fe2ffd 100644 --- a/src/lock/print.cpp +++ b/src/lock/print.cpp @@ -75,11 +75,11 @@ enum lck_t { LCK_btr_dont_gc, // Prevent removal of b-tree page from index LCK_shared_counter, // Database-wide shared counter LCK_tra_pc, // Precommitted transaction lock + LCK_rel_gc, // Allow garbage collection for relation LCK_fun_exist, // Function existence lock LCK_rel_rescan, // Relation forced rescan lock LCK_crypt, // Crypt lock for single crypt thread LCK_crypt_status, // Notifies about changed database encryption status - LCK_idx_reserve, // Index reservation lock LCK_record_gc // Record-level GC lock }; @@ -1240,22 +1240,74 @@ static void prt_lock(OUTFILE outfile, const lhb* LOCK_header, const lbl* lock, U // in : format const UCHAR* q = lock->lbl_key; - SLONG key; - memcpy(&key, q, sizeof(SLONG)); - q += sizeof(SLONG); + ULONG pageno; + memcpy(&pageno, q, sizeof(ULONG)); + q += sizeof(ULONG); ULONG pg_space; - memcpy(&pg_space, q, sizeof(SLONG)); + memcpy(&pg_space, q, sizeof(ULONG)); - FPRINTF(outfile, "\tKey: %04" ULONGFORMAT":%06" SLONGFORMAT",", pg_space, key); + FPRINTF(outfile, "\tKey: %04" ULONGFORMAT":%06" ULONGFORMAT",", pg_space, pageno); } - else if (lock->lbl_length == 4) + else if ((lock->lbl_series == Jrd::LCK_relation || lock->lbl_series == Jrd::LCK_rel_gc) && + lock->lbl_length == sizeof(ULONG) + sizeof(SINT64)) // Jrd::jrd_rel::getRelLockKeyLength() + { + const UCHAR* q = lock->lbl_key; + + ULONG rel_id; + memcpy(&rel_id, q, sizeof(ULONG)); + q += sizeof(ULONG); + + SINT64 instance_id; + memcpy(&instance_id, q, sizeof(SINT64)); + + FPRINTF(outfile, "\tKey: %04" ULONGFORMAT":%09" SQUADFORMAT",", rel_id, instance_id); + } + else if ((lock->lbl_series == Jrd::LCK_tra || + lock->lbl_series == Jrd::LCK_tra_pc || + lock->lbl_series == Jrd::LCK_attachment || + lock->lbl_series == Jrd::LCK_monitor || + lock->lbl_series == Jrd::LCK_cancel) && + lock->lbl_length == sizeof(SINT64)) + { + SINT64 key; + memcpy(&key, lock->lbl_key, lock->lbl_length); + + FPRINTF(outfile, "\tKey: %09" SQUADFORMAT",", key); + } + else if (lock->lbl_series == Jrd::LCK_record_gc && + lock->lbl_length == sizeof(SINT64)) + { + SINT64 key; + memcpy(&key, lock->lbl_key, lock->lbl_length); + + const ULONG pageno = key >> 16; + const ULONG line = (ULONG) (key & MAX_USHORT); + + FPRINTF(outfile, "\tKey: %06" ULONGFORMAT":%04" ULONGFORMAT",", pageno, line); + } + else if ((lock->lbl_series == Jrd::LCK_idx_exist || lock->lbl_series == Jrd::LCK_expression) && + lock->lbl_length == sizeof(SLONG)) { SLONG key; - memcpy(&key, lock->lbl_key, 4); + memcpy(&key, lock->lbl_key, lock->lbl_length); + + const ULONG rel_id = key >> 16; + const ULONG idx_id = (ULONG) (key & MAX_USHORT); + + FPRINTF(outfile, "\tKey: %04" ULONGFORMAT":%04" ULONGFORMAT",", rel_id, idx_id); + } + else if (lock->lbl_length == sizeof(SLONG)) + { + SLONG key; + memcpy(&key, lock->lbl_key, lock->lbl_length); FPRINTF(outfile, "\tKey: %06" SLONGFORMAT",", key); } + else if (lock->lbl_length == 0) + { + FPRINTF(outfile, "\tKey: "); + } else { UCHAR temp[512]; diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 0d83660cde..f4d0686241 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=4 MinorVer=0 RevNo=0 -BuildNum=514 +BuildNum=582 NowAt=`pwd` cd `dirname $0` diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index 45406dd5d2..b7e2149d6c 100644 --- a/src/msgs/facilities2.sql +++ b/src/msgs/facilities2.sql @@ -1,11 +1,11 @@ /* MAX_NUMBER is the next number to be used, always one more than the highest message number. */ set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUMBER) VALUES (?, ?, ?, ?); -- -('2016-11-08 11:46:00', 'JRD', 0, 807) +('2017-02-24 22:00:00', 'JRD', 0, 818) ('2015-03-17 18:33:00', 'QLI', 1, 533) ('2015-01-07 18:01:51', 'GFIX', 3, 134) ('1996-11-07 13:39:40', 'GPRE', 4, 1) -('2016-02-23 00:00:00', 'DSQL', 7, 40) +('2017-02-05 20:37:00', 'DSQL', 7, 41) ('2016-12-27 12:30:00', 'DYN', 8, 297) ('1996-11-07 13:39:40', 'INSTALL', 10, 1) ('1996-11-07 13:38:41', 'TEST', 11, 4) @@ -15,7 +15,7 @@ set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUM ('2006-09-10 03:04:31', 'JRD_BUGCHK', 15, 307) ('2016-05-26 13:53:45', 'ISQL', 17, 196) ('2010-07-10 10:50:30', 'GSEC', 18, 105) -('2015-01-07 18:01:51', 'GSTAT', 21, 58) +('2017-03-09 21:51:33', 'GSTAT', 21, 59) ('2013-12-19 17:31:31', 'FBSVCMGR', 22, 58) ('2009-07-18 12:12:12', 'UTL', 23, 2) ('2016-03-20 15:30:00', 'NBACKUP', 24, 80) diff --git a/src/msgs/messages2.sql b/src/msgs/messages2.sql index e5a6051a1e..555363cf9a 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -914,6 +914,17 @@ Data source : @4', NULL, NULL) ('dsql_window_cant_overr_frame', NULL, 'ExprNodes.cpp', NULL, 0, 804, NULL, 'Cannot override the window @1 because it has a frame clause. Tip: it can be used without parenthesis in OVER', NULL, NULL); ('dsql_window_duplicate', NULL, 'ExprNodes.cpp', NULL, 0, 805, NULL, 'Duplicate window definition for @1', NULL, NULL); ('sql_too_long', 'prepareStatement', 'dsql.cpp', NULL, 0, 806, NULL, 'SQL statement is too long. Maximum size is @1 bytes.', NULL, NULL); +('cfg_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 807, NULL, 'Config level timeout expired.', NULL, NULL); +('att_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 808, NULL, 'Attachment level timeout expired.', NULL, NULL); +('req_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 809, NULL, 'Statement level timeout expired.', NULL, NULL); +('att_shut_killed', NULL, 'jrd.cpp', NULL, 0, 810, NULL, 'Killed by database administrator.', NULL, NULL); +('att_shut_idle', NULL, 'jrd.cpp', NULL, 0, 811, NULL, 'Idle timeout expired.', NULL, NULL); +('att_shut_db_down', NULL, 'jrd.cpp', NULL, 0, 812, NULL, 'Database is shutdown.', NULL, NULL); +('att_shut_engine', NULL, 'jrd.cpp', NULL, 0, 813, NULL, 'Engine is shutdown.', NULL, NULL); +('overriding_without_identity', NULL, 'StmtNodes.cpp', NULL, 0, 814, NULL, 'OVERRIDING clause can be used only when an identity column is present in the INSERT''s field list for table/view @1', NULL, NULL); +('overriding_system_invalid', NULL, 'StmtNodes.cpp', NULL, 0, 815, NULL, 'OVERRIDING SYSTEM VALUE can be used only for identity column defined as ''GENERATED ALWAYS'' in INSERT for table/view @1', NULL, NULL); +('overriding_user_invalid', NULL, 'StmtNodes.cpp', NULL, 0, 816, NULL, 'OVERRIDING USER VALUE can be used only for identity column defined as ''GENERATED BY DEFAULT'' in INSERT for table/view @1', NULL, NULL); +('overriding_system_missing', NULL, 'StmtNodes.cpp', NULL, 0, 817, NULL, 'OVERRIDING SYSTEM VALUE should be used to override the value of an identity column defined as ''GENERATED ALWAYS'' in table/view @1', NULL, NULL); -- QLI (NULL, NULL, NULL, NULL, 1, 0, NULL, 'expected type', NULL, NULL); (NULL, NULL, NULL, NULL, 1, 1, NULL, 'bad block type', NULL, NULL); @@ -1701,6 +1712,7 @@ COMMIT WORK; ('dsql_wrong_param_num', NULL, NULL, NULL, 7, 39, NULL, 'Wrong number of parameters (expected @1, got @2)', NULL, NULL); -- Do not change the arguments of the previous DSQL messages. ('dsql_invalid_drop_ss_clause', 'CreateAlterTriggerNode::dsqlPass', 'DdlNodes.epp', NULL, 7, 40, NULL, 'Invalid DROP SQL SECURITY clause', NULL, NULL); +('upd_ins_cannot_default', 'pass1_update_or_insert', 'pass1.cpp', NULL, 7, 41, NULL, 'UPDATE OR INSERT value for field @1, part of the implicit or explicit MATCHING clause, cannot be DEFAULT', NULL, NULL); -- Write the new DSQL messages here. -- DYN (NULL, NULL, 'dyn.c', NULL, 8, 1, NULL, 'ODS version not supported by DYN', NULL, NULL); @@ -3247,6 +3259,7 @@ Analyzing database pages ...', NULL, NULL); (NULL, 'main', 'dba.epp', NULL, 21, 55, NULL, 'no encrypted database support, only -e and -h can be used', NULL, NULL) (NULL, 'main', 'dba.epp', NULL, 21, 56, NULL, ' Empty pages: @1, full pages: @2', NULL, NULL); (NULL, 'dba_in_sw_table', 'dbaswi.h', NULL, 21, 57, NULL, ' -role SQL role name', NULL, NULL); +(NULL, 'main', 'dba.epp', NULL, 21, 58, NULL, 'Other pages: total @1, ENCRYPTED @2 (DB problem!), non-crypted @3', NULL, NULL) -- FBSVCMGR -- All messages use the new format. ('fbsvcmgr_bad_am', 'putAccessMode', 'fbsvcmgr.cpp', NULL, 22, 1, NULL, 'Wrong value for access mode', NULL, NULL); diff --git a/src/msgs/msg.sql b/src/msgs/msg.sql index dfc673fe60..85c536df3f 100644 --- a/src/msgs/msg.sql +++ b/src/msgs/msg.sql @@ -29,7 +29,7 @@ CREATE DOMAIN SQL_STATE AS CHAR(5); CREATE DOMAIN SQL_SUBCLASS AS CHAR(3); CREATE DOMAIN SYMBOL AS VARCHAR(32); CREATE DOMAIN TEMPLATE AS BLOB SUB_TYPE 0 SEGMENT SIZE 256; -CREATE DOMAIN TEXT AS VARCHAR(118); +CREATE DOMAIN TEXT AS VARCHAR(138); CREATE DOMAIN TRANSLATOR AS VARCHAR(32); CREATE DOMAIN TRANS_DATE AS TIMESTAMP; CREATE DOMAIN TRANS_NOTES AS BLOB SUB_TYPE 0 SEGMENT SIZE 0; diff --git a/src/msgs/system_errors2.sql b/src/msgs/system_errors2.sql index 0be14bbbf8..00a86ba11f 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -813,6 +813,17 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-833, '42', '000', 0, 804, 'dsql_window_cant_overr_frame', NULL, NULL) (-833, '42', '000', 0, 805, 'dsql_window_duplicate', NULL, NULL) (-902, '54', '001', 0, 806, 'sql_too_long', NULL, NULL) +(-901, 'HY', '008', 0, 807, 'cfg_stmt_timeout', NULL, NULL) +(-901, 'HY', '008', 0, 808, 'att_stmt_timeout', NULL, NULL) +(-901, 'HY', '008', 0, 809, 'req_stmt_timeout', NULL, NULL) +(-902, '08', '003', 0, 810, 'att_shut_killed', NULL, NULL) +(-902, '08', '003', 0, 811, 'att_shut_idle', NULL, NULL) +(-902, '08', '003', 0, 812, 'att_shut_db_down', NULL, NULL) +(-902, '08', '003', 0, 813, 'att_shut_engine', NULL, NULL) +(-902, '42', '000', 0, 814, 'overriding_without_identity', NULL, NULL) +(-902, '42', '000', 0, 815, 'overriding_system_invalid', NULL, NULL) +(-902, '42', '000', 0, 816, 'overriding_user_invalid', NULL, NULL) +(-902, '42', '000', 0, 817, 'overriding_system_missing', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL) @@ -881,6 +892,7 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-802, '07', '002', 7, 38, 'dsql_no_output_sqlda', NULL, NULL) (-313, '07', '001', 7, 39, 'dsql_wrong_param_num', NULL, NULL) (-817, '42', '000', 7, 40, 'dsql_invalid_drop_ss_clause', NULL, NULL) +(-313, '42', '000', 7, 41, 'upd_ins_cannot_default', NULL, NULL) -- DYN (-901, '42', '000', 8, 37, 'dyn_filter_not_found', NULL, NULL) (-901, '42', '000', 8, 41, 'dyn_func_not_found', NULL, NULL) diff --git a/src/remote/SockAddr.h b/src/remote/SockAddr.h index b6c5610601..996cc1ec47 100644 --- a/src/remote/SockAddr.h +++ b/src/remote/SockAddr.h @@ -56,6 +56,8 @@ private: socklen_t len; static const unsigned MAX_LEN = sizeof(sa_data); + void checkAndFixFamily(); + public: void clear(); const SockAddr& operator = (const SockAddr& x); @@ -83,6 +85,27 @@ public: void unmapV4(); }; +// Definitions below taken from sources at correspondent operating systems. +// If something else arrives, it should be added here and into checkAndFixFamily() also. + +#define AF_INET6_POSIX 10 +#define AF_INET6_WINDOWS 23 + +inline void SockAddr::checkAndFixFamily() +{ +#if AF_INET6 == AF_INET6_POSIX + if (data.sock.sa_family == AF_INET6_WINDOWS) +#elif AF_INET6 == AF_INET6_WINDOWS + if (data.sock.sa_family == AF_INET6_POSIX) +#else +#error Unknown value of AF_INET6 ! +#endif + { + data.sock.sa_family = AF_INET6; + fb_assert(len == sizeof(sockaddr_in6)); + } +} + inline void SockAddr::clear() { @@ -95,6 +118,8 @@ inline const SockAddr& SockAddr::operator = (const SockAddr& x) { memcpy(&data, &x.data, MAX_LEN); len = x.len; + + checkAndFixFamily(); return *this; } @@ -105,6 +130,8 @@ inline SockAddr::SockAddr(const unsigned char* p_data, unsigned p_len) p_len = MAX_LEN; memcpy(&data, p_data, p_len); len = p_len; + + checkAndFixFamily(); } diff --git a/src/remote/client/interface.cpp b/src/remote/client/interface.cpp index 1548659784..d4c88583f2 100644 --- a/src/remote/client/interface.cpp +++ b/src/remote/client/interface.cpp @@ -322,6 +322,28 @@ public: void free(CheckStatusWrapper* status); unsigned getFlags(CheckStatusWrapper* status); + unsigned int getTimeout(CheckStatusWrapper* status) + { + if (statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_STMT_TOUT) + { + status->setErrors(Arg::Gds(isc_wish_list).value()); + return 0; + } + + return statement->rsr_timeout; + } + + void setTimeout(CheckStatusWrapper* status, unsigned int timeOut) + { + if (timeOut && statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_STMT_TOUT) + { + status->setErrors(Arg::Gds(isc_wish_list).value()); + return; + } + + statement->rsr_timeout = timeOut; + } + public: Statement(Rsr* handle, Attachment* a, unsigned aDialect) : metadata(getPool(), this, NULL), @@ -509,6 +531,11 @@ public: void detach(CheckStatusWrapper* status); void dropDatabase(CheckStatusWrapper* status); + unsigned int getIdleTimeout(CheckStatusWrapper* status); + void setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut); + unsigned int getStatementTimeout(CheckStatusWrapper* status); + void setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut); + public: Attachment(Rdb* handle, const PathName& path) : rdb(handle), dbPath(getPool(), path) @@ -529,7 +556,9 @@ public: Statement* createStatement(CheckStatusWrapper* status, unsigned dialect); private: + void execWithCheck(CheckStatusWrapper* status, const string& stmt); void freeClientData(CheckStatusWrapper* status, bool force = false); + SLONG getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem); Rdb* rdb; const PathName dbPath; @@ -1730,6 +1759,103 @@ void Attachment::dropDatabase(CheckStatusWrapper* status) } +SLONG Attachment::getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem) +{ + UCHAR buff[16]; + + getInfo(status, 1, &infoItem, sizeof(buff), buff); + if (status->getState() & IStatus::STATE_ERRORS) + return 0; + + const UCHAR* p = buff; + const UCHAR* const end = buff + sizeof(buff); + UCHAR item; + while ((item = *p++) != isc_info_end && p < end - 1) + { + const SLONG length = gds__vax_integer(p, 2); + p += 2; + + if (item == infoItem) + return gds__vax_integer(p, (SSHORT)length); + + fb_assert(false); + + p += length; + } + return 0; +} + + +void Attachment::execWithCheck(CheckStatusWrapper* status, const string& stmt) +{ +/************************************** + * + * Used to execute "SET xxx TIMEOUT" statements. Checks for protocol version + * and convert expected SQL error into isc_wish_list error. The only possible + * case is when modern network server works with legacy engine. + * + **************************************/ + if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) + { + execute(status, NULL, stmt.length(), stmt.c_str(), SQL_DIALECT_CURRENT, NULL, NULL, NULL, NULL); + + if (!(status->getState() & IStatus::STATE_ERRORS)) + return; + + // handle isc_dsql_token_unk_err + const ISC_STATUS* errs = status->getErrors(); + + if (!fb_utils::containsErrorCode(errs, isc_sqlerr) || + !fb_utils::containsErrorCode(errs, isc_dsql_token_unk_err)) + { + return; + } + + status->init(); + } + + status->setErrors(Arg::Gds(isc_wish_list).value()); +} + + +unsigned int Attachment::getIdleTimeout(CheckStatusWrapper* status) +{ + if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) + return getSingleInfo(status, fb_info_ses_idle_timeout_att); + + status->setErrors(Arg::Gds(isc_wish_list).value()); + return 0; +} + + +void Attachment::setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut) +{ + string stmt; + stmt.printf("SET SESSION IDLE TIMEOUT %lu", timeOut); + + execWithCheck(status, stmt); +} + + +unsigned int Attachment::getStatementTimeout(CheckStatusWrapper* status) +{ + if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) + return getSingleInfo(status, fb_info_statement_timeout_att); + + status->setErrors(Arg::Gds(isc_wish_list).value()); + return 0; +} + + +void Attachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut) +{ + string stmt; + stmt.printf("SET STATEMENT TIMEOUT %lu", timeOut); + + execWithCheck(status, stmt); +} + + Firebird::ITransaction* Statement::execute(CheckStatusWrapper* status, Firebird::ITransaction* apiTra, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer) { @@ -1857,6 +1983,7 @@ Firebird::ITransaction* Statement::execute(CheckStatusWrapper* status, Firebird: sqldata->p_sqldata_out_blr.cstr_length = out_blr_length; sqldata->p_sqldata_out_blr.cstr_address = const_cast(out_blr); sqldata->p_sqldata_out_message_number = 0; // out_msg_type + sqldata->p_sqldata_timeout = statement->rsr_timeout; send_packet(port, packet); @@ -2021,6 +2148,7 @@ ResultSet* Statement::openCursor(CheckStatusWrapper* status, Firebird::ITransact sqldata->p_sqldata_out_blr.cstr_length = out_blr_length; sqldata->p_sqldata_out_blr.cstr_address = const_cast(out_blr); sqldata->p_sqldata_out_message_number = 0; // out_msg_type + sqldata->p_sqldata_timeout = statement->rsr_timeout; send_partial_packet(port, packet); defer_packet(port, packet, true); diff --git a/src/remote/inet.cpp b/src/remote/inet.cpp index 45320c230c..5a1273651d 100644 --- a/src/remote/inet.cpp +++ b/src/remote/inet.cpp @@ -621,7 +621,8 @@ rem_port* INET_analyze(ClntAuthBlock* cBlock, REMOTE_PROTOCOL(PROTOCOL_VERSION12, ptype_lazy_send, 3), REMOTE_PROTOCOL(PROTOCOL_VERSION13, ptype_lazy_send, 4), REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_lazy_send, 5), - REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_lazy_send, 6) + REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_lazy_send, 6), + REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_lazy_send, 7) }; fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions)); cnct->p_cnct_count = FB_NELEM(protocols_to_try); @@ -638,7 +639,7 @@ rem_port* INET_analyze(ClntAuthBlock* cBlock, rem_port* port = inet_try_connect(packet, rdb, file_name, node_name, dpb, config, ref_db_name, af); P_ACPT* accept; - for(;;) + for (;;) { accept = NULL; switch (packet->p_operation) diff --git a/src/remote/os/win32/wnet.cpp b/src/remote/os/win32/wnet.cpp index 6af1d51371..0b4bbcff01 100644 --- a/src/remote/os/win32/wnet.cpp +++ b/src/remote/os/win32/wnet.cpp @@ -173,7 +173,9 @@ rem_port* WNET_analyze(ClntAuthBlock* cBlock, REMOTE_PROTOCOL(PROTOCOL_VERSION11, ptype_batch_send, 2), REMOTE_PROTOCOL(PROTOCOL_VERSION12, ptype_batch_send, 3), REMOTE_PROTOCOL(PROTOCOL_VERSION13, ptype_batch_send, 4), - REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5) + REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5), + REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_batch_send, 6), + REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_batch_send, 7) }; fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions)); cnct->p_cnct_count = FB_NELEM(protocols_to_try); diff --git a/src/remote/os/win32/xnet.cpp b/src/remote/os/win32/xnet.cpp index f45862e91b..8533aee3ca 100644 --- a/src/remote/os/win32/xnet.cpp +++ b/src/remote/os/win32/xnet.cpp @@ -305,7 +305,9 @@ rem_port* XNET_analyze(ClntAuthBlock* cBlock, REMOTE_PROTOCOL(PROTOCOL_VERSION11, ptype_batch_send, 2), REMOTE_PROTOCOL(PROTOCOL_VERSION12, ptype_batch_send, 3), REMOTE_PROTOCOL(PROTOCOL_VERSION13, ptype_batch_send, 4), - REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5) + REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5), + REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_batch_send, 6), + REMOTE_PROTOCOL(PROTOCOL_VERSION16, ptype_batch_send, 7) }; fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions)); cnct->p_cnct_count = FB_NELEM(protocols_to_try); diff --git a/src/remote/protocol.cpp b/src/remote/protocol.cpp index ea31ff484f..4cc06be3e4 100644 --- a/src/remote/protocol.cpp +++ b/src/remote/protocol.cpp @@ -646,6 +646,11 @@ bool_t xdr_protocol(XDR* xdrs, PACKET* p) } MAP(xdr_short, reinterpret_cast(sqldata->p_sqldata_out_message_number)); } + { // scope + rem_port* port = (rem_port*) xdrs->x_public; + if (port->port_protocol >= PROTOCOL_STMT_TOUT) + MAP(xdr_u_long, sqldata->p_sqldata_timeout); + } DEBUG_PRINTSIZE(xdrs, p->p_operation); return P_TRUE(xdrs, p); diff --git a/src/remote/protocol.h b/src/remote/protocol.h index 35dc0e9d89..c33ff0a818 100644 --- a/src/remote/protocol.h +++ b/src/remote/protocol.h @@ -83,9 +83,15 @@ const USHORT PROTOCOL_VERSION13 = (FB_PROTOCOL_FLAG | 13); const USHORT PROTOCOL_VERSION14 = (FB_PROTOCOL_FLAG | 14); // Protocol 15: -// - supports crypt key callback at connect phaze +// - supports crypt key callback at connect phase -const USHORT PROTOCOL_VERSION15 = (FB_PROTOCOL_FLAG | 15); +const USHORT PROTOCOL_VERSION15 = (FB_PROTOCOL_FLAG | 15); + +// Protocol 16: +// - supports statement timeouts + +const USHORT PROTOCOL_VERSION16 = (FB_PROTOCOL_FLAG | 16); +const USHORT PROTOCOL_STMT_TOUT = PROTOCOL_VERSION16; // Architecture types @@ -577,6 +583,7 @@ typedef struct p_sqldata CSTRING p_sqldata_out_blr; // blr describing output message USHORT p_sqldata_out_message_number; ULONG p_sqldata_status; // final eof status + ULONG p_sqldata_timeout; // statement timeout } P_SQLDATA; typedef struct p_sqlfree diff --git a/src/remote/remote.h b/src/remote/remote.h index 4f73c3cdbe..c2fca51dfc 100644 --- a/src/remote/remote.h +++ b/src/remote/remote.h @@ -476,6 +476,7 @@ struct Rsr : public Firebird::GlobalStorage, public TypedHandle Firebird::string rsr_cursor_name; // Name for cursor to be set on open bool rsr_delayed_format; // Out format was delayed on execute, set it on fetch + unsigned int rsr_timeout; // Statement timeout to be set on open\execute Rsr** rsr_self; public: @@ -498,7 +499,7 @@ public: rsr_format(0), rsr_message(0), rsr_buffer(0), rsr_status(0), rsr_id(0), rsr_fmt_length(0), rsr_rows_pending(0), rsr_msgs_waiting(0), rsr_reorder_level(0), rsr_batch_count(0), - rsr_cursor_name(getPool()), rsr_delayed_format(false), rsr_self(NULL) + rsr_cursor_name(getPool()), rsr_delayed_format(false), rsr_timeout(0), rsr_self(NULL) { } ~Rsr() diff --git a/src/remote/server/server.cpp b/src/remote/server/server.cpp index bd6c50bde9..b1accd8efd 100644 --- a/src/remote/server/server.cpp +++ b/src/remote/server/server.cpp @@ -111,7 +111,7 @@ class NetworkCallback : public VersionedIface length) - l = length; - memcpy(d, data, l); + if (replyLength > wakeLength) + replyLength = wakeLength; + memcpy(replyData, wakeData, replyLength); wake = true; sem.release(); @@ -164,8 +164,8 @@ public: private: rem_port* port; Semaphore sem; - unsigned int l; - void* d; + unsigned int replyLength; + void* replyData; bool stopped; public: @@ -1815,7 +1815,7 @@ static bool accept_connection(rem_port* port, P_CNCT* connect, PACKET* send) protocol < end; protocol++) { if ((protocol->p_cnct_version >= PROTOCOL_VERSION10 && - protocol->p_cnct_version <= PROTOCOL_VERSION15) && + protocol->p_cnct_version <= PROTOCOL_VERSION16) && (protocol->p_cnct_architecture == arch_generic || protocol->p_cnct_architecture == ARCHITECTURE) && protocol->p_cnct_weight >= weight) @@ -3383,6 +3383,9 @@ ISC_STATUS rem_port::execute_statement(P_OP op, P_SQLDATA* sqldata, PACKET* send unsigned flags = statement->rsr_iface->getFlags(&status_vector); check(&status_vector); + statement->rsr_iface->setTimeout(&status_vector, sqldata->p_sqldata_timeout); + check(&status_vector); + if ((flags & IStatement::FLAG_HAS_CURSOR) && (out_msg_length == 0)) { statement->rsr_cursor = @@ -5286,6 +5289,8 @@ ISC_STATUS rem_port::send_response( PACKET* sendL, char buffer[1024]; char* p = buffer; char* bufferEnd = p + sizeof(buffer); + // Set limit of status vector size since old client 2.5 and below cannot correctly handle them + const FB_SIZE_T limit = port_protocol < PROTOCOL_VERSION13 ? ISC_STATUS_LENGTH : 0; for (bool sw = true; *old_vector && sw;) { @@ -5293,6 +5298,8 @@ ISC_STATUS rem_port::send_response( PACKET* sendL, { case isc_arg_warning: case isc_arg_gds: + if (limit && new_vector.getCount() > limit - 3) // 2 for numbers and 1 reserved for isc_arg_end + break; new_vector.push(*old_vector++); // The status codes are converted to their offsets so that they @@ -5309,11 +5316,15 @@ ISC_STATUS rem_port::send_response( PACKET* sendL, switch (*old_vector) { case isc_arg_cstring: + if (limit && new_vector.getCount() > limit - 4) + break; new_vector.push(*old_vector++); // fall through ... case isc_arg_string: case isc_arg_number: + if (limit && new_vector.getCount() > limit - 3) + break; new_vector.push(*old_vector++); new_vector.push(*old_vector++); continue; @@ -5324,11 +5335,15 @@ ISC_STATUS rem_port::send_response( PACKET* sendL, case isc_arg_interpreted: case isc_arg_sql_state: + if (limit && new_vector.getCount() > limit - 3) + break; new_vector.push(*old_vector++); new_vector.push(*old_vector++); continue; } + if (limit && new_vector.getCount() > limit - 3) + break; const int l = (p < bufferEnd) ? fb_interpret(p, bufferEnd - p, &old_vector) : 0; if (l == 0) break; diff --git a/src/utilities/fbtracemgr/traceMgrMain.cpp b/src/utilities/fbtracemgr/traceMgrMain.cpp index 5071a3065b..520db30921 100644 --- a/src/utilities/fbtracemgr/traceMgrMain.cpp +++ b/src/utilities/fbtracemgr/traceMgrMain.cpp @@ -40,6 +40,11 @@ #include #endif +#ifdef WIN_NT +#include +#include +#endif + namespace Firebird { class TraceSvcUtil : public TraceSvcIntf @@ -313,6 +318,11 @@ int CLIB_ROUTINE main(int argc, char* argv[]) setlocale(LC_CTYPE, ""); #endif +#ifdef WIN_NT + int binout = fileno(stdout); + _setmode(binout, _O_BINARY); +#endif + atexit(&atexit_fb_shutdown); AutoPtr uSvc(UtilSvc::createStandalone(argc, argv)); diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp index df4ced0b12..387fcb102e 100644 --- a/src/utilities/gstat/dba.epp +++ b/src/utilities/gstat/dba.epp @@ -676,10 +676,15 @@ int gstat(Firebird::UtilSvc* uSvc) ++non; } + bool hasCrypted() + { + return enc > 0; + } + private: ULONG enc, non; }; - Statist data, index, blob; + Statist data, index, blob, other; for (page = 0; true; ++page) { @@ -700,6 +705,9 @@ int gstat(Firebird::UtilSvc* uSvc) case pag_blob: blob.log(p->pag_flags); break; + default: + other.log(p->pag_flags); + break; } } @@ -707,6 +715,8 @@ int gstat(Firebird::UtilSvc* uSvc) data.print(52); index.print(53); blob.print(54); + if (other.hasCrypted()) + other.print(58); dba_exit(FINI_OK, tddba); } diff --git a/src/utilities/ntrace/TracePluginImpl.cpp b/src/utilities/ntrace/TracePluginImpl.cpp index 89269cf432..16aed87c2a 100644 --- a/src/utilities/ntrace/TracePluginImpl.cpp +++ b/src/utilities/ntrace/TracePluginImpl.cpp @@ -33,6 +33,7 @@ #include "PluginLogWriter.h" #include "os/platform.h" #include "consts_pub.h" +#include "codetext.h" #include "../../common/isc_f_proto.h" #include "../../jrd/RuntimeStatistics.h" #include "../../common/dsc.h" @@ -97,7 +98,9 @@ TracePluginImpl::TracePluginImpl(IPluginBase* plugin, transactions(getDefaultMemoryPool()), statements(getDefaultMemoryPool()), services(getDefaultMemoryPool()), - unicodeCollation(*getDefaultMemoryPool()) + unicodeCollation(*getDefaultMemoryPool()), + include_codes(*getDefaultMemoryPool()), + exclude_codes(*getDefaultMemoryPool()) { const char* ses_name = initInfo->getTraceSessionName(); session_name = ses_name && *ses_name ? ses_name : " "; @@ -132,7 +135,7 @@ TracePluginImpl::TracePluginImpl(IPluginBase* plugin, string filter(config.include_filter); ISC_systemToUtf8(filter); - include_matcher = FB_NEW SimilarToMatcher >( + include_matcher = FB_NEW TraceSimilarToMatcher( *getDefaultMemoryPool(), textType, (const UCHAR*) filter.c_str(), filter.length(), '\\', true); } @@ -143,7 +146,7 @@ TracePluginImpl::TracePluginImpl(IPluginBase* plugin, string filter(config.exclude_filter); ISC_systemToUtf8(filter); - exclude_matcher = FB_NEW SimilarToMatcher >( + exclude_matcher = FB_NEW TraceSimilarToMatcher( *getDefaultMemoryPool(), textType, (const UCHAR*) filter.c_str(), filter.length(), '\\', true); } @@ -163,6 +166,13 @@ TracePluginImpl::TracePluginImpl(IPluginBase* plugin, } } + // parse filters for gds error codes + if (!config.include_gds_codes.isEmpty()) + str2Array(config.include_gds_codes, include_codes); + + if (!config.exclude_gds_codes.isEmpty()) + str2Array(config.exclude_gds_codes, exclude_codes); + operational = true; log_init(); } @@ -496,18 +506,31 @@ void TracePluginImpl::appendTableCounts(const PerformanceInfo *info) if (!config.print_perf || info->pin_count == 0) return; - record.append(NEWLINE - "Table Natural Index Update Insert Delete Backout Purge Expunge" NEWLINE - "***************************************************************************************************************" NEWLINE ); + const TraceCounts* trc = info->pin_tables; + const TraceCounts* trc_end = trc + info->pin_count; - const TraceCounts* trc; - const TraceCounts* trc_end; + FB_SIZE_T max_len = 0; + for (; trc < trc_end; trc++) + { + FB_SIZE_T len = fb_strlen(trc->trc_relation_name); + if (max_len < len) + max_len = len; + } + + if (max_len < 32) + max_len = 32; + + record.append(NEWLINE"Table"); + record.append(max_len - 5, ' '); + record.append(" Natural Index Update Insert Delete Backout Purge Expunge" NEWLINE); + record.append(max_len + 80, '*'); + record.append(NEWLINE); string temp; - for (trc = info->pin_tables, trc_end = trc + info->pin_count; trc < trc_end; trc++) + for (trc = info->pin_tables; trc < trc_end; trc++) { record.append(trc->trc_relation_name); - record.append(MAX_SQL_IDENTIFIER_LEN - fb_strlen(trc->trc_relation_name), ' '); + record.append(max_len - fb_strlen(trc->trc_relation_name), ' '); for (int j = 0; j < DBB_max_rel_count; j++) { if (trc->trc_counters[j] == 0) @@ -548,6 +571,109 @@ void TracePluginImpl::formatStringArgument(string& result, const UCHAR* str, siz } +bool TracePluginImpl::filterStatus(const ISC_STATUS* status, GdsCodesArray& arr) +{ + FB_SIZE_T pos; + + while (*status != isc_arg_end) + { + const ISC_STATUS s = *status; + + switch (s) + { + case isc_arg_gds: + case isc_arg_warning: + if (arr.find(status[1], pos)) + return true; + status += 2; + break; + + case isc_arg_cstring: + status += 3; + break; + + default: + status += 2; + break; + } + } + + return false; +} + + +namespace { + +class GdsName2CodeMap +{ +public: + GdsName2CodeMap(MemoryPool& pool) : + m_map(pool) + { + for (int i = 0; codes[i].code_string; i++) + m_map.put(codes[i].code_string, codes[i].code_number); + } + + bool find(const char* name, ISC_STATUS& code) const + { + return m_map.get(name, code); + } + +private: + class NocaseCmp + { + public: + static bool greaterThan(const char* i1, const char* i2) + { + return fb_utils::stricmp(i1, i2) > 0; + } + }; + + GenericMap >, NocaseCmp > m_map; +}; + +} // namespace + +static InitInstance gdsNamesMap; + +void TracePluginImpl::str2Array(const Firebird::string& str, GdsCodesArray& arr) +{ + // input: string with comma-delimited list of gds codes values and\or gds codes names + // output: sorted array of gds codes values + + const char *sep = " ,"; + + FB_SIZE_T p1 = 0, p2 = 0; + while (p2 < str.length()) + { + p2 = str.find_first_of(sep, p1); + if (p2 == string::npos) + p2 = str.length(); + + string s = str.substr(p1, p2 - p1); + + ISC_STATUS code = atol(s.c_str()); + + if (!code && !gdsNamesMap().find(s.c_str(), code)) + { + fatal_exception::raiseFmt( + "Error parsing error codes filter: \n" + "\t%s\n" + "\tbad item is: %s, at position: %d", + str.c_str(), s.c_str(), p1 + 1); + } + + // avoid duplicates + + FB_SIZE_T ins_pos; + if (!arr.find(code, ins_pos)) + arr.insert(ins_pos, code); + + p1 = str.find_first_not_of(sep, p2); + } +} + + void TracePluginImpl::appendParams(ITraceParams* params) { const FB_SIZE_T paramcount = params->getCount(); @@ -931,14 +1057,22 @@ void TracePluginImpl::appendServiceQueryParams(size_t send_item_length, void TracePluginImpl::log_init() { - record.printf("\tSESSION_%d %s" NEWLINE "\t%s" NEWLINE, session_id, session_name.c_str(), config.db_filename.c_str()); - logRecord("TRACE_INIT"); + if (config.log_initfini) + { + record.printf("\tSESSION_%d %s" NEWLINE "\t%s" NEWLINE, + session_id, session_name.c_str(), config.db_filename.c_str()); + logRecord("TRACE_INIT"); + } } void TracePluginImpl::log_finalize() { - record.printf("\tSESSION_%d %s" NEWLINE "\t%s" NEWLINE, session_id, session_name.c_str(), config.db_filename.c_str()); - logRecord("TRACE_FINI"); + if (config.log_initfini) + { + record.printf("\tSESSION_%d %s" NEWLINE "\t%s" NEWLINE, + session_id, session_name.c_str(), config.db_filename.c_str()); + logRecord("TRACE_FINI"); + } logWriter->release(); logWriter = NULL; @@ -2037,14 +2171,31 @@ void TracePluginImpl::log_event_trigger_execute(ITraceDatabaseConnection* connec void TracePluginImpl::log_event_error(ITraceConnection* connection, ITraceStatusVector* status, const char* function) { - if (!config.log_errors) - return; - string event_type; - if (status->hasError()) + if (config.log_errors && status->hasError()) + { + const ISC_STATUS* errs = status->getStatus()->getErrors(); + + if (!include_codes.isEmpty() && !filterStatus(errs, include_codes)) + return; + + if (!exclude_codes.isEmpty() && filterStatus(errs, exclude_codes)) + return; + event_type.printf("ERROR AT %s", function); - else if (status->hasWarning()) + } + else if (config.log_warnings && status->hasWarning()) + { + const ISC_STATUS* warns = status->getStatus()->getWarnings(); + + if (!include_codes.isEmpty() && !filterStatus(warns, include_codes)) + return; + + if (!exclude_codes.isEmpty() && filterStatus(warns, exclude_codes)) + return; + event_type.printf("WARNING AT %s", function); + } else return; diff --git a/src/utilities/ntrace/TracePluginImpl.h b/src/utilities/ntrace/TracePluginImpl.h index adb58aac78..2e1412d5f4 100644 --- a/src/utilities/ntrace/TracePluginImpl.h +++ b/src/utilities/ntrace/TracePluginImpl.h @@ -169,8 +169,14 @@ private: Firebird::RWLock renameLock; UnicodeCollationHolder unicodeCollation; - Firebird::AutoPtr > > - include_matcher, exclude_matcher; + typedef Firebird::SimilarToMatcher > > + TraceSimilarToMatcher; + Firebird::AutoPtr include_matcher, exclude_matcher; + + // Filters for gds error codes + typedef Firebird::SortedArray GdsCodesArray; + GdsCodesArray include_codes; + GdsCodesArray exclude_codes; void appendGlobalCounts(const PerformanceInfo* info); void appendTableCounts(const PerformanceInfo* info); @@ -178,6 +184,8 @@ private: void appendServiceQueryParams(size_t send_item_length, const ntrace_byte_t* send_items, size_t recv_item_length, const ntrace_byte_t* recv_items); void formatStringArgument(Firebird::string& result, const UCHAR* str, size_t len); + bool filterStatus(const ISC_STATUS* status, GdsCodesArray& arr); + void str2Array(const Firebird::string& str, GdsCodesArray& arr); // register various objects void register_connection(Firebird::ITraceDatabaseConnection* connection); diff --git a/src/utilities/ntrace/fbtrace.conf b/src/utilities/ntrace/fbtrace.conf index 8cc4e208e6..bff9a7fca7 100644 --- a/src/utilities/ntrace/fbtrace.conf +++ b/src/utilities/ntrace/fbtrace.conf @@ -95,6 +95,24 @@ database # Put errors happened #log_errors = false + # Put warnings + #log_warnings = false + + # Filters for errors and warnings GDS codes. + # Comma separated list of GDS codes values and\or names. + # For example: deadlock, req_sync, 335544321 + + # Include filter. If empty, trace all errors\warnings events. + # Else trace event if any code from list is found in status-vector. + #include_gds_codes + + # Exclude filter. If empty, trace all errors\warnings events. + # Else trace event if no code from list is found in status-vector. + #exclude_gds_codes + + # Put trace session init and finish messages + #log_initfini = true + # Sweep activity #log_sweep = false @@ -199,6 +217,24 @@ services # Put errors happened #log_errors = false + + # Put warnings + #log_warnings = false + + # Filters for errors and warnings GDS codes. + # Comma separated list of GDS codes values and\or names. + # For example: deadlock, req_sync, 335544321 + + # Include filter. If empty, trace all errors\warnings events. + # Else trace event if any code from list is found in status-vector. + #include_gds_codes + + # Exclude filter. If empty, trace all errors\warnings events. + # Else trace event if no code from list is found in status-vector. + #exclude_gds_codes + + # Put trace session init and finish messages + #log_initfini = true } diff --git a/src/utilities/ntrace/paramtable.h b/src/utilities/ntrace/paramtable.h index 0441666f9d..5c8a67e975 100644 --- a/src/utilities/ntrace/paramtable.h +++ b/src/utilities/ntrace/paramtable.h @@ -36,6 +36,10 @@ STR_PARAMETER(include_filter, "") STR_PARAMETER(exclude_filter, "") PATH_PARAMETER(log_filename, "") BOOL_PARAMETER(log_errors, false) +BOOL_PARAMETER(log_warnings, false) +STR_PARAMETER(include_gds_codes, "") +STR_PARAMETER(exclude_gds_codes, "") +BOOL_PARAMETER(log_initfini, true) BOOL_PARAMETER(enabled, false) UINT_PARAMETER(max_log_size, 0) diff --git a/src/yvalve/YObjects.h b/src/yvalve/YObjects.h index a08f9fcce8..1162b4d46e 100644 --- a/src/yvalve/YObjects.h +++ b/src/yvalve/YObjects.h @@ -49,6 +49,7 @@ class YRequest; class YResultSet; class YService; class YStatement; +class IscStatement; class YTransaction; class YObject @@ -98,10 +99,13 @@ public: void destroy(unsigned dstrFlags) { Firebird::MutexLockGuard guard(mtx, FB_FUNCTION); - FB_SIZE_T i; - while ((i = array.getCount()) > 0) - array[i - 1]->destroy(dstrFlags); + // Call destroy() only once even if handle is not removed from array + // by this call for any reason + for (int i = array.getCount() - 1; i >= 0; i--) + array[i]->destroy(dstrFlags); + + clear(); } void assign(HandleArray& from) @@ -375,6 +379,9 @@ public: void free(Firebird::CheckStatusWrapper* status); unsigned getFlags(Firebird::CheckStatusWrapper* status); + unsigned int getTimeout(Firebird::CheckStatusWrapper* status); + void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + public: Firebird::Mutex statementMutex; YAttachment* attachment; @@ -466,6 +473,11 @@ public: Firebird::IMessageMetadata* inMetadata, void* inBuffer, Firebird::IMessageMetadata* outMetadata, void* outBuffer); + unsigned int getIdleTimeout(Firebird::CheckStatusWrapper* status); + void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status); + void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut); + public: Firebird::IProvider* provider; Firebird::PathName dbPath; @@ -473,6 +485,7 @@ public: HandleArray childEvents; HandleArray childRequests; HandleArray childStatements; + HandleArray childIscStatements; HandleArray childTransactions; Firebird::Array cleanupHandlers; Firebird::StatusHolder savedStatus; // Do not use raise() method of this class in yValve. diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index 466196f3f4..61a8b8ce38 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -342,7 +342,10 @@ static const UCHAR op_line, op_args, 0}, subproc_decl[] = { op_subproc_decl, 0}, subfunc_decl[] = { op_subfunc_decl, 0}, - window_win[] = { op_byte, op_window_win, 0}; + window_win[] = { op_byte, op_window_win, 0}, + relation_field[] = { op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_literal, op_pad, op_line, 0}, + store3[] = { op_line, op_byte, op_line, op_verb, op_verb, op_verb, 0}; #include "../jrd/blp.h" @@ -3869,35 +3872,6 @@ public: { #ifndef WIN_NT PathUtils::concatPath(lockPrefix, WORKFILE, LOCKDIR); -#else -#ifdef WIN9X_SUPPORT - // shell32.dll version 5.0 and later supports SHGetFolderPath entry point - HMODULE hShFolder = LoadLibrary("shell32.dll"); - PFNSHGETFOLDERPATHA pfnSHGetFolderPath = - (PFNSHGETFOLDERPATHA) GetProcAddress(hShFolder, "SHGetFolderPathA"); - - if (!pfnSHGetFolderPath) - { - // For old OS versions fall back to shfolder.dll - FreeLibrary(hShFolder); - hShFolder = LoadLibrary("shfolder.dll"); - pfnSHGetFolderPath = - (PFNSHGETFOLDERPATHA) GetProcAddress(hShFolder, "SHGetFolderPathA"); - } - - char cmnData[MAXPATHLEN]; - if (pfnSHGetFolderPath && - pfnSHGetFolderPath(NULL, CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE, NULL, - SHGFP_TYPE_CURRENT, cmnData) == S_OK) - { - PathUtils::concatPath(lockPrefix, cmnData, LOCKDIR); - } - else - { - // If shfolder.dll is missing or API fails fall back to using old style location for locks - lockPrefix = prefix; - } - FreeLibrary(hShFolder); #else char cmnData[MAXPATHLEN]; if (SHGetSpecialFolderPath(NULL, cmnData, CSIDL_COMMON_APPDATA, TRUE)) @@ -3908,7 +3882,6 @@ public: { lockPrefix = prefix; // emergency default } -#endif // WIN9X_SUPPORT #endif // WIN_NT } lockPrefix.copyTo(fb_prefix_lock_val, sizeof(fb_prefix_lock_val)); diff --git a/src/yvalve/keywords.cpp b/src/yvalve/keywords.cpp index 838fe9a4b7..1753775d5e 100644 --- a/src/yvalve/keywords.cpp +++ b/src/yvalve/keywords.cpp @@ -221,6 +221,7 @@ static const TOK tokens[] = {TOK_HAVING, "HAVING", false}, {TOK_HOUR, "HOUR", false}, {TOK_IDENTITY, "IDENTITY", true}, + {TOK_IDLE, "IDLE", true}, {TOK_IF, "IF", true}, {TOK_IGNORE, "IGNORE", true}, {TOK_IIF, "IIF", true}, @@ -308,6 +309,7 @@ static const TOK tokens[] = {TOK_OVER, "OVER", false}, {TOK_OVERFLOW, "OVERFLOW", true}, {TOK_OVERLAY, "OVERLAY", true}, + {TOK_OVERRIDING, "OVERRIDING", true}, {TOK_PACKAGE, "PACKAGE", true}, {TOK_PAD, "PAD", true}, {TOK_PAGE, "PAGE", true}, @@ -394,6 +396,7 @@ static const TOK tokens[] = {TOK_SENSITIVE, "SENSITIVE", false}, {TOK_SEQUENCE, "SEQUENCE", true}, {TOK_SERVERWIDE, "SERVERWIDE", true}, + {TOK_SESSION, "SESSION", true}, {TOK_SET, "SET", false}, {TOK_SHADOW, "SHADOW", true}, {TOK_SHARED, "SHARED", true}, diff --git a/src/yvalve/why.cpp b/src/yvalve/why.cpp index 4b27496e75..0acb5ed8e0 100644 --- a/src/yvalve/why.cpp +++ b/src/yvalve/why.cpp @@ -634,55 +634,6 @@ int SQLDAMetadata::detach() } -class IscStatement : public RefCounted, public GlobalStorage, public YObject -{ -public: - static const ISC_STATUS ERROR_CODE = isc_bad_stmt_handle; - - explicit IscStatement(YAttachment* aAttachment) - : cursorName(getPool()), - attachment(aAttachment), - statement(NULL), - userHandle(NULL), - pseudoOpened(false), - delayedFormat(false) - { } - - FB_API_HANDLE& getHandle(); - void openCursor(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, - IMessageMetadata* inMetadata, UCHAR* buffer, IMessageMetadata* outMetadata); - void closeCursor(CheckStatusWrapper* status, bool raise); - void closeStatement(CheckStatusWrapper* status); - - void execute(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, - IMessageMetadata* inMetadata, UCHAR* inBuffer, IMessageMetadata* outMetadata, UCHAR* outBuffer); - FB_BOOLEAN fetch(CheckStatusWrapper* status, IMessageMetadata* outMetadata, UCHAR* outBuffer); - - void checkPrepared(ISC_STATUS code = isc_unprepared_stmt) const - { - if (!statement) - Arg::Gds(code).raise(); - } - - void checkCursorOpened() const - { - if (!statement || !statement->cursor) - Arg::Gds(isc_dsql_cursor_not_open).raise(); - } - - void checkCursorClosed() const - { - if (statement && statement->cursor) - Arg::Gds(isc_dsql_cursor_open_err).raise(); - } - - string cursorName; - YAttachment* attachment; - YStatement* statement; - FB_API_HANDLE* userHandle; - bool pseudoOpened, delayedFormat; -}; - GlobalPtr handleMappingLock; GlobalPtr > > > services; GlobalPtr > > > attachments; @@ -766,13 +717,6 @@ RefPtr translateHandle(GlobalPtr return RefPtr(*obj); } -FB_API_HANDLE& IscStatement::getHandle() -{ - if (!handle) - makeHandle(&statements, this, handle); - return handle; -} - //------------------------------------- const int SHUTDOWN_TIMEOUT = 5000; // 5 sec @@ -1241,6 +1185,61 @@ namespace Why RefPtr nextRef; }; + class IscStatement : public RefCounted, public GlobalStorage, public YObject + { + public: + static const ISC_STATUS ERROR_CODE = isc_bad_stmt_handle; + + explicit IscStatement(YAttachment* aAttachment) + : cursorName(getPool()), + attachment(aAttachment), + statement(NULL), + userHandle(NULL), + pseudoOpened(false), + delayedFormat(false) + { } + + ~IscStatement() override; + + FB_API_HANDLE& getHandle(); + void destroy(unsigned); + + void openCursor(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, + IMessageMetadata* inMetadata, UCHAR* buffer, IMessageMetadata* outMetadata); + + void closeCursor(CheckStatusWrapper* status, bool raise); + void closeStatement(CheckStatusWrapper* status); + + void execute(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, + IMessageMetadata* inMetadata, UCHAR* inBuffer, IMessageMetadata* outMetadata, UCHAR* outBuffer); + + FB_BOOLEAN fetch(CheckStatusWrapper* status, IMessageMetadata* outMetadata, UCHAR* outBuffer); + + void checkPrepared(ISC_STATUS code = isc_unprepared_stmt) const + { + if (!statement) + Arg::Gds(code).raise(); + } + + void checkCursorOpened() const + { + if (!statement || !statement->cursor) + Arg::Gds(isc_dsql_cursor_not_open).raise(); + } + + void checkCursorClosed() const + { + if (statement && statement->cursor) + Arg::Gds(isc_dsql_cursor_open_err).raise(); + } + + string cursorName; + YAttachment* attachment; + YStatement* statement; + FB_API_HANDLE* userHandle; + bool pseudoOpened, delayedFormat; + }; + template <> YEntry::YEntry(CheckStatusWrapper* aStatus, YAttachment* aAttachment, int checkAttachment) : ref(aAttachment), nextRef(NULL) @@ -1311,103 +1310,6 @@ namespace Why } // namespace Why -namespace { - void IscStatement::openCursor(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, - IMessageMetadata* inMetadata, UCHAR* buffer, IMessageMetadata* outMetadata) - { - checkCursorClosed(); - - // Transaction is not optional for statement returning result set - RefPtr transaction = translateHandle(transactions, traHandle);; - - statement->openCursor(status, transaction, inMetadata, buffer, outMetadata, 0); - - if (status->getState() & Firebird::IStatus::STATE_ERRORS) - return; - - fb_assert(statement->cursor); - - delayedFormat = (outMetadata == DELAYED_OUT_FORMAT); - } - - void IscStatement::closeCursor(CheckStatusWrapper* status, bool raise) - { - if (statement && statement->cursor) - { - fb_assert(!pseudoOpened); - - statement->cursor->close(status); - if (status->getState() & Firebird::IStatus::STATE_ERRORS) - status_exception::raise(status); - - statement->cursor = NULL; - } - else if (pseudoOpened) - pseudoOpened = false; - else if (raise) - Arg::Gds(isc_dsql_cursor_close_err).raise(); - } - - void IscStatement::closeStatement(CheckStatusWrapper* status) - { - if (statement) - { - statement->free(status); - if (status->getState() & Firebird::IStatus::STATE_ERRORS) - status_exception::raise(status); - - statement = NULL; - } - } - - void IscStatement::execute(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, - IMessageMetadata* inMetadata, UCHAR* inBuffer, IMessageMetadata* outMetadata, - UCHAR* outBuffer) - { - checkCursorClosed(); - - RefPtr transaction; - if (traHandle && *traHandle) - transaction = translateHandle(transactions, traHandle); - - ITransaction* newTrans = statement->execute(status, transaction, - inMetadata, inBuffer, outMetadata, outBuffer); - - if (!(status->getState() & Firebird::IStatus::STATE_ERRORS)) - { - if (transaction && !newTrans) - { - transaction->destroy(0); - *traHandle = 0; - } - else if (!transaction && newTrans) - { - // in this case we know for sure that newTrans points to YTransaction - if (traHandle) - *traHandle = static_cast(newTrans)->getHandle(); - } - } - } - - FB_BOOLEAN IscStatement::fetch(CheckStatusWrapper* status, IMessageMetadata* outMetadata, - UCHAR* outBuffer) - { - checkCursorOpened(); - - if (delayedFormat) - { - statement->cursor->setDelayedOutputFormat(status, outMetadata); - - if (status->getState() & Firebird::IStatus::STATE_ERRORS) - return FB_FALSE; - - delayedFormat = false; - } - - return statement->cursor->fetchNext(status, outBuffer) == IStatus::RESULT_OK; - } -} - struct TEB { FB_API_HANDLE* teb_database; @@ -2163,6 +2065,7 @@ ISC_STATUS API_ROUTINE isc_dsql_allocate_statement(ISC_STATUS* userStatus, FB_AP statement = FB_NEW IscStatement(attachment); statement->addRef(); + attachment->childIscStatements.add(statement); *stmtHandle = statement->getHandle(); } catch (const Exception& e) @@ -2586,8 +2489,10 @@ ISC_STATUS API_ROUTINE isc_dsql_free_statement(ISC_STATUS* userStatus, FB_API_HA // Release everything statement->closeCursor(&statusWrapper, false); statement->closeStatement(&statusWrapper); - statement->release(); - removeHandle(&statements, *stmtHandle); + // statement->userHandle is not erased here because this routine can be called + // against a copy of original variable. + // This call must release statement and clean handles + statement->destroy(0); *stmtHandle = 0; } else if (option & DSQL_unprepare) @@ -2759,6 +2664,29 @@ ISC_STATUS API_ROUTINE isc_dsql_set_cursor_name(ISC_STATUS* userStatus, FB_API_H } +// Set statement timeout. +ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle, + ULONG timeout) +{ + StatusVector status(userStatus); + CheckStatusWrapper statusWrapper(&status); + + try + { + RefPtr statement(translateHandle(statements, stmtHandle)); + + if (statement->statement) + statement->statement->setTimeout(&statusWrapper, timeout); + } + catch (const Exception& e) + { + e.stuffException(&statusWrapper); + } + + return status[1]; +} + + // Provide information on sql statement. ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle, SSHORT itemLength, const SCHAR* items, SSHORT bufferLength, SCHAR* buffer) @@ -4201,8 +4129,6 @@ void YStatement::destroy(unsigned dstrFlags) attachment->childStatements.remove(this); attachment = NULL; - removeHandle(&statements, handle); - destroy2(dstrFlags); } @@ -4443,6 +4369,156 @@ void YStatement::free(CheckStatusWrapper* status) } } +//------------------------------------- + +IscStatement::~IscStatement() +{ + if (userHandle) + { + *userHandle = 0; + userHandle = nullptr; + } + + removeHandle(&statements, handle); +} + +void IscStatement::destroy(unsigned) +{ + attachment->childIscStatements.remove(this); + attachment = NULL; + release(); +} + +FB_API_HANDLE& IscStatement::getHandle() +{ + if (!handle) + makeHandle(&statements, this, handle); + return handle; +} + +void IscStatement::openCursor(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, + IMessageMetadata* inMetadata, UCHAR* buffer, IMessageMetadata* outMetadata) +{ + checkCursorClosed(); + + // Transaction is not optional for statement returning result set + RefPtr transaction = translateHandle(transactions, traHandle);; + + statement->openCursor(status, transaction, inMetadata, buffer, outMetadata, 0); + + if (status->getState() & Firebird::IStatus::STATE_ERRORS) + return; + + fb_assert(statement->cursor); + + delayedFormat = (outMetadata == DELAYED_OUT_FORMAT); +} + +void IscStatement::closeCursor(CheckStatusWrapper* status, bool raise) +{ + if (statement && statement->cursor) + { + fb_assert(!pseudoOpened); + + statement->cursor->close(status); + if (status->getState() & Firebird::IStatus::STATE_ERRORS) + status_exception::raise(status); + + statement->cursor = NULL; + } + else if (pseudoOpened) + pseudoOpened = false; + else if (raise) + Arg::Gds(isc_dsql_cursor_close_err).raise(); +} + +void IscStatement::closeStatement(CheckStatusWrapper* status) +{ + if (statement) + { + statement->free(status); + if (status->getState() & Firebird::IStatus::STATE_ERRORS) + status_exception::raise(status); + + statement = NULL; + } +} + +void IscStatement::execute(CheckStatusWrapper* status, FB_API_HANDLE* traHandle, + IMessageMetadata* inMetadata, UCHAR* inBuffer, IMessageMetadata* outMetadata, + UCHAR* outBuffer) +{ + checkCursorClosed(); + + RefPtr transaction; + if (traHandle && *traHandle) + transaction = translateHandle(transactions, traHandle); + + ITransaction* newTrans = statement->execute(status, transaction, + inMetadata, inBuffer, outMetadata, outBuffer); + + if (!(status->getState() & Firebird::IStatus::STATE_ERRORS)) + { + if (transaction && !newTrans) + { + transaction->destroy(0); + *traHandle = 0; + } + else if (!transaction && newTrans) + { + // in this case we know for sure that newTrans points to YTransaction + if (traHandle) + *traHandle = static_cast(newTrans)->getHandle(); + } + } +} + +FB_BOOLEAN IscStatement::fetch(CheckStatusWrapper* status, IMessageMetadata* outMetadata, UCHAR* outBuffer) +{ + checkCursorOpened(); + + if (delayedFormat) + { + statement->cursor->setDelayedOutputFormat(status, outMetadata); + + if (status->getState() & Firebird::IStatus::STATE_ERRORS) + return FB_FALSE; + + delayedFormat = false; + } + + return statement->cursor->fetchNext(status, outBuffer) == IStatus::RESULT_OK; +} + +unsigned int YStatement::getTimeout(CheckStatusWrapper* status) +{ + try + { + YEntry entry(status, this); + return entry.next()->getTimeout(status); + } + catch (const Exception& e) + { + e.stuffException(status); + } + + return 0; +} + + +void YStatement::setTimeout(CheckStatusWrapper* status, unsigned int timeOut) +{ + try + { + YEntry entry(status, this); + entry.next()->setTimeout(status, timeOut); + } + catch (const Exception& e) + { + e.stuffException(status); + } +} + //------------------------------------- @@ -4955,6 +5031,7 @@ YAttachment::YAttachment(IProvider* aProvider, IAttachment* aNext, const PathNam childEvents(getPool()), childRequests(getPool()), childStatements(getPool()), + childIscStatements(getPool()), childTransactions(getPool()), cleanupHandlers(getPool()) { @@ -4987,6 +5064,7 @@ void YAttachment::destroy(unsigned dstrFlags) childRequests.destroy(dstrFlags & ~DF_RELEASE); childStatements.destroy(dstrFlags & ~DF_RELEASE); + childIscStatements.destroy(dstrFlags & ~DF_RELEASE); childBlobs.destroy(dstrFlags & ~DF_RELEASE); childEvents.destroy(dstrFlags & ~DF_RELEASE); childTransactions.destroy(dstrFlags & ~DF_RELEASE); @@ -5517,6 +5595,66 @@ void YAttachment::getNextTransaction(CheckStatusWrapper* status, ITransaction* t } +unsigned int YAttachment::getIdleTimeout(CheckStatusWrapper* status) +{ + try + { + YEntry entry(status, this); + return entry.next()->getIdleTimeout(status); + } + catch (const Exception& e) + { + e.stuffException(status); + } + + return 0; +} + + +void YAttachment::setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut) +{ + try + { + YEntry entry(status, this); + entry.next()->setIdleTimeout(status, timeOut); + } + catch (const Exception& e) + { + e.stuffException(status); + } +} + + +unsigned int YAttachment::getStatementTimeout(CheckStatusWrapper* status) +{ + try + { + YEntry entry(status, this); + return entry.next()->getStatementTimeout(status); + } + catch (const Exception& e) + { + e.stuffException(status); + } + + return 0; +} + + +void YAttachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut) +{ + try + { + YEntry entry(status, this); + entry.next()->setStatementTimeout(status, timeOut); + } + catch (const Exception& e) + { + e.stuffException(status); + } +} + + //------------------------------------- diff --git a/src/yvalve/why_proto.h b/src/yvalve/why_proto.h index d08953a611..c8d659d827 100644 --- a/src/yvalve/why_proto.h +++ b/src/yvalve/why_proto.h @@ -89,6 +89,7 @@ ISC_STATUS API_ROUTINE isc_dsql_prepare_m(ISC_STATUS*, FB_API_HANDLE*, SCHAR*); ISC_STATUS API_ROUTINE isc_dsql_set_cursor_name(ISC_STATUS*, FB_API_HANDLE*, const SCHAR*, USHORT); +ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS*, FB_API_HANDLE*, ULONG timeout); ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS*, FB_API_HANDLE*, SSHORT, const SCHAR*, SSHORT, SCHAR*); //ISC_STATUS API_ROUTINE isc_prepare_transaction2(ISC_STATUS*, FB_API_HANDLE*, USHORT, diff --git a/travis.sh b/travis.sh old mode 100644 new mode 100755 index 8973a3ea76..e2abb0209f --- a/travis.sh +++ b/travis.sh @@ -1,11 +1,5 @@ #!/usr/bin/env bash -start_server() { - until ./gen/Release/firebird/bin/firebird; do - echo "Firebird crashed with exit code $?. Restarting..." >&2 - done -} - dummy_output() { while true; do sleep 9m