Tarantool development patches archive
 help / color / mirror / Atom feed
From: Konstantin Osipov <kostja@tarantool.org>
To: Kirill Shcherbatov <kshcherbatov@tarantool.org>
Cc: tarantool-patches@freelists.org, korablev@tarantool.org
Subject: [tarantool-patches] Re: [PATCH v2 10/12] sql: refactor builtins signatures with port
Date: Wed, 10 Jul 2019 21:47:13 +0300	[thread overview]
Message-ID: <20190710184713.GC5619@atlas> (raw)
In-Reply-To: <fbb0cb03304068de4b6755aabddda282e85254a8.1562756438.git.kshcherbatov@tarantool.org>

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/07/10 14:02]:
> Reworked SQL builtins signatures to use args and ret abstract
> ports to pass arguments and return the execution result.
> The new approach allows to support calling sql builtins functions
> from Lua in future and to use uniform func_call API in
> OP_Function opcode.

I don't know why you ignored my comment on telegram: I think this
is a non-goal and SQL functions don't have to use _func api for
calls. It is a performance issue as well, since obstructs possible JITting
of expressions. Did you discuss this with Nikita as I asked you on
telegram?

> 
> Needed for #4113, #2200, #2233
> ---
>  src/box/call.c        |    1 +
>  src/box/execute.c     |    2 +
>  src/box/lua/call.c    |    2 +
>  src/box/lua/lua_sql.c |   41 +-
>  src/box/port.c        |    2 +
>  src/box/port.h        |   19 +
>  src/box/sql/analyze.c |  130 ++--
>  src/box/sql/func.c    | 1400 +++++++++++++++++++++++++----------------
>  src/box/sql/main.c    |   58 +-
>  src/box/sql/printf.c  |    7 +-
>  src/box/sql/sqlInt.h  |  101 +--
>  src/box/sql/vdbe.c    |  101 ++-
>  src/box/sql/vdbeInt.h |   18 +-
>  src/box/sql/vdbeapi.c |  163 +----
>  src/box/sql/vdbemem.c |   51 +-
>  src/lib/core/port.h   |   31 +
>  16 files changed, 1197 insertions(+), 930 deletions(-)
> 
> diff --git a/src/box/call.c b/src/box/call.c
> index ac2bf3004..b459a9839 100644
> --- a/src/box/call.c
> +++ b/src/box/call.c
> @@ -73,6 +73,7 @@ static const struct port_vtab port_msgpack_vtab = {
>  	.dump_lua = port_msgpack_dump_lua,
>  	.dump_plain = NULL,
>  	.get_msgpack = port_msgpack_get_msgpack,
> +	.get_vdbemem = NULL,
>  	.destroy = NULL,
>  };
>  
> diff --git a/src/box/execute.c b/src/box/execute.c
> index a9ca2e67a..0dc1bc46d 100644
> --- a/src/box/execute.c
> +++ b/src/box/execute.c
> @@ -109,6 +109,8 @@ const struct port_vtab port_sql_vtab = {
>  	/* .dump_lua = */ port_sql_dump_lua,
>  	/* .dump_plain = */ NULL,
>  	/* .get_msgpack = */ NULL,
> +	/* .get_vdbemem = */ NULL,
> +	/* .get_context = */ NULL,
>  	/* .destroy = */ port_sql_destroy,
>  };
>  
> diff --git a/src/box/lua/call.c b/src/box/lua/call.c
> index 95fac4834..4297218cd 100644
> --- a/src/box/lua/call.c
> +++ b/src/box/lua/call.c
> @@ -507,6 +507,8 @@ static const struct port_vtab port_lua_vtab = {
>  	.dump_lua = port_lua_dump_lua,
>  	.dump_plain = port_lua_dump_plain,
>  	.get_msgpack = port_lua_get_msgpack,
> +	.get_vdbemem = NULL,
> +	.get_context = NULL,
>  	.destroy = port_lua_destroy,
>  };
>  
> diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
> index 4d90a53de..0c5797fa2 100644
> --- a/src/box/lua/lua_sql.c
> +++ b/src/box/lua/lua_sql.c
> @@ -34,6 +34,7 @@
>  
>  #include "box/lua/call.h"
>  #include "box/sql/sqlInt.h"
> +#include "box/port.h"
>  #include "box/sql/vdbeInt.h"
>  
>  struct lua_sql_func_info {
> @@ -47,15 +48,28 @@ struct lua_sql_func_info {
>   * Lua func should be previously registered in sql
>   * (see lbox_sql_create_function).
>   */
> -static void
> -lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
> +static int
> +lua_sql_call(struct func *func, struct port *args, struct port *ret)
> +{
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	lua_State *L = lua_newthread(tarantool_L);
>  	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
> -	struct lua_sql_func_info *func_info = sql_user_data(pCtx);
> +	struct lua_sql_func_info *func_info = sql_user_data(ctx);
>  
>  	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
> -	for (int i = 0; i < nVal; i++) {
> -		sql_value *param = apVal[i];
> +	for (uint32_t i = 0; i < argc; i++) {
> +		sql_value *param = (sql_value *)&argv[i];
>  		switch (sql_value_type(param)) {
>  		case MP_INT:
>  			luaL_pushint64(L, sql_value_int64(param));
> @@ -79,38 +93,37 @@ lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
>  		default:
>  			diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported "\
>  				 "type passed to Lua");
> -			pCtx->is_aborted = true;
>  			goto error;
>  		}
>  	}
>  	if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
>  		diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
> -		pCtx->is_aborted = true;
>  		goto error;
>  	}
>  	switch(lua_type(L, -1)) {
>  	case LUA_TBOOLEAN:
> -		sql_result_bool(pCtx, lua_toboolean(L, -1));
> +		mem_set_bool(val, lua_toboolean(L, -1));
>  		break;
>  	case LUA_TNUMBER:
> -		sql_result_double(pCtx, lua_tonumber(L, -1));
> +		sqlVdbeMemSetDouble(val, lua_tonumber(L, -1));
>  		break;
>  	case LUA_TSTRING:
> -		sql_result_text(pCtx, lua_tostring(L, -1), -1,
> -				    SQL_TRANSIENT);
> +		 if (sqlVdbeMemSetStr(val, lua_tostring(L, -1), -1,
> +					1, SQL_TRANSIENT) != 0)
> +			return -1;
>  		break;
>  	case LUA_TNIL:
> -		sql_result_null(pCtx);
> +		sqlVdbeMemSetNull(val);
>  		break;
>  	default:
>  		diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
>  			 "passed from Lua");
> -		pCtx->is_aborted = true;
>  		goto error;
>  	}
> +	return 0;
>  error:
>  	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
> -	return;
> +	return -1;
>  }
>  
>  static void
> diff --git a/src/box/port.c b/src/box/port.c
> index 7f552bcfe..9e4ab9453 100644
> --- a/src/box/port.c
> +++ b/src/box/port.c
> @@ -146,5 +146,7 @@ const struct port_vtab port_tuple_vtab = {
>  	.dump_lua = port_tuple_dump_lua,
>  	.dump_plain = NULL,
>  	.get_msgpack = NULL,
> +	.get_vdbemem = NULL,
> +	.get_context = NULL,
>  	.destroy = port_tuple_destroy,
>  };
> diff --git a/src/box/port.h b/src/box/port.h
> index a7f5d81bd..f14d61189 100644
> --- a/src/box/port.h
> +++ b/src/box/port.h
> @@ -95,6 +95,25 @@ static_assert(sizeof(struct port_msgpack) <= sizeof(struct port),
>  void
>  port_msgpack_create(struct port *port, const char *data, uint32_t data_sz);
>  
> +struct sql_value;
> +struct sql_context;
> +
> +/** Port implementation used with vdbe memory variables. */
> +struct port_vdbemem {
> +	const struct port_vtab *vtab;
> +	struct sql_context *ctx;
> +	struct sql_value *mem;
> +	uint32_t size;
> +};
> +
> +static_assert(sizeof(struct port_vdbemem) <= sizeof(struct port),
> +	      "sizeof(struct port_vdbemem) must be <= sizeof(struct port)");
> +
> +/** Initialize a port to dump data in sql vdbe memory. */
> +void
> +port_vdbemem_create(struct port *base, struct sql_value *mem, uint32_t size,
> +		    struct sql_context *ctx);
> +
>  /** Port for storing the result of a Lua CALL/EVAL. */
>  struct port_lua {
>  	const struct port_vtab *vtab;
> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
> index 512904cc1..7764b48c5 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -109,6 +109,7 @@
>  #include "box/index.h"
>  #include "box/key_def.h"
>  #include "box/schema.h"
> +#include "box/port.h"
>  #include "third_party/qsort_arg.h"
>  
>  #include "sqlInt.h"
> @@ -263,9 +264,19 @@ stat4Destructor(void *pOld)
>   * return value is BLOB, but it is really just a pointer to the Stat4Accum
>   * object.
>   */
> -static void
> -statInit(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_stat_init(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	Stat4Accum *p;
>  	int nCol;		/* Number of columns in index being sampled */
>  	int nKeyCol;		/* Number of key columns */
> @@ -275,8 +286,7 @@ statInit(sql_context * context, int argc, sql_value ** argv)
>  	int mxSample = SQL_STAT4_SAMPLES;
>  
>  	/* Decode the three function arguments */
> -	UNUSED_PARAMETER(argc);
> -	nCol = sql_value_int(argv[0]);
> +	nCol = sql_value_int(&argv[0]);
>  	assert(nCol > 0);
>  	/* Tarantool: we use an additional artificial column for the reason
>  	 * that Tarantool's indexes don't contain PK columns after key columns.
> @@ -284,7 +294,7 @@ statInit(sql_context * context, int argc, sql_value ** argv)
>  	 * identical rows, we have to use this artificial column.
>  	 */
>  	nColUp = sizeof(tRowcnt) < 8 ? (nCol + 2) & ~1 : nCol + 1;
> -	nKeyCol = sql_value_int(argv[1]);
> +	nKeyCol = sql_value_int(&argv[1]);
>  	assert(nKeyCol <= nCol);
>  	assert(nKeyCol > 0);
>  
> @@ -295,12 +305,10 @@ statInit(sql_context * context, int argc, sql_value ** argv)
>  	    + sizeof(tRowcnt) * nColUp	/* Stat4Accum.anLt */
>  	    + sizeof(Stat4Sample) * (nCol + 1 + mxSample)	/* Stat4Accum.aBest[], a[] */
>  	    + sizeof(tRowcnt) * 3 * nColUp * (nCol + 1 + mxSample);
> -	db = sql_context_db_handle(context);
> +	db = sql_get();
>  	p = sqlDbMallocZero(db, n);
> -	if (p == 0) {
> -		context->is_aborted = true;
> -		return;
> -	}
> +	if (p == NULL)
> +		return -1;
>  
>  	p->db = db;
>  	p->nRow = 0;
> @@ -316,12 +324,12 @@ statInit(sql_context * context, int argc, sql_value ** argv)
>  		p->iGet = -1;
>  		p->mxSample = mxSample;
>  		p->nPSample =
> -		    (tRowcnt) (sql_value_int64(argv[2]) /
> +		    (tRowcnt) (sql_value_int64(&argv[2]) /
>  			       (mxSample / 3 + 1) + 1);
>  		p->current.anLt = &p->current.anEq[nColUp];
>  		p->iPrn =
>  		    0x689e962d * (u32) nCol ^ 0xd0944565 *
> -		    (u32) sql_value_int(argv[2]);
> +		    (u32) sql_value_int(&argv[2]);
>  
>  		/* Set up the Stat4Accum.a[] and aBest[] arrays */
>  		p->a = (struct Stat4Sample *)&p->current.anLt[nColUp];
> @@ -347,7 +355,8 @@ statInit(sql_context * context, int argc, sql_value ** argv)
>  	 * (given by the 3rd parameter) is never used and can be any positive
>  	 * value.
>  	 */
> -	sql_result_blob(context, p, sizeof(*p), stat4Destructor);
> +	return sqlVdbeMemSetStr(val, (const char *)p, sizeof(*p), 0,
> +				stat4Destructor) != 0 ? -1 : 0;
>  }
>  
>  /*
> @@ -535,16 +544,21 @@ samplePushPrevious(Stat4Accum * p, int iChng)
>   *
>   * The R parameter is only used for STAT4
>   */
> -static void
> -statPush(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_stat_push(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	(void) ret;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +
>  	int i;
>  	/* The three function arguments */
> -	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
> -	int iChng = sql_value_int(argv[1]);
> +	Stat4Accum *p = (Stat4Accum *) sql_value_blob(&argv[0]);
> +	int iChng = sql_value_int(&argv[1]);
>  
> -	UNUSED_PARAMETER(argc);
> -	UNUSED_PARAMETER(context);
>  	assert(p->nCol > 0);
>  	/* iChng == p->nCol means that the current and previous rows are identical */
>  	assert(iChng <= p->nCol);
> @@ -569,8 +583,8 @@ statPush(sql_context * context, int argc, sql_value ** argv)
>  		}
>  	}
>  	p->nRow++;
> -	sampleSetKey(p->db, &p->current, sql_value_bytes(argv[2]),
> -		     sql_value_blob(argv[2]));
> +	sampleSetKey(p->db, &p->current, sql_value_bytes(&argv[2]),
> +		     sql_value_blob(&argv[2]));
>  	p->current.iHash = p->iPrn = p->iPrn * 1103515245 + 12345;
>  	{
>  		tRowcnt nLt = p->current.anLt[p->nCol];
> @@ -592,6 +606,7 @@ statPush(sql_context * context, int argc, sql_value ** argv)
>  			}
>  		}
>  	}
> +	return 0;
>  }
>  
>  #define STAT_GET_STAT1 0	/* "stat" column of stat1 table */
> @@ -608,12 +623,22 @@ statPush(sql_context * context, int argc, sql_value ** argv)
>   * The content to returned is determined by the parameter J
>   * which is one of the STAT_GET_xxxx values defined above.
>   */
> -static void
> -statGet(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_stat_get(struct func *func, struct port *args, struct port *ret)
>  {
> -	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	struct Stat4Accum *p = (struct Stat4Accum *) sql_value_blob(&argv[0]);
>  	/* STAT4 have a parameter on this routine. */
> -	int eCall = sql_value_int(argv[1]);
> +	int eCall = sql_value_int(&argv[1]);
>  	assert(argc == 2);
>  	assert(eCall == STAT_GET_STAT1 || eCall == STAT_GET_NEQ
>  	       || eCall == STAT_GET_KEY || eCall == STAT_GET_NLT
> @@ -644,10 +669,8 @@ statGet(sql_context * context, int argc, sql_value ** argv)
>  		int i;
>  
>  		char *zRet = sqlMallocZero((p->nKeyCol + 1) * 25);
> -		if (zRet == 0) {
> -			context->is_aborted = true;
> -			return;
> -		}
> +		if (zRet == NULL)
> +			return -1;
>  
>  		sql_snprintf(24, zRet, "%llu", (u64) p->nRow);
>  		z = zRet + sqlStrlen30(zRet);
> @@ -659,8 +682,8 @@ statGet(sql_context * context, int argc, sql_value ** argv)
>  			assert(p->current.anEq[i]);
>  		}
>  		assert(z[0] == '\0' && z > zRet);
> -
> -		sql_result_text(context, zRet, -1, sql_free);
> +		if (sqlVdbeMemSetStr(val, zRet, -1, 1, SQL_DYNAMIC) != 0)
> +			return -1;
>  	} else if (eCall == STAT_GET_KEY) {
>  		if (p->iGet < 0) {
>  			samplePushPrevious(p, 0);
> @@ -668,8 +691,9 @@ statGet(sql_context * context, int argc, sql_value ** argv)
>  		}
>  		if (p->iGet < p->nSample) {
>  			Stat4Sample *pS = p->a + p->iGet;
> -			sql_result_blob(context, pS->aKey, pS->nKey,
> -					    SQL_TRANSIENT);
> +			if (sqlVdbeMemSetStr(val, (const char *)pS->aKey,
> +					     pS->nKey, 0, SQL_TRANSIENT) != 0)
> +				return -1;
>  		}
>  	} else {
>  		tRowcnt *aCnt = 0;
> @@ -687,12 +711,11 @@ statGet(sql_context * context, int argc, sql_value ** argv)
>  			p->iGet++;
>  			break;
>  		}
> -	}
> +		}
>  
> -	char *zRet = sqlMallocZero(p->nCol * 25);
> -	if (zRet == 0) {
> -		context->is_aborted = true;
> -	} else {
> +		char *zRet = sqlMallocZero(p->nCol * 25);
> +		if (zRet == NULL)
> +			return -1;
>  		int i;
>  		char *z = zRet;
>  		for (i = 0; i < p->nCol; i++) {
> @@ -701,17 +724,15 @@ statGet(sql_context * context, int argc, sql_value ** argv)
>  		}
>  		assert(z[0] == '\0' && z > zRet);
>  		z[-1] = '\0';
> -		sql_result_text(context, zRet, -1, sql_free);
> +		if (sqlVdbeMemSetStr(val, zRet, -1, 1, SQL_DYNAMIC) != 0)
> +			return -1;
>  	}
> -
> -}
> -#ifndef SQL_DEBUG
> -UNUSED_PARAMETER(argc);
> -#endif
> +	return 0;
>  }
>  
>  static void
> -callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
> +vdbe_emit_analyze_stat_get(struct Vdbe * v, int regStat4, int iParam,
> +			   int regOut)
>  {
>  	assert(regOut != regStat4 && regOut != regStat4 + 1);
>  	sqlVdbeAddOp2(v, OP_Integer, iParam, regStat4 + 1);
> @@ -964,7 +985,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
>  		sqlVdbeChangeP5(v, 3);
>  		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
>  		/* Add the entry to the stat1 table. */
> -		callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
> +		vdbe_emit_analyze_stat_get(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
>  		enum field_type types[4] = { FIELD_TYPE_STRING,
>  					     FIELD_TYPE_STRING,
>  					     FIELD_TYPE_STRING,
> @@ -982,12 +1003,12 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
>  		int sample_key_reg = col_reg + part_count;
>  		parse->nMem = MAX(parse->nMem, col_reg + part_count);
>  		int next_addr = sqlVdbeCurrentAddr(v);
> -		callStatGet(v, stat4_reg, STAT_GET_KEY, sample_key_reg);
> +		vdbe_emit_analyze_stat_get(v, stat4_reg, STAT_GET_KEY, sample_key_reg);
>  		int is_null_addr = sqlVdbeAddOp1(v, OP_IsNull,
>  						     sample_key_reg);
> -		callStatGet(v, stat4_reg, STAT_GET_NEQ, eq_reg);
> -		callStatGet(v, stat4_reg, STAT_GET_NLT, lt_reg);
> -		callStatGet(v, stat4_reg, STAT_GET_NDLT, dlt_reg);
> +		vdbe_emit_analyze_stat_get(v, stat4_reg, STAT_GET_NEQ, eq_reg);
> +		vdbe_emit_analyze_stat_get(v, stat4_reg, STAT_GET_NLT, lt_reg);
> +		vdbe_emit_analyze_stat_get(v, stat4_reg, STAT_GET_NDLT, dlt_reg);
>  		sqlVdbeAddOp4Int(v, OP_NotFound, tab_cursor, next_addr,
>  				     sample_key_reg, 0);
>  		/*
> @@ -1752,9 +1773,12 @@ void
>  sql_register_analyze_builtins(void)
>  {
>  	static FuncDef funcs[] = {
> -		FUNCTION(_sql_stat_get, 2, 0, 0, statGet, FIELD_TYPE_ANY),
> -		FUNCTION(_sql_stat_push, 3, 0, 0, statPush, FIELD_TYPE_ANY),
> -		FUNCTION(_sql_stat_init, 3, 0, 0, statInit, FIELD_TYPE_ANY),
> +		FUNCTION(_sql_stat_get, 2, 0, 0, sql_builtin_stat_get,
> +			 FIELD_TYPE_ANY),
> +		FUNCTION(_sql_stat_push, 3, 0, 0, sql_builtin_stat_push,
> +			 FIELD_TYPE_ANY),
> +		FUNCTION(_sql_stat_init, 3, 0, 0, sql_builtin_stat_init,
> +			 FIELD_TYPE_ANY),
>  	};
>  	sqlInsertBuiltinFuncs(funcs, nelem(funcs));
>  }
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index d59aba9ee..18db8530d 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -39,6 +39,7 @@
>  #include "version.h"
>  #include "coll/coll.h"
>  #include "box/func.h"
> +#include "box/port.h"
>  #include "tarantoolInt.h"
>  #include "box/session.h"
>  #include <unicode/ustring.h>
> @@ -71,45 +72,99 @@ sqlSkipAccumulatorLoad(sql_context * context)
>  	context->skipFlag = 1;
>  }
>  
> +static const struct port_vtab port_vdbemem_vtab;
> +
> +void
> +port_vdbemem_create(struct port *base, struct sql_value *mem, uint32_t size,
> +		    struct sql_context *ctx)
> +{
> +	struct port_vdbemem *port = (struct port_vdbemem *) base;
> +	port->vtab = &port_vdbemem_vtab;
> +	port->mem = mem;
> +	port->size = size;
> +	port->ctx = ctx;
> +}
> +
> +static struct sql_value *
> +port_vdbemem_get_vdbemem(struct port *base, uint32_t *size)
> +{
> +	struct port_vdbemem *port = (struct port_vdbemem *) base;
> +	assert(port->vtab == &port_vdbemem_vtab);
> +	*size = port->size;
> +	return port->mem;
> +}
> +
> +void *
> +port_vdbemem_get_context(struct port *base)
> +{
> +	struct port_vdbemem *port = (struct port_vdbemem *) base;
> +	return port->ctx;
> +}
> +
> +static const struct port_vtab port_vdbemem_vtab = {
> +	.dump_msgpack = NULL,
> +	.dump_msgpack_16 = NULL,
> +	.dump_lua = NULL,
> +	.dump_plain = NULL,
> +	.get_msgpack = NULL,
> +	.get_vdbemem = port_vdbemem_get_vdbemem,
> +	.get_context = port_vdbemem_get_context,
> +	.destroy = NULL,
> +};
> +
>  /*
>   * Implementation of the non-aggregate min() and max() functions
>   */
> -static void
> -minmaxFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_minmax(struct func *func, struct port *args, struct port *ret)
>  {
> -	int i;
> -	int mask;		/* 0 for min() or 0xffffffff for max() */
> -	int iBest;
> -	struct coll *pColl;
> -
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
>  	assert(argc > 1);
> -	mask = sql_user_data(context) == 0 ? 0 : -1;
> -	pColl = sqlGetFuncCollSeq(context);
> +	/* 0 for min() or 0xffffffff for max() */
> +	int mask = sql_user_data(ctx) == 0 ? 0 : -1;
>  	assert(mask == -1 || mask == 0);
> -	iBest = 0;
> -	if (sql_value_is_null(argv[0]))
> -		return;
> -	for (i = 1; i < argc; i++) {
> -		if (sql_value_is_null(argv[i]))
> -			return;
> -		if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
> -		    0) {
> -			testcase(mask == 0);
> -			iBest = i;
> -		}
> +	struct coll *coll = sqlGetFuncCollSeq(ctx);
> +
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	int best = 0;
> +	if (sql_value_is_null(argv))
> +		return 0;
> +	for (uint32_t i = 1; i < argc; i++) {
> +		if (sql_value_is_null(&argv[i]))
> +			return 0;
> +		if ((sqlMemCompare(&argv[best], &argv[i], coll) ^ mask) >= 0)
> +			best = i;
>  	}
> -	sql_result_value(context, argv[iBest]);
> +	return sqlVdbeMemCopy(val, &argv[best]) != 0 ? -1 : 0;
>  }
>  
>  /*
>   * Return the type of the argument.
>   */
> -static void
> -typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
> +static int
> +sql_builtin_typeof(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
>  	const char *z = 0;
> -	UNUSED_PARAMETER(NotUsed);
> -	switch (sql_value_type(argv[0])) {
> +	switch (sql_value_type(&argv[0])) {
>  	case MP_INT:
>  		z = "integer";
>  		break;
> @@ -129,40 +184,48 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
>  		z = "null";
>  		break;
>  	}
> -	sql_result_text(context, z, -1, SQL_STATIC);
> +	return sqlVdbeMemSetStr(val, z, -1, 1, SQL_STATIC) != 0 ? -1 : 0;
>  }
>  
>  /*
>   * Implementation of the length() function
>   */
> -static void
> -lengthFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_length(struct func *func, struct port *args, struct port *ret)
>  {
> -	int len;
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
>  
>  	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	switch (sql_value_type(argv[0])) {
> +	switch (sql_value_type(&argv[0])) {
>  	case MP_BIN:
>  	case MP_INT:
>  	case MP_DOUBLE:{
> -			sql_result_int(context,
> -					   sql_value_bytes(argv[0]));
> +			sqlVdbeMemSetInt64(val, sql_value_bytes(&argv[0]));
>  			break;
>  		}
>  	case MP_STR:{
> -			const unsigned char *z = sql_value_text(argv[0]);
> +			const unsigned char *z = sql_value_text(&argv[0]);
>  			if (z == 0)
> -				return;
> -			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
> -			sql_result_int(context, len);
> +				return 0;
> +			int len = sql_utf8_char_count(z,
> +						sql_value_bytes(&argv[0]));
> +			sqlVdbeMemSetInt64(val, len);
>  			break;
>  		}
>  	default:{
> -			sql_result_null(context);
> +			sqlVdbeMemSetNull(val);
>  			break;
>  		}
>  	}
> +	return 0;
>  }
>  
>  /*
> @@ -171,36 +234,43 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
>   * IMP: R-23979-26855 The abs(X) function returns the absolute value of
>   * the numeric argument X.
>   */
> -static void
> -absFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_abs(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	switch (sql_value_type(argv[0])) {
> +	switch (sql_value_type(&argv[0])) {
>  	case MP_INT:{
> -			i64 iVal = sql_value_int64(argv[0]);
> +			i64 iVal = sql_value_int64(&argv[0]);
>  			if (iVal < 0) {
>  				if (iVal == SMALLEST_INT64) {
>  					diag_set(ClientError, ER_SQL_EXECUTE,
>  						 "integer is overflowed");
> -					context->is_aborted = true;
> -					return;
> +					return -1;
>  				}
>  				iVal = -iVal;
>  			}
> -			sql_result_int64(context, iVal);
> +			sqlVdbeMemSetInt64(val, iVal);
>  			break;
>  		}
>  	case MP_NIL:{
>  			/* IMP: R-37434-19929 Abs(X) returns NULL if X is NULL. */
> -			sql_result_null(context);
> +			sqlVdbeMemSetNull(val);
>  			break;
>  		}
>  	case MP_BOOL: {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES, "number",
>  			 "boolean");
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
>  	default:{
>  			/* Because sql_value_double() returns 0.0 if the argument is not
> @@ -208,13 +278,14 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>  			 * IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
>  			 * that cannot be converted to a numeric value.
>  			 */
> -			double rVal = sql_value_double(argv[0]);
> +			double rVal = sql_value_double(&argv[0]);
>  			if (rVal < 0)
>  				rVal = -rVal;
> -			sql_result_double(context, rVal);
> +			sqlVdbeMemSetDouble(val, rVal);
>  			break;
>  		}
>  	}
> +	return 0;
>  }
>  
>  /**
> @@ -228,17 +299,28 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>   * more than the number of bytes in haystack prior to the first
>   * occurrence of needle, or 0 if needle never occurs in haystack.
>   */
> -static void
> -position_func(struct sql_context *context, int argc, struct Mem **argv)
> +static int
> +sql_builtin_position(struct func *func, struct port *args, struct port *ret)
>  {
> -	UNUSED_PARAMETER(argc);
> -	struct Mem *needle = argv[0];
> -	struct Mem *haystack = argv[1];
> +	(void)func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	struct Mem *needle = &argv[0];
> +	struct Mem *haystack = &argv[1];
>  	enum mp_type needle_type = sql_value_type(needle);
>  	enum mp_type haystack_type = sql_value_type(haystack);
>  
>  	if (haystack_type == MP_NIL || needle_type == MP_NIL)
> -		return;
> +		return 0;
>  	/*
>  	 * Position function can be called only with string
>  	 * or blob params.
> @@ -251,8 +333,7 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
>  	if (inconsistent_type_arg != NULL) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES, "TEXT or BLOB",
>  			 mem_type_to_str(inconsistent_type_arg));
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
>  	/*
>  	 * Both params of Position function must be of the same
> @@ -261,8 +342,7 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
>  	if (haystack_type != needle_type) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 mem_type_to_str(needle), mem_type_to_str(haystack));
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
>  
>  	int n_needle_bytes = sql_value_bytes(needle);
> @@ -328,7 +408,7 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
>  					       n_haystack_bytes);
>  			}
>  			int beg_offset = 0;
> -			struct coll *coll = sqlGetFuncCollSeq(context);
> +			struct coll *coll = sqlGetFuncCollSeq(ctx);
>  			int c;
>  			for (c = 0; c + n_needle_chars <= n_haystack_chars; c++) {
>  				if (coll->cmp((const char *) haystack_str + beg_offset,
> @@ -348,34 +428,47 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
>  		}
>  	}
>  finish:
> -	sql_result_int(context, position);
> +	sqlVdbeMemSetInt64(val, position);
> +	return 0;
>  }
>  
>  /*
>   * Implementation of the printf() function.
>   */
> -static void
> -printfFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_printf(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	PrintfArguments x;
>  	StrAccum str;
>  	const char *zFormat;
>  	int n;
> -	sql *db = sql_context_db_handle(context);
> +	struct sql *db = sql_get();
>  
> -	if (argc >= 1
> -	    && (zFormat = (const char *)sql_value_text(argv[0])) != 0) {
> +	if (argc >= 1 &&
> +	    (zFormat = (const char *)sql_value_text(&argv[0])) != 0) {
>  		x.nArg = argc - 1;
>  		x.nUsed = 0;
> -		x.apArg = argv + 1;
> +		x.apArg = &argv[1];
>  		sqlStrAccumInit(&str, db, 0, 0,
>  				    db->aLimit[SQL_LIMIT_LENGTH]);
>  		str.printfFlags = SQL_PRINTF_SQLFUNC;
>  		sqlXPrintf(&str, zFormat, &x);
>  		n = str.nChar;
> -		sql_result_text(context, sqlStrAccumFinish(&str), n,
> -				    SQL_DYNAMIC);
> +		if (sqlVdbeMemSetStr(val, sqlStrAccumFinish(&str), n,
> +				     1, SQL_DYNAMIC) != 0)
> +			return -1;
>  	}
> +	return 0;
>  }
>  
>  /*
> @@ -390,9 +483,20 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
>   *
>   * If p2 is negative, return the p2 characters preceding p1.
>   */
> -static void
> -substrFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_substr(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql *db = sql_get();
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	const unsigned char *z;
>  	const unsigned char *z2;
>  	int len;
> @@ -401,36 +505,33 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  	int negP2 = 0;
>  
>  	assert(argc == 3 || argc == 2);
> -	if (sql_value_is_null(argv[1])
> -	    || (argc == 3 && sql_value_is_null(argv[2]))
> -	    ) {
> -		return;
> -	}
> -	p0type = sql_value_type(argv[0]);
> -	p1 = sql_value_int(argv[1]);
> +	if (sql_value_is_null(&argv[1]) ||
> +	    (argc == 3 && sql_value_is_null(&argv[2])))
> +		return 0;
> +	p0type = sql_value_type(&argv[0]);
> +	p1 = sql_value_int(&argv[1]);
>  	if (p0type == MP_BIN) {
> -		len = sql_value_bytes(argv[0]);
> -		z = sql_value_blob(argv[0]);
> +		len = sql_value_bytes(&argv[0]);
> +		z = sql_value_blob(&argv[0]);
>  		if (z == 0)
> -			return;
> -		assert(len == sql_value_bytes(argv[0]));
> +			return 0;
> +		assert(len == sql_value_bytes(&argv[0]));
>  	} else {
> -		z = sql_value_text(argv[0]);
> +		z = sql_value_text(&argv[0]);
>  		if (z == 0)
> -			return;
> +			return 0;
>  		len = 0;
>  		if (p1 < 0)
> -			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
> +			len = sql_utf8_char_count(z, sql_value_bytes(&argv[0]));
>  	}
>  	if (argc == 3) {
> -		p2 = sql_value_int(argv[2]);
> +		p2 = sql_value_int(&argv[2]);
>  		if (p2 < 0) {
>  			p2 = -p2;
>  			negP2 = 1;
>  		}
>  	} else {
> -		p2 = sql_context_db_handle(context)->
> -		    aLimit[SQL_LIMIT_LENGTH];
> +		p2 = db->aLimit[SQL_LIMIT_LENGTH];
>  	}
>  	if (p1 < 0) {
>  		p1 += len;
> @@ -459,7 +560,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  		 * used because '\0' is not supposed to be
>  		 * end-of-string symbol.
>  		 */
> -		int byte_size = sql_value_bytes(argv[0]);
> +		int byte_size = sql_value_bytes(&argv[0]);
>  		int n_chars = sql_utf8_char_count(z, byte_size);
>  		int cnt = 0;
>  		int i = 0;
> @@ -475,38 +576,51 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  			cnt++;
>  		}
>  		z2 += i;
> -		sql_result_text64(context, (char *)z, z2 - z,
> -				      SQL_TRANSIENT);
> +		if (sqlVdbeMemSetStr(val, (char *)z, z2 - z, 1,
> +				     SQL_TRANSIENT) != 0)
> +			return -1;
>  	} else {
>  		if (p1 + p2 > len) {
>  			p2 = len - p1;
>  			if (p2 < 0)
>  				p2 = 0;
>  		}
> -		sql_result_blob64(context, (char *)&z[p1], (u64) p2,
> -				      SQL_TRANSIENT);
> +		if (sqlVdbeMemSetStr(val, (char *)&z[p1], (u64) p2, 0,
> +				     SQL_TRANSIENT) != 0)
> +			return -1;
>  	}
> +	return 0;
>  }
>  
>  /*
>   * Implementation of the round() function
>   */
> -static void
> -roundFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_round(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	int n = 0;
>  	double r;
>  	assert(argc == 1 || argc == 2);
>  	if (argc == 2) {
> -		if (sql_value_is_null(argv[1]))
> -			return;
> -		n = sql_value_int(argv[1]);
> +		if (sql_value_is_null(&argv[1]))
> +			return 0;
> +		n = sql_value_int(&argv[1]);
>  		if (n < 0)
>  			n = 0;
>  	}
> -	if (sql_value_is_null(argv[0]))
> -		return;
> -	r = sql_value_double(argv[0]);
> +	if (sql_value_is_null(&argv[0]))
> +		return 0;
> +	r = sql_value_double(&argv[0]);
>  	/* If Y==0 and X will fit in a 64-bit int,
>  	 * handle the rounding directly,
>  	 * otherwise use printf.
> @@ -519,7 +633,8 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>  		const char *rounded_value = tt_sprintf("%.*f", n, r);
>  		sqlAtoF(rounded_value, &r, sqlStrlen30(rounded_value));
>  	}
> -	sql_result_double(context, r);
> +	sqlVdbeMemSetDouble(val, r);
> +	return 0;
>  }
>  
>  /*
> @@ -528,24 +643,15 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>   * maximum string or blob length, then raise an error and return
>   * NULL.
>   */
> -static void *
> -contextMalloc(sql_context * context, i64 nByte)
> +static char *
> +sql_blob_alloc(int64_t size)
>  {
> -	char *z;
> -	sql *db = sql_context_db_handle(context);
> -	assert(nByte > 0);
> -	testcase(nByte == db->aLimit[SQL_LIMIT_LENGTH]);
> -	testcase(nByte == db->aLimit[SQL_LIMIT_LENGTH] + 1);
> -	if (nByte > db->aLimit[SQL_LIMIT_LENGTH]) {
> +	struct sql *db = sql_get();
> +	if (size > db->aLimit[SQL_LIMIT_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> -		context->is_aborted = true;
> -		z = 0;
> -	} else {
> -		z = sqlMalloc(nByte);
> -		if (z == NULL)
> -			context->is_aborted = true;
> +		return NULL;
>  	}
> -	return z;
> +	return sql_malloc64(size);
>  }
>  
>  /*
> @@ -553,29 +659,38 @@ contextMalloc(sql_context * context, i64 nByte)
>   */
>  
>  #define ICU_CASE_CONVERT(case_type)                                            \
> -static void                                                                    \
> -case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
> +static int                                                                     \
> +sql_builtin_ICU##case_type(struct func *func, struct port *args,               \
> +			   struct port *ret)                                   \
>  {                                                                              \
> +	(void) func;                                                           \
> +	uint32_t argc;                                                         \
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);        \
> +	if (argv == NULL)                                                      \
> +		return -1;                                                     \
> +	struct sql_context *ctx = (struct sql_context *)port_get_context(args);\
> +	assert(ctx != NULL);                                                   \
> +	struct Mem *val = vdbemem_alloc_on_region(1);                           \
> +	if (val == NULL)                                                       \
> +		return -1;                                                     \
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);            \
>  	char *z1;                                                              \
>  	const char *z2;                                                        \
>  	int n;                                                                 \
> -	UNUSED_PARAMETER(argc);                                                \
> -	z2 = (char *)sql_value_text(argv[0]);                              \
> -	n = sql_value_bytes(argv[0]);                                      \
> +	z2 = (char *)sql_value_text(&argv[0]);                                 \
> +	n = sql_value_bytes(&argv[0]);                                         \
>  	/*                                                                     \
>  	 * Verify that the call to _bytes()                                    \
>  	 * does not invalidate the _text() pointer.                            \
>  	 */                                                                    \
> -	assert(z2 == (char *)sql_value_text(argv[0]));                     \
> +	assert(z2 == (char *)sql_value_text(&argv[0]));                        \
>  	if (!z2)                                                               \
> -		return;                                                        \
> -	z1 = contextMalloc(context, ((i64) n) + 1);                            \
> -	if (z1 == NULL) {                                                      \
> -		context->is_aborted = true;                                    \
> -		return;                                                        \
> -	}                                                                      \
> +		return 0;                                                      \
> +	z1 = sql_blob_alloc(((i64) n) + 1);                                    \
> +	if (z1 == NULL)                                                        \
> +		return -1;                                                     \
>  	UErrorCode status = U_ZERO_ERROR;                                      \
> -	struct coll *coll = sqlGetFuncCollSeq(context);                    \
> +	struct coll *coll = sqlGetFuncCollSeq(ctx);                            \
>  	const char *locale = NULL;                                             \
>  	if (coll != NULL && coll->type == COLL_TYPE_ICU) {                     \
>  		locale = ucol_getLocaleByType(coll->collator,                  \
> @@ -586,17 +701,15 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
>  	int len = ucasemap_utf8To##case_type(case_map, z1, n, z2, n, &status); \
>  	if (len > n) {                                                         \
>  		status = U_ZERO_ERROR;                                         \
> -		sql_free(z1);                                              \
> -		z1 = contextMalloc(context, ((i64) len) + 1);                  \
> -		if (z1 == NULL) {                                              \
> -			context->is_aborted = true;                            \
> -			return;                                                \
> -		}                                                              \
> +		sql_free(z1);                                                  \
> +		z1 = sql_blob_alloc(((i64) len) + 1);                          \
> +		if (z1 == NULL)                                                \
> +			return -1;                                             \
>  		ucasemap_utf8To##case_type(case_map, z1, len, z2, n, &status); \
>  	}                                                                      \
>  	ucasemap_close(case_map);                                              \
> -	sql_result_text(context, z1, len, sql_free);                   \
> -}                                                                              \
> +	return sqlVdbeMemSetStr(val, z1, len, 1, SQL_DYNAMIC) != 0 ? -1 : 0;   \
> +}
>  
>  ICU_CASE_CONVERT(Lower);
>  ICU_CASE_CONVERT(Upper);
> @@ -607,21 +720,27 @@ ICU_CASE_CONVERT(Upper);
>   * as VDBE code so that unused argument values do not have to be
>   * computed.
>   * However, we still need some kind of function implementation for
> - * this routines in the function table. The noopFunc macro
> - * provides this. noopFunc will never be called so it doesn't
> - * matter what the implementation is. We might as well use the
> - * "version()" function as a substitute.
> + * this routines in the function table. The sql_builtin_noop macro
> + * provides this. sql_builtin_noop will never be called so it
> + * doesn't matter what the implementation is. We might as well
> + * use the "version()" function as a substitute.
>   */
> -#define noopFunc sql_func_version /* Substitute function - never called */
> +#define sql_builtin_noop sql_builtin_version
>  
>  /*
>   * Implementation of random().  Return a random integer.
>   */
> -static void
> -randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
> +static int
> +sql_builtin_random(struct func *func, struct port *args, struct port *ret)
>  {
> -	sql_int64 r;
> -	UNUSED_PARAMETER2(NotUsed, NotUsed2);
> +	(void) func;
> +	(void) args;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	int64_t r;
>  	sql_randomness(sizeof(r), &r);
>  	if (r < 0) {
>  		/* We need to prevent a random number of 0x8000000000000000
> @@ -634,28 +753,36 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
>  		 */
>  		r = -(r & LARGEST_INT64);
>  	}
> -	sql_result_int64(context, r);
> +	sqlVdbeMemSetInt64(val, r);
> +	return 0;
>  }
>  
>  /*
> - * Implementation of randomblob(N).  Return a random blob
> + * Implementation of sql_builtin_random_blob(N). Return a random blob
>   * that is N bytes long.
>   */
> -static void
> -randomBlob(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_random_blob(struct func *func, struct port *args, struct port *ret)
>  {
> -	int n;
> -	unsigned char *p;
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	n = sql_value_int(argv[0]);
> +	int n = sql_value_int(&argv[0]);
>  	if (n < 1)
> -		return;
> -	p = contextMalloc(context, n);
> -	if (p) {
> -		sql_randomness(n, p);
> -		sql_result_blob(context, (char *)p, n, sql_free);
> -	}
> +		return 0;
> +	char *p = sql_blob_alloc(n);
> +	if (p == NULL)
> +		return -1;
> +	sql_randomness(n, p);
> +	return sqlVdbeMemSetStr(val, p, n, 0, SQL_DYNAMIC) != 0 ? -1 : 0;
>  }
>  
>  #define Utf8Read(s, e) \
> @@ -880,49 +1007,57 @@ sql_strlike_ci(const char *zPattern, const char *zStr, unsigned int esc)
>   * Both arguments (A and B) must be of type TEXT. If one arguments
>   * is NULL then result is NULL as well.
>   */
> -static void
> -likeFunc(sql_context *context, int argc, sql_value **argv)
> +static int
> +sql_builtin_like(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	u32 escape = SQL_END_OF_STRING;
>  	int nPat;
> -	sql *db = sql_context_db_handle(context);
> +	struct sql *db = sql_get();
>  	bool is_like_ci =
>  		(current_session()->sql_flags & LIKE_CASE_SENS_FLAG) == 0;
> -	int rhs_type = sql_value_type(argv[0]);
> -	int lhs_type = sql_value_type(argv[1]);
> +	int rhs_type = sql_value_type(&argv[0]);
> +	int lhs_type = sql_value_type(&argv[1]);
>  
>  	if (lhs_type != MP_STR || rhs_type != MP_STR) {
>  		if (lhs_type == MP_NIL || rhs_type == MP_NIL)
> -			return;
> +			return 0;
>  		char *inconsistent_type = rhs_type != MP_STR ?
> -					  mem_type_to_str(argv[0]) :
> -					  mem_type_to_str(argv[1]);
> +					  mem_type_to_str(&argv[0]) :
> +					  mem_type_to_str(&argv[1]);
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES, "TEXT",
>  			 inconsistent_type);
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
> -	const char *zB = (const char *) sql_value_text(argv[0]);
> -	const char *zA = (const char *) sql_value_text(argv[1]);
> -	const char *zB_end = zB + sql_value_bytes(argv[0]);
> -	const char *zA_end = zA + sql_value_bytes(argv[1]);
> +	const char *zB = (const char *) sql_value_text(&argv[0]);
> +	const char *zA = (const char *) sql_value_text(&argv[1]);
> +	const char *zB_end = zB + sql_value_bytes(&argv[0]);
> +	const char *zA_end = zA + sql_value_bytes(&argv[1]);
>  
>  	/*
>  	 * Limit the length of the LIKE pattern to avoid problems
>  	 * of deep recursion and N*N behavior in
>  	 * sql_utf8_pattern_compare().
>  	 */
> -	nPat = sql_value_bytes(argv[0]);
> +	nPat = sql_value_bytes(&argv[0]);
>  	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]);
>  	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH] + 1);
>  	if (nPat > db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "LIKE pattern is too "\
>  			 "complex");
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
>  	/* Encoding did not change */
> -	assert(zB == (const char *) sql_value_text(argv[0]));
> +	assert(zB == (const char *) sql_value_text(&argv[0]));
>  
>  	if (argc == 3) {
>  		/*
> @@ -930,29 +1065,28 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>  		 * single UTF-8 character. Otherwise, return an
>  		 * error.
>  		 */
> -		const unsigned char *zEsc = sql_value_text(argv[2]);
> +		const unsigned char *zEsc = sql_value_text(&argv[2]);
>  		if (zEsc == 0)
> -			return;
> -		if (sql_utf8_char_count(zEsc, sql_value_bytes(argv[2])) != 1) {
> +			return 0;
> +		if (sql_utf8_char_count(zEsc, sql_value_bytes(&argv[2])) != 1) {
>  			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
>  				 "expression must be a single character");
> -			context->is_aborted = true;
> -			return;
> +			return -1;
>  		}
>  		escape = sqlUtf8Read(&zEsc);
>  	}
>  	if (!zA || !zB)
> -		return;
> +		return 0;
>  	int res;
>  	res = sql_utf8_pattern_compare(zB, zA, zB_end, zA_end,
>  				       is_like_ci, escape);
>  	if (res == INVALID_PATTERN) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "LIKE pattern can only "\
>  			 "contain UTF-8 characters");
> -		context->is_aborted = true;
> -		return;
> +		return -1;
>  	}
> -	sql_result_bool(context, res == MATCH);
> +	mem_set_bool(val, res == MATCH);
> +	return 0;
>  }
>  
>  /*
> @@ -960,14 +1094,26 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>   * argument if the arguments are different.  The result is NULL if the
>   * arguments are equal to each other.
>   */
> -static void
> -nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
> +static int
> +sql_builtin_nullif(struct func *func, struct port *args, struct port *ret)
>  {
> -	struct coll *pColl = sqlGetFuncCollSeq(context);
> -	UNUSED_PARAMETER(NotUsed);
> -	if (sqlMemCompare(argv[0], argv[1], pColl) != 0) {
> -		sql_result_value(context, argv[0]);
> -	}
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	struct coll *pColl = sqlGetFuncCollSeq(ctx);
> +	if (sqlMemCompare(&argv[0], &argv[1], pColl) != 0 &&
> +	    sqlVdbeMemCopy(val, &argv[0]) != 0)
> +		return -1;
> +	return 0;
>  }
>  
>  /**
> @@ -978,12 +1124,17 @@ nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
>   * @param unused1 Unused.
>   * @param unused2 Unused.
>   */
> -static void
> -sql_func_version(struct sql_context *context,
> -		 MAYBE_UNUSED int unused1,
> -		 MAYBE_UNUSED sql_value **unused2)
> +static int
> +sql_builtin_version(struct func *func, struct port *args, struct port *ret)
>  {
> -	sql_result_text(context, tarantool_version(), -1, SQL_STATIC);
> +	(void) func;
> +	(void) args;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	return sqlVdbeMemSetStr(val, tarantool_version(), -1, 1,
> +				SQL_STATIC) != 0 ? -1 : 0;
>  }
>  
>  /* Array for converting from half-bytes (nybbles) into ASCII hex
> @@ -1001,108 +1152,126 @@ static const char hexdigits[] = {
>   * "NULL".  Otherwise, the argument is enclosed in single quotes with
>   * single-quote escapes.
>   */
> -static void
> -quoteFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_quote(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	switch (sql_value_type(argv[0])) {
> +	switch (sql_value_type(&argv[0])) {
>  	case MP_DOUBLE:{
>  			double r1, r2;
>  			char zBuf[50];
> -			r1 = sql_value_double(argv[0]);
> +			r1 = sql_value_double(&argv[0]);
>  			sql_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
>  			sqlAtoF(zBuf, &r2, 20);
>  			if (r1 != r2) {
>  				sql_snprintf(sizeof(zBuf), zBuf, "%!.20e",
>  						 r1);
>  			}
> -			sql_result_text(context, zBuf, -1,
> -					    SQL_TRANSIENT);
> +			if (sqlVdbeMemSetStr(val, zBuf, -1, 1,
> +					     SQL_TRANSIENT) != 0)
> +				return -1;
>  			break;
>  		}
>  	case MP_INT:{
> -			sql_result_value(context, argv[0]);
> +			if (sqlVdbeMemCopy(val, &argv[0]) != 0)
> +				return -1;
>  			break;
>  		}
>  	case MP_BIN:{
> -			char *zText = 0;
> -			char const *zBlob = sql_value_blob(argv[0]);
> -			int nBlob = sql_value_bytes(argv[0]);
> -			assert(zBlob == sql_value_blob(argv[0]));	/* No encoding change */
> -			zText =
> -			    (char *)contextMalloc(context,
> -						  (2 * (i64) nBlob) + 4);
> -			if (zText) {
> -				int i;
> -				for (i = 0; i < nBlob; i++) {
> -					zText[(i * 2) + 2] =
> -					    hexdigits[(zBlob[i] >> 4) & 0x0F];
> -					zText[(i * 2) + 3] =
> -					    hexdigits[(zBlob[i]) & 0x0F];
> -				}
> -				zText[(nBlob * 2) + 2] = '\'';
> -				zText[(nBlob * 2) + 3] = '\0';
> -				zText[0] = 'X';
> -				zText[1] = '\'';
> -				sql_result_text(context, zText, -1,
> -						    SQL_TRANSIENT);
> -				sql_free(zText);
> +			char const *zBlob = sql_value_blob(&argv[0]);
> +			int nBlob = sql_value_bytes(&argv[0]);
> +			assert(zBlob == sql_value_blob(&argv[0]));	/* No encoding change */
> +			char *z = sql_blob_alloc((2 * (i64) nBlob) + 4);
> +			if (z == NULL)
> +				return -1;
> +			for (int i = 0; i < nBlob; i++) {
> +				z[(i * 2) + 2] =
> +					hexdigits[(zBlob[i] >> 4) & 0x0F];
> +				z[(i * 2) + 3] =
> +					hexdigits[(zBlob[i]) & 0x0F];
>  			}
> +			z[(nBlob * 2) + 2] = '\'';
> +			z[(nBlob * 2) + 3] = '\0';
> +			z[0] = 'X';
> +			z[1] = '\'';
> +			if (sqlVdbeMemSetStr(val, z, -1, 1, SQL_DYNAMIC) != 0)
> +				return -1;
>  			break;
>  		}
>  	case MP_STR:{
>  			int i, j;
>  			u64 n;
> -			const unsigned char *zArg = sql_value_text(argv[0]);
> -			char *z;
> -
> +			const unsigned char *zArg = sql_value_text(&argv[0]);
>  			if (zArg == 0)
> -				return;
> +				return 0;
>  			for (i = 0, n = 0; zArg[i]; i++) {
>  				if (zArg[i] == '\'')
>  					n++;
>  			}
> -			z = contextMalloc(context, ((i64) i) + ((i64) n) + 3);
> -			if (z) {
> -				z[0] = '\'';
> -				for (i = 0, j = 1; zArg[i]; i++) {
> -					z[j++] = zArg[i];
> -					if (zArg[i] == '\'') {
> -						z[j++] = '\'';
> -					}
> +			char *z = sql_blob_alloc(((i64) i) + ((i64) n) + 3);
> +			if (z == NULL)
> +				return -1;
> +			z[0] = '\'';
> +			for (i = 0, j = 1; zArg[i]; i++) {
> +				z[j++] = zArg[i];
> +				if (zArg[i] == '\'') {
> +					z[j++] = '\'';
>  				}
> -				z[j++] = '\'';
> -				z[j] = 0;
> -				sql_result_text(context, z, j,
> -						    sql_free);
>  			}
> +			z[j++] = '\'';
> +			z[j] = 0;
> +			if (sqlVdbeMemSetStr(val, z, j, 1, SQL_DYNAMIC) != 0)
> +				return -1;
>  			break;
>  		}
>  	case MP_BOOL: {
> -		sql_result_text(context, sql_value_boolean(argv[0]) ?
> -				"true" : "false", -1, SQL_TRANSIENT);
> +		if (sqlVdbeMemSetStr(val, sql_value_boolean(&argv[0]) ?
> +				     "true" : "false", -1, 1, SQL_STATIC) != 0)
> +			return -1;
>  		break;
>  	}
>  	default:{
> -			assert(sql_value_is_null(argv[0]));
> -			sql_result_text(context, "NULL", 4, SQL_STATIC);
> +			assert(sql_value_is_null(&argv[0]));
> +			if (sqlVdbeMemSetStr(val, "NULL", 4, 1,
> +					     SQL_STATIC) != 0)
> +				return -1;
>  			break;
>  		}
>  	}
> +	return 0;
>  }
>  
>  /*
>   * The unicode() function.  Return the integer unicode code-point value
>   * for the first character of the input string.
>   */
> -static void
> -unicodeFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_unicode(struct func *func, struct port *args, struct port *ret)
>  {
> -	const unsigned char *z = sql_value_text(argv[0]);
> -	(void)argc;
> -	if (z && z[0])
> -		sql_result_int(context, sqlUtf8Read(&z));
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
> +	const unsigned char *z = sql_value_text(&argv[0]);
> +	if (z != NULL && z[0] != '\0')
> +		sqlVdbeMemSetInt64(val, sqlUtf8Read(&z));
> +	return 0;
>  }
>  
>  /*
> @@ -1110,20 +1279,27 @@ unicodeFunc(sql_context * context, int argc, sql_value ** argv)
>   * an integer.  It constructs a string where each character of the string
>   * is the unicode character for the corresponding integer argument.
>   */
> -static void
> -charFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_char(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	unsigned char *z, *zOut;
> -	int i;
>  	zOut = z = sql_malloc64(argc * 4 + 1);
> -	if (z == NULL) {
> -		context->is_aborted = true;
> -		return;
> -	}
> -	for (i = 0; i < argc; i++) {
> +	if (z == NULL)
> +		return -1;
> +	for (uint32_t i = 0; i < argc; i++) {
>  		sql_int64 x;
>  		unsigned c;
> -		x = sql_value_int64(argv[i]);
> +		x = sql_value_int64(&argv[i]);
>  		if (x < 0 || x > 0x10ffff)
>  			x = 0xfffd;
>  		c = (unsigned)(x & 0x1fffff);
> @@ -1143,52 +1319,79 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
>  			*zOut++ = 0x80 + (u8) (c & 0x3F);
>  		}
>  	}
> -	sql_result_text64(context, (char *)z, zOut - z, sql_free);
> +	if (zOut - z > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> +		sql_free(z);
> +		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> +		return -1;
> +	}
> +	return sqlVdbeMemSetStr(val, (char *)z, zOut - z, 1,
> +				SQL_DYNAMIC) != 0 ? -1 : 0;
>  }
>  
>  /*
>   * The hex() function.  Interpret the argument as a blob.  Return
>   * a hexadecimal rendering as text.
>   */
> -static void
> -hexFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_hex(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	assert(argc == 1);
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	int i, n;
>  	const unsigned char *pBlob;
>  	char *zHex, *z;
> -	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	pBlob = sql_value_blob(argv[0]);
> -	n = sql_value_bytes(argv[0]);
> -	assert(pBlob == sql_value_blob(argv[0]));	/* No encoding change */
> -	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
> -	if (zHex) {
> -		for (i = 0; i < n; i++, pBlob++) {
> -			unsigned char c = *pBlob;
> -			*(z++) = hexdigits[(c >> 4) & 0xf];
> -			*(z++) = hexdigits[c & 0xf];
> -		}
> -		*z = 0;
> -		sql_result_text(context, zHex, n * 2, sql_free);
> +	pBlob = sql_value_blob(&argv[0]);
> +	n = sql_value_bytes(&argv[0]);
> +	assert(pBlob == sql_value_blob(&argv[0]));	/* No encoding change */
> +	z = zHex = sql_blob_alloc(((i64) n) * 2 + 1);
> +	if (z == NULL)
> +		return -1;
> +	for (i = 0; i < n; i++, pBlob++) {
> +		unsigned char c = *pBlob;
> +		*(z++) = hexdigits[(c >> 4) & 0xf];
> +		*(z++) = hexdigits[c & 0xf];
>  	}
> +	*z = 0;
> +	return sqlVdbeMemSetStr(val, zHex, n * 2, 1, SQL_DYNAMIC) != 0 ? -1 : 0;
>  }
>  
>  /*
>   * The zeroblob(N) function returns a zero-filled blob of size N bytes.
>   */
> -static void
> -zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_zeroblob(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	i64 n;
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
> -	n = sql_value_int64(argv[0]);
> +	n = sql_value_int64(&argv[0]);
>  	if (n < 0)
>  		n = 0;
> -	if (sql_result_zeroblob64(context, n) != 0) {
> +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> -		context->is_aborted = true;
> +		return -1;
>  	}
> +	sqlVdbeMemSetZeroBlob(val, n);
> +	return 0;
>  }
>  
>  /*
> @@ -1197,9 +1400,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>   * from A by replacing every occurrence of B with C.  The match
>   * must be exact.  Collating sequences are not used.
>   */
> -static void
> -replaceFunc(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_replace(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +
>  	const unsigned char *zStr;	/* The input string A */
>  	const unsigned char *zPattern;	/* The pattern string B */
>  	const unsigned char *zRep;	/* The replacement string C */
> @@ -1213,35 +1426,30 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
>  
>  	assert(argc == 3);
>  	UNUSED_PARAMETER(argc);
> -	zStr = sql_value_text(argv[0]);
> +	zStr = sql_value_text(&argv[0]);
>  	if (zStr == 0)
> -		return;
> -	nStr = sql_value_bytes(argv[0]);
> -	assert(zStr == sql_value_text(argv[0]));	/* No encoding change */
> -	zPattern = sql_value_text(argv[1]);
> -	if (zPattern == 0) {
> -		assert(sql_value_is_null(argv[1])
> -		       || sql_context_db_handle(context)->mallocFailed);
> -		return;
> -	}
> -	nPattern = sql_value_bytes(argv[1]);
> +		return 0;
> +	nStr = sql_value_bytes(&argv[0]);
> +	assert(zStr == sql_value_text(&argv[0]));
> +	zPattern = sql_value_text(&argv[1]);
> +	if (zPattern == 0)
> +		return 0;
> +	nPattern = sql_value_bytes(&argv[1]);
>  	if (nPattern == 0) {
> -		assert(! sql_value_is_null(argv[1]));
> -		sql_result_value(context, argv[0]);
> -		return;
> +		assert(! sql_value_is_null(&argv[1]));
> +		return sqlVdbeMemCopy(val, &argv[0]) != 0 ? -1 : 0;
>  	}
> -	assert(zPattern == sql_value_text(argv[1]));	/* No encoding change */
> -	zRep = sql_value_text(argv[2]);
> +	assert(zPattern == sql_value_text(&argv[1]));
> +	zRep = sql_value_text(&argv[2]);
>  	if (zRep == 0)
> -		return;
> -	nRep = sql_value_bytes(argv[2]);
> -	assert(zRep == sql_value_text(argv[2]));
> +		return 0;
> +	nRep = sql_value_bytes(&argv[2]);
> +	assert(zRep == sql_value_text(&argv[2]));
>  	nOut = nStr + 1;
>  	assert(nOut < SQL_MAX_LENGTH);
> -	zOut = contextMalloc(context, (i64) nOut);
> -	if (zOut == 0) {
> -		return;
> -	}
> +	zOut = (unsigned char *)sql_blob_alloc(nOut);
> +	if (zOut == NULL)
> +		return -1;
>  	loopLimit = nStr - nPattern;
>  	for (i = j = 0; i <= loopLimit; i++) {
>  		if (zStr[i] != zPattern[0]
> @@ -1249,23 +1457,21 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
>  			zOut[j++] = zStr[i];
>  		} else {
>  			u8 *zOld;
> -			sql *db = sql_context_db_handle(context);
> +			struct sql *db = sql_get();
>  			nOut += nRep - nPattern;
>  			testcase(nOut - 1 == db->aLimit[SQL_LIMIT_LENGTH]);
>  			testcase(nOut - 2 == db->aLimit[SQL_LIMIT_LENGTH]);
>  			if (nOut - 1 > db->aLimit[SQL_LIMIT_LENGTH]) {
>  				diag_set(ClientError, ER_SQL_EXECUTE, "string "\
>  					 "or blob too big");
> -				context->is_aborted = true;
>  				sql_free(zOut);
> -				return;
> +				return -1;
>  			}
>  			zOld = zOut;
>  			zOut = sql_realloc64(zOut, (int)nOut);
>  			if (zOut == 0) {
> -				context->is_aborted = true;
>  				sql_free(zOld);
> -				return;
> +				return -1;
>  			}
>  			memcpy(&zOut[j], zRep, nRep);
>  			j += nRep;
> @@ -1277,23 +1483,25 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
>  	j += nStr - i;
>  	assert(j <= nOut);
>  	zOut[j] = 0;
> -	sql_result_text(context, (char *)zOut, j, sql_free);
> +	return sqlVdbeMemSetStr(val, (char *)zOut, j, 1,
> +				SQL_DYNAMIC) != 0 ? -1 : 0;
>  }
>  
>  /**
>   * Remove characters included in @a trim_set from @a input_str
>   * until encounter a character that doesn't belong to @a trim_set.
>   * Remove from the side specified by @a flags.
> - * @param context SQL context.
> + * @param ret SQL value to store result.
>   * @param flags Trim specification: left, right or both.
>   * @param trim_set The set of characters for trimming.
>   * @param char_len Lengths of each UTF-8 character in @a trim_set.
>   * @param char_cnt A number of UTF-8 characters in @a trim_set.
>   * @param input_str Input string for trimming.
>   * @param input_str_sz Input string size in bytes.
> + * @return 0 On success, -1 otherwise.
>   */
> -static void
> -trim_procedure(struct sql_context *context, enum trim_side_mask flags,
> +static int
> +trim_procedure(struct Mem *val, enum trim_side_mask flags,
>  	       const unsigned char *trim_set, const uint8_t *char_len,
>  	       int char_cnt, const unsigned char *input_str, int input_str_sz)
>  {
> @@ -1332,8 +1540,8 @@ trim_procedure(struct sql_context *context, enum trim_side_mask flags,
>  		}
>  	}
>  finish:
> -	sql_result_text(context, (char *)input_str, input_str_sz,
> -			SQL_TRANSIENT);
> +	return sqlVdbeMemSetStr(val, (char *)input_str, input_str_sz, 1,
> +				SQL_TRANSIENT) != 0 ? -1 : 0;
>  }
>  
>  /**
> @@ -1348,8 +1556,7 @@ finish:
>   * @retval -1 Memory allocation error.
>   */
>  static int
> -trim_prepare_char_len(struct sql_context *context,
> -		      const unsigned char *trim_set, int trim_set_sz,
> +trim_prepare_char_len(const unsigned char *trim_set, int trim_set_sz,
>  		      uint8_t **char_len)
>  {
>  	/*
> @@ -1364,7 +1571,7 @@ trim_prepare_char_len(struct sql_context *context,
>  		return 0;
>  	}
>  
> -	if ((*char_len = (uint8_t *)contextMalloc(context, char_cnt)) == NULL)
> +	if ((*char_len = (uint8_t *)sql_blob_alloc(char_cnt)) == NULL)
>  		return -1;
>  
>  	int i = 0, j = 0;
> @@ -1385,20 +1592,29 @@ trim_prepare_char_len(struct sql_context *context,
>   * Call trimming procedure with TRIM_BOTH as the flags and " " as
>   * the trimming set.
>   */
> -static void
> -trim_func_one_arg(struct sql_context *context, int argc, sql_value **argv)
> +static int
> +sql_builtin_trim_one_arg(struct func *func, struct port *args, struct port *ret)
>  {
> -	assert(argc == 1);
> -	(void) argc;
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
>  
> +	assert(argc == 1);
>  	const unsigned char *input_str;
> -	if ((input_str = sql_value_text(argv[0])) == NULL)
> -		return;
> +	if ((input_str = sql_value_text(&argv[0])) == NULL)
> +		return 0;
>  
> -	int input_str_sz = sql_value_bytes(argv[0]);
> +	int input_str_sz = sql_value_bytes(&argv[0]);
>  	uint8_t len_one = 1;
> -	trim_procedure(context, TRIM_BOTH, (const unsigned char *) " ",
> +	trim_procedure(val, TRIM_BOTH, (const unsigned char *) " ",
>  		       &len_one, 1, input_str, input_str_sz);
> +	return 0;
>  }
>  
>  /**
> @@ -1412,33 +1628,46 @@ trim_func_one_arg(struct sql_context *context, int argc, sql_value **argv)
>   * If user has specified side keyword only, then call trimming
>   * procedure with the specified side and " " as the trimming set.
>   */
> -static void
> -trim_func_two_args(struct sql_context *context, int argc, sql_value **argv)
> +static int
> +sql_builtin_trim_two_args(struct func *func, struct port *args,
> +			  struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
>  	assert(argc == 2);
> -	(void) argc;
>  
>  	const unsigned char *input_str, *trim_set;
> -	if ((input_str = sql_value_text(argv[1])) == NULL)
> -		return;
> +	if ((input_str = sql_value_text(&argv[1])) == NULL)
> +		return 0;
>  
> -	int input_str_sz = sql_value_bytes(argv[1]);
> -	if (sql_value_type(argv[0]) == MP_INT) {
> +	int input_str_sz = sql_value_bytes(&argv[1]);
> +	if (sql_value_type(&argv[0]) == MP_INT) {
>  		uint8_t len_one = 1;
> -		trim_procedure(context, sql_value_int(argv[0]),
> -			       (const unsigned char *) " ", &len_one, 1,
> -			       input_str, input_str_sz);
> -	} else if ((trim_set = sql_value_text(argv[0])) != NULL) {
> -		int trim_set_sz = sql_value_bytes(argv[0]);
> -		uint8_t *char_len;
> -		int char_cnt = trim_prepare_char_len(context, trim_set,
> -						     trim_set_sz, &char_len);
> -		if (char_cnt == -1)
> -			return;
> -		trim_procedure(context, TRIM_BOTH, trim_set, char_len, char_cnt,
> -			       input_str, input_str_sz);
> +		if (trim_procedure(val, sql_value_int(&argv[0]),
> +				   (const unsigned char *) " ", &len_one, 1,
> +				   input_str, input_str_sz) != 0)
> +			return -1;
> +	} else if ((trim_set = sql_value_text(&argv[0])) != NULL) {
> +		int trim_set_sz = sql_value_bytes(&argv[0]);
> +		uint8_t *char_len = NULL;
> +		int char_cnt =
> +			trim_prepare_char_len(trim_set, trim_set_sz, &char_len);
> +		if (char_cnt == -1 ||
> +		    trim_procedure(val, TRIM_BOTH, trim_set, char_len,
> +				   char_cnt, input_str, input_str_sz) != 0) {
> +			sql_free(char_len);
> +			return -1;
> +		}
>  		sql_free(char_len);
>  	}
> +	return 0;
>  }
>  
>  /**
> @@ -1448,28 +1677,37 @@ trim_func_two_args(struct sql_context *context, int argc, sql_value **argv)
>   * If user has specified side keyword and <character_set>, then
>   * call trimming procedure with that args.
>   */
> -static void
> -trim_func_three_args(struct sql_context *context, int argc, sql_value **argv)
> +static int
> +sql_builtin_trim_three_args(struct func *func, struct port *args,
> +			    struct port *ret)
>  {
> +	(void) func;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
>  	assert(argc == 3);
> -	(void) argc;
>  
> -	assert(sql_value_type(argv[0]) == MP_INT);
> +	assert(sql_value_type(&argv[0]) == MP_INT);
>  	const unsigned char *input_str, *trim_set;
> -	if ((input_str = sql_value_text(argv[2])) == NULL ||
> -	    (trim_set = sql_value_text(argv[1])) == NULL)
> -		return;
> +	if ((input_str = sql_value_text(&argv[2])) == NULL ||
> +	    (trim_set = sql_value_text(&argv[1])) == NULL)
> +		return 0;
>  
> -	int trim_set_sz = sql_value_bytes(argv[1]);
> -	int input_str_sz = sql_value_bytes(argv[2]);
> +	int trim_set_sz = sql_value_bytes(&argv[1]);
> +	int input_str_sz = sql_value_bytes(&argv[2]);
>  	uint8_t *char_len;
> -	int char_cnt = trim_prepare_char_len(context, trim_set, trim_set_sz,
> -					     &char_len);
> +	int char_cnt = trim_prepare_char_len(trim_set, trim_set_sz, &char_len);
>  	if (char_cnt == -1)
> -		return;
> -	trim_procedure(context, sql_value_int(argv[0]), trim_set, char_len,
> -		       char_cnt, input_str, input_str_sz);
> +		return -1;
> +	int rc = trim_procedure(val, sql_value_int(&argv[0]), trim_set, char_len,
> +				char_cnt, input_str, input_str_sz);
>  	sql_free(char_len);
> +	return rc;
>  }
>  
>  /*
> @@ -1495,72 +1733,101 @@ struct SumCtx {
>   * value.  TOTAL never fails, but SUM might through an exception if
>   * it overflows an integer.
>   */
> -static void
> -sum_step(struct sql_context *context, int argc, sql_value **argv)
> +static int
> +sql_builtin_sum_step(struct func *func, struct port *args, struct port *ret)
>  {
> +	(void) func;
> +	(void) ret;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
>  	assert(argc == 1);
> -	UNUSED_PARAMETER(argc);
> -	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
> -	int type = sql_value_type(argv[0]);
> +	struct SumCtx *p = sql_aggregate_context(ctx, sizeof(*p));
> +	int type = sql_value_type(&argv[0]);
>  	if (type == MP_NIL || p == NULL)
> -		return;
> +		return 0;
>  	if (type != MP_DOUBLE && type != MP_INT) {
> -		if (mem_apply_numeric_type(argv[0]) != 0) {
> +		if (mem_apply_numeric_type(&argv[0]) != 0) {
>  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> -				 sql_value_text(argv[0]), "number");
> -			context->is_aborted = true;
> -			return;
> +				 sql_value_text(&argv[0]), "number");
> +			return -1;
>  		}
> -		type = sql_value_type(argv[0]);
> +		type = sql_value_type(&argv[0]);
>  	}
>  	p->cnt++;
>  	if (type == MP_INT) {
> -		int64_t v = sql_value_int64(argv[0]);
> +		int64_t v = sql_value_int64(&argv[0]);
>  		p->rSum += v;
>  		if ((p->approx | p->overflow) == 0 &&
>  		    sqlAddInt64(&p->iSum, v) != 0) {
>  			p->overflow = 1;
>  		}
>  	} else {
> -		p->rSum += sql_value_double(argv[0]);
> +		p->rSum += sql_value_double(&argv[0]);
>  		p->approx = 1;
>  	}
> +	return 0;
>  }
>  
> -static void
> -sumFinalize(sql_context * context)
> +static int
> +sql_builtin_sum_finalize(struct func *func, struct port *args, struct port *ret)
>  {
> -	SumCtx *p;
> -	p = sql_aggregate_context(context, 0);
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct SumCtx *p = sql_aggregate_context(ctx, 0);
>  	if (p && p->cnt > 0) {
>  		if (p->overflow) {
> -			diag_set(ClientError, ER_SQL_EXECUTE, "integer "\
> -				 "overflow");
> -			context->is_aborted = true;
> +			diag_set(ClientError, ER_SQL_EXECUTE,
> +				 "integer overflow");
> +			return -1;
>  		} else if (p->approx) {
> -			sql_result_double(context, p->rSum);
> +			sqlVdbeMemSetDouble(val, p->rSum);
>  		} else {
> -			sql_result_int64(context, p->iSum);
> +			sqlVdbeMemSetInt64(val, p->iSum);
>  		}
>  	}
> +	return 0;
>  }
>  
> -static void
> -avgFinalize(sql_context * context)
> +static int
> +sql_builtin_avg_finalize(struct func *func, struct port *args,
> +			 struct port *ret)
>  {
> -	SumCtx *p;
> -	p = sql_aggregate_context(context, 0);
> -	if (p && p->cnt > 0) {
> -		sql_result_double(context, p->rSum / (double)p->cnt);
> -	}
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct SumCtx *p = sql_aggregate_context(ctx, 0);
> +	if (p && p->cnt > 0)
> +		sqlVdbeMemSetDouble(val, p->rSum / (double)p->cnt);
> +	return 0;
>  }
>  
> -static void
> -totalFinalize(sql_context * context)
> +static int
> +sql_builtin_total_finalize(struct func *func, struct port *args,
> +			   struct port *ret)
>  {
> -	SumCtx *p;
> -	p = sql_aggregate_context(context, 0);
> -	sql_result_double(context, p ? p->rSum : (double)0);
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct SumCtx *p = sql_aggregate_context(ctx, 0);
> +	sqlVdbeMemSetDouble(val, p != NULL ? p->rSum : (double)0);
> +	return 0;
>  }
>  
>  /*
> @@ -1575,45 +1842,67 @@ struct CountCtx {
>  /*
>   * Routines to implement the count() aggregate function.
>   */
> -static void
> -countStep(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_count_step(struct func *func, struct port *args, struct port *ret)
>  {
> -	CountCtx *p;
> -	p = sql_aggregate_context(context, sizeof(*p));
> -	if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
> +	(void) func;
> +	(void) ret;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct CountCtx *p = sql_aggregate_context(ctx, sizeof(*p));
> +	if ((argc == 0 || ! sql_value_is_null(&argv[0])) && p)
>  		p->n++;
> -	}
> +	return 0;
>  }
>  
> -static void
> -countFinalize(sql_context * context)
> +static int
> +sql_builtin_count_finalize(struct func *func, struct port *args,
> +			   struct port *ret)
>  {
> -	CountCtx *p;
> -	p = sql_aggregate_context(context, 0);
> -	sql_result_int64(context, p ? p->n : 0);
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct CountCtx *p = sql_aggregate_context(ctx, 0);
> +	sqlVdbeMemSetInt64(val, p != NULL ? p->n : 0);
> +	return 0;
>  }
>  
>  /*
>   * Routines to implement min() and max() aggregate functions.
>   */
> -static void
> -minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> +static int
> +sql_builtin_minmax_step(struct func *func, struct port *args, struct port *ret)
>  {
> -	Mem *pArg = (Mem *) argv[0];
> +	(void) func;
> +	(void) ret;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	Mem *pArg = (Mem *) &argv[0];
>  	Mem *pBest;
> -	UNUSED_PARAMETER(NotUsed);
>  
> -	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
> -	if (!pBest)
> -		return;
> +	pBest = (Mem *) sql_aggregate_context(ctx, sizeof(*pBest));
> +	if (pBest == NULL)
> +		return -1;
>  
> -	if (sql_value_is_null(argv[0])) {
> +	if (sql_value_is_null(&argv[0])) {
>  		if (pBest->flags)
> -			sqlSkipAccumulatorLoad(context);
> +			sqlSkipAccumulatorLoad(ctx);
>  	} else if (pBest->flags) {
>  		int max;
>  		int cmp;
> -		struct coll *pColl = sqlGetFuncCollSeq(context);
> +		struct coll *pColl = sqlGetFuncCollSeq(ctx);
>  		/* This step function is used for both the min() and max() aggregates,
>  		 * the only difference between the two being that the sense of the
>  		 * comparison is inverted. For the max() aggregate, the
> @@ -1622,88 +1911,120 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
>  		 * Therefore the next statement sets variable 'max' to 1 for the max()
>  		 * aggregate, or 0 for min().
>  		 */
> -		max = sql_user_data(context) != 0;
> +		max = sql_user_data(ctx) != 0;
>  		cmp = sqlMemCompare(pBest, pArg, pColl);
>  		if ((max && cmp < 0) || (!max && cmp > 0)) {
> -			sqlVdbeMemCopy(pBest, pArg);
> +			if (sqlVdbeMemCopy(pBest, pArg) != 0)
> +				return -1;
>  		} else {
> -			sqlSkipAccumulatorLoad(context);
> +			sqlSkipAccumulatorLoad(ctx);
>  		}
>  	} else {
> -		pBest->db = sql_context_db_handle(context);
> -		sqlVdbeMemCopy(pBest, pArg);
> +		pBest->db = sql_get();
> +		if (sqlVdbeMemCopy(pBest, pArg) != 0)
> +			return -1;
>  	}
> +	return 0;
>  }
>  
> -static void
> -minMaxFinalize(sql_context * context)
> +static int
> +sql_builtin_minmax_finalize(struct func *func, struct port *args,
> +			    struct port *ret)
>  {
> -	sql_value *pRes;
> -	pRes = (sql_value *) sql_aggregate_context(context, 0);
> -	if (pRes) {
> -		if (pRes->flags) {
> -			sql_result_value(context, pRes);
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +	struct Mem *res =
> +		(struct Mem *) sql_aggregate_context(ctx, 0);
> +	if (res != NULL) {
> +		if (res->flags != 0 &&
> +		    sqlVdbeMemCopy(val, res) != 0) {
> +			sqlVdbeMemRelease(res);
> +			return -1;
>  		}
> -		sqlVdbeMemRelease(pRes);
> +		sqlVdbeMemRelease(res);
>  	}
> +	return 0;
>  }
>  
>  /*
>   * group_concat(EXPR, ?SEPARATOR?)
>   */
> -static void
> -groupConcatStep(sql_context * context, int argc, sql_value ** argv)
> +static int
> +sql_builtin_group_concat_step(struct func *func, struct port *args,
> +			      struct port *ret)
>  {
> -	const char *zVal;
> -	StrAccum *pAccum;
> -	const char *zSep;
> -	int nVal, nSep;
> +	(void) func;
> +	(void) ret;
> +	uint32_t argc;
> +	struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> +	if (argv == NULL)
> +		return -1;
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +
>  	assert(argc == 1 || argc == 2);
> -	if (sql_value_is_null(argv[0]))
> -		return;
> -	pAccum =
> -	    (StrAccum *) sql_aggregate_context(context, sizeof(*pAccum));
> -
> -	if (pAccum) {
> -		sql *db = sql_context_db_handle(context);
> -		int firstTerm = pAccum->mxAlloc == 0;
> -		pAccum->mxAlloc = db->aLimit[SQL_LIMIT_LENGTH];
> -		if (!firstTerm) {
> -			if (argc == 2) {
> -				zSep = (char *)sql_value_text(argv[1]);
> -				nSep = sql_value_bytes(argv[1]);
> -			} else {
> -				zSep = ",";
> -				nSep = 1;
> -			}
> -			if (zSep)
> -				sqlStrAccumAppend(pAccum, zSep, nSep);
> +	if (sql_value_is_null(&argv[0]))
> +		return 0;
> +	struct StrAccum *p =
> +		(struct StrAccum *) sql_aggregate_context(ctx, sizeof(*p));
> +	if (p == NULL)
> +		return -1;
> +
> +	struct sql *db = sql_get();
> +	int firstTerm = p->mxAlloc == 0;
> +	p->mxAlloc = db->aLimit[SQL_LIMIT_LENGTH];
> +	if (!firstTerm) {
> +		const char *zSep = NULL;
> +		int nSep;
> +		if (argc == 2) {
> +			zSep = (char *)sql_value_text(&argv[1]);
> +			nSep = sql_value_bytes(&argv[1]);
> +		} else {
> +			zSep = ",";
> +			nSep = 1;
>  		}
> -		zVal = (char *)sql_value_text(argv[0]);
> -		nVal = sql_value_bytes(argv[0]);
> -		if (zVal)
> -			sqlStrAccumAppend(pAccum, zVal, nVal);
> +		if (zSep != NULL)
> +			sqlStrAccumAppend(p, zSep, nSep);
>  	}
> +	const char *z = (char *)sql_value_text(&argv[0]);
> +	int n = sql_value_bytes(&argv[0]);
> +	if (z != NULL)
> +		sqlStrAccumAppend(p, z, n);
> +	return 0;
>  }
>  
> -static void
> -groupConcatFinalize(sql_context * context)
> +static int
> +sql_builtin_group_concat_finalize(struct func *func, struct port *args,
> +				  struct port *ret)
>  {
> -	StrAccum *pAccum;
> -	pAccum = sql_aggregate_context(context, 0);
> -	if (pAccum) {
> -		if (pAccum->accError == STRACCUM_TOOBIG) {
> -			diag_set(ClientError, ER_SQL_EXECUTE, "string or blob "\
> -				 "too big");
> -			context->is_aborted = true;
> -		} else if (pAccum->accError == STRACCUM_NOMEM) {
> -			context->is_aborted = true;
> -		} else {
> -			sql_result_text(context,
> -					    sqlStrAccumFinish(pAccum),
> -					    pAccum->nChar, sql_free);
> -		}
> +	(void) func;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> +	assert(ctx != NULL);
> +
> +
> +	struct StrAccum *p = sql_aggregate_context(ctx, 0);
> +	if (p == NULL)
> +		return 0;
> +	if (p->accError == STRACCUM_TOOBIG) {
> +		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> +		return -1;
> +	} else if (p->accError == STRACCUM_NOMEM) {
> +		return -1;
> +	} else {
> +		if (sqlVdbeMemSetStr(val, sqlStrAccumFinish(p), p->nChar,
> +				     1, SQL_DYNAMIC) != 0)
> +			return -1;
>  	}
> +	return 0;
>  }
>  
>  int
> @@ -1743,66 +2064,77 @@ sqlRegisterBuiltinFunctions(void)
>  	 * For peak efficiency, put the most frequently used function last.
>  	 */
>  	static FuncDef aBuiltinFunc[] = {
> -		FUNCTION_COLL(trim, 1, 3, 0, trim_func_one_arg),
> -		FUNCTION_COLL(trim, 2, 3, 0, trim_func_two_args),
> -		FUNCTION_COLL(trim, 3, 3, 0, trim_func_three_args),
> -		FUNCTION(min, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
> +		FUNCTION_COLL(trim, 1, 3, 0, sql_builtin_trim_one_arg),
> +		FUNCTION_COLL(trim, 2, 3, 0, sql_builtin_trim_two_args),
> +		FUNCTION_COLL(trim, 3, 3, 0, sql_builtin_trim_three_args),
> +		FUNCTION(min, -1, 0, 1, sql_builtin_minmax, FIELD_TYPE_SCALAR),
>  		FUNCTION(min, 0, 0, 1, 0, FIELD_TYPE_SCALAR),
> -		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
> -			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
> -		FUNCTION(max, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
> +		AGGREGATE2(min, 1, 0, 1, sql_builtin_minmax_step,
> +			   sql_builtin_minmax_finalize, SQL_FUNC_MINMAX,
> +			   FIELD_TYPE_SCALAR),
> +		FUNCTION(max, -1, 1, 1, sql_builtin_minmax, FIELD_TYPE_SCALAR),
>  		FUNCTION(max, 0, 1, 1, 0, FIELD_TYPE_SCALAR),
> -		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
> -			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
> -		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
> +		AGGREGATE2(max, 1, 1, 1, sql_builtin_minmax_step,
> +			   sql_builtin_minmax_finalize, SQL_FUNC_MINMAX,
> +			   FIELD_TYPE_SCALAR),
> +		FUNCTION2(typeof, 1, 0, 0, sql_builtin_typeof, SQL_FUNC_TYPEOF,
>  			  FIELD_TYPE_STRING),
> -		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
> +		FUNCTION2(length, 1, 0, 0, sql_builtin_length, SQL_FUNC_LENGTH,
>  			  FIELD_TYPE_INTEGER),
> -		FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
> -		FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
> -		FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
> -		FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
> -		FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
> -		FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
> -		FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
> -		FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc),
> -		FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc),
> -		FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
> -		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
> +		FUNCTION(position, 2, 0, 1, sql_builtin_position,
> +			 FIELD_TYPE_INTEGER),
> +		FUNCTION(printf, -1, 0, 0, sql_builtin_printf,
> +			 FIELD_TYPE_STRING),
> +		FUNCTION(unicode, 1, 0, 0, sql_builtin_unicode,
> +			 FIELD_TYPE_STRING),
> +		FUNCTION(char, -1, 0, 0, sql_builtin_char, FIELD_TYPE_STRING),
> +		FUNCTION(abs, 1, 0, 0, sql_builtin_abs, FIELD_TYPE_NUMBER),
> +		FUNCTION(round, 1, 0, 0, sql_builtin_round, FIELD_TYPE_INTEGER),
> +		FUNCTION(round, 2, 0, 0, sql_builtin_round, FIELD_TYPE_INTEGER),
> +		FUNCTION_COLL(upper, 1, 0, 1, sql_builtin_ICUUpper),
> +		FUNCTION_COLL(lower, 1, 0, 1, sql_builtin_ICULower),
> +		FUNCTION(hex, 1, 0, 0, sql_builtin_hex, FIELD_TYPE_STRING),
> +		FUNCTION2(ifnull, 2, 0, 0, sql_builtin_noop, SQL_FUNC_COALESCE,
>  			  FIELD_TYPE_INTEGER),
> -		VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
> -		VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_SCALAR),
> -		FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
> -		FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
> -		FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
> -		VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
> -		FUNCTION_COLL(replace, 3, 0, 0, replaceFunc),
> -		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_SCALAR),
> -		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
> -		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
> -		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
> -			  FIELD_TYPE_NUMBER),
> -		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
> -			  FIELD_TYPE_NUMBER),
> -		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
> -			  FIELD_TYPE_NUMBER),
> -		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
> -			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
> -		AGGREGATE(count, 1, 0, 0, countStep, countFinalize,
> +		VFUNCTION(random, 0, 0, 0, sql_builtin_random,
> +			  FIELD_TYPE_INTEGER),
> +		VFUNCTION(randomblob, 1, 0, 0, sql_builtin_random_blob,
> +			  FIELD_TYPE_SCALAR),
> +		FUNCTION(nullif, 2, 0, 1, sql_builtin_nullif,
> +			 FIELD_TYPE_SCALAR),
> +		FUNCTION(version, 0, 0, 0, sql_builtin_version,
> +			 FIELD_TYPE_STRING),
> +		FUNCTION(quote, 1, 0, 0, sql_builtin_quote, FIELD_TYPE_STRING),
> +		VFUNCTION(row_count, 0, 0, 0, sql_builtin_row_count,
> +			  FIELD_TYPE_INTEGER),
> +		FUNCTION_COLL(replace, 3, 0, 0, sql_builtin_replace),
> +		FUNCTION(zeroblob, 1, 0, 0, sql_builtin_zeroblob,
> +			 FIELD_TYPE_SCALAR),
> +		FUNCTION_COLL(substr, 2, 0, 0, sql_builtin_substr),
> +		FUNCTION_COLL(substr, 3, 0, 0, sql_builtin_substr),
> +		AGGREGATE(sum, 1, 0, 0, sql_builtin_sum_step,
> +			  sql_builtin_sum_finalize, FIELD_TYPE_NUMBER),
> +		AGGREGATE(total, 1, 0, 0, sql_builtin_sum_step,
> +			  sql_builtin_total_finalize, FIELD_TYPE_NUMBER),
> +		AGGREGATE(avg, 1, 0, 0, sql_builtin_sum_step,
> +			  sql_builtin_avg_finalize, FIELD_TYPE_NUMBER),
> +		AGGREGATE2(count, 0, 0, 0, sql_builtin_count_step,
> +			   sql_builtin_count_finalize, SQL_FUNC_COUNT,
> +			   FIELD_TYPE_INTEGER),
> +		AGGREGATE(count, 1, 0, 0, sql_builtin_count_step,
> +			  sql_builtin_count_finalize, FIELD_TYPE_INTEGER),
> +		AGGREGATE(group_concat, 1, 0, 0, sql_builtin_group_concat_step,
> +			  sql_builtin_group_concat_finalize, FIELD_TYPE_STRING),
> +		AGGREGATE(group_concat, 2, 0, 0, sql_builtin_group_concat_step,
> +			  sql_builtin_group_concat_finalize, FIELD_TYPE_STRING),
> +		FUNCTION2(like, 2, 1, 0, sql_builtin_like, SQL_FUNC_LIKE,
> +			  FIELD_TYPE_INTEGER),
> +		FUNCTION2(like, 3, 1, 0, sql_builtin_like, SQL_FUNC_LIKE,
>  			  FIELD_TYPE_INTEGER),
> -		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
> -			  groupConcatFinalize, FIELD_TYPE_STRING),
> -		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
> -			  groupConcatFinalize, FIELD_TYPE_STRING),
> -
> -		LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE,
> -			 FIELD_TYPE_INTEGER),
> -		LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE,
> -			 FIELD_TYPE_INTEGER),
>  		FUNCTION(coalesce, 1, 0, 0, 0, FIELD_TYPE_SCALAR),
>  		FUNCTION(coalesce, 0, 0, 0, 0, FIELD_TYPE_SCALAR),
> -		FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
> -			  FIELD_TYPE_SCALAR),
> +		FUNCTION2(coalesce, -1, 0, 0, sql_builtin_noop,
> +			  SQL_FUNC_COALESCE, FIELD_TYPE_SCALAR),
>  	};
>  	sql_register_analyze_builtins();
>  	sqlRegisterDateTimeFunctions();
> diff --git a/src/box/sql/main.c b/src/box/sql/main.c
> index 89d83d242..baaf7dcc9 100644
> --- a/src/box/sql/main.c
> +++ b/src/box/sql/main.c
> @@ -38,6 +38,7 @@
>  #include "sqlInt.h"
>  #include "vdbeInt.h"
>  #include "version.h"
> +#include "box/port.h"
>  #include "box/session.h"
>  
>  /*
> @@ -151,12 +152,18 @@ sql_initialize(void)
>  	return 0;
>  }
>  
> -void
> -sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
> -	      MAYBE_UNUSED sql_value **unused2)
> +int
> +sql_builtin_row_count(struct func *func, struct port *args, struct port *ret)
>  {
> -	sql *db = sql_context_db_handle(context);
> -	sql_result_int(context, db->nChange);
> +	(void) func;
> +	(void) args;
> +	struct Mem *val = vdbemem_alloc_on_region(1);
> +	if (val == NULL)
> +		return -1;
> +	port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> +	struct sql *db = sql_get();
> +	sqlVdbeMemSetInt64(val, db->nChange);
> +	return 0;
>  }
>  
>  /*
> @@ -214,16 +221,15 @@ sqlRollbackAll(Vdbe * pVdbe)
>   * is returned and the mallocFailed flag cleared.
>   */
>  int
> -sqlCreateFunc(sql * db,
> -		  const char *zFunctionName,
> -		  enum field_type type,
> -		  int nArg,
> -		  int flags,
> -		  void *pUserData,
> -		  void (*xSFunc) (sql_context *, int, sql_value **),
> -		  void (*xStep) (sql_context *, int, sql_value **),
> -		  void (*xFinal) (sql_context *),
> -		  FuncDestructor * pDestructor)
> +sqlCreateFunc(struct sql * db, const char *zFunctionName, enum field_type type,
> +	      int nArg, int flags, void *pUserData,
> +	      int (*xSFunc) (struct func *func, struct port *args,
> +			     struct port *ret),
> +	      int (*xStep) (struct func *func, struct port *args,
> +			    struct port *ret),
> +	      int (*xFinal) (struct func *func, struct port *args,
> +			     struct port *ret),
> +	      struct FuncDestructor * pDestructor)
>  {
>  	FuncDef *p;
>  	int extraFlags;
> @@ -284,19 +290,17 @@ sqlCreateFunc(sql * db,
>  	return 0;
>  }
>  
> +
>  int
> -sql_create_function_v2(sql * db,
> -			   const char *zFunc,
> -			   enum field_type type,
> -			   int nArg,
> -			   int flags,
> -			   void *p,
> -			   void (*xSFunc) (sql_context *, int,
> -					   sql_value **),
> -			   void (*xStep) (sql_context *, int,
> -					  sql_value **),
> -			   void (*xFinal) (sql_context *),
> -			   void (*xDestroy) (void *))
> +sql_create_function_v2(struct sql * db, const char *zFunc, enum field_type type,
> +		       int nArg, int flags, void *p,
> +		       int (*xSFunc) (struct func *func, struct port *args,
> +				      struct port *ret),
> +		       int (*xStep) (struct func *func, struct port *args,
> +				     struct port *ret),
> +		       int (*xFinal) (struct func *func, struct port *args,
> +				      struct port *ret),
> +		       void (*xDestroy) (void *ctx))
>  {
>  	FuncDestructor *pArg = 0;
>  
> diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
> index 98372f028..b9e6fbc31 100644
> --- a/src/box/sql/printf.c
> +++ b/src/box/sql/printf.c
> @@ -10,6 +10,7 @@
>   * sql.
>   */
>  #include "sqlInt.h"
> +#include "vdbeInt.h"
>  
>  /*
>   * Conversion types fall into various categories as defined by the
> @@ -143,7 +144,7 @@ getIntArg(PrintfArguments * p)
>  {
>  	if (p->nArg <= p->nUsed)
>  		return 0;
> -	return sql_value_int64(p->apArg[p->nUsed++]);
> +	return sql_value_int64(&p->apArg[p->nUsed++]);
>  }
>  
>  static double
> @@ -151,7 +152,7 @@ getDoubleArg(PrintfArguments * p)
>  {
>  	if (p->nArg <= p->nUsed)
>  		return 0.0;
> -	return sql_value_double(p->apArg[p->nUsed++]);
> +	return sql_value_double(&p->apArg[p->nUsed++]);
>  }
>  
>  static char *
> @@ -159,7 +160,7 @@ getTextArg(PrintfArguments * p)
>  {
>  	if (p->nArg <= p->nUsed)
>  		return 0;
> -	return (char *)sql_value_text(p->apArg[p->nUsed++]);
> +	return (char *)sql_value_text(&p->apArg[p->nUsed++]);
>  }
>  
>  /*
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 515fce3a9..ce15500e5 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -263,6 +263,7 @@
>  #include <assert.h>
>  #include <stddef.h>
>  
> +struct port;
>  typedef long long int sql_int64;
>  typedef unsigned long long int sql_uint64;
>  typedef sql_int64 sql_int64;
> @@ -379,52 +380,6 @@ sql_value_is_null(sql_value *value)
>  	return sql_value_type(value) == MP_NIL;
>  }
>  
> -sql *
> -sql_context_db_handle(sql_context *);
> -
> -
> -void
> -sql_result_blob(sql_context *, const void *,
> -		    int, void (*)(void *));
> -
> -void
> -sql_result_blob64(sql_context *, const void *,
> -		      sql_uint64, void (*)(void *));
> -
> -void
> -sql_result_double(sql_context *, double);
> -
> -void
> -sql_result_int(sql_context *, int);
> -
> -void
> -sql_result_bool(struct sql_context *ctx, bool value);
> -
> -void
> -sql_result_int64(sql_context *, sql_int64);
> -
> -void
> -sql_result_null(sql_context *);
> -
> -void
> -sql_result_text(sql_context *, const char *,
> -		    int, void (*)(void *));
> -
> -void
> -sql_result_text64(sql_context *, const char *,
> -		      sql_uint64, void (*)(void *));
> -
> -void
> -sql_result_value(sql_context *,
> -		     sql_value *);
> -
> -void
> -sql_result_zeroblob(sql_context *, int n);
> -
> -int
> -sql_result_zeroblob64(sql_context *,
> -			  sql_uint64 n);
> -
>  char *
>  sql_mprintf(const char *, ...);
>  char *
> @@ -537,9 +492,8 @@ sql_randomness(int N, void *P);
>  /**
>   * Return the number of affected rows in the last SQL statement.
>   */
> -void
> -sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
> -	      MAYBE_UNUSED sql_value **unused2);
> +int
> +sql_builtin_row_count(struct func *func, struct port *args, struct port *ret);
>  
>  void *
>  sql_user_data(sql_context *);
> @@ -569,22 +523,15 @@ sql_initialize(void);
>  #define SQL_DETERMINISTIC    0x800
>  
>  int
> -sql_create_function_v2(sql * db,
> -			   const char *zFunctionName,
> -			   enum field_type type,
> -			   int nArg,
> -			   int flags,
> -			   void *pApp,
> -			   void (*xFunc) (sql_context *,
> -					  int,
> -					  sql_value **),
> -			   void (*xStep) (sql_context *,
> -					  int,
> -					  sql_value **),
> -			   void (*xFinal)
> -			   (sql_context *),
> -			   void (*xDestroy) (void *)
> -	);
> +sql_create_function_v2(struct sql * db, const char *zFunc, enum field_type type,
> +		       int nArg, int flags, void *p,
> +		       int (*xSFunc) (struct func *func, struct port *args,
> +				      struct port *ret),
> +		       int (*xStep) (struct func *func, struct port *args,
> +				     struct port *ret),
> +		       int (*xFinal) (struct func *func, struct port *args,
> +				      struct port *ret),
> +		       void (*xDestroy) (void *ctx));
>  
>  #define SQL_OPEN_READONLY         0x00000001	/* Ok for sql_open_v2() */
>  #define SQL_OPEN_READWRITE        0x00000002	/* Ok for sql_open_v2() */
> @@ -1109,7 +1056,7 @@ struct sql {
>  	u8 dfltLockMode;	/* Default locking-mode for attached dbs */
>  	u8 mTrace;		/* zero or more sql_TRACE flags */
>  	u32 magic;		/* Magic number for detect library misuse */
> -	/** Value returned by sql_row_count(). */
> +	/** Value returned by sql_builtin_row_count(). */
>  	int nChange;
>  	int aLimit[SQL_N_LIMIT];	/* Limits */
>  	int nMaxSorterMmap;	/* Maximum size of regions mapped by sorter */
> @@ -1219,8 +1166,8 @@ struct FuncDef {
>  	u16 funcFlags;		/* Some combination of sql_FUNC_* */
>  	void *pUserData;	/* User data parameter */
>  	FuncDef *pNext;		/* Next function with same name */
> -	void (*xSFunc) (sql_context *, int, sql_value **);	/* func or agg-step */
> -	void (*xFinalize) (sql_context *);	/* Agg finalizer */
> +	int (*xSFunc) (struct func *func, struct port *args, struct port *ret);
> +	int (*xFinalize) (struct func *func, struct port *args, struct port *ret);
>  	const char *zName;	/* SQL name of the function. */
>  	union {
>  		FuncDef *pHash;	/* Next with a different name but the same hash */
> @@ -2667,7 +2614,7 @@ int sqlIsNaN(double);
>  struct PrintfArguments {
>  	int nArg;		/* Total number of arguments */
>  	int nUsed;		/* Number of arguments used so far */
> -	sql_value **apArg;	/* The argument values */
> +	struct Mem *apArg;	/* The argument values */
>  };
>  
>  void sqlVXPrintf(StrAccum *, const char *, va_list);
> @@ -4273,12 +4220,16 @@ sql_key_info_to_key_def(struct sql_key_info *key_info);
>  int
>  sql_is_like_func(struct sql *db, struct Expr *expr, int *is_like_ci);
>  
> -int sqlCreateFunc(sql *, const char *, enum field_type,
> -		      int, int, void *,
> -		      void (*)(sql_context *, int, sql_value **),
> -		      void (*)(sql_context *, int, sql_value **),
> -		      void (*)(sql_context *),
> -		      FuncDestructor * pDestructor);
> +int
> +sqlCreateFunc(struct sql * db, const char *zFunctionName, enum field_type type,
> +	      int nArg, int flags, void *pUserData,
> +	      int (*xSFunc) (struct func *func, struct port *args,
> +			     struct port *ret),
> +	      int (*xStep) (struct func *func, struct port *args,
> +			    struct port *ret),
> +	      int (*xFinal) (struct func *func, struct port *args,
> +			     struct port *ret),
> +	      struct FuncDestructor * pDestructor);
>  
>  /** Set OOM error flag. */
>  static inline void
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index c8887f9b7..92c99877a 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -44,6 +44,7 @@
>  #include "box/fk_constraint.h"
>  #include "box/txn.h"
>  #include "box/tuple.h"
> +#include "box/port.h"
>  #include "sqlInt.h"
>  #include "vdbeInt.h"
>  #include "tarantoolInt.h"
> @@ -1719,21 +1720,18 @@ case OP_CollSeq: {
>   * See also: Function0, AggStep, AggFinal
>   */
>  case OP_Function0: {
> -	int n;
>  	sql_context *pCtx;
>  
>  	assert(pOp->p4type==P4_FUNCDEF);
> -	n = pOp->p5;
>  	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
> -	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
> -	assert(pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n);
> -	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
> +	assert(pOp->p5 == 0 ||
> +	       (pOp->p2 > 0 && pOp->p2 + pOp->p5 <= (p->nMem+1 - p->nCursor)+1));
> +	assert(pOp->p3 < pOp->p2 || pOp->p3>=pOp->p2 + pOp->p5);
> +	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx));
>  	if (pCtx==0) goto no_mem;
> -	pCtx->pOut = 0;
>  	pCtx->pFunc = pOp->p4.pFunc;
>  	pCtx->iOp = (int)(pOp - aOp);
>  	pCtx->pVdbe = p;
> -	pCtx->argc = n;
>  	pOp->p4type = P4_FUNCCTX;
>  	pOp->p4.pCtx = pCtx;
>  	pOp->opcode = OP_Function;
> @@ -1741,7 +1739,6 @@ case OP_Function0: {
>  	FALLTHROUGH;
>  }
>  case OP_Function: {
> -	int i;
>  	sql_context *pCtx;
>  
>  	assert(pOp->p4type==P4_FUNCCTX);
> @@ -1753,33 +1750,39 @@ case OP_Function: {
>  	 * reinitializes the relavant parts of the sql_context object
>  	 */
>  	pOut = &aMem[pOp->p3];
> -	if (pCtx->pOut != pOut) {
> -		pCtx->pOut = pOut;
> -		for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
> -	}
> +	struct region *region = &fiber()->gc;
> +	size_t region_svp = region_used(region);
> +	struct port args, ret;
> +	port_vdbemem_create(&args, (struct sql_value *)&aMem[pOp->p2],
> +			    pOp->p5, pCtx);
>  
> -	memAboutToChange(p, pCtx->pOut);
> +	memAboutToChange(p, pOut);
>  #ifdef SQL_DEBUG
> -	for(i=0; i<pCtx->argc; i++) {
> -		assert(memIsValid(pCtx->argv[i]));
> -		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
> +	for(int i = 0; i < pOp->p5; i++) {
> +		assert(memIsValid(&aMem[pOp->p2 + i]));
> +		REGISTER_TRACE(p, pOp->p2+i, &aMem[pOp->p2 + i]);
>  	}
>  #endif
> -	MemSetTypeFlag(pCtx->pOut, MEM_Null);
> -	pCtx->is_aborted = false;
> -	(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
> -
> -	/* If the function returned an error, throw an exception */
> -	if (pCtx->is_aborted)
> +	MemSetTypeFlag(pOut, MEM_Null);
> +	rc = (*pCtx->pFunc->xSFunc)(NULL, &args, &ret);
> +	if (rc != 0)
> +		goto abort_due_to_error;
> +	uint32_t size;
> +	struct Mem *mem = (struct Mem *)port_get_vdbemem(&ret, &size);
> +	if (mem == NULL) {
> +		region_truncate(region, region_svp);
>  		goto abort_due_to_error;
> +	}
> +	*pOut = mem[0];
> +	region_truncate(region, region_svp);
>  
>  	/* Copy the result of the function into register P3 */
>  	if (pOut->flags & (MEM_Str|MEM_Blob)) {
> -		if (sqlVdbeMemTooBig(pCtx->pOut)) goto too_big;
> +		if (sqlVdbeMemTooBig(pOut)) goto too_big;
>  	}
>  
> -	REGISTER_TRACE(p, pOp->p3, pCtx->pOut);
> -	UPDATE_MAX_BLOBSIZE(pCtx->pOut);
> +	REGISTER_TRACE(p, pOp->p3, pOut);
> +	UPDATE_MAX_BLOBSIZE(pOut);
>  	break;
>  }
>  
> @@ -4964,21 +4967,19 @@ case OP_DecrJumpZero: {      /* jump, in1 */
>   * step function.
>   */
>  case OP_AggStep0: {
> -	int n;
>  	sql_context *pCtx;
>  
> -	assert(pOp->p4type==P4_FUNCDEF);
> -	n = pOp->p5;
> +	assert(pOp->p4type == P4_FUNCDEF);
>  	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
> -	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
> -	assert(pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n);
> -	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
> +	assert(pOp->p5 == 0 ||
> +	       (pOp->p2 > 0 && pOp->p2 + pOp->p5 <= (p->nMem+1 - p->nCursor)+1));
> +	assert(pOp->p3 < pOp->p2 || pOp->p3 >= pOp->p2 + pOp->p5);
> +	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx));
>  	if (pCtx==0) goto no_mem;
>  	pCtx->pMem = 0;
>  	pCtx->pFunc = pOp->p4.pFunc;
>  	pCtx->iOp = (int)(pOp - aOp);
>  	pCtx->pVdbe = p;
> -	pCtx->argc = n;
>  	pOp->p4type = P4_FUNCCTX;
>  	pOp->p4.pCtx = pCtx;
>  	pOp->opcode = OP_AggStep;
> @@ -4988,41 +4989,33 @@ case OP_AggStep0: {
>  case OP_AggStep: {
>  	int i;
>  	sql_context *pCtx;
> -	Mem *pMem;
>  	Mem t;
>  
>  	assert(pOp->p4type==P4_FUNCCTX);
>  	pCtx = pOp->p4.pCtx;
> -	pMem = &aMem[pOp->p3];
> +	pCtx->pMem = &aMem[pOp->p3];
>  
> -	/* If this function is inside of a trigger, the register array in aMem[]
> -	 * might change from one evaluation to the next.  The next block of code
> -	 * checks to see if the register array has changed, and if so it
> -	 * reinitializes the relavant parts of the sql_context object
> -	 */
> -	if (pCtx->pMem != pMem) {
> -		pCtx->pMem = pMem;
> -		for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
> -	}
> +	struct region *region = &fiber()->gc;
> +	size_t region_svp = region_used(region);
> +	struct port args, ret;
> +	port_vdbemem_create(&args, (struct sql_value *)&aMem[pOp->p2],
> +			    pOp->p5, pCtx);
>  
>  #ifdef SQL_DEBUG
> -	for(i=0; i<pCtx->argc; i++) {
> -		assert(memIsValid(pCtx->argv[i]));
> -		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
> +	for(i = 0; i < pOp->p5; i++) {
> +		assert(memIsValid(&aMem[pOp->p2 + i]));
> +		REGISTER_TRACE(p, pOp->p2+i, &aMem[pOp->p2 + i]);
>  	}
>  #endif
>  
> -	pMem->n++;
>  	sqlVdbeMemInit(&t, db, MEM_Null);
> -	pCtx->pOut = &t;
> -	pCtx->is_aborted = false;
> +	pOut = &t;
>  	pCtx->skipFlag = 0;
> -	(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
> -	if (pCtx->is_aborted) {
> -		sqlVdbeMemRelease(&t);
> +	rc = (*pCtx->pFunc->xSFunc)(NULL, &args, &ret);
> +	region_truncate(region, region_svp);
> +	if (rc != 0)
>  		goto abort_due_to_error;
> -	}
> -	assert(t.flags==MEM_Null);
> +
>  	if (pCtx->skipFlag) {
>  		assert(pOp[-1].opcode==OP_CollSeq);
>  		i = pOp[-1].p1;
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 6bfeecc85..4c8363a22 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -181,10 +181,7 @@ struct Mem {
>  	u32 uTemp;		/* Transient storage for serial_type in OP_MakeRecord */
>  	sql *db;		/* The associated database connection */
>  	void (*xDel) (void *);	/* Destructor for Mem.z - only valid if MEM_Dyn */
> -#ifdef SQL_DEBUG
>  	Mem *pScopyFrom;	/* This Mem is a shallow copy of pScopyFrom */
> -	void *pFiller;		/* So that sizeof(Mem) is a multiple of 8 */
> -#endif
>  };
>  
>  /*
> @@ -294,19 +291,11 @@ mem_apply_numeric_type(struct Mem *record);
>   * (Mem) which are only defined there.
>   */
>  struct sql_context {
> -	Mem *pOut;		/* The return value is stored here */
>  	FuncDef *pFunc;		/* Pointer to function information */
>  	Mem *pMem;		/* Memory cell used to store aggregate context */
>  	Vdbe *pVdbe;		/* The VM that owns this context */
>  	int iOp;		/* Instruction number of OP_Function */
> -	/*
> -	 * True, if an error occurred during the execution of the
> -	 * function.
> -	 */
> -	bool is_aborted;
>  	u8 skipFlag;		/* Skip accumulator loading if true */
> -	u8 argc;		/* Number of arguments */
> -	sql_value *argv[1];	/* Argument set */
>  };
>  
>  /* A bitfield type for use inside of structures.  Always follow with :N where
> @@ -584,4 +573,11 @@ char *
>  sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count,
>  			  uint32_t *tuple_size, struct region *region);
>  
> +/**
> + * Allocate a sequence of initialized vdbe memory registers
> + * on region.
> + */
> +struct Mem *
> +vdbemem_alloc_on_region(uint32_t count);
> +
>  #endif				/* !defined(SQL_VDBEINT_H) */
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index ef0ab79d2..d014e8258 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -69,6 +69,23 @@ invokeProfileCallback(sql * db, Vdbe * p)
>  #define checkProfileCallback(DB,P) \
>     if( ((P)->startTime)>0 ){ invokeProfileCallback(DB,P); }
>  
> +struct Mem *
> +vdbemem_alloc_on_region(uint32_t count)
> +{
> +	struct region *region = &fiber()->gc;
> +	struct Mem *ret = region_alloc(region, count * sizeof(*ret));
> +	if (ret == NULL) {
> +		diag_set(OutOfMemory, count * sizeof(*ret), "sqlValueNew", "ret");
> +		return NULL;
> +	}
> +	memset(ret, 0, count * sizeof(*ret));
> +	for (uint32_t i = 0; i < count; i++) {
> +		sqlVdbeMemInit(&ret[i], sql_get(), MEM_Null);
> +		assert(memIsValid(&ret[i]));
> +	}
> +	return ret;
> +}
> +
>  /*
>   * The following routine destroys a virtual machine that is created by
>   * the sql_compile() routine. The integer returned is an SQL_
> @@ -254,22 +271,9 @@ sql_value_free(sql_value * pOld)
>   * The invokeValueDestructor(P,X) routine invokes destructor function X()
>   * on value P is not going to be used and need to be destroyed.
>   */
> -static void
> -setResultStrOrError(sql_context * pCtx,	/* Function context */
> -		    const char *z,	/* String pointer */
> -		    int n,	/* Bytes in string, or negative */
> -		    void (*xDel) (void *)	/* Destructor function */
> -    )
> -{
> -	if (sqlVdbeMemSetStr(pCtx->pOut, z, n, 1, xDel) != 0)
> -		pCtx->is_aborted = true;
> -}
>  
>  static int
> -invokeValueDestructor(const void *p,	/* Value to destroy */
> -		      void (*xDel) (void *),	/* The destructor */
> -		      sql_context *pCtx	/* Set an error if no NULL */
> -    )
> +invokeValueDestructor(const void *p, void (*xDel) (void *))
>  {
>  	assert(xDel != SQL_DYNAMIC);
>  	if (xDel == 0) {
> @@ -279,114 +283,9 @@ invokeValueDestructor(const void *p,	/* Value to destroy */
>  	} else {
>  		xDel((void *)p);
>  	}
> -	if (pCtx) {
> -		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob is too "\
> -			 "big");
> -		pCtx->is_aborted = true;
> -	}
>  	return -1;
>  }
>  
> -void
> -sql_result_blob(sql_context * pCtx,
> -		    const void *z, int n, void (*xDel) (void *)
> -    )
> -{
> -	assert(n >= 0);
> -	if (sqlVdbeMemSetStr(pCtx->pOut, z, n, 0, xDel) != 0)
> -		pCtx->is_aborted = true;
> -}
> -
> -void
> -sql_result_blob64(sql_context * pCtx,
> -		      const void *z, sql_uint64 n, void (*xDel) (void *)
> -    )
> -{
> -	assert(xDel != SQL_DYNAMIC);
> -	if (n > 0x7fffffff) {
> -		(void)invokeValueDestructor(z, xDel, pCtx);
> -	} else {
> -		setResultStrOrError(pCtx, z, (int)n, xDel);
> -	}
> -}
> -
> -void
> -sql_result_double(sql_context * pCtx, double rVal)
> -{
> -	sqlVdbeMemSetDouble(pCtx->pOut, rVal);
> -}
> -
> -void
> -sql_result_int(sql_context * pCtx, int iVal)
> -{
> -	sqlVdbeMemSetInt64(pCtx->pOut, (i64) iVal);
> -}
> -
> -void
> -sql_result_bool(struct sql_context *ctx, bool value)
> -{
> -	mem_set_bool(ctx->pOut, value);
> -}
> -
> -void
> -sql_result_int64(sql_context * pCtx, i64 iVal)
> -{
> -	sqlVdbeMemSetInt64(pCtx->pOut, iVal);
> -}
> -
> -void
> -sql_result_null(sql_context * pCtx)
> -{
> -	sqlVdbeMemSetNull(pCtx->pOut);
> -}
> -
> -void
> -sql_result_text(sql_context * pCtx,
> -		    const char *z, int n, void (*xDel) (void *)
> -    )
> -{
> -	setResultStrOrError(pCtx, z, n, xDel);
> -}
> -
> -void
> -sql_result_text64(sql_context * pCtx,
> -		      const char *z,
> -		      sql_uint64 n,
> -		      void (*xDel) (void *))
> -{
> -	assert(xDel != SQL_DYNAMIC);
> -	if (n > 0x7fffffff) {
> -		(void)invokeValueDestructor(z, xDel, pCtx);
> -	} else {
> -		setResultStrOrError(pCtx, z, (int)n, xDel);
> -	}
> -}
> -
> -void
> -sql_result_value(sql_context * pCtx, sql_value * pValue)
> -{
> -	sqlVdbeMemCopy(pCtx->pOut, pValue);
> -}
> -
> -void
> -sql_result_zeroblob(sql_context * pCtx, int n)
> -{
> -	sqlVdbeMemSetZeroBlob(pCtx->pOut, n);
> -}
> -
> -int
> -sql_result_zeroblob64(sql_context * pCtx, u64 n)
> -{
> -	Mem *pOut = pCtx->pOut;
> -	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
> -		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob is too "\
> -			 "big");
> -		return -1;
> -	}
> -	sqlVdbeMemSetZeroBlob(pCtx->pOut, (int)n);
> -	return 0;
> -}
> -
>  /*
>   * Execute the statement pStmt, either until a row of data is ready, the
>   * statement is completely executed or an error occurs.
> @@ -475,23 +374,6 @@ sql_user_data(sql_context * p)
>  	return p->pFunc->pUserData;
>  }
>  
> -/*
> - * Extract the user data from a sql_context structure and return a
> - * pointer to it.
> - *
> - * IMPLEMENTATION-OF: R-46798-50301 The sql_context_db_handle() interface
> - * returns a copy of the pointer to the database connection (the 1st
> - * parameter) of the sql_create_function() and
> - * sql_create_function16() routines that originally registered the
> - * application defined function.
> - */
> -sql *
> -sql_context_db_handle(sql_context * p)
> -{
> -	assert(p && p->pOut);
> -	return p->pOut->db;
> -}
> -
>  /*
>   * Return the current time for a statement.  If the current time
>   * is requested more than once within the same run of a single prepared
> @@ -512,7 +394,7 @@ sqlStmtCurrentTime(sql_context * p)
>  	    p->pVdbe != 0 ? &p->pVdbe->iCurrentTime : &iTime;
>  #endif
>  	if (*piTime == 0) {
> -		rc = sqlOsCurrentTimeInt64(p->pOut->db->pVfs, piTime);
> +		rc = sqlOsCurrentTimeInt64(sql_get()->pVfs, piTime);
>  		if (rc)
>  			*piTime = 0;
>  	}
> @@ -614,10 +496,7 @@ columnNullValue(void)
>  		    /* .uTemp      = */ (u32) 0,
>  		    /* .db         = */ (sql *) 0,
>  		    /* .xDel       = */ (void (*)(void *))0,
> -#ifdef SQL_DEBUG
>  		    /* .pScopyFrom = */ (Mem *) 0,
> -		    /* .pFiller    = */ (void *)0,
> -#endif
>  	};
>  	return &nullMem;
>  }
> @@ -934,7 +813,7 @@ sql_bind_blob64(sql_stmt * pStmt,
>  {
>  	assert(xDel != SQL_DYNAMIC);
>  	if (nData > 0x7fffffff) {
> -		return invokeValueDestructor(zData, xDel, 0);
> +		return invokeValueDestructor(zData, xDel);
>  	} else {
>  		return sql_bind_blob(pStmt, i, zData, (int)nData, xDel);
>  	}
> @@ -1017,7 +896,7 @@ sql_bind_text64(sql_stmt * pStmt,
>  {
>  	assert(xDel != SQL_DYNAMIC);
>  	if (nData > 0x7fffffff) {
> -		return invokeValueDestructor(zData, xDel, 0);
> +		return invokeValueDestructor(zData, xDel);
>  	} else {
>  		return bindText(pStmt, i, zData, (int)nData, xDel);
>  	}
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index f52035b0e..7bd5cdd71 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -41,6 +41,7 @@
>  #include "tarantoolInt.h"
>  #include "box/schema.h"
>  #include "box/tuple.h"
> +#include "box/port.h"
>  #include "mpstream.h"
>  
>  #ifdef SQL_DEBUG
> @@ -315,26 +316,31 @@ sqlVdbeMemStringify(Mem * pMem, u8 bForce)
>  int
>  sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc)
>  {
> +	int rc = 0;
>  	if (ALWAYS(pFunc && pFunc->xFinalize)) {
> -		sql_context ctx;
> -		Mem t;
>  		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
> +		struct sql_context ctx;
>  		memset(&ctx, 0, sizeof(ctx));
> -		memset(&t, 0, sizeof(t));
> -		t.flags = MEM_Null;
> -		t.db = pMem->db;
> -		ctx.pOut = &t;
>  		ctx.pMem = pMem;
>  		ctx.pFunc = pFunc;
> -		pFunc->xFinalize(&ctx);	/* IMP: R-24505-23230 */
> +		struct region *region = &fiber()->gc;
> +		size_t region_svp = region_used(region);
> +		struct port args, ret;
> +		port_vdbemem_create(&args, NULL, 0, &ctx);
> +		rc = pFunc->xFinalize(NULL, &args, &ret);
>  		assert((pMem->flags & MEM_Dyn) == 0);
>  		if (pMem->szMalloc > 0)
>  			sqlDbFree(pMem->db, pMem->zMalloc);
> -		memcpy(pMem, &t, sizeof(t));
> -		if (ctx.is_aborted)
> -			return -1;
> +		uint32_t size;
> +		struct Mem *mem = (struct Mem *)port_get_vdbemem(&ret, &size);
> +		if (mem != NULL) {
> +			*pMem = mem[0];
> +		} else {
> +			rc = -1;
> +		}
> +		region_truncate(region, region_svp);
>  	}
> -	return 0;
> +	return rc;
>  }
>  
>  /*
> @@ -1239,7 +1245,6 @@ valueFromFunction(sql * db,	/* The database connection */
>  		  struct ValueNewStat4Ctx *pCtx	/* Second argument for valueNew() */
>      )
>  {
> -	sql_context ctx;	/* Context object for function invocation */
>  	sql_value **apVal = 0;	/* Function arguments */
>  	int nVal = 0;		/* Size of apVal[] array */
>  	FuncDef *pFunc = 0;	/* Function definition */
> @@ -1260,6 +1265,8 @@ valueFromFunction(sql * db,	/* The database connection */
>  	    ) {
>  		return 0;
>  	}
> +	struct region *region = &fiber()->gc;
> +	size_t region_svp = region_used(region);
>  
>  	if (pList) {
>  		apVal =
> @@ -1285,15 +1292,25 @@ valueFromFunction(sql * db,	/* The database connection */
>  	}
>  
>  	assert(!pCtx->pParse->is_aborted);
> -	memset(&ctx, 0, sizeof(ctx));
> -	ctx.pOut = pVal;
> -	ctx.pFunc = pFunc;
> -	pFunc->xSFunc(&ctx, nVal, apVal);
> -	assert(!ctx.is_aborted);
> +
> +	struct port args, ret;
> +	port_vdbemem_create(&args, (struct sql_value *)apVal, nVal, NULL);
> +	if ((*pFunc->xSFunc)(NULL, &args, &ret) != 0) {
> +		rc = -1;
> +		goto value_from_function_out;
> +	}
>  	sql_value_apply_type(pVal, type);
>  	assert(rc == 0);
> +	uint32_t size;
> +	struct Mem *mem = (struct Mem *)port_get_vdbemem(&ret, &size);
> +	if (mem == NULL) {
> +		rc = -1;
> +		goto value_from_function_out;
> +	}
> +	*pVal = mem[0];
>  
>   value_from_function_out:
> +	region_truncate(region, region_svp);
>  	if (rc != 0)
>  		pVal = 0;
>  	if (apVal) {
> diff --git a/src/lib/core/port.h b/src/lib/core/port.h
> index 09a026df5..cd8cd31f6 100644
> --- a/src/lib/core/port.h
> +++ b/src/lib/core/port.h
> @@ -40,6 +40,7 @@ extern "C" {
>  struct obuf;
>  struct lua_State;
>  struct port;
> +struct sql_value;
>  
>  /**
>   * A single port represents a destination of any output. One such
> @@ -98,6 +99,24 @@ struct port_vtab {
>  	 * is responsible for cleaning up.
>  	 **/
>  	const char *(*get_msgpack)(struct port *port, uint32_t *size);
> +	/**
> +	 * Get the content of a port as a sequence of vdbe memory
> +	 * variables. The SQL VDBE uses this representation for
> +	 * process data. This API is usefull to pass VDBE
> +	 * variables to sql builtin functions.
> +	 * The lifecycle of the returned value is
> +	 * implementation-specific: it may either be returned
> +	 * directly from the port, in which case the data will
> +	 * stay alive as long as the port is alive, or it may be
> +	 * allocated on the fiber()->gc, in which case the caller
> +	 * is responsible for cleaning up.
> +	 */
> +	struct sql_value *(*get_vdbemem)(struct port *port, uint32_t *size);
> +	/**
> +	 * Get additional implementation-specific information
> +	 * stored in port. Used for aggregate functions in SQL.
> +	 */
> +	void *(*get_context)(struct port *port);
>  	/** Destroy a port and release associated resources. */
>  	void (*destroy)(struct port *port);
>  };
> @@ -150,6 +169,18 @@ port_get_msgpack(struct port *port, uint32_t *size)
>  	return port->vtab->get_msgpack(port, size);
>  }
>  
> +static inline struct sql_value *
> +port_get_vdbemem(struct port *port, uint32_t *size)
> +{
> +	return port->vtab->get_vdbemem(port, size);
> +}
> +
> +static inline const void *
> +port_get_context(struct port *port)
> +{
> +	return port->vtab->get_context(port);
> +}
> +
>  #if defined(__cplusplus)
>  } /* extern "C" */
>  #endif /* defined __cplusplus */
> -- 
> 2.21.0
> 

-- 
Konstantin Osipov, Moscow, Russia

  reply	other threads:[~2019-07-10 18:47 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-07-10 11:00 [tarantool-patches] [PATCH v2 00/12] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 01/12] sql: get rid of SOUNDEX, MATCH Kirill Shcherbatov
2019-07-10 18:45   ` [tarantool-patches] " Konstantin Osipov
2019-07-12  8:44   ` Kirill Yukhin
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 10/12] sql: refactor builtins signatures with port Kirill Shcherbatov
2019-07-10 18:47   ` Konstantin Osipov [this message]
2019-07-11  7:33     ` [tarantool-patches] " Kirill Shcherbatov
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 11/12] box: use own vtab per each function object Kirill Shcherbatov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 12/12] sql: use schema's func hash instead of FuncDef hash Kirill Shcherbatov
2019-07-10 20:22   ` [tarantool-patches] " Konstantin Osipov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 02/12] sql: get rid of LIKELY, UNLIKELY and LIKEHOOD Kirill Shcherbatov
2019-07-10 19:02   ` [tarantool-patches] " Konstantin Osipov
2019-07-11  7:38     ` Kirill Shcherbatov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 03/12] sql: put analyze helpers to FuncDef cache Kirill Shcherbatov
2019-07-10 19:04   ` [tarantool-patches] " Konstantin Osipov
2019-07-12  8:47   ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 04/12] sql: rework LIKE case-insensitive mode Kirill Shcherbatov
2019-07-10 19:09   ` [tarantool-patches] " Konstantin Osipov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 05/12] sql: replace bool is_derived_coll marker with flag Kirill Shcherbatov
2019-07-10 19:10   ` [tarantool-patches] " Konstantin Osipov
2019-07-12  8:48   ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 06/12] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
2019-07-10 19:11   ` [tarantool-patches] " Konstantin Osipov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 07/12] sql: move LIKE UConverter object to collation library Kirill Shcherbatov
2019-07-12  8:49   ` [tarantool-patches] " Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 08/12] sql: rfc for SQL and Lua functions Kirill Shcherbatov
2019-07-10 19:17   ` [tarantool-patches] " Konstantin Osipov
2019-07-10 19:18     ` Konstantin Osipov
2019-07-11  7:40       ` Kirill Shcherbatov
2019-07-11 13:59   ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 09/12] box: introduce Lua persistent functions Kirill Shcherbatov
2019-07-10 19:26   ` [tarantool-patches] " Konstantin Osipov
2019-07-12 21:49   ` Konstantin Osipov
2019-07-13 13:55     ` Kirill Yukhin
2019-07-13 14:17       ` Kirill Yukhin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190710184713.GC5619@atlas \
    --to=kostja@tarantool.org \
    --cc=korablev@tarantool.org \
    --cc=kshcherbatov@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --subject='[tarantool-patches] Re: [PATCH v2 10/12] sql: refactor builtins signatures with port' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox