diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6f262c6d..7fb1c54475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,10 @@ * [CORE-5064](http://tracker.firebirdsql.org/browse/CORE-5064): Add datatypes (VAR)BINARY(n) and BINARY VARYING(n) as aliases for (VAR)CHAR(n) CHARACTER SET OCTETS Contributor(s): Dimitry Sibiryakov +* [CORE-4436](http://tracker.firebirdsql.org/browse/CORE-4436): Support for different hash algorithms in HASH system function + Reference(s): [doc/sql.extensions/README.builtin_functions.txt](https://github.com/FirebirdSQL/firebird/raw/master/doc/sql.extensions/README.builtin_functions.txt) + Contributor(s): Adriano dos Santos Fernandes + * [CORE-2557](http://tracker.firebirdsql.org/browse/CORE-2557): Grants on MON$ tables Contributor(s): Alex Peshkoff diff --git a/doc/sql.extensions/README.builtin_functions.txt b/doc/sql.extensions/README.builtin_functions.txt index 564afced79..f397d52b90 100644 --- a/doc/sql.extensions/README.builtin_functions.txt +++ b/doc/sql.extensions/README.builtin_functions.txt @@ -473,13 +473,21 @@ HASH ---- Function: - Returns a HASH of a string. + Returns a HASH of a string using a specified algorithm. Format: - HASH( ) + HASH( [ USING ] ) + + algorithm ::= { MD5 | SHA1 | SHA256 | SHA512 } + +Important: + - The syntax without USING is very discouraged and maintained for backward compatibility. + It returns a 64 bit integer and produces very bad hashes that easily result in collisions. + - The syntax with USING is introduced in FB 4.0 and returns VARCHAR strings with OCTETS charset. Example: select hash(x) from y; + select hash(x using sha256) from y; ---- diff --git a/lang_helpers/gds_codes.ftn b/lang_helpers/gds_codes.ftn index 6c6301d9d9..0814e509c9 100644 --- a/lang_helpers/gds_codes.ftn +++ b/lang_helpers/gds_codes.ftn @@ -1714,6 +1714,8 @@ C -- PARAMETER (GDS__subfunc_not_impl = 335545150) INTEGER*4 GDS__subproc_not_impl PARAMETER (GDS__subproc_not_impl = 335545151) + INTEGER*4 GDS__sysf_invalid_hash_algorithm + PARAMETER (GDS__sysf_invalid_hash_algorithm = 335545152) INTEGER*4 GDS__gfix_db_name PARAMETER (GDS__gfix_db_name = 335740929) INTEGER*4 GDS__gfix_invalid_sw diff --git a/lang_helpers/gds_codes.pas b/lang_helpers/gds_codes.pas index ca96b073d1..b77d7eeef2 100644 --- a/lang_helpers/gds_codes.pas +++ b/lang_helpers/gds_codes.pas @@ -1709,6 +1709,8 @@ const gds_subfunc_not_impl = 335545150; isc_subproc_not_impl = 335545151; gds_subproc_not_impl = 335545151; + isc_sysf_invalid_hash_algorithm = 335545152; + gds_sysf_invalid_hash_algorithm = 335545152; isc_gfix_db_name = 335740929; gds_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 2c52a669b9..5ee2adefbc 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -7709,7 +7709,6 @@ system_function_std_syntax | EXP | FLOOR | GEN_UUID - | HASH | LEFT | LN | LOG @@ -7769,6 +7768,14 @@ system_function_special_syntax newNode(MAKE_const_slong($3))->add($5)->add($7)); $$->dsqlSpecialSyntax = true; } + | HASH '(' value ')' + { $$ = newNode(*$1, newNode($3)); } + | HASH '(' value USING valid_symbol_name ')' + { + $$ = newNode(*$1, + newNode($3)->add(MAKE_str_constant(newIntlString($5->c_str()), CS_ASCII))); + $$->dsqlSpecialSyntax = true; + } | OVERLAY '(' value PLACING value FROM value FOR value ')' { $$ = newNode(*$1, diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index 93df2212fa..efc8dab155 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -853,6 +853,7 @@ static const struct { {"subproc_defvaldecl", 335545149}, {"subfunc_not_impl", 335545150}, {"subproc_not_impl", 335545151}, + {"sysf_invalid_hash_algorithm", 335545152}, {"gfix_db_name", 335740929}, {"gfix_invalid_sw", 335740930}, {"gfix_incmp_sw", 335740932}, diff --git a/src/include/gen/iberror.h b/src/include/gen/iberror.h index dbfa5d8837..e28e087c71 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -887,6 +887,7 @@ const ISC_STATUS isc_subfunc_defvaldecl = 335545148L; const ISC_STATUS isc_subproc_defvaldecl = 335545149L; const ISC_STATUS isc_subfunc_not_impl = 335545150L; const ISC_STATUS isc_subproc_not_impl = 335545151L; +const ISC_STATUS isc_sysf_invalid_hash_algorithm = 335545152L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -1361,7 +1362,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 = 1305; +const ISC_STATUS isc_err_max = 1306; #else /* c definitions */ @@ -2218,6 +2219,7 @@ const ISC_STATUS isc_err_max = 1305; #define isc_subproc_defvaldecl 335545149L #define isc_subfunc_not_impl 335545150L #define isc_subproc_not_impl 335545151L +#define isc_sysf_invalid_hash_algorithm 335545152L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2692,7 +2694,7 @@ const ISC_STATUS isc_err_max = 1305; #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 1305 +#define isc_err_max 1306 #endif diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index c0fe687d81..20b2f0ab2d 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -856,6 +856,7 @@ Data source : @4"}, /* eds_statement */ {335545149, "Default values for parameters are not allowed in definition of the previously declared sub-procedure @1"}, /* subproc_defvaldecl */ {335545150, "Sub-function @1 was declared but not implemented"}, /* subfunc_not_impl */ {335545151, "Sub-procedure @1 was declared but not implemented"}, /* subproc_not_impl */ + {335545152, "Invalid HASH algorithm @1"}, /* sysf_invalid_hash_algorithm */ {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 */ diff --git a/src/include/gen/sql_code.h b/src/include/gen/sql_code.h index 62a5c81d5c..363b018a4d 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -852,6 +852,7 @@ static const struct { {335545149, -901}, /* 829 subproc_defvaldecl */ {335545150, -901}, /* 830 subfunc_not_impl */ {335545151, -901}, /* 831 subproc_not_impl */ + {335545152, -901}, /* 832 sysf_invalid_hash_algorithm */ {335740929, -901}, /* 1 gfix_db_name */ {335740930, -901}, /* 2 gfix_invalid_sw */ {335740932, -901}, /* 4 gfix_incmp_sw */ diff --git a/src/include/gen/sql_state.h b/src/include/gen/sql_state.h index d66f3ac9dc..69949ec9ab 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -852,6 +852,7 @@ static const struct { {335545149, "42000"}, // 829 subproc_defvaldecl {335545150, "42000"}, // 830 subfunc_not_impl {335545151, "42000"}, // 831 subproc_not_impl + {335545152, "42000"}, // 832 sysf_invalid_hash_algorithm {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index 16e6ded39e..35e0d53bf2 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -53,6 +53,7 @@ #include "../jrd/trace/TraceObjects.h" #include "../jrd/Collation.h" #include "../common/classes/FpeControl.h" +#include #include using namespace Firebird; @@ -101,6 +102,37 @@ enum TrigonFunction }; +struct HashAlgorithmDescriptor +{ + const char* name; + USHORT length; + std::function create; + + static const HashAlgorithmDescriptor* find(const char* name); +}; + +static const HashAlgorithmDescriptor hashAlgorithmDescriptors[] = { + {"MD5", 16, [](MemoryPool& pool) { return FB_NEW_POOL(pool) Md5HashContext(pool); }}, + {"SHA1", 20, [](MemoryPool& pool) { return FB_NEW_POOL(pool) Sha1HashContext(pool); }}, + {"SHA256", 32, [](MemoryPool& pool) { return FB_NEW_POOL(pool) Sha256HashContext(pool); }}, + {"SHA512", 64, [](MemoryPool& pool) { return FB_NEW_POOL(pool) Sha512HashContext(pool); }} +}; + +const HashAlgorithmDescriptor* HashAlgorithmDescriptor::find(const char* name) +{ + unsigned count = FB_NELEM(hashAlgorithmDescriptors); + + for (unsigned i = 0; i < count; ++i) + { + if (strcmp(name, hashAlgorithmDescriptors[i].name) == 0) + return &hashAlgorithmDescriptors[i]; + } + + status_exception::raise(Arg::Gds(isc_sysf_invalid_hash_algorithm) << name); + return nullptr; +} + + // constants const int oneDay = 86400; @@ -145,6 +177,7 @@ void makeBinShift(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, d void makeCeilFloor(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); void makeDateAdd(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); void makeGetSetContext(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); +void makeHash(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); void makeLeftRight(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); void makeMod(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); void makeOverlay(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args); @@ -901,6 +934,27 @@ void makeGetSetContext(DataTypeUtilBase* /*dataTypeUtil*/, const SysFunction* fu } +void makeHash(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, + int argsCount, const dsc** args) +{ + fb_assert(argsCount >= function->minArgCount); + + if (argsCount == 1) + makeInt64Result(dataTypeUtil, function, result, argsCount, args); + else if (argsCount >= 2) + { + if (!args[1]->dsc_address || !args[1]->isText()) // not a constant + status_exception::raise(Arg::Gds(isc_sysf_invalid_hash_algorithm) << ""); + + MetaName algorithmName; + MOV_get_metaname(JRD_get_thread_data(), args[1], algorithmName); + + result->makeVarying(HashAlgorithmDescriptor::find(algorithmName.c_str())->length, ttype_binary); + result->setNullable(args[0]->isNullable()); + } +} + + void makeLeftRight(DataTypeUtilBase* dataTypeUtil, const SysFunction* function, dsc* result, int argsCount, const dsc** args) { @@ -2640,7 +2694,7 @@ dsc* evlSetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar dsc* evlHash(thread_db* tdbb, const SysFunction*, const NestValueArray& args, impure_value* impure) { - fb_assert(args.getCount() == 1); + fb_assert(args.getCount() >= 1); jrd_req* request = tdbb->getRequest(); @@ -2648,8 +2702,27 @@ dsc* evlHash(thread_db* tdbb, const SysFunction*, const NestValueArray& args, if (request->req_flags & req_null) // return NULL if value is NULL return NULL; - WeakHashContext hashContext; - impure->vlu_misc.vlu_int64 = 0; + AutoPtr hashContext; + MemoryPool& pool = *request->req_pool; + + if (args.getCount() >= 2) + { + const dsc* algorithmDesc = EVL_expr(tdbb, request, args[1]); + if (request->req_flags & req_null) // return NULL if algorithm is NULL + return NULL; + + if (!algorithmDesc->isText()) + status_exception::raise(Arg::Gds(isc_sysf_invalid_hash_algorithm) << ""); + + MetaName algorithmName; + MOV_get_metaname(tdbb, algorithmDesc, algorithmName); + hashContext.reset(HashAlgorithmDescriptor::find(algorithmName.c_str())->create(pool)); + } + else + { + hashContext.reset(FB_NEW_POOL(pool) WeakHashContext()); + impure->vlu_misc.vlu_int64 = 0; + } if (value->isBlob()) { @@ -2660,7 +2733,7 @@ dsc* evlHash(thread_db* tdbb, const SysFunction*, const NestValueArray& args, while (!(blob->blb_flags & BLB_eof)) { const ULONG length = blob->BLB_get_data(tdbb, buffer, sizeof(buffer), false); - hashContext.update(buffer, length); + hashContext->update(buffer, length); } blob->BLB_close(tdbb); @@ -2670,17 +2743,26 @@ dsc* evlHash(thread_db* tdbb, const SysFunction*, const NestValueArray& args, UCHAR* address; MoveBuffer buffer; const ULONG length = MOV_make_string2(tdbb, value, value->getTextType(), &address, buffer, false); - hashContext.update(address, length); + hashContext->update(address, length); } HashContext::Buffer resultBuffer; - hashContext.finish(resultBuffer); + hashContext->finish(resultBuffer); - fb_assert(resultBuffer.getCount() == sizeof(SINT64)); - memcpy(&impure->vlu_misc.vlu_int64, resultBuffer.begin(), sizeof(SINT64)); + if (args.getCount() >= 2) + { + dsc result; + result.makeText(resultBuffer.getCount(), ttype_binary, resultBuffer.begin()); + EVL_make_value(tdbb, &result, impure); + } + else + { + fb_assert(resultBuffer.getCount() == sizeof(SINT64)); + memcpy(&impure->vlu_misc.vlu_int64, resultBuffer.begin(), sizeof(SINT64)); - // make descriptor for return value - impure->vlu_desc.makeInt64(0, &impure->vlu_misc.vlu_int64); + // make descriptor for return value + impure->vlu_desc.makeInt64(0, &impure->vlu_misc.vlu_int64); + } return &impure->vlu_desc; } @@ -4262,7 +4344,7 @@ const SysFunction SysFunction::functions[] = {"EXP", 1, 1, setParamsDblDec, makeDblDecResult, evlExp, NULL}, {"FLOOR", 1, 1, setParamsDblDec, makeCeilFloor, evlFloor, NULL}, {"GEN_UUID", 0, 0, NULL, makeUuid, evlGenUuid, NULL}, - {"HASH", 1, 1, NULL, makeInt64Result, evlHash, NULL}, + {"HASH", 1, 2, NULL, makeHash, evlHash, NULL}, {"LEFT", 2, 2, setParamsSecondInteger, makeLeftRight, evlLeft, NULL}, {"LN", 1, 1, setParamsDblDec, makeDblDecResult, evlLnLog10, (void*) funLnat}, {"LOG", 2, 2, setParamsDblDec, makeDblDecResult, evlLog, NULL}, diff --git a/src/jrd/mov.cpp b/src/jrd/mov.cpp index 1b9d85803c..77a73d164f 100644 --- a/src/jrd/mov.cpp +++ b/src/jrd/mov.cpp @@ -176,7 +176,7 @@ void MOV_get_metaname(Jrd::thread_db* tdbb, const dsc* desc, MetaName& name) fb_assert(length && ptr); fb_assert(length <= MAX_SQL_IDENTIFIER_LEN); - fb_assert(ttype == ttype_metadata); + fb_assert(ttype == ttype_ascii || ttype == ttype_metadata); name.assign(reinterpret_cast(ptr), length); } diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index 567ad4b2e5..4cc0705e59 100644 --- a/src/msgs/facilities2.sql +++ b/src/msgs/facilities2.sql @@ -1,7 +1,7 @@ /* 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 (?, ?, ?, ?); -- -('2017-07-10 12:27:00', 'JRD', 0, 832) +('2017-07-24 12:10:00', 'JRD', 0, 833) ('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) diff --git a/src/msgs/messages2.sql b/src/msgs/messages2.sql index 3f7a2c5a75..88e363d8c1 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -939,6 +939,7 @@ Data source : @4', NULL, NULL) ('subproc_defvaldecl', NULL, 'StmtNodes.cpp', NULL, 0, 829, NULL, 'Default values for parameters are not allowed in definition of the previously declared sub-procedure @1', NULL, NULL); ('subfunc_not_impl', NULL, 'StmtNodes.cpp', NULL, 0, 830, NULL, 'Sub-function @1 was declared but not implemented', NULL, NULL); ('subproc_not_impl', NULL, 'StmtNodes.cpp', NULL, 0, 831, NULL, 'Sub-procedure @1 was declared but not implemented', NULL, NULL); +('sysf_invalid_hash_algorithm', NULL, 'SysFunction.cpp', NULL, 0, 832, NULL, 'Invalid HASH algorithm @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); diff --git a/src/msgs/system_errors2.sql b/src/msgs/system_errors2.sql index 648e2ec1fb..4db7057ec5 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -838,6 +838,7 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-901, '42', '000', 0, 829, 'subproc_defvaldecl', NULL, NULL) (-901, '42', '000', 0, 830, 'subfunc_not_impl', NULL, NULL) (-901, '42', '000', 0, 831, 'subproc_not_impl', NULL, NULL) +(-901, '42', '000', 0, 832, 'sysf_invalid_hash_algorithm', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL)