[tarantool-patches] [PATCH v2 10/12] sql: refactor builtins signatures with port

Kirill Shcherbatov kshcherbatov at tarantool.org
Wed Jul 10 14:00:58 MSK 2019


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.

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





More information about the Tarantool-patches mailing list