From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtpng2.m.smailru.net (smtpng2.m.smailru.net [94.100.179.3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id C25C4469719 for ; Mon, 28 Sep 2020 23:07:49 +0300 (MSK) References: <66362762-8791-bea3-745f-afc1e3eaa199@tarantool.org> From: Vladislav Shpilevoy Message-ID: <208ccd32-fb16-c2dc-3af7-2a8cb437160e@tarantool.org> Date: Mon, 28 Sep 2020 22:07:41 +0200 MIME-Version: 1.0 In-Reply-To: <66362762-8791-bea3-745f-afc1e3eaa199@tarantool.org> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Subject: Re: [Tarantool-discussions] SQL built-in functions position List-Id: Tarantool development process List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Mergen Imeev , Peter Gulutzan , Nikita Pettik , kyukhin@tarantool.org, tsafin@tarantool.org, sergos@tarantool.org Cc: tarantool-discussions@dev.tarantool.org Hi! On 27.09.2020 17:18, Mergen Imeev wrote: > Hi all. I have a question that I would like to discuss. > > The question is about SQL built-in functions. At the moment these functions are > partially described in _func and partially in src/box/sql/func.c. I received two > completely different suggestions from my reviewers on what to do with these > functions: > 1) Move definitions completely to _func. Remove definitions from func.c. > 2) Move definitions completely to func.c. Remove definitions from _func. > > In the first case, users will be able to see the function definitions. Also, in > the future, we may allow these functions to be called from Lua (although not > sure if this is necessary). The main idea is 'all functions have the same > interface'. > > In the second case, the implementation is simpler, and we can more easily > implement some features, such as "virtual" functions. For users, the definition > can only be seen in the documentation. The main idea is 'SQL built-in functions > are part of SQL'. > > Which of these approaches do you think is more beneficial to us? I am the one who proposed the second option. I proposed it because of several reasons. ================================================== ## Reason 1 The implementation is ugly by design. Since SQL functions are strictly typed, and some of them may take more than one type, we were forced to implement some kind of function overload by mangling function names. We are not trying to implement C++ here, it goes against the _func basic schema. To workaround that there were added a new ugly option: is_overloaded. Overloaded means that there is still one function but declared twice, just to check types. For example, LENGTH and LENGTH_VARBINARY, TRIM, TRIM_VARBINARY, and so on. That leads to an issue, that we have not existing functions in _func. For example: tarantool> box.space._func.index.name:select({'LENGTH_VARBINARY'}) --- - - [68, 1, 'LENGTH_VARBINARY', 1, 'SQL_BUILTIN', '', 'function', ['varbinary'], 'integer', 'none', 'none', true, false, true, ['SQL'], {'is_overloaded': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] ... tarantool> box.execute("SELECT LENGTH_VARBINARY('abc')") --- - null - Function 'LENGTH_VARBINARY' does not exist ... Doesn't this look bad? That is -1 point to the argument about 'it is good to have function visible in _func'. They are visible, but it does not mean anything. ==================== ## Reason 2 SQL has vararg functions - the ones which take unknown number of arguments. That led to addition of one another ugly option: has_vararg. When it is true, all arguments after last declared argument are forced to have the same type as the last argument. This looks wrong from all possible sides. Firstly, if a function has variable argument count, why the hell should they all be of the same type? It is a silly restriction motivated entirely by SQL specifics like SUM() taking all arguments of the same type, or LEAST(), or GREATEST() and so on. That is very-SQL thing. I can't imagine when such a restriction may be needed in other supported languages. Secondly, like with the option 'is_overloaded' it complicates output of _func and makes it harder for a user to create a function, when he sees so many irrelevant options, needed for SQL only. Just take a look at the _func output in the end of this email to see how bad is it [1]. Thirdly, it simply does not work for functions which are really vararg, and not some kind of aggregates, ironically. For example, PRINTF(). To workaround the workaround-option has_vararg such functions simply turn off their type checks by making the last argument SCALAR. Well, this looks really bad. It would become a bit simpler if we would split that option in two - has_vararg to make an equivalent of ... in C with values of any type, and is_aggregate to check all values have the same type. But still ugly. ==================== ## Reason 3 SQL built-in functions are a part of the language. When we store them separately, we risk to get SQL implementation different from the built-in SQL functions schema. For example, we will add a new built-in function, and a user will upgrade, but won't be able to use it until he upgrades the schema, and we will need to support that in SQL implementation - that some built-in functions actually may not exist. ==================== ## Reason 4 Some of the functions are supposed to be used for aggregated values, and this makes their implementation not reusable in other languages. That in turn makes their presence in the common _func space, used for all functions, irrelevant. I am talking about SUM(), COUNT(), AVG(), probably there are more. ================================================================================ Now talking of the points I received about how good would it be to have these functions in _func. ==================== ## Built-in functions require privileges? - No. Cite from Peter's email: The current built-in functions are harmless, except perhaps for RANDOMBLOB with a huge value. However, in future maybe there will be built-in functions that should require privileges. In that case, I believe, they will have to be in _func (and someday in _vfunc) so that grant() will work for them. Cite from Nikita's email: allows to grant and verify priveleges in the same way and so forth What are examples of such built-in functions? Built-in functions are a part of the language. Restricting access to SQL built-ins is like restricting access to 'next()', 'string.upper()', 'os.time()' and other functions in Lua. Or restricting usage of 'operator+' in C++. They are a part of the language. If something needs restrictions, it does not seem to be a part of the language. If a function is sandboxed, so it does not affect other users, I don't see why would it need restrictions. All built-ins are sandboxed. ==================== ## Built-in functions prevent duplicates in _func? - No. Cite from Nikita's email: It makes name collisions check simple This is a matter of a few code lines to add that check to _func triggers to check new function name in all languages, if necessary. Or we could just make the functions hash do the checking. So we won't even need to involve SQL-specific code. That is by the way questionable. I would think more if we want to forbid definition of functions clashing with built-ins. That would be a 'legal' overload, if we would look into _func first, and then into built-ins, but I don't really know about that. Didn't think of it properly yet. ==================== ## Storage in _func does not change _func schema and documentation? - No. Cite from Peter's email: I did not document in the manual's SQL section that built-in functions will be in _func, so removing them is not a regression from documented behaviour. It is good that their presence in _func is not documented. Because even if it would, and we would go for _func extension, we would change _func schema and options anyway, because of the new options 'is_overloaded' and 'has_vararg', and new fake functions with _VARBINARY suffix. Cite from Nikita's email: Built-ins are already declaraed in _func, so reverting this thing would result in another one unnecessary schema change and upgrade (so I doubt that implementation would be somehow 'simpler') This is also wrong. Both versions change _func. But the version with extending _func makes it bigger, adds new ugly SQL-specific options, and adds not existing functions with fake names. In the version about functions-as-part-of-language _func is cleared from these functions, and nothing more happens with the schema. So the patch will be simpler for sure, even though may get bigger due to more removals. Besides, their removal from _func would also allow to get rid of the crutch with language 'SQL_BUILTIN' in _func. It will be present in C, but won't exist in _func. This is not a language. Languages are SQL and Lua. SQL_BUILTIN was a crutch from the beginning, to make the functions work anyhow. ==================== ## Users benefit from seeing SQL-specific functions in _func? - No. Look at [1]. The output format is hardly readable. Not only because of the new options (partially because of them), but also because 1) some functions here don't really exist - LENGTH_VARBINARY, POSITION_VARBINARY, TRIM_VARBINARY, SUBSTR_VARBINARY, 2) because of lots of other fields of _func, which make the output super hard to read and understand what is what, even now. _func is a dead end if someone wants to see function definitions. For that it would be better to introduce a new pretty printer, somewhere in box.func maybe. ==================== ## Storage in _func unifies functions? - _func output is unified, yes. Other things - no. That is the only unification. Code still is going to separate them. By introduction of the new _func options you just move the complexity from a private place related to SQL only to the public space, related to the whole _func and other languages. ==================== ## Reuse SQL functions in Lua and other languages? - No. Cite from Nikita's email: Finally part of functions can turn out to be really usefull in Lua someday such as date()/time() It is not a secret, that all the languages we support already have date/time functions. In Lua these are 'os.date', 'os.time', 'fiber.clock', 'fiber.time' and more. In C these are all the standard C functions such as 'time()', 'gettimeofdat()', 'clock_gettime()', 'clock()', and more. Also lots of functions for time formatting, but I don't remember their names. So what exactly are the SQL built-in functions so much needed in Lua and C? Looking at the unreadable _func output below, I can't imagine why somebody need any of these functions out of SQL. ================================================================================ ## References: [1] Space _func select. - [2, 1, 'TRIM', 1, 'SQL_BUILTIN', '', 'function', ['string', 'unsigned', 'string'], 'string', 'none', 'none', true, false, true, ['SQL'], {'has_overload': true, 'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [3, 1, 'TYPEOF', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [4, 1, 'PRINTF', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'string', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [5, 1, 'UNICODE', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [6, 1, 'CHAR', 1, 'SQL_BUILTIN', '', 'function', ['unsigned'], 'string', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [7, 1, 'HEX', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [8, 1, 'VERSION', 1, 'SQL_BUILTIN', '', 'function', [], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [9, 1, 'QUOTE', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [10, 1, 'REPLACE', 1, 'SQL_BUILTIN', '', 'function', ['string', 'string', 'string'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [11, 1, 'SUBSTR', 1, 'SQL_BUILTIN', '', 'function', ['string', 'integer', 'integer'], 'string', 'none', 'none', true, false, true, ['SQL'], {'has_overload': true, 'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [12, 1, 'GROUP_CONCAT', 1, 'SQL_BUILTIN', '', 'function', ['scalar', 'scalar'], 'string', 'group', 'none', false, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [13, 1, 'JULIANDAY', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [14, 1, 'DATE', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [15, 1, 'TIME', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [16, 1, 'DATETIME', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [17, 1, 'STRFTIME', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [18, 1, 'CURRENT_TIME', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [19, 1, 'CURRENT_TIMESTAMP', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [20, 1, 'CURRENT_DATE', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [21, 1, 'LENGTH', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'integer', 'none', 'none', true, false, true, ['SQL'], {'has_overload': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [22, 1, 'POSITION', 1, 'SQL_BUILTIN', '', 'function', ['string', 'string'], 'integer', 'none', 'none', true, false, true, ['SQL'], {'has_overload': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [23, 1, 'ROUND', 1, 'SQL_BUILTIN', '', 'function', ['double', 'unsigned'], 'double', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [24, 1, 'UPPER', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [25, 1, 'LOWER', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [26, 1, 'IFNULL', 1, 'SQL_BUILTIN', '', 'function', ['scalar', 'scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [27, 1, 'RANDOM', 1, 'SQL_BUILTIN', '', 'function', [], 'integer', 'none', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [28, 1, 'CEIL', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [29, 1, 'CEILING', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [30, 1, 'CHARACTER_LENGTH', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'integer', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [31, 1, 'CHAR_LENGTH', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'integer', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [32, 1, 'FLOOR', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [33, 1, 'MOD', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [34, 1, 'OCTET_LENGTH', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [35, 1, 'ROW_COUNT', 1, 'SQL_BUILTIN', '', 'function', [], 'integer', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [36, 1, 'COUNT', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'integer', 'group', 'none', false, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [37, 1, 'LIKE', 1, 'SQL_BUILTIN', '', 'function', ['string', 'string', 'string'], 'boolean', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [38, 1, 'ABS', 1, 'SQL_BUILTIN', '', 'function', ['number'], 'number', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [39, 1, 'EXP', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [40, 1, 'LN', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [41, 1, 'POWER', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [42, 1, 'SQRT', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [43, 1, 'SUM', 1, 'SQL_BUILTIN', '', 'function', ['number'], 'number', 'group', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [44, 1, 'TOTAL', 1, 'SQL_BUILTIN', '', 'function', ['number'], 'number', 'group', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [45, 1, 'AVG', 1, 'SQL_BUILTIN', '', 'function', ['number'], 'number', 'group', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [46, 1, 'RANDOMBLOB', 1, 'SQL_BUILTIN', '', 'function', ['unsigned'], 'varbinary', 'none', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [47, 1, 'NULLIF', 1, 'SQL_BUILTIN', '', 'function', ['scalar', 'scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [48, 1, 'ZEROBLOB', 1, 'SQL_BUILTIN', '', 'function', ['unsigned'], 'varbinary', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [49, 1, 'MIN', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'group', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [50, 1, 'MAX', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'group', 'none', false, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [51, 1, 'COALESCE', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [52, 1, 'EVERY', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [53, 1, 'EXISTS', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [54, 1, 'EXTRACT', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [55, 1, 'SOME', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [56, 1, 'GREATER', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [57, 1, 'LESSER', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [58, 1, 'SOUNDEX', 1, 'SQL_BUILTIN', '', 'function', ['string'], 'string', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [59, 1, 'LIKELIHOOD', 1, 'SQL_BUILTIN', '', 'function', ['scalar', 'double'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [60, 1, 'LIKELY', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [61, 1, 'UNLIKELY', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [62, 1, '_sql_stat_get', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [63, 1, '_sql_stat_push', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [64, 1, '_sql_stat_init', 1, 'SQL_BUILTIN', '', 'function', [], 'any', 'none', 'none', false, false, true, [], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [65, 1, 'LUA', 1, 'LUA', 'function(code) return assert(loadstring(code))() end', 'function', ['string'], 'any', 'none', 'none', false, false, true, ['LUA', 'SQL'], {}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [66, 1, 'GREATEST', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [67, 1, 'LEAST', 1, 'SQL_BUILTIN', '', 'function', ['scalar'], 'scalar', 'none', 'none', true, false, true, ['SQL'], {'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [68, 1, 'LENGTH_VARBINARY', 1, 'SQL_BUILTIN', '', 'function', ['varbinary'], 'integer', 'none', 'none', true, false, true, ['SQL'], {'is_overloaded': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [69, 1, 'POSITION_VARBINARY', 1, 'SQL_BUILTIN', '', 'function', ['varbinary', 'varbinary'], 'integer', 'none', 'none', true, false, true, ['SQL'], {'is_overloaded': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [70, 1, 'TRIM_VARBINARY', 1, 'SQL_BUILTIN', '', 'function', ['varbinary', 'unsigned', 'varbinary'], 'string', 'none', 'none', true, false, true, ['SQL'], {'is_overloaded': true, 'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] - [71, 1, 'SUBSTR_VARBINARY', 1, 'SQL_BUILTIN', '', 'function', ['varbinary', 'integer', 'integer'], 'string', 'none', 'none', true, false, true, ['SQL'], {'is_overloaded': true, 'has_vararg': true}, '', '2020-08-14 16:27:52', '2020-08-14 16:27:52'] ...