Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 01/15] sql: fix possible undefined behavior during cast
       [not found] <cover.1632220375.git.imeevma@gmail.com>
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 02/15] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch fixes possible undefined behavior during the implicit cast of
INTEGER to DOUBLE. The problem is, if the INTEGER is close enough to
2^64, it will be cast to 2^64 when it is cast to DOUBLE. Since we have a
check for loss of precision, this will cause this DOUBLE to be cast to
an INTEGER, which will result in undefined behavior since this DOUBLE is
outside the range of INTEGER.
---
 src/box/sql/mem.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 5e23c901c..cc0fd836b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -695,7 +695,7 @@ uint_to_double_precise(struct Mem *mem)
 	assert(mem->type == MEM_TYPE_UINT);
 	double d;
 	d = (double)mem->u.u;
-	if (mem->u.u != (uint64_t)d)
+	if (d == (double)UINT64_MAX || mem->u.u != (uint64_t)d)
 		return -1;
 	mem->u.r = d;
 	mem->flags = 0;
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 02/15] sql: use register P1 for number of arguments
       [not found] <cover.1632220375.git.imeevma@gmail.com>
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 01/15] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 04/15] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch makes OP_FunctionByName, OP_AggStep and OP_BuiltinFunction to
use register P1 for the number of arguments instead of register P5. This
makes it easier to use these opcodes.

Needed for #4145
---
 src/box/sql/expr.c    |  5 ++--
 src/box/sql/select.c  |  3 +-
 src/box/sql/vdbe.c    | 64 ++++++++++++++++++++-----------------------
 src/box/sql/vdbeInt.h |  1 -
 4 files changed, 32 insertions(+), 41 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index ee21c1ede..3b5df5292 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4107,18 +4107,17 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 						  (char *)coll, P4_COLLSEQ);
 			}
 			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
-				sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask,
+				sqlVdbeAddOp4(v, OP_BuiltinFunction0, nFarg,
 					      r1, target, (char *)func,
 					      P4_FUNC);
 			} else {
-				sqlVdbeAddOp4(v, OP_FunctionByName, constMask,
+				sqlVdbeAddOp4(v, OP_FunctionByName, nFarg,
 					      r1, target,
 					      sqlDbStrNDup(pParse->db,
 							   func->def->name,
 							   func->def->name_len),
 					      P4_DYNAMIC);
 			}
-			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
 			}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 2fe38e319..92e40aef6 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5639,9 +5639,8 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			sqlVdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
 					  (char *)coll, P4_COLLSEQ);
 		}
-		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
+		sqlVdbeAddOp3(v, OP_AggStep0, nArg, regAgg, pF->iMem);
 		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
-		sqlVdbeChangeP5(v, (u8) nArg);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
 		if (addrNext) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 44533fb3e..a0f1be454 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1182,32 +1182,24 @@ case OP_CollSeq: {
 	break;
 }
 
-/* Opcode: BuiltinFunction0 P1 P2 P3 P4 P5
- * Synopsis: r[P3]=func(r[P2@P5])
+/* Opcode: BuiltinFunction0 P1 P2 P3 P4 *
+ * Synopsis: r[P3]=func(r[P2@P1])
  *
  * Invoke a user function (P4 is a pointer to a FuncDef object that
- * defines the function) with P5 arguments taken from register P2 and
+ * defines the function) with P1 arguments taken from register P2 and
  * successors.  The result of the function is stored in register P3.
  * Register P3 must not be one of the function inputs.
  *
- * P1 is a 32-bit bitmask indicating whether or not each argument to the
- * function was determined to be constant at compile time. If the first
- * argument was constant then bit 0 of P1 is set.
- *
  * See also: BuiltinFunction, AggStep, AggFinal
  */
-/* Opcode: BuiltinFunction P1 P2 P3 P4 P5
- * Synopsis: r[P3]=func(r[P2@P5])
+/* Opcode: BuiltinFunction P1 P2 P3 P4 *
+ * Synopsis: r[P3]=func(r[P2@P1])
  *
  * Invoke a user function (P4 is a pointer to an sql_context object that
- * contains a pointer to the function to be run) with P5 arguments taken
+ * contains a pointer to the function to be run) with P1 arguments taken
  * from register P2 and successors.  The result of the function is stored
  * in register P3.  Register P3 must not be one of the function inputs.
  *
- * P1 is a 32-bit bitmask indicating whether or not each argument to the
- * function was determined to be constant at compile time. If the first
- * argument was constant then bit 0 of P1 is set.
- *
  * SQL functions are initially coded as OP_BuiltinFunction0 with
  * P4 pointing to a FuncDef object.  But on first evaluation,
  * the P4 operand is automatically converted into an sql_context
@@ -1223,7 +1215,7 @@ case OP_BuiltinFunction0: {
 	sql_context *pCtx;
 
 	assert(pOp->p4type == P4_FUNC);
-	n = pOp->p5;
+	n = pOp->p1;
 	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);
@@ -1233,7 +1225,6 @@ case OP_BuiltinFunction0: {
 	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
-	pCtx->argc = n;
 	pOp->p4type = P4_FUNCCTX;
 	pOp->p4.pCtx = pCtx;
 	pOp->opcode = OP_BuiltinFunction;
@@ -1242,6 +1233,7 @@ case OP_BuiltinFunction0: {
 }
 case OP_BuiltinFunction: {
 	int i;
+	int argc = pOp->p1;
 	sql_context *pCtx;
 
 	assert(pOp->p4type==P4_FUNCCTX);
@@ -1255,11 +1247,12 @@ case OP_BuiltinFunction: {
 	pOut = vdbe_prepare_null_out(p, pOp->p3);
 	if (pCtx->pOut != pOut) {
 		pCtx->pOut = pOut;
-		for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
+		for(i = 0; i < argc; ++i)
+			pCtx->argv[i] = &aMem[pOp->p2 + i];
 	}
 
 #ifdef SQL_DEBUG
-	for(i=0; i<pCtx->argc; i++) {
+	for(i = 0; i < argc; i++) {
 		assert(memIsValid(pCtx->argv[i]));
 		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
 	}
@@ -1267,7 +1260,7 @@ case OP_BuiltinFunction: {
 	pCtx->is_aborted = false;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, pCtx->argc, pCtx->argv);
+	func->call(pCtx, argc, pCtx->argv);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -1283,11 +1276,11 @@ case OP_BuiltinFunction: {
 	break;
 }
 
-/* Opcode: FunctionByName * P2 P3 P4 P5
- * Synopsis: r[P3]=func(r[P2@P5])
+/* Opcode: FunctionByName P1 P2 P3 P4 *
+ * Synopsis: r[P3]=func(r[P2@P1])
  *
  * Invoke a user function (P4 is a pointer to a function object
- * that defines the function) with P5 arguments taken from
+ * that defines the function) with P1 arguments taken from
  * register P2 and successors. The result of the function is
  * stored in register P3.
  */
@@ -1303,7 +1296,7 @@ case OP_FunctionByName: {
 	 * turn out to be invalid after call.
 	 */
 	enum field_type returns = func->def->returns;
-	int argc = pOp->p5;
+	int argc = pOp->p1;
 	struct Mem *argv = &aMem[pOp->p2];
 	struct port args, ret;
 
@@ -4185,26 +4178,26 @@ case OP_DecrJumpZero: {      /* jump, in1 */
 }
 
 
-/* Opcode: AggStep0 * P2 P3 P4 P5
- * Synopsis: accum=r[P3] step(r[P2@P5])
+/* Opcode: AggStep0 P1 P2 P3 P4 *
+ * Synopsis: accum=r[P3] step(r[P2@P1])
  *
  * Execute the step function for an aggregate.  The
- * function has P5 arguments.   P4 is a pointer to the FuncDef
+ * function has P1 arguments.   P4 is a pointer to the FuncDef
  * structure that specifies the function.  Register P3 is the
  * accumulator.
  *
- * The P5 arguments are taken from register P2 and its
+ * The P1 arguments are taken from register P2 and its
  * successors.
  */
-/* Opcode: AggStep * P2 P3 P4 P5
- * Synopsis: accum=r[P3] step(r[P2@P5])
+/* Opcode: AggStep P1 P2 P3 P4 *
+ * Synopsis: accum=r[P3] step(r[P2@P1])
  *
  * Execute the step function for an aggregate.  The
- * function has P5 arguments.   P4 is a pointer to an sql_context
+ * function has P1 arguments.   P4 is a pointer to an sql_context
  * object that is used to run the function.  Register P3 is
  * as the accumulator.
  *
- * The P5 arguments are taken from register P2 and its
+ * The P1 arguments are taken from register P2 and its
  * successors.
  *
  * This opcode is initially coded as OP_AggStep0.  On first evaluation,
@@ -4228,7 +4221,6 @@ case OP_AggStep0: {
 	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
-	pCtx->argc = n;
 	pOp->p4type = P4_FUNCCTX;
 	pOp->p4.pCtx = pCtx;
 	pOp->opcode = OP_AggStep;
@@ -4237,6 +4229,7 @@ case OP_AggStep0: {
 }
 case OP_AggStep: {
 	int i;
+	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
 	Mem t;
@@ -4252,11 +4245,12 @@ case OP_AggStep: {
 	 */
 	if (pCtx->pMem != pMem) {
 		pCtx->pMem = pMem;
-		for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
+		for(i = 0; i < argc; ++i)
+			pCtx->argv[i] = &aMem[pOp->p2 + i];
 	}
 
 #ifdef SQL_DEBUG
-	for(i=0; i<pCtx->argc; i++) {
+	for(i = 0; i < argc; i++) {
 		assert(memIsValid(pCtx->argv[i]));
 		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
 	}
@@ -4269,7 +4263,7 @@ case OP_AggStep: {
 	pCtx->skipFlag = 0;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, pCtx->argc, pCtx->argv);
+	func->call(pCtx, argc, pCtx->argv);
 	if (pCtx->is_aborted) {
 		mem_destroy(&t);
 		goto abort_due_to_error;
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index cfe743b94..575ab3f3d 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -182,7 +182,6 @@ struct sql_context {
 	 */
 	bool is_aborted;
 	u8 skipFlag;		/* Skip accumulator loading if true */
-	u8 argc;		/* Number of arguments */
 	sql_value *argv[1];	/* Argument set */
 };
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 04/15] sql: move collation to struct sql_context
       [not found] <cover.1632220375.git.imeevma@gmail.com>
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 01/15] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 02/15] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch makes it easier to get a collation by a function.

Needed for #4145
---
 src/box/sql/date.c    | 43 -------------------------------------------
 src/box/sql/expr.c    |  6 +-----
 src/box/sql/func.c    | 26 ++++++--------------------
 src/box/sql/select.c  | 12 +++++-------
 src/box/sql/sqlInt.h  |  3 +--
 src/box/sql/vdbe.c    | 18 +++---------------
 src/box/sql/vdbeInt.h |  4 +---
 src/box/sql/vdbeapi.c | 27 ---------------------------
 8 files changed, 17 insertions(+), 122 deletions(-)

diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index dbf460498..914a00dd2 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -1247,46 +1247,3 @@ ctimestampFunc(sql_context * context,
 	datetimeFunc(context, 0, 0);
 }
 #endif				/* !defined(SQL_OMIT_DATETIME_FUNCS) */
-
-#ifdef SQL_OMIT_DATETIME_FUNCS
-/*
- * If the library is compiled to omit the full-scale date and time
- * handling (to get a smaller binary), the following minimal version
- * of the functions current_time(), current_date() and current_timestamp()
- * are included instead. This is to support column declarations that
- * include "DEFAULT CURRENT_TIME" etc.
- *
- * This function uses the C-library functions time(), gmtime()
- * and strftime(). The format string to pass to strftime() is supplied
- * as the user-data for the function.
- */
-static void
-currentTimeFunc(sql_context * context, int argc, sql_value ** argv)
-{
-	time_t t;
-	char *zFormat = (char *)sql_user_data(context);
-	sql_int64 iT;
-	struct tm *pTm;
-	struct tm sNow;
-	char zBuf[20];
-
-	UNUSED_PARAMETER(argc);
-	UNUSED_PARAMETER(argv);
-
-	iT = sqlStmtCurrentTime(context);
-	if (iT <= 0)
-		return;
-	t = iT / 1000 - 10000 * (sql_int64) 21086676;
-#if HAVE_GMTIME_R
-	pTm = gmtime_r(&t, &sNow);
-#else
-	pTm = gmtime(&t);
-	if (pTm)
-		memcpy(&sNow, pTm, sizeof(sNow));
-#endif
-	if (pTm) {
-		strftime(zBuf, 20, zFormat, &sNow);
-		sql_result_text(context, zBuf, -1, SQL_TRANSIENT);
-	}
-}
-#endif
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 6446ef091..ab7d95f7e 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4102,13 +4102,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				pParse->is_aborted = true;
 				return 0;
 			}
-			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
-				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
-						  (char *)coll, P4_COLLSEQ);
-			}
 			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
 				struct sql_context *ctx =
-					sql_context_new(v, func, nFarg);
+					sql_context_new(func, nFarg, coll);
 				if (ctx == NULL) {
 					pParse->is_aborted = true;
 					return -1;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9009f9e4f..b4461c6ee 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -100,20 +100,6 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
 	mem_set_uuid(ctx->pOut, &uuid);
 }
 
-/*
- * Return the collating function associated with a function.
- */
-static struct coll *
-sqlGetFuncCollSeq(sql_context * context)
-{
-	VdbeOp *pOp;
-	assert(context->pVdbe != 0);
-	pOp = &context->pVdbe->aOp[context->iOp - 1];
-	assert(pOp->opcode == OP_CollSeq);
-	assert(pOp->p4type == P4_COLLSEQ || pOp->p4.pColl == NULL);
-	return pOp->p4.pColl;
-}
-
 /*
  * Indicate that the accumulator load should be skipped on this
  * iteration of the aggregate loop.
@@ -141,7 +127,7 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	pColl = sqlGetFuncCollSeq(context);
+	pColl = context->coll;
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
 	if (mem_is_null(argv[0]))
@@ -402,7 +388,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 = context->coll;
 			int c;
 			for (c = 0; c + n_needle_chars <= n_haystack_chars; c++) {
 				if (coll->cmp((const char *) haystack_str + beg_offset,
@@ -667,7 +653,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 		return;                                                        \
 	}                                                                      \
 	UErrorCode status = U_ZERO_ERROR;                                      \
-	struct coll *coll = sqlGetFuncCollSeq(context);                    \
+	struct coll *coll = context->coll;                                     \
 	const char *locale = NULL;                                             \
 	if (coll != NULL && coll->type == COLL_TYPE_ICU) {                     \
 		locale = ucol_getLocaleByType(coll->collator,                  \
@@ -1029,7 +1015,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	if (!zA || !zB)
 		return;
 	int res;
-	struct coll *coll = sqlGetFuncCollSeq(context);
+	struct coll *coll = context->coll;
 	assert(coll != NULL);
 	res = sql_utf8_pattern_compare(zB, zA, zB_end, zA_end, coll, escape);
 
@@ -1050,7 +1036,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 static void
 nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
 {
-	struct coll *pColl = sqlGetFuncCollSeq(context);
+	struct coll *pColl = context->coll;
 	UNUSED_PARAMETER(NotUsed);
 	if (mem_cmp_scalar(argv[0], argv[1], pColl) != 0)
 		sql_result_value(context, argv[0]);
@@ -1761,7 +1747,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (!mem_is_null(pBest))
 			sqlSkipAccumulatorLoad(context);
 	} else if (!mem_is_null(pBest)) {
-		struct coll *pColl = sqlGetFuncCollSeq(context);
+		struct coll *pColl = context->coll;
 		/*
 		 * This step function is used for both the min()
 		 * and max() aggregates, the only difference
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 459325a88..2880f8ea0 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5621,8 +5621,8 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			pParse->is_aborted = true;
 			return;
 		}
+		struct coll *coll = NULL;
 		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
-			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
 			assert(pList != 0);	/* pList!=0 if pF->pFunc has NEEDCOLL */
@@ -5636,10 +5636,9 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			}
 			if (regHit == 0 && pAggInfo->nAccumulator)
 				regHit = ++pParse->nMem;
-			sqlVdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
-					  (char *)coll, P4_COLLSEQ);
+			sqlVdbeAddOp1(v, OP_SkipLoad, regHit);
 		}
-		struct sql_context *ctx = sql_context_new(v, pF->func, nArg);
+		struct sql_context *ctx = sql_context_new(pF->func, nArg, coll);
 		if (ctx == NULL) {
 			pParse->is_aborted = true;
 			return;
@@ -6751,7 +6750,7 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
 }
 
 struct sql_context *
-sql_context_new(struct Vdbe *vdbe, struct func *func, uint32_t argc)
+sql_context_new(struct func *func, uint32_t argc, struct coll *coll)
 {
 	uint32_t size = sizeof(struct sql_context);
 	if (argc > 1)
@@ -6763,7 +6762,6 @@ sql_context_new(struct Vdbe *vdbe, struct func *func, uint32_t argc)
 	ctx->func = func;
 	ctx->is_aborted = false;
 	ctx->skipFlag = 0;
-	ctx->pVdbe = vdbe;
-	ctx->iOp = 0;
+	ctx->coll = coll;
 	return ctx;
 }
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index c2701dbde..710bd86cf 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4142,7 +4142,7 @@ void sqlStrAccumReset(StrAccum *);
 void sqlSelectDestInit(SelectDest *, int, int, int);
 
 struct sql_context *
-sql_context_new(struct Vdbe *vdbe, struct func *func, uint32_t argc);
+sql_context_new(struct func *func, uint32_t argc, struct coll *coll);
 
 /*
  * Create an expression to load @a column from datasource
@@ -4204,7 +4204,6 @@ void sqlParser(void *, int, Token, Parse *);
 int sqlParserStackPeak(void *);
 #endif
 
-sql_int64 sqlStmtCurrentTime(sql_context *);
 int sqlVdbeParameterIndex(Vdbe *, const char *, int);
 int sqlTransferBindings(sql_stmt *, sql_stmt *);
 int sqlReprepare(Vdbe *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 2ff7ce8f4..12dc9126b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1159,23 +1159,13 @@ case OP_Remainder: {           /* same as TK_REM, in1, in2, out3 */
 	break;
 }
 
-/* Opcode: CollSeq P1 * * P4
- *
- * P4 is a pointer to a CollSeq struct. If the next call to a user function
- * or aggregate calls sqlGetFuncCollSeq(), this collation sequence will
- * be returned. This is used by the built-in min(), max() and nullif()
- * functions.
+/* Opcode: SkipLoad P1 * * * *
  *
  * If P1 is not zero, then it is a register that a subsequent min() or
  * max() aggregate will set to true if the current row is not the minimum or
  * maximum.  The P1 register is initialized to false by this instruction.
- *
- * The interface used by the implementation of the aforementioned functions
- * to retrieve the collation sequence set by this opcode is not available
- * publicly.  Only built-in functions have access to this feature.
  */
-case OP_CollSeq: {
-	assert(pOp->p4type==P4_COLLSEQ || pOp->p4.pColl == NULL);
+case OP_SkipLoad: {
 	if (pOp->p1) {
 		mem_set_bool(&aMem[pOp->p1], false);
 	}
@@ -1199,7 +1189,6 @@ case OP_BuiltinFunction: {
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
-	pCtx->iOp = (int)(pOp - aOp);
 
 	/* 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
@@ -4160,7 +4149,6 @@ case OP_AggStep: {
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
-	pCtx->iOp = (int)(pOp - aOp);
 	pMem = &aMem[pOp->p3];
 
 	/* If this function is inside of a trigger, the register array in aMem[]
@@ -4195,7 +4183,7 @@ case OP_AggStep: {
 	}
 	assert(mem_is_null(&t));
 	if (pCtx->skipFlag) {
-		assert(pOp[-1].opcode==OP_CollSeq);
+		assert(pOp[-1].opcode == OP_SkipLoad);
 		i = pOp[-1].p1;
 		if (i) mem_set_bool(&aMem[i], true);
 	}
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 575ab3f3d..b9a18f1a1 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -173,9 +173,7 @@ struct sql_context {
 	/* A pointer to function implementation. */
 	struct func *func;
 	Mem *pMem;		/* Memory cell used to store aggregate context */
-	Vdbe *pVdbe;		/* The VM that owns this context */
-	/** Instruction number of OP_BuiltinFunction0. */
-	int iOp;
+	struct coll *coll;
 	/*
 	 * True, if an error occurred during the execution of the
 	 * function.
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 115940227..6e598513c 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -349,33 +349,6 @@ sql_context_db_handle(sql_context * p)
 	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
- * statement, the exact same time is returned for each invocation regardless
- * of the amount of time that elapses between invocations.  In other words,
- * the time returned is always the time of the first call.
- */
-sql_int64
-sqlStmtCurrentTime(sql_context * p)
-{
-	int rc;
-#ifndef SQL_ENABLE_OR_STAT4
-	sql_int64 *piTime = &p->pVdbe->iCurrentTime;
-	assert(p->pVdbe != 0);
-#else
-	sql_int64 iTime = 0;
-	sql_int64 *piTime =
-	    p->pVdbe != 0 ? &p->pVdbe->iCurrentTime : &iTime;
-#endif
-	if (*piTime == 0) {
-		rc = sqlOsCurrentTimeInt64(p->pOut->db->pVfs, piTime);
-		if (rc)
-			*piTime = 0;
-	}
-	return *piTime;
-}
-
 /*
  * Allocate or return the aggregate context for a user function.  A new
  * context is allocated on the first call.  Subsequent calls return the
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (2 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 04/15] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-25 11:06   ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch introduces function mem_append(). This function appends the
given string to the end of the STRING or VARBINARY contained in MEM.
In case MEM needs to increase the size of allocated memory, additional
memory is allocated in an attempt to reduce the total number of
allocations. The main use of this function is to add multiple strings to
a single MEM.

Needed for #4145
---
 src/box/sql/mem.c     | 19 +++++++++++++++++++
 src/box/sql/mem.h     |  8 ++++++++
 src/box/sql/vdbeaux.c |  9 ++-------
 3 files changed, 29 insertions(+), 7 deletions(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index cc0fd836b..a9011fc63 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1953,6 +1953,25 @@ mem_move(struct Mem *to, struct Mem *from)
 	from->zMalloc = NULL;
 }
 
+int
+mem_append(struct Mem *mem, const char *value, uint32_t len)
+{
+	assert((mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR)) != 0);
+	int new_size = mem->n + len;
+	if (((mem->flags & (MEM_Static | MEM_Dyn | MEM_Ephem)) != 0) ||
+	    mem->szMalloc < new_size) {
+		/*
+		 * Force exponential buffer size growth to avoid having to call
+		 * this routine too often.
+		 */
+		if (sqlVdbeMemGrow(mem, new_size + mem->n, 1) != 0)
+			return -1;
+	}
+	memcpy(&mem->z[mem->n], value, len);
+	mem->n = new_size;
+	return 0;
+}
+
 int
 mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 0da45b8af..54a1e931b 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -597,6 +597,14 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from);
 void
 mem_move(struct Mem *to, struct Mem *from);
 
+/**
+ * Append the given string to the end of the STRING or VARBINARY contained in
+ * MEM. In case MEM needs to increase the size of allocated memory, additional
+ * memory is allocated in an attempt to reduce the total number of allocations.
+ */
+int
+mem_append(struct Mem *mem, const char *value, uint32_t len);
+
 /**
  * Concatenate strings or binaries from the first and the second MEMs and write
  * to the result MEM. In case the first MEM or the second MEM is NULL, the
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index b7e44a386..6f13d2b03 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1300,14 +1300,9 @@ sqlVdbeList(Vdbe * p)
 					if (mem_copy_bin(pSub, bin, size) != 0)
 						return -1;
 				} else if (j == nSub) {
-					struct Mem tmp;
-					mem_create(&tmp);
-					uint32_t size = sizeof(SubProgram *);
 					char *bin = (char *)&pOp->p4.pProgram;
-					mem_set_bin_ephemeral(&tmp, bin, size);
-					int rc = mem_concat(pSub, &tmp, pSub);
-					mem_destroy(&tmp);
-					if (rc != 0)
+					uint32_t size = sizeof(SubProgram *);
+					if (mem_append(pSub, bin, size) != 0)
 						return -1;
 				}
 			}
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (3 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:47   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM() Mergen Imeev via Tarantool-patches
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

We don't need this function, since it is easier to call finalizers
directly. This patch also allows us to make further simplifications.

Needed for #4145
---
 src/box/sql/mem.c  | 31 +------------------------------
 src/box/sql/mem.h  | 12 ------------
 src/box/sql/vdbe.c | 30 +++++++++++++++++++++++-------
 3 files changed, 24 insertions(+), 49 deletions(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index a9011fc63..fb304d868 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -217,11 +217,7 @@ mem_create(struct Mem *mem)
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
-	    (mem->flags & MEM_Dyn) != 0) {
-		if (mem->type == MEM_TYPE_AGG)
-			sql_vdbemem_finalize(mem, mem->u.func);
-		assert(mem->type != MEM_TYPE_AGG);
+	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0) {
 		if ((mem->flags & MEM_Dyn) != 0) {
 			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
 			mem->xDel((void *)mem->z);
@@ -3065,31 +3061,6 @@ sqlVdbeMemTooBig(Mem * p)
 	return 0;
 }
 
-int
-sql_vdbemem_finalize(struct Mem *mem, struct func *func)
-{
-	assert(func != NULL);
-	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
-	sql_context ctx;
-	memset(&ctx, 0, sizeof(ctx));
-	Mem t;
-	memset(&t, 0, sizeof(t));
-	t.type = MEM_TYPE_NULL;
-	assert(t.flags == 0);
-	t.db = mem->db;
-	ctx.pOut = &t;
-	ctx.pMem = mem;
-	ctx.func = func;
-	((struct func_sql_builtin *)func)->finalize(&ctx);
-	assert((mem->flags & MEM_Dyn) == 0);
-	if (mem->szMalloc > 0)
-		sqlDbFree(mem->db, mem->zMalloc);
-	memcpy(mem, &t, sizeof(t));
-	return ctx.is_aborted ? -1 : 0;
-}
-
 int
 sqlVdbeRecordCompareMsgpack(const void *key1,
 				struct UnpackedRecord *key2)
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 54a1e931b..c0993e573 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -969,18 +969,6 @@ int sqlVdbeMemTooBig(Mem *);
 #define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
 			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
 
-/** MEM manipulate functions. */
-
-/**
- * Memory cell mem contains the context of an aggregate function.
- * This routine calls the finalize method for that function. The
- * result of the aggregate is stored back into mem.
- *
- * Returns -1 if the finalizer reports an error. 0 otherwise.
- */
-int
-sql_vdbemem_finalize(struct Mem *mem, struct func *func);
-
 /**
  * Perform comparison of two tuples: unpacked (key1) and packed (key2)
  *
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 12dc9126b..dfab86c50 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4204,16 +4204,32 @@ case OP_AggStep: {
  * the step function was not previously called.
  */
 case OP_AggFinal: {
-	Mem *pMem;
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
-	pMem = &aMem[pOp->p1];
-	assert(mem_is_null(pMem) || mem_is_agg(pMem));
-	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
+	struct Mem *pIn1 = &aMem[pOp->p1];
+	assert(mem_is_null(pIn1) || mem_is_agg(pIn1));
+
+	struct sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	struct Mem t;
+	memset(&t, 0, sizeof(t));
+	t.type = MEM_TYPE_NULL;
+	assert(t.flags == 0);
+	t.db = pIn1->db;
+	ctx.pOut = &t;
+	ctx.pMem = pIn1;
+	ctx.func = pOp->p4.func;
+	func->finalize(&ctx);
+	assert((pIn1->flags & MEM_Dyn) == 0);
+	if (pIn1->szMalloc > 0)
+		sqlDbFree(pIn1->db, pIn1->zMalloc);
+	memcpy(pIn1, &t, sizeof(t));
+
+	if (ctx.is_aborted)
 		goto abort_due_to_error;
-	UPDATE_MAX_BLOBSIZE(pMem);
-	if (sqlVdbeMemTooBig(pMem)) {
+	UPDATE_MAX_BLOBSIZE(pIn1);
+	if (sqlVdbeMemTooBig(pIn1) != 0)
 		goto too_big;
-	}
 	break;
 }
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (4 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL() Mergen Imeev via Tarantool-patches
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch makes SUM() accept DOUBLE values by default. Also, after this
patch SUM() will be able to work with DECIMAL values.

Part of #4145
Part of #6355
---
 src/box/sql/func.c                       | 39 ++++++++++++++++--------
 src/box/sql/vdbe.c                       |  1 -
 test/sql-tap/built-in-functions.test.lua | 35 +++++++++++++++++++--
 3 files changed, 59 insertions(+), 16 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b4461c6ee..b06955302 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -53,6 +53,29 @@
 static struct mh_strnptr_t *built_in_functions = NULL;
 static struct func_sql_builtin **functions;
 
+/** Implementation of the SUM() function. */
+static void
+step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		return mem_copy_as_ephemeral(ctx->pMem, argv[0]);
+	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the SUM() function. */
+static void
+fin_sum(struct sql_context *ctx)
+{
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1654,17 +1677,6 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 		context->is_aborted = true;
 }
 
-static void
-sumFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0)
-		mem_set_null(context->pOut);
-	else
-		mem_copy_as_ephemeral(context->pOut, &p->mem);
-}
-
 static void
 avgFinalize(sql_context * context)
 {
@@ -2113,8 +2125,9 @@ static struct sql_func_definition definitions[] = {
 	{"SUBSTR", 3,
 	 {FIELD_TYPE_VARBINARY, FIELD_TYPE_INTEGER, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_VARBINARY, substrFunc, NULL},
-	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, sum_step, sumFinalize},
-	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step, sumFinalize},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
 	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, sum_step,
 	 totalFinalize},
 	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index dfab86c50..a74ee29ea 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4207,7 +4207,6 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
 	struct Mem *pIn1 = &aMem[pOp->p1];
-	assert(mem_is_null(pIn1) || mem_is_agg(pIn1));
 
 	struct sql_context ctx;
 	memset(&ctx, 0, sizeof(ctx));
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 6fae811dc..8daf41c1d 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,8 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(52)
+test:plan(55)
+
+local dec = require('decimal')
 
 --
 -- Make sure that number of arguments check is checked properly for SQL built-in
@@ -455,7 +457,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT SUM(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 test:do_catchsql_test(
@@ -545,4 +547,33 @@ test:do_test(
         {name = "COLUMN_2", type = "scalar"},
     })
 
+-- Make sure SUM() accepts and returns DOUBLE by default.
+test:do_test(
+    "builtins-4.1.1",
+    function()
+        return box.execute([[SELECT SUM(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.1.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT SUM(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure SUM() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.1.3",
+    [[
+        SELECT SUM(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        dec.new(124.432)
+    }
+)
+
 test:finish_test()
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (5 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-25 11:20   ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG() Mergen Imeev via Tarantool-patches
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch makes TOTAL() accept DOUBLE values by default. Also, after
this patch TOTAL() will be able to work with DECIMAL values.

Part of #4145
Part of #6355
---
 src/box/sql/func.c                       | 47 ++++++++++++++++--------
 test/sql-tap/built-in-functions.test.lua | 33 ++++++++++++++++-
 2 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b06955302..12a6a5a2c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -76,6 +76,32 @@ fin_sum(struct sql_context *ctx)
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the TOTAL() function. */
+static void
+step_total(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		mem_set_double(ctx->pMem, 0.0);
+	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the TOTAL() function. */
+static void
+fin_total(struct sql_context *ctx)
+{
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_double(ctx->pMem));
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		mem_set_double(ctx->pOut, 0.0);
+	else
+		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1693,17 +1719,6 @@ avgFinalize(sql_context * context)
 		context->is_aborted = true;
 }
 
-static void
-totalFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0)
-		mem_set_double(context->pOut, 0.0);
-	else
-		mem_copy_as_ephemeral(context->pOut, &p->mem);
-}
-
 /*
  * The following structure keeps track of state information for the
  * count() aggregate function.
@@ -2128,10 +2143,12 @@ static struct sql_func_definition definitions[] = {
 	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
 	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
 	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
-	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, sum_step,
-	 totalFinalize},
-	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step,
-	 totalFinalize},
+	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
+	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
+	{"TOTAL", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
 
 	{"TRIM", 2, {FIELD_TYPE_STRING, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_STRING, trim_func, NULL},
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 8daf41c1d..507d06549 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(55)
+test:plan(58)
 
 local dec = require('decimal')
 
@@ -497,7 +497,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 --
@@ -576,4 +576,33 @@ test:do_execsql_test(
     }
 )
 
+-- Make sure TOTAL() accepts DOUBLE by default.
+test:do_test(
+    "builtins-4.2.1",
+    function()
+        return box.execute([[SELECT TOTAL(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.2.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure TOTAL() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.2.3",
+    [[
+        SELECT TOTAL(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        124.432
+    }
+)
+
 test:finish_test()
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (6 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT() Mergen Imeev via Tarantool-patches
                   ` (5 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On 15.09.2021 00:24, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 12a6a5a2c..9e0c09206 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -102,6 +102,44 @@ fin_total(struct sql_context *ctx)
>>  		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
>>  }
>>  
>> +/** Implementation of the AVG() function. */
>> +static void
>> +step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
>> +{
>> +	assert(argc == 1);
>> +	(void)argc;
>> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
>> +	if (argv[0]->type == MEM_TYPE_NULL)
>> +		return;
>> +	if (ctx->pMem->type == MEM_TYPE_NULL) {
>> +		uint32_t size = 2 * sizeof(struct Mem);
>> +		struct Mem *mems = sqlDbMallocRawNN(sql_get(), size);
>
> Previously only size of mem + uint32 was allocated, now it is 2 size
> of mem. Lets stick to the more compact version. For the division in the
> end you can create a mem on the stack, for instance.
Thanks, fixed.

Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9e0c09206..b5f154fb1 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -111,20 +111,26 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
 	if (argv[0]->type == MEM_TYPE_NULL)
 		return;
+	struct Mem *mem;
+	uint32_t *count;
 	if (ctx->pMem->type == MEM_TYPE_NULL) {
-		uint32_t size = 2 * sizeof(struct Mem);
-		struct Mem *mems = sqlDbMallocRawNN(sql_get(), size);
-		mem_create(&mems[0]);
-		mem_create(&mems[1]);
-		mem_copy_as_ephemeral(&mems[0], argv[0]);
-		mem_set_uint(&mems[1], 1);
-		mem_set_bin_allocated(ctx->pMem, (char *)mems, size);
+		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
+		mem = sqlDbMallocRawNN(sql_get(), size);
+		if (mem == NULL) {
+			ctx->is_aborted = true;
+			return;
+		}
+		count = (uint32_t *)(mem + 1);
+		mem_create(mem);
+		*count = 1;
+		mem_copy_as_ephemeral(mem, argv[0]);
+		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
 		return;
 	}
-	struct Mem *mems = (struct Mem *)ctx->pMem->z;
-	assert(mems[1].type = MEM_TYPE_UINT);
-	++mems[1].u.u;
-	if (mem_add(&mems[0], argv[0], &mems[0]) != 0)
+	mem = (struct Mem *)ctx->pMem->z;
+	count = (uint32_t *)(mem + 1);
+	++*count;
+	if (mem_add(mem, argv[0], mem) != 0)
 		ctx->is_aborted = true;
 }
 
@@ -135,8 +141,12 @@ fin_avg(struct sql_context *ctx)
 	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
 	if (ctx->pMem->type == MEM_TYPE_NULL)
 		return mem_set_null(ctx->pOut);
-	struct Mem *mems = (struct Mem *)ctx->pMem->z;
-	if (mem_div(&mems[0], &mems[1], ctx->pOut) != 0)
+	struct Mem *mem = (struct Mem *)ctx->pMem->z;
+	uint32_t *count = (uint32_t *)(mem + 1);
+	struct Mem mem_count;
+	mem_create(&mem_count);
+	mem_set_uint(&mem_count, *count);
+	if (mem_div(mem, &mem_count, ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 

New patch:

commit 48ee23c47c696c5e002347841b652677ca877507
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:19:53 2021 +0300

    sql: rework AVG()
    
    This patch makes AVG() accept DOUBLE values by default. Also, after this
    patch AVG() will be able to work with DECIMAL values.
    
    Part of #4145
    Part of #6355

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 12a6a5a2c..b5f154fb1 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -102,6 +102,54 @@ fin_total(struct sql_context *ctx)
 		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the AVG() function. */
+static void
+step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	struct Mem *mem;
+	uint32_t *count;
+	if (ctx->pMem->type == MEM_TYPE_NULL) {
+		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
+		mem = sqlDbMallocRawNN(sql_get(), size);
+		if (mem == NULL) {
+			ctx->is_aborted = true;
+			return;
+		}
+		count = (uint32_t *)(mem + 1);
+		mem_create(mem);
+		*count = 1;
+		mem_copy_as_ephemeral(mem, argv[0]);
+		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
+		return;
+	}
+	mem = (struct Mem *)ctx->pMem->z;
+	count = (uint32_t *)(mem + 1);
+	++*count;
+	if (mem_add(mem, argv[0], mem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the AVG() function. */
+static void
+fin_avg(struct sql_context *ctx)
+{
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		return mem_set_null(ctx->pOut);
+	struct Mem *mem = (struct Mem *)ctx->pMem->z;
+	uint32_t *count = (uint32_t *)(mem + 1);
+	struct Mem mem_count;
+	mem_create(&mem_count);
+	mem_set_uint(&mem_count, *count);
+	if (mem_div(mem, &mem_count, ctx->pOut) != 0)
+		ctx->is_aborted = true;
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1656,69 +1704,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * An instance of the following structure holds the context of a
- * sum() or avg() aggregate computation.
- */
-typedef struct SumCtx SumCtx;
-struct SumCtx {
-	struct Mem mem;
-	uint32_t count;
-};
-
-/*
- * Routines used to compute the sum, average, and total.
- *
- * The SUM() function follows the (broken) SQL standard which means
- * that it returns NULL if it sums over no inputs.  TOTAL returns
- * 0.0 in that case.  In addition, TOTAL always returns a float where
- * SUM might return an integer if it never encounters a floating point
- * 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)
-{
-	assert(argc == 1);
-	UNUSED_PARAMETER(argc);
-	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
-	if (p == NULL) {
-		context->is_aborted = true;
-		return;
-	}
-	if (p->count == 0) {
-		mem_create(&p->mem);
-		assert(context->func->def->returns == FIELD_TYPE_INTEGER ||
-		       context->func->def->returns == FIELD_TYPE_DOUBLE);
-		if (context->func->def->returns == FIELD_TYPE_INTEGER)
-			mem_set_uint(&p->mem, 0);
-		else
-			mem_set_double(&p->mem, 0.0);
-	}
-	if (argv[0]->type == MEM_TYPE_NULL)
-		return;
-	++p->count;
-	assert(mem_is_num(argv[0]));
-	if (mem_add(&p->mem, argv[0], &p->mem) != 0)
-		context->is_aborted = true;
-}
-
-static void
-avgFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0) {
-		mem_set_null(context->pOut);
-		return;
-	}
-	struct Mem mem;
-	mem_create(&mem);
-	mem_set_uint(&mem, p->count);
-	if (mem_div(&p->mem, &mem, context->pOut) != 0)
-		context->is_aborted = true;
-}
-
 /*
  * The following structure keeps track of state information for the
  * count() aggregate function.
@@ -2015,8 +2000,9 @@ struct sql_func_definition {
 static struct sql_func_definition definitions[] = {
 	{"ABS", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, absFunc, NULL},
 	{"ABS", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, absFunc, NULL},
-	{"AVG", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, sum_step, avgFinalize},
-	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step, avgFinalize},
+	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_avg, fin_avg},
 	{"CHAR", -1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_STRING, charFunc, NULL},
 	{"CHAR_LENGTH", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_INTEGER, lengthFunc,
 	 NULL},
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 507d06549..08a63b86d 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(58)
+test:plan(61)
 
 local dec = require('decimal')
 
@@ -477,7 +477,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT AVG(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 test:do_catchsql_test(
@@ -605,4 +605,33 @@ test:do_execsql_test(
     }
 )
 
+-- Make sure AVG() accepts and returns DOUBLE by default.
+test:do_test(
+    "builtins-4.1.1",
+    function()
+        return box.execute([[SELECT AVG(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.1.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT AVG(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure AVG() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.1.3",
+    [[
+        SELECT AVG(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        dec.new(62.216)
+    }
+)
+
 test:finish_test()

^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (7 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-25 11:34   ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX() Mergen Imeev via Tarantool-patches
                   ` (4 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch does some refactoring for the SQL built-in aggregate function
COUNT().

Part of #4145
---
 src/box/sql/func.c | 64 +++++++++++++++++++---------------------------
 1 file changed, 26 insertions(+), 38 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b5f154fb1..113df423a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -150,6 +150,29 @@ fin_avg(struct sql_context *ctx)
 		ctx->is_aborted = true;
 }
 
+/** Implementation of the COUNT() function. */
+static void
+step_count(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 0 || argc == 1);
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		mem_set_uint(ctx->pMem, 0);
+	if (argc == 1 && argv[0]->type == MEM_TYPE_NULL)
+		return;
+	assert(ctx->pMem->type == MEM_TYPE_UINT);
+	++ctx->pMem->u.u;
+}
+
+/** Finalizer for the COUNT() function. */
+static void
+fin_count(struct sql_context *ctx)
+{
+	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_uint(ctx->pMem));
+	if (ctx->pMem->type == MEM_TYPE_NULL)
+		return mem_set_uint(ctx->pOut, 0);
+	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1704,41 +1727,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * The following structure keeps track of state information for the
- * count() aggregate function.
- */
-typedef struct CountCtx CountCtx;
-struct CountCtx {
-	i64 n;
-};
-
-/*
- * Routines to implement the count() aggregate function.
- */
-static void
-countStep(sql_context * context, int argc, sql_value ** argv)
-{
-	CountCtx *p;
-	if (argc != 0 && argc != 1) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "COUNT", "0 or 1", argc);
-		context->is_aborted = true;
-		return;
-	}
-	p = sql_aggregate_context(context, sizeof(*p));
-	if ((argc == 0 || !mem_is_null(argv[0])) && p != NULL)
-		p->n++;
-}
-
-static void
-countFinalize(sql_context * context)
-{
-	CountCtx *p;
-	p = sql_aggregate_context(context, 0);
-	sql_result_uint(context, p ? p->n : 0);
-}
-
 /*
  * Routines to implement min() and max() aggregate functions.
  */
@@ -2008,9 +1996,9 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 	{"COALESCE", -1, {FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR, sql_builtin_stub,
 	 NULL},
-	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, countStep, countFinalize},
-	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, countStep,
-	 countFinalize},
+	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, step_count, fin_count},
+	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, step_count,
+	 fin_count},
 
 	{"GREATEST", -1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxFunc,
 	 NULL},
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (8 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-25 11:36   ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT() Mergen Imeev via Tarantool-patches
                   ` (3 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch does some refactoring for the SQL built-in aggregate
functions MIN() and MAX().

Part of #4145
---
 src/box/sql/func.c | 162 +++++++++++++++++++--------------------------
 src/box/sql/vdbe.c |   1 -
 2 files changed, 69 insertions(+), 94 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 113df423a..f699aa927 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -173,6 +173,46 @@ fin_count(struct sql_context *ctx)
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the MIN() and MAX() functions. */
+static void
+step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	if (argv[0]->type == MEM_TYPE_NULL) {
+		if (ctx->pMem->type != MEM_TYPE_NULL)
+			ctx->skipFlag = 1;
+		return;
+	}
+	if (ctx->pMem->type == MEM_TYPE_NULL) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+
+	uint32_t flags = ((struct func_sql_builtin *)ctx->func)->flags;
+	bool is_max = (flags & SQL_FUNC_MAX) != 0;
+	/*
+	 * 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.
+	 */
+	int cmp = mem_cmp_scalar(ctx->pMem, argv[0], ctx->coll);
+	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+	ctx->skipFlag = 1;
+}
+
+/** Finalizer for the MIN() and MAX() functions. */
+static void
+fin_minmax(struct sql_context *ctx)
+{
+	mem_copy(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -220,16 +260,6 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
 	mem_set_uuid(ctx->pOut, &uuid);
 }
 
-/*
- * Indicate that the accumulator load should be skipped on this
- * iteration of the aggregate loop.
- */
-static void
-sqlSkipAccumulatorLoad(sql_context * context)
-{
-	context->skipFlag = 1;
-}
-
 /*
  * Implementation of the non-aggregate min() and max() functions
  */
@@ -1727,60 +1757,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * Routines to implement min() and max() aggregate functions.
- */
-static void
-minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
-{
-	Mem *pArg = (Mem *) argv[0];
-	Mem *pBest;
-	UNUSED_PARAMETER(NotUsed);
-
-	struct func_sql_builtin *func =
-		(struct func_sql_builtin *)context->func;
-	pBest = sql_context_agg_mem(context);
-	if (!pBest)
-		return;
-
-	if (mem_is_null(argv[0])) {
-		if (!mem_is_null(pBest))
-			sqlSkipAccumulatorLoad(context);
-	} else if (!mem_is_null(pBest)) {
-		struct coll *pColl = context->coll;
-		/*
-		 * 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.
-		 */
-		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
-		int cmp = mem_cmp_scalar(pBest, pArg, pColl);
-		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-			if (mem_copy(pBest, pArg) != 0)
-				context->is_aborted = true;
-		} else {
-			sqlSkipAccumulatorLoad(context);
-		}
-	} else {
-		pBest->db = sql_context_db_handle(context);
-		if (mem_copy(pBest, pArg) != 0)
-			context->is_aborted = true;
-	}
-}
-
-static void
-minMaxFinalize(sql_context * context)
-{
-	struct Mem *mem = context->pMem;
-	struct Mem *res;
-	if (mem_get_agg(mem, (void **)&res) != 0)
-		return;
-	if (!mem_is_null(res))
-		sql_result_value(context, res);
-	mem_destroy(res);
-}
-
 /*
  * group_concat(EXPR, ?SEPARATOR?)
  */
@@ -2052,35 +2028,35 @@ static struct sql_func_definition definitions[] = {
 	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
 	 NULL},
 
-	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
-	 minMaxFinalize},
-
-	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
-	 minMaxFinalize},
+	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
+	 fin_minmax},
+
+	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
+	 fin_minmax},
 
 	{"NULLIF", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
 	 nullifFunc, NULL},
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index a74ee29ea..8804e3d18 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4169,7 +4169,6 @@ case OP_AggStep: {
 	}
 #endif
 
-	pMem->n++;
 	mem_create(&t);
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT()
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (9 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
                   ` (2 subsequent siblings)
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch simplifies SQL built-in aggregate function GROUP_CONCAT().

Part of #4145
---
 src/box/sql/func.c | 121 +++++++++++++++++++--------------------------
 1 file changed, 50 insertions(+), 71 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index f699aa927..001a8641c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -213,6 +213,52 @@ fin_minmax(struct sql_context *ctx)
 	mem_copy(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the GROUP_CONCAT() function. */
+static void
+step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1 || argc == 2);
+	(void)argc;
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
+	if (ctx->pMem->type == MEM_TYPE_NULL) {
+		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+	const char *sep = NULL;
+	int sep_len = 0;
+	if (argc == 1) {
+		sep = ",";
+		sep_len = 1;
+	} else if (argv[1]->type == MEM_TYPE_NULL) {
+		sep = "";
+		sep_len = 0;
+	} else {
+		assert(mem_is_same_type(argv[0], argv[0]));
+		sep = argv[1]->z;
+		sep_len = argv[1]->n;
+	}
+	if (sep_len > 0) {
+		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
+			ctx->is_aborted = true;
+			return;
+		}
+	}
+	if (mem_append(ctx->pMem, argv[0]->z, argv[0]->n) != 0) {
+		ctx->is_aborted = true;
+		return;
+	}
+}
+
+/** Finalizer for the GROUP_CONCAT() function. */
+static void
+fin_group_concat(struct sql_context *ctx)
+{
+	mem_copy(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1757,73 +1803,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * group_concat(EXPR, ?SEPARATOR?)
- */
-static void
-groupConcatStep(sql_context * context, int argc, sql_value ** argv)
-{
-	const char *zVal;
-	StrAccum *pAccum;
-	const char *zSep;
-	int nVal, nSep;
-	if (argc != 1 && argc != 2) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "GROUP_CONCAT", "1 or 2", argc);
-		context->is_aborted = true;
-		return;
-	}
-	if (mem_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 = mem_as_str0(argv[1]);
-				nSep = mem_len_unsafe(argv[1]);
-			} else {
-				zSep = ",";
-				nSep = 1;
-			}
-			if (zSep)
-				sqlStrAccumAppend(pAccum, zSep, nSep);
-		}
-		zVal = mem_as_str0(argv[0]);
-		nVal = mem_len_unsafe(argv[0]);
-		if (zVal)
-			sqlStrAccumAppend(pAccum, zVal, nVal);
-	}
-}
-
-static void
-groupConcatFinalize(sql_context * context)
-{
-	StrAccum *pAccum;
-	pAccum = sql_aggregate_context(context, 0);
-	if (pAccum) {
-		if (pAccum->accError == STRACCUM_TOOBIG) {
-			diag_set(ClientError, ER_SQL_EXECUTE, "string or binary"\
-				 "string is too big");
-			context->is_aborted = true;
-		} else if (pAccum->accError == STRACCUM_NOMEM) {
-			context->is_aborted = true;
-		} else {
-			char *str = sqlStrAccumFinish(pAccum);
-			int len = pAccum->nChar;
-			assert(len >= 0);
-			if (context->func->def->returns == FIELD_TYPE_STRING)
-				mem_set_str_dynamic(context->pOut, str, len);
-			else
-				mem_set_bin_dynamic(context->pOut, str, len);
-		}
-	}
-}
-
 int
 sql_is_like_func(struct Expr *expr)
 {
@@ -1991,13 +1970,13 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING,
-	 groupConcatStep, groupConcatFinalize},
+	 step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_STRING, FIELD_TYPE_STRING},
-	 FIELD_TYPE_STRING, groupConcatStep, groupConcatFinalize},
+	 FIELD_TYPE_STRING, step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY,
-	 groupConcatStep, groupConcatFinalize},
+	 step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_VARBINARY, FIELD_TYPE_VARBINARY},
-	 FIELD_TYPE_VARBINARY, groupConcatStep, groupConcatFinalize},
+	 FIELD_TYPE_VARBINARY, step_group_concat, fin_group_concat},
 
 	{"HEX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_STRING, hexFunc, NULL},
 	{"IFNULL", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (10 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT() Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:50   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 14/15] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Thank you for the review. I replaced struct sql_context by struct MEM as the
only argument of finalizer functions.

Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index c524cd949..cdfb020cb 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -84,12 +84,13 @@ step_total(struct sql_context *ctx, int argc, struct Mem **argv)
 }
 
 /** Finalizer for the TOTAL() function. */
-static void
-fin_total(struct sql_context *ctx)
+static int
+fin_total(struct Mem *mem)
 {
-	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_double(ctx->pOut));
-	if (ctx->pOut->type == MEM_TYPE_NULL)
-		mem_set_double(ctx->pOut, 0.0);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_double(mem));
+	if (mem->type == MEM_TYPE_NULL)
+		mem_set_double(mem, 0.0);
+	return 0;
 }
 
 /** Implementation of the AVG() function. */
@@ -125,19 +126,20 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 }
 
 /** Finalizer for the AVG() function. */
-static void
-fin_avg(struct sql_context *ctx)
+static int
+fin_avg(struct Mem *mem)
 {
-	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_bin(ctx->pOut));
-	if (ctx->pOut->type == MEM_TYPE_NULL)
-		return mem_set_null(ctx->pOut);
-	struct Mem *mem = (struct Mem *)ctx->pOut->z;
-	uint32_t *count = (uint32_t *)(mem + 1);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_bin(mem));
+	if (mem->type == MEM_TYPE_NULL) {
+		mem_set_null(mem);
+		return 0;
+	}
+	struct Mem *sum = (struct Mem *)mem->z;
+	uint32_t *count = (uint32_t *)(sum + 1);
 	struct Mem mem_count;
 	mem_create(&mem_count);
 	mem_set_uint(&mem_count, *count);
-	if (mem_div(mem, &mem_count, ctx->pOut) != 0)
-		ctx->is_aborted = true;
+	return mem_div(sum, &mem_count, mem);
 }
 
 /** Implementation of the COUNT() function. */
@@ -154,12 +156,13 @@ step_count(struct sql_context *ctx, int argc, struct Mem **argv)
 }
 
 /** Finalizer for the COUNT() function. */
-static void
-fin_count(struct sql_context *ctx)
+static int
+fin_count(struct Mem *mem)
 {
-	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_uint(ctx->pOut));
-	if (ctx->pOut->type == MEM_TYPE_NULL)
-		return mem_set_uint(ctx->pOut, 0);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_uint(mem));
+	if (mem->type == MEM_TYPE_NULL)
+		mem_set_uint(mem, 0);
+	return 0;
 }
 
 /** Implementation of the MIN() and MAX() functions. */
@@ -1908,7 +1911,7 @@ struct sql_func_definition {
 	/** Call implementation with given arguments. */
 	void (*call)(sql_context *ctx, int argc, sql_value **argv);
 	/** Call finalization function for this implementation. */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8782c072a..97e71e82c 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4371,7 +4371,7 @@ struct func_sql_builtin {
 	 * A VDBE-memory-compatible finalize method
 	 * (is valid only for aggregate function).
 	 */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 1d22c4a40..65bb60d2f 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4201,11 +4201,7 @@ case OP_AggFinal: {
 	struct Mem *pIn1 = &aMem[pOp->p1];
 
 	if (func->finalize != NULL) {
-		struct sql_context ctx;
-		memset(&ctx, 0, sizeof(ctx));
-		ctx.pOut = pIn1;
-		func->finalize(&ctx);
-		if (ctx.is_aborted)
+		if (func->finalize(pIn1) != 0)
 			goto abort_due_to_error;
 	}
 	UPDATE_MAX_BLOBSIZE(pIn1);


New patch:


commit a9ea2350922b160beabb993d12d6e426f81db7b6
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:50:33 2021 +0300

    sql: remove copying of result in finalizers
    
    This patch removes copying of the result in the finalizers of the SQL
    built-in aggregate functions.
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 001a8641c..cdfb020cb 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -59,47 +59,38 @@ step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_num(ctx->pOut));
 	if (argv[0]->type == MEM_TYPE_NULL)
 		return;
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		return mem_copy_as_ephemeral(ctx->pMem, argv[0]);
-	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+	if (ctx->pOut->type == MEM_TYPE_NULL)
+		return mem_copy_as_ephemeral(ctx->pOut, argv[0]);
+	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
-/** Finalizer for the SUM() function. */
-static void
-fin_sum(struct sql_context *ctx)
-{
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
-	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
-}
-
 /** Implementation of the TOTAL() function. */
 static void
 step_total(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_num(ctx->pOut));
 	if (argv[0]->type == MEM_TYPE_NULL)
 		return;
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		mem_set_double(ctx->pMem, 0.0);
-	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+	if (ctx->pOut->type == MEM_TYPE_NULL)
+		mem_set_double(ctx->pOut, 0.0);
+	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
 /** Finalizer for the TOTAL() function. */
-static void
-fin_total(struct sql_context *ctx)
+static int
+fin_total(struct Mem *mem)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_double(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		mem_set_double(ctx->pOut, 0.0);
-	else
-		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_double(mem));
+	if (mem->type == MEM_TYPE_NULL)
+		mem_set_double(mem, 0.0);
+	return 0;
 }
 
 /** Implementation of the AVG() function. */
@@ -108,12 +99,12 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
+	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_bin(ctx->pOut));
 	if (argv[0]->type == MEM_TYPE_NULL)
 		return;
 	struct Mem *mem;
 	uint32_t *count;
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
+	if (ctx->pOut->type == MEM_TYPE_NULL) {
 		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
 		mem = sqlDbMallocRawNN(sql_get(), size);
 		if (mem == NULL) {
@@ -124,10 +115,10 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 		mem_create(mem);
 		*count = 1;
 		mem_copy_as_ephemeral(mem, argv[0]);
-		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
+		mem_set_bin_allocated(ctx->pOut, (char *)mem, size);
 		return;
 	}
-	mem = (struct Mem *)ctx->pMem->z;
+	mem = (struct Mem *)ctx->pOut->z;
 	count = (uint32_t *)(mem + 1);
 	++*count;
 	if (mem_add(mem, argv[0], mem) != 0)
@@ -135,19 +126,20 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 }
 
 /** Finalizer for the AVG() function. */
-static void
-fin_avg(struct sql_context *ctx)
+static int
+fin_avg(struct Mem *mem)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		return mem_set_null(ctx->pOut);
-	struct Mem *mem = (struct Mem *)ctx->pMem->z;
-	uint32_t *count = (uint32_t *)(mem + 1);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_bin(mem));
+	if (mem->type == MEM_TYPE_NULL) {
+		mem_set_null(mem);
+		return 0;
+	}
+	struct Mem *sum = (struct Mem *)mem->z;
+	uint32_t *count = (uint32_t *)(sum + 1);
 	struct Mem mem_count;
 	mem_create(&mem_count);
 	mem_set_uint(&mem_count, *count);
-	if (mem_div(mem, &mem_count, ctx->pOut) != 0)
-		ctx->is_aborted = true;
+	return mem_div(sum, &mem_count, mem);
 }
 
 /** Implementation of the COUNT() function. */
@@ -155,22 +147,22 @@ static void
 step_count(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 0 || argc == 1);
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		mem_set_uint(ctx->pMem, 0);
+	if (ctx->pOut->type == MEM_TYPE_NULL)
+		mem_set_uint(ctx->pOut, 0);
 	if (argc == 1 && argv[0]->type == MEM_TYPE_NULL)
 		return;
-	assert(ctx->pMem->type == MEM_TYPE_UINT);
-	++ctx->pMem->u.u;
+	assert(ctx->pOut->type == MEM_TYPE_UINT);
+	++ctx->pOut->u.u;
 }
 
 /** Finalizer for the COUNT() function. */
-static void
-fin_count(struct sql_context *ctx)
+static int
+fin_count(struct Mem *mem)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_uint(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
-		return mem_set_uint(ctx->pOut, 0);
-	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+	assert(mem->type == MEM_TYPE_NULL || mem_is_uint(mem));
+	if (mem->type == MEM_TYPE_NULL)
+		mem_set_uint(mem, 0);
+	return 0;
 }
 
 /** Implementation of the MIN() and MAX() functions. */
@@ -180,12 +172,12 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	assert(argc == 1);
 	(void)argc;
 	if (argv[0]->type == MEM_TYPE_NULL) {
-		if (ctx->pMem->type != MEM_TYPE_NULL)
+		if (ctx->pOut->type != MEM_TYPE_NULL)
 			ctx->skipFlag = 1;
 		return;
 	}
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
-		if (mem_copy(ctx->pMem, argv[0]) != 0)
+	if (ctx->pOut->type == MEM_TYPE_NULL) {
+		if (mem_copy(ctx->pOut, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -197,22 +189,15 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	 * the only difference between the two being that the sense of the
 	 * comparison is inverted.
 	 */
-	int cmp = mem_cmp_scalar(ctx->pMem, argv[0], ctx->coll);
+	int cmp = mem_cmp_scalar(ctx->pOut, argv[0], ctx->coll);
 	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-		if (mem_copy(ctx->pMem, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
 	ctx->skipFlag = 1;
 }
 
-/** Finalizer for the MIN() and MAX() functions. */
-static void
-fin_minmax(struct sql_context *ctx)
-{
-	mem_copy(ctx->pOut, ctx->pMem);
-}
-
 /** Implementation of the GROUP_CONCAT() function. */
 static void
 step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
@@ -222,8 +207,8 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	if (argv[0]->type == MEM_TYPE_NULL)
 		return;
 	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
-		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
+	if (ctx->pOut->type == MEM_TYPE_NULL) {
+		if (mem_copy_str(ctx->pOut, argv[0]->z, argv[0]->n) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -241,24 +226,17 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 		sep_len = argv[1]->n;
 	}
 	if (sep_len > 0) {
-		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
+		if (mem_append(ctx->pOut, sep, sep_len) != 0) {
 			ctx->is_aborted = true;
 			return;
 		}
 	}
-	if (mem_append(ctx->pMem, argv[0]->z, argv[0]->n) != 0) {
+	if (mem_append(ctx->pOut, argv[0]->z, argv[0]->n) != 0) {
 		ctx->is_aborted = true;
 		return;
 	}
 }
 
-/** Finalizer for the GROUP_CONCAT() function. */
-static void
-fin_group_concat(struct sql_context *ctx)
-{
-	mem_copy(ctx->pOut, ctx->pMem);
-}
-
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1933,7 +1911,7 @@ struct sql_func_definition {
 	/** Call implementation with given arguments. */
 	void (*call)(sql_context *ctx, int argc, sql_value **argv);
 	/** Call finalization function for this implementation. */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
@@ -1970,13 +1948,13 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING,
-	 step_group_concat, fin_group_concat},
+	 step_group_concat, NULL},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_STRING, FIELD_TYPE_STRING},
-	 FIELD_TYPE_STRING, step_group_concat, fin_group_concat},
+	 FIELD_TYPE_STRING, step_group_concat, NULL},
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY,
-	 step_group_concat, fin_group_concat},
+	 step_group_concat, NULL},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_VARBINARY, FIELD_TYPE_VARBINARY},
-	 FIELD_TYPE_VARBINARY, step_group_concat, fin_group_concat},
+	 FIELD_TYPE_VARBINARY, step_group_concat, NULL},
 
 	{"HEX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_STRING, hexFunc, NULL},
 	{"IFNULL", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
@@ -2007,35 +1985,23 @@ static struct sql_func_definition definitions[] = {
 	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
 	 NULL},
 
-	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
-	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax, NULL},
 	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
-	 fin_minmax},
-
-	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
-	 fin_minmax},
+	 NULL},
+	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax, NULL},
+
+	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax, NULL},
 	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
-	 fin_minmax},
+	 NULL},
+	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax, NULL},
 
 	{"NULLIF", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
 	 nullifFunc, NULL},
@@ -2069,9 +2035,9 @@ static struct sql_func_definition definitions[] = {
 	{"SUBSTR", 3,
 	 {FIELD_TYPE_VARBINARY, FIELD_TYPE_INTEGER, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_VARBINARY, substrFunc, NULL},
-	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
-	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
-	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, NULL},
+	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, NULL},
+	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, NULL},
 	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
 	 fin_total},
 	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
@@ -2330,7 +2296,7 @@ sql_built_in_functions_cache_init(void)
 		assert(desc->argc != -1 || dict->argc_min != dict->argc_max);
 		def->param_count = desc->argc;
 		def->returns = desc->result;
-		def->aggregate = desc->finalize == NULL ?
+		def->aggregate = (dict->flags & SQL_FUNC_AGG) == 0 ?
 				 FUNC_AGGREGATE_NONE : FUNC_AGGREGATE_GROUP;
 		def->language = FUNC_LANGUAGE_SQL_BUILTIN;
 		def->name_len = len;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 710bd86cf..97e71e82c 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -484,18 +484,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_aggregate_context(sql_context *,
-			  int nBytes);
-
-/**
- * Allocate or return the aggregate context containing struct MEM for a user
- * function. A new context is allocated on the first call. Subsequent calls
- * return the same context that was returned on prior calls.
- */
-struct Mem *
-sql_context_agg_mem(struct sql_context *context);
-
 int
 sql_column_count(sql_stmt * pStmt);
 
@@ -4383,7 +4371,7 @@ struct func_sql_builtin {
 	 * A VDBE-memory-compatible finalize method
 	 * (is valid only for aggregate function).
 	 */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 8804e3d18..65bb60d2f 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4145,7 +4145,6 @@ case OP_AggStep: {
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
-	Mem t;
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
@@ -4156,8 +4155,8 @@ case OP_AggStep: {
 	 * 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;
+	if (pCtx->pOut != pMem) {
+		pCtx->pOut = pMem;
 		for(i = 0; i < argc; ++i)
 			pCtx->argv[i] = &aMem[pOp->p2 + i];
 	}
@@ -4169,18 +4168,12 @@ case OP_AggStep: {
 	}
 #endif
 
-	mem_create(&t);
-	pCtx->pOut = &t;
-	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
 	func->call(pCtx, argc, pCtx->argv);
-	if (pCtx->is_aborted) {
-		mem_destroy(&t);
+	if (pCtx->is_aborted)
 		goto abort_due_to_error;
-	}
-	assert(mem_is_null(&t));
 	if (pCtx->skipFlag) {
 		assert(pOp[-1].opcode == OP_SkipLoad);
 		i = pOp[-1].p1;
@@ -4207,24 +4200,10 @@ case OP_AggFinal: {
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
 	struct Mem *pIn1 = &aMem[pOp->p1];
 
-	struct sql_context ctx;
-	memset(&ctx, 0, sizeof(ctx));
-	struct Mem t;
-	memset(&t, 0, sizeof(t));
-	t.type = MEM_TYPE_NULL;
-	assert(t.flags == 0);
-	t.db = pIn1->db;
-	ctx.pOut = &t;
-	ctx.pMem = pIn1;
-	ctx.func = pOp->p4.func;
-	func->finalize(&ctx);
-	assert((pIn1->flags & MEM_Dyn) == 0);
-	if (pIn1->szMalloc > 0)
-		sqlDbFree(pIn1->db, pIn1->zMalloc);
-	memcpy(pIn1, &t, sizeof(t));
-
-	if (ctx.is_aborted)
-		goto abort_due_to_error;
+	if (func->finalize != NULL) {
+		if (func->finalize(pIn1) != 0)
+			goto abort_due_to_error;
+	}
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (sqlVdbeMemTooBig(pIn1) != 0)
 		goto too_big;
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index b9a18f1a1..5e2692d06 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -172,7 +172,6 @@ struct sql_context {
 	Mem *pOut;		/* The return value is stored here */
 	/* A pointer to function implementation. */
 	struct func *func;
-	Mem *pMem;		/* Memory cell used to store aggregate context */
 	struct coll *coll;
 	/*
 	 * True, if an error occurred during the execution of the
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e598513c..39f560c32 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -349,45 +349,6 @@ sql_context_db_handle(sql_context * p)
 	return p->pOut->db;
 }
 
-/*
- * Allocate or return the aggregate context for a user function.  A new
- * context is allocated on the first call.  Subsequent calls return the
- * same context that was returned on prior calls.
- */
-void *
-sql_aggregate_context(sql_context * p, int nByte)
-{
-	assert(p != NULL && p->func != NULL);
-	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	if (!mem_is_agg(p->pMem) && mem_set_agg(p->pMem, p->func, nByte) != 0)
-		return NULL;
-	void *accum;
-	if (mem_get_agg(p->pMem, &accum) != 0)
-		return NULL;
-	return accum;
-}
-
-struct Mem *
-sql_context_agg_mem(struct sql_context *ctx)
-{
-	assert(ctx != NULL && ctx->func != NULL);
-	assert(ctx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(ctx->func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	struct Mem *mem;
-	if (!mem_is_agg(ctx->pMem)) {
-		if (mem_set_agg(ctx->pMem, ctx->func, sizeof(*mem)) != 0)
-			return NULL;
-		if (mem_get_agg(ctx->pMem, (void **)&mem) != 0)
-			return NULL;
-		mem_create(mem);
-		return mem;
-	}
-	if (mem_get_agg(ctx->pMem, (void **)&mem) != 0)
-		return NULL;
-	return mem;
-}
-
 /*
  * Return the number of columns in the result set for the statement pStmt.
  */

^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 14/15] sql: remove MEM_TYPE_AGG
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (11 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
  13 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removed MEM_TYPE_AGG, which is no longer used due to recent
changes.

Part of #4145
---
 src/box/sql/mem.c | 25 -------------------------
 src/box/sql/mem.h |  9 +--------
 2 files changed, 1 insertion(+), 33 deletions(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index fb304d868..51c580d1b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -605,22 +605,6 @@ mem_set_frame(struct Mem *mem, struct VdbeFrame *frame)
 	mem->u.pFrame = frame;
 }
 
-int
-mem_set_agg(struct Mem *mem, struct func *func, int size)
-{
-	mem_clear(mem);
-	if (size <= 0)
-		return 0;
-	if (sqlVdbeMemGrow(mem, size, 0) != 0)
-		return -1;
-	memset(mem->z, 0, size);
-	mem->n = size;
-	mem->type = MEM_TYPE_AGG;
-	assert(mem->flags == 0);
-	mem->u.func = func;
-	return 0;
-}
-
 void
 mem_set_null_clear(struct Mem *mem)
 {
@@ -1884,15 +1868,6 @@ mem_len(const struct Mem *mem, uint32_t *len)
 	return 0;
 }
 
-int
-mem_get_agg(const struct Mem *mem, void **accum)
-{
-	if (mem->type != MEM_TYPE_AGG)
-		return -1;
-	*accum = mem->z;
-	return 0;
-}
-
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index c0993e573..9395f3224 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -54,7 +54,6 @@ enum mem_type {
 	MEM_TYPE_INVALID	= 1 << 11,
 	MEM_TYPE_FRAME		= 1 << 12,
 	MEM_TYPE_PTR		= 1 << 13,
-	MEM_TYPE_AGG		= 1 << 14,
 };
 
 /*
@@ -187,12 +186,6 @@ mem_is_array(const struct Mem *mem)
 	return mem->type == MEM_TYPE_ARRAY;
 }
 
-static inline bool
-mem_is_agg(const struct Mem *mem)
-{
-	return mem->type == MEM_TYPE_AGG;
-}
-
 static inline bool
 mem_is_bytes(const struct Mem *mem)
 {
@@ -967,7 +960,7 @@ int sqlVdbeMemTooBig(Mem *);
  * that needs to be deallocated to avoid a leak.
  */
 #define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
-			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
+			   ((X)->type & MEM_TYPE_FRAME) != 0)
 
 /**
  * Perform comparison of two tuples: unpacked (key1) and packed (key2)
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context
       [not found] <cover.1632220375.git.imeevma@gmail.com>
                   ` (12 preceding siblings ...)
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 14/15] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
@ 2021-09-21 10:59 ` Mergen Imeev via Tarantool-patches
  2021-09-22 22:51   ` Vladislav Shpilevoy via Tarantool-patches
  13 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-21 10:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Since the function arguments are always sequential in the array of all
VDBE MEMs, we don't need to store the position of each argument. The
position of the first MEM and the number of arguments are sufficient to
describe all the arguments.

Part of #4145
---
 src/box/sql/expr.c    |   2 +-
 src/box/sql/func.c    | 317 ++++++++++++++++++++++--------------------
 src/box/sql/main.c    |   5 +-
 src/box/sql/select.c  |   9 +-
 src/box/sql/sqlInt.h  |   7 +-
 src/box/sql/vdbe.c    |  40 ++----
 src/box/sql/vdbeInt.h |   1 -
 7 files changed, 184 insertions(+), 197 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index ab7d95f7e..db8355f33 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4104,7 +4104,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			}
 			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
 				struct sql_context *ctx =
-					sql_context_new(func, nFarg, coll);
+					sql_context_new(func, coll);
 				if (ctx == NULL) {
 					pParse->is_aborted = true;
 					return -1;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index cdfb020cb..6cbbbac74 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -55,31 +55,31 @@ static struct func_sql_builtin **functions;
 
 /** Implementation of the SUM() function. */
 static void
-step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
+step_sum(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_num(ctx->pOut));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	if (argv[0].type == MEM_TYPE_NULL)
 		return;
 	if (ctx->pOut->type == MEM_TYPE_NULL)
-		return mem_copy_as_ephemeral(ctx->pOut, argv[0]);
-	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
+		return mem_copy_as_ephemeral(ctx->pOut, &argv[0]);
+	if (mem_add(ctx->pOut, &argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
 /** Implementation of the TOTAL() function. */
 static void
-step_total(struct sql_context *ctx, int argc, struct Mem **argv)
+step_total(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_num(ctx->pOut));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	if (argv[0].type == MEM_TYPE_NULL)
 		return;
 	if (ctx->pOut->type == MEM_TYPE_NULL)
 		mem_set_double(ctx->pOut, 0.0);
-	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
+	if (mem_add(ctx->pOut, &argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
@@ -95,12 +95,12 @@ fin_total(struct Mem *mem)
 
 /** Implementation of the AVG() function. */
 static void
-step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
+step_avg(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(ctx->pOut->type == MEM_TYPE_NULL || mem_is_bin(ctx->pOut));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	if (argv[0].type == MEM_TYPE_NULL)
 		return;
 	struct Mem *mem;
 	uint32_t *count;
@@ -114,14 +114,14 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 		count = (uint32_t *)(mem + 1);
 		mem_create(mem);
 		*count = 1;
-		mem_copy_as_ephemeral(mem, argv[0]);
+		mem_copy_as_ephemeral(mem, &argv[0]);
 		mem_set_bin_allocated(ctx->pOut, (char *)mem, size);
 		return;
 	}
 	mem = (struct Mem *)ctx->pOut->z;
 	count = (uint32_t *)(mem + 1);
 	++*count;
-	if (mem_add(mem, argv[0], mem) != 0)
+	if (mem_add(mem, &argv[0], mem) != 0)
 		ctx->is_aborted = true;
 }
 
@@ -144,12 +144,12 @@ fin_avg(struct Mem *mem)
 
 /** Implementation of the COUNT() function. */
 static void
-step_count(struct sql_context *ctx, int argc, struct Mem **argv)
+step_count(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 0 || argc == 1);
 	if (ctx->pOut->type == MEM_TYPE_NULL)
 		mem_set_uint(ctx->pOut, 0);
-	if (argc == 1 && argv[0]->type == MEM_TYPE_NULL)
+	if (argc == 1 && argv[0].type == MEM_TYPE_NULL)
 		return;
 	assert(ctx->pOut->type == MEM_TYPE_UINT);
 	++ctx->pOut->u.u;
@@ -167,17 +167,17 @@ fin_count(struct Mem *mem)
 
 /** Implementation of the MIN() and MAX() functions. */
 static void
-step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
+step_minmax(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	if (argv[0]->type == MEM_TYPE_NULL) {
+	if (argv[0].type == MEM_TYPE_NULL) {
 		if (ctx->pOut->type != MEM_TYPE_NULL)
 			ctx->skipFlag = 1;
 		return;
 	}
 	if (ctx->pOut->type == MEM_TYPE_NULL) {
-		if (mem_copy(ctx->pOut, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, &argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -189,9 +189,9 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	 * the only difference between the two being that the sense of the
 	 * comparison is inverted.
 	 */
-	int cmp = mem_cmp_scalar(ctx->pOut, argv[0], ctx->coll);
+	int cmp = mem_cmp_scalar(ctx->pOut, &argv[0], ctx->coll);
 	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-		if (mem_copy(ctx->pOut, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, &argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -200,15 +200,15 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 
 /** Implementation of the GROUP_CONCAT() function. */
 static void
-step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
+step_group_concat(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1 || argc == 2);
 	(void)argc;
-	if (argv[0]->type == MEM_TYPE_NULL)
+	if (argv[0].type == MEM_TYPE_NULL)
 		return;
-	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
+	assert(mem_is_str(&argv[0]) || mem_is_bin(&argv[0]));
 	if (ctx->pOut->type == MEM_TYPE_NULL) {
-		if (mem_copy_str(ctx->pOut, argv[0]->z, argv[0]->n) != 0)
+		if (mem_copy_str(ctx->pOut, argv[0].z, argv[0].n) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -217,13 +217,13 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	if (argc == 1) {
 		sep = ",";
 		sep_len = 1;
-	} else if (argv[1]->type == MEM_TYPE_NULL) {
+	} else if (argv[1].type == MEM_TYPE_NULL) {
 		sep = "";
 		sep_len = 0;
 	} else {
-		assert(mem_is_same_type(argv[0], argv[0]));
-		sep = argv[1]->z;
-		sep_len = argv[1]->n;
+		assert(mem_is_same_type(&argv[0], &argv[0]));
+		sep = argv[1].z;
+		sep_len = argv[1].n;
 	}
 	if (sep_len > 0) {
 		if (mem_append(ctx->pOut, sep, sep_len) != 0) {
@@ -231,7 +231,7 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 			return;
 		}
 	}
-	if (mem_append(ctx->pOut, argv[0]->z, argv[0]->n) != 0) {
+	if (mem_append(ctx->pOut, argv[0].z, argv[0].n) != 0) {
 		ctx->is_aborted = true;
 		return;
 	}
@@ -256,7 +256,7 @@ mem_as_bin(struct Mem *mem)
 }
 
 static void
-sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
+sql_func_uuid(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	if (argc > 1) {
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UUID",
@@ -266,9 +266,9 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
 	}
 	if (argc == 1) {
 		uint64_t version;
-		if (mem_get_uint(argv[0], &version) != 0) {
+		if (mem_get_uint(&argv[0], &version) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(argv[0]), "integer");
+				 mem_str(&argv[0]), "integer");
 			ctx->is_aborted = true;
 			return;
 		}
@@ -288,7 +288,7 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
  * Implementation of the non-aggregate min() and max() functions
  */
 static void
-minmaxFunc(sql_context * context, int argc, sql_value ** argv)
+minmaxFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int i;
 	int iBest;
@@ -304,30 +304,30 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	pColl = context->coll;
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
 	for (i = 1; i < argc; i++) {
-		if (mem_is_null(argv[i]))
+		if (mem_is_null(&argv[i]))
 			return;
-		if ((mem_cmp_scalar(argv[iBest], argv[i], pColl) ^ mask) >= 0)
+		if ((mem_cmp_scalar(&argv[iBest], &argv[i], pColl) ^ mask) >= 0)
 			iBest = i;
 	}
-	sql_result_value(context, argv[iBest]);
+	sql_result_value(context, &argv[iBest]);
 }
 
 /*
  * Return the type of the argument.
  */
 static void
-typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
+typeofFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
 	const char *z = 0;
-	UNUSED_PARAMETER(NotUsed);
-	if ((argv[0]->flags & MEM_Number) != 0)
+	if ((argv[0].flags & MEM_Number) != 0)
 		return mem_set_str0_static(context->pOut, "number");
-	if ((argv[0]->flags & MEM_Scalar) != 0)
+	if ((argv[0].flags & MEM_Scalar) != 0)
 		return mem_set_str0_static(context->pOut, "scalar");
-	switch (argv[0]->type) {
+	switch (argv[0].type) {
 	case MEM_TYPE_INT:
 	case MEM_TYPE_UINT:
 		z = "integer";
@@ -366,13 +366,13 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
  * Implementation of the length() function
  */
 static void
-lengthFunc(sql_context * context, int argc, sql_value ** argv)
+lengthFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int len;
 
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
+	switch (sql_value_type(&argv[0])) {
 	case MP_BIN:
 	case MP_ARRAY:
 	case MP_MAP:
@@ -380,16 +380,16 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_UINT:
 	case MP_BOOL:
 	case MP_DOUBLE:{
-			mem_as_bin(argv[0]);
-			sql_result_uint(context, mem_len_unsafe(argv[0]));
+			mem_as_bin(&argv[0]);
+			sql_result_uint(context, mem_len_unsafe(&argv[0]));
 			break;
 		}
 	case MP_EXT:
 	case MP_STR:{
-			const unsigned char *z = mem_as_ustr(argv[0]);
+			const unsigned char *z = mem_as_ustr(&argv[0]);
 			if (z == 0)
 				return;
-			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(&argv[0]));
 			sql_result_uint(context, len);
 			break;
 		}
@@ -407,17 +407,17 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
  * the numeric argument X.
  */
 static void
-absFunc(sql_context * context, int argc, sql_value ** argv)
+absFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
+	switch (sql_value_type(&argv[0])) {
 	case MP_UINT: {
-		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
+		sql_result_uint(context, mem_get_uint_unsafe(&argv[0]));
 		break;
 	}
 	case MP_INT: {
-		int64_t value = mem_get_int_unsafe(argv[0]);
+		int64_t value = mem_get_int_unsafe(&argv[0]);
 		assert(value < 0);
 		sql_result_uint(context, -value);
 		break;
@@ -433,7 +433,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_ARRAY:
 	case MP_MAP: {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "number",
-			 mem_str(argv[0]));
+			 mem_str(&argv[0]));
 		context->is_aborted = true;
 		return;
 	}
@@ -442,7 +442,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal = mem_get_double_unsafe(argv[0]);
+			double rVal = mem_get_double_unsafe(&argv[0]);
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -463,11 +463,11 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
  * occurrence of needle, or 0 if needle never occurs in haystack.
  */
 static void
-position_func(struct sql_context *context, int argc, struct Mem **argv)
+position_func(struct sql_context *context, int argc, struct Mem *argv)
 {
 	UNUSED_PARAMETER(argc);
-	struct Mem *needle = argv[0];
-	struct Mem *haystack = argv[1];
+	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);
 
@@ -590,7 +590,7 @@ finish:
  * Implementation of the printf() function.
  */
 static void
-printfFunc(sql_context * context, int argc, sql_value ** argv)
+printfFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	PrintfArguments x;
 	StrAccum str;
@@ -598,14 +598,22 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
 	int n;
 	sql *db = sql_context_db_handle(context);
 
-	if (argc >= 1 && (zFormat = mem_as_str0(argv[0])) != NULL) {
+	if (argc >= 1 && (zFormat = mem_as_str0(&argv[0])) != NULL) {
 		x.nArg = argc - 1;
 		x.nUsed = 0;
-		x.apArg = argv + 1;
+		x.apArg = sqlDbMallocRawNN(sql_get(),
+					   (argc - 1) * sizeof(*x.apArg));
+		if (x.apArg == NULL) {
+			context->is_aborted = true;
+			return;
+		}
+		for (int i = 1; i < argc; ++i)
+			x.apArg[i - 1] = &argv[i];
 		sqlStrAccumInit(&str, db, 0, 0,
 				    db->aLimit[SQL_LIMIT_LENGTH]);
 		str.printfFlags = SQL_PRINTF_SQLFUNC;
 		sqlXPrintf(&str, zFormat, &x);
+		sqlDbFree(sql_get(), x.apArg);
 		n = str.nChar;
 		sql_result_text(context, sqlStrAccumFinish(&str), n,
 				    SQL_DYNAMIC);
@@ -625,7 +633,7 @@ 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)
+substrFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	const unsigned char *z;
 	const unsigned char *z2;
@@ -640,26 +648,26 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
+	if (mem_is_null(&argv[1]) || (argc == 3 && mem_is_null(&argv[2])))
 		return;
-	p0type = sql_value_type(argv[0]);
-	p1 = mem_get_int_unsafe(argv[1]);
+	p0type = sql_value_type(&argv[0]);
+	p1 = mem_get_int_unsafe(&argv[1]);
 	if (p0type == MP_BIN) {
-		z = mem_as_bin(argv[0]);
-		len = mem_len_unsafe(argv[0]);
+		z = mem_as_bin(&argv[0]);
+		len = mem_len_unsafe(&argv[0]);
 		if (z == 0)
 			return;
-		assert(len == mem_len_unsafe(argv[0]));
+		assert(len == mem_len_unsafe(&argv[0]));
 	} else {
-		z = mem_as_ustr(argv[0]);
+		z = mem_as_ustr(&argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
 		if (p1 < 0)
-			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(&argv[0]));
 	}
 	if (argc == 3) {
-		p2 = mem_get_int_unsafe(argv[2]);
+		p2 = mem_get_int_unsafe(&argv[2]);
 		if (p2 < 0) {
 			p2 = -p2;
 			negP2 = 1;
@@ -695,7 +703,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 = mem_len_unsafe(argv[0]);
+		int byte_size = mem_len_unsafe(&argv[0]);
 		int n_chars = sql_utf8_char_count(z, byte_size);
 		int cnt = 0;
 		int i = 0;
@@ -726,7 +734,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
  * Implementation of the round() function
  */
 static void
-roundFunc(sql_context * context, int argc, sql_value ** argv)
+roundFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n = 0;
 	double r;
@@ -737,21 +745,21 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	if (argc == 2) {
-		if (mem_is_null(argv[1]))
+		if (mem_is_null(&argv[1]))
 			return;
-		n = mem_get_int_unsafe(argv[1]);
+		n = mem_get_int_unsafe(&argv[1]);
 		if (n < 0)
 			n = 0;
 	}
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
-	if (!mem_is_num(argv[0]) && !mem_is_str(argv[0])) {
+	if (!mem_is_num(&argv[0]) && !mem_is_str(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "number");
+			 mem_str(&argv[0]), "number");
 		context->is_aborted = true;
 		return;
 	}
-	r = mem_get_double_unsafe(argv[0]);
+	r = mem_get_double_unsafe(&argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -774,7 +782,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
  * NULL.
  */
 static void *
-contextMalloc(sql_context * context, i64 nByte)
+contextMalloc(struct sql_context *context, i64 nByte)
 {
 	char *z;
 	sql *db = sql_context_db_handle(context);
@@ -799,26 +807,26 @@ 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)   \
+case_type##ICUFunc(sql_context *context, int argc, struct Mem *argv)           \
 {                                                                              \
 	char *z1;                                                              \
 	const char *z2;                                                        \
 	int n;                                                                 \
 	UNUSED_PARAMETER(argc);                                                \
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||                      \
-	    mem_is_array(argv[0])) {                                           \
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||                    \
+	    mem_is_array(&argv[0])) {                                          \
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "string",         \
-			 mem_str(argv[0]));                                    \
+			 mem_str(&argv[0]));                                   \
 		context->is_aborted = true;                                    \
 		return;                                                        \
 	}                                                                      \
-	z2 = mem_as_str0(argv[0]);                                             \
-	n = mem_len_unsafe(argv[0]);                                           \
+	z2 = mem_as_str0(&argv[0]);                                            \
+	n = mem_len_unsafe(&argv[0]);                                          \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
 	 */                                                                    \
-	assert(z2 == mem_as_str0(argv[0]));                                    \
+	assert(z2 == mem_as_str0(&argv[0]));                                   \
 	if (!z2)                                                               \
 		return;                                                        \
 	z1 = contextMalloc(context, ((i64) n) + 1);                            \
@@ -868,10 +876,11 @@ ICU_CASE_CONVERT(Upper);
  * Implementation of random().  Return a random integer.
  */
 static void
-randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
+randomFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	int64_t r;
-	UNUSED_PARAMETER2(NotUsed, NotUsed2);
 	sql_randomness(sizeof(r), &r);
 	sql_result_int(context, r);
 }
@@ -881,20 +890,20 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
  * that is N bytes long.
  */
 static void
-randomBlob(sql_context * context, int argc, sql_value ** argv)
+randomBlob(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n;
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
-	    mem_is_array(argv[0])) {
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||
+	    mem_is_array(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "number");
+			 mem_str(&argv[0]), "number");
 		context->is_aborted = true;
 		return;
 	}
-	n = mem_get_int_unsafe(argv[0]);
+	n = mem_get_int_unsafe(&argv[0]);
 	if (n < 1)
 		return;
 	p = contextMalloc(context, n);
@@ -1124,7 +1133,7 @@ sql_utf8_pattern_compare(const char *pattern,
  * is NULL then result is NULL as well.
  */
 static void
-likeFunc(sql_context *context, int argc, sql_value **argv)
+likeFunc(sql_context *context, int argc, struct Mem *argv)
 {
 	u32 escape = SQL_END_OF_STRING;
 	int nPat;
@@ -1135,29 +1144,29 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		return;
 	}
 	sql *db = sql_context_db_handle(context);
-	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;
 		const char *str = rhs_type != MP_STR ?
-				  mem_str(argv[0]) : mem_str(argv[1]);
+				  mem_str(&argv[0]) : mem_str(&argv[1]);
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "string", str);
 		context->is_aborted = true;
 		return;
 	}
-	const char *zB = mem_as_str0(argv[0]);
-	const char *zA = mem_as_str0(argv[1]);
-	const char *zB_end = zB + mem_len_unsafe(argv[0]);
-	const char *zA_end = zA + mem_len_unsafe(argv[1]);
+	const char *zB = mem_as_str0(&argv[0]);
+	const char *zA = mem_as_str0(&argv[1]);
+	const char *zB_end = zB + mem_len_unsafe(&argv[0]);
+	const char *zA_end = zA + mem_len_unsafe(&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 = mem_len_unsafe(argv[0]);
+	nPat = mem_len_unsafe(&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]) {
@@ -1167,7 +1176,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		return;
 	}
 	/* Encoding did not change */
-	assert(zB == mem_as_str0(argv[0]));
+	assert(zB == mem_as_str0(&argv[0]));
 
 	if (argc == 3) {
 		/*
@@ -1175,10 +1184,10 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		 * single UTF-8 character. Otherwise, return an
 		 * error.
 		 */
-		const unsigned char *zEsc = mem_as_ustr(argv[2]);
+		const unsigned char *zEsc = mem_as_ustr(&argv[2]);
 		if (zEsc == 0)
 			return;
-		if (sql_utf8_char_count(zEsc, mem_len_unsafe(argv[2])) != 1) {
+		if (sql_utf8_char_count(zEsc, mem_len_unsafe(&argv[2])) != 1) {
 			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
 				 "expression must be a single character");
 			context->is_aborted = true;
@@ -1208,12 +1217,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
  * arguments are equal to each other.
  */
 static void
-nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
+nullifFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
 	struct coll *pColl = context->coll;
-	UNUSED_PARAMETER(NotUsed);
-	if (mem_cmp_scalar(argv[0], argv[1], pColl) != 0)
-		sql_result_value(context, argv[0]);
+	if (mem_cmp_scalar(&argv[0], &argv[1], pColl) != 0)
+		sql_result_value(context, &argv[0]);
 }
 
 /**
@@ -1225,10 +1234,10 @@ nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
  * @param unused2 Unused.
  */
 static void
-sql_func_version(struct sql_context *context,
-		 MAYBE_UNUSED int unused1,
-		 MAYBE_UNUSED sql_value **unused2)
+sql_func_version(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	sql_result_text(context, tarantool_version(), -1, SQL_STATIC);
 }
 
@@ -1248,14 +1257,14 @@ static const char hexdigits[] = {
  * single-quote escapes.
  */
 static void
-quoteFunc(sql_context * context, int argc, sql_value ** argv)
+quoteFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (argv[0]->type) {
+	switch (argv[0].type) {
 	case MEM_TYPE_UUID: {
 		char buf[UUID_STR_LEN + 1];
-		tt_uuid_to_string(&argv[0]->u.uuid, &buf[0]);
+		tt_uuid_to_string(&argv[0].u.uuid, &buf[0]);
 		sql_result_text(context, buf, UUID_STR_LEN, SQL_TRANSIENT);
 		break;
 	}
@@ -1263,16 +1272,16 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MEM_TYPE_DEC:
 	case MEM_TYPE_UINT:
 	case MEM_TYPE_INT: {
-			sql_result_value(context, argv[0]);
+			sql_result_value(context, &argv[0]);
 			break;
 		}
 	case MEM_TYPE_BIN:
 	case MEM_TYPE_ARRAY:
 	case MEM_TYPE_MAP: {
 			char *zText = 0;
-			char const *zBlob = mem_as_bin(argv[0]);
-			int nBlob = mem_len_unsafe(argv[0]);
-			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
+			char const *zBlob = mem_as_bin(&argv[0]);
+			int nBlob = mem_len_unsafe(&argv[0]);
+			assert(zBlob == mem_as_bin(&argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
 						  (2 * (i64) nBlob) + 4);
@@ -1297,7 +1306,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MEM_TYPE_STR: {
 			int i, j;
 			u64 n;
-			const unsigned char *zArg = mem_as_ustr(argv[0]);
+			const unsigned char *zArg = mem_as_ustr(&argv[0]);
 			char *z;
 
 			if (zArg == 0)
@@ -1324,12 +1333,12 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 		}
 	case MEM_TYPE_BOOL: {
 		sql_result_text(context,
-				SQL_TOKEN_BOOLEAN(mem_get_bool_unsafe(argv[0])),
+				SQL_TOKEN_BOOLEAN(argv[0].u.b),
 				-1, SQL_TRANSIENT);
 		break;
 	}
 	default:{
-			assert(mem_is_null(argv[0]));
+			assert(mem_is_null(&argv[0]));
 			sql_result_text(context, "NULL", 4, SQL_STATIC);
 			break;
 		}
@@ -1341,9 +1350,9 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
  * for the first character of the input string.
  */
 static void
-unicodeFunc(sql_context * context, int argc, sql_value ** argv)
+unicodeFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
-	const unsigned char *z = mem_as_ustr(argv[0]);
+	const unsigned char *z = mem_as_ustr(&argv[0]);
 	(void)argc;
 	if (z && z[0])
 		sql_result_uint(context, sqlUtf8Read(&z));
@@ -1355,7 +1364,7 @@ unicodeFunc(sql_context * context, int argc, sql_value ** argv)
  * is the unicode character for the corresponding integer argument.
  */
 static void
-charFunc(sql_context * context, int argc, sql_value ** argv)
+charFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	unsigned char *z, *zOut;
 	int i;
@@ -1367,10 +1376,10 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 	for (i = 0; i < argc; i++) {
 		uint64_t x;
 		unsigned c;
-		if (sql_value_type(argv[i]) == MP_INT)
+		if (sql_value_type(&argv[i]) == MP_INT)
 			x = 0xfffd;
 		else
-			x = mem_get_uint_unsafe(argv[i]);
+			x = mem_get_uint_unsafe(&argv[i]);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
@@ -1398,16 +1407,16 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
  * a hexadecimal rendering as text.
  */
 static void
-hexFunc(sql_context * context, int argc, sql_value ** argv)
+hexFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int i, n;
 	const unsigned char *pBlob;
 	char *zHex, *z;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	pBlob = mem_as_bin(argv[0]);
-	n = mem_len_unsafe(argv[0]);
-	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
+	pBlob = mem_as_bin(&argv[0]);
+	n = mem_len_unsafe(&argv[0]);
+	assert(pBlob == mem_as_bin(&argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
 		for (i = 0; i < n; i++, pBlob++) {
@@ -1424,12 +1433,12 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
  * The zeroblob(N) function returns a zero-filled blob of size N bytes.
  */
 static void
-zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
+zeroblobFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	n = mem_get_int_unsafe(argv[0]);
+	n = mem_get_int_unsafe(&argv[0]);
 	if (n < 0)
 		n = 0;
 	if (sql_result_zeroblob64(context, n) != 0) {
@@ -1446,7 +1455,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
  * must be exact.  Collating sequences are not used.
  */
 static void
-replaceFunc(sql_context * context, int argc, sql_value ** argv)
+replaceFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	const unsigned char *zStr;	/* The input string A */
 	const unsigned char *zPattern;	/* The pattern string B */
@@ -1461,29 +1470,29 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 
 	assert(argc == 3);
 	UNUSED_PARAMETER(argc);
-	zStr = mem_as_ustr(argv[0]);
+	zStr = mem_as_ustr(&argv[0]);
 	if (zStr == 0)
 		return;
-	nStr = mem_len_unsafe(argv[0]);
-	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
-	zPattern = mem_as_ustr(argv[1]);
+	nStr = mem_len_unsafe(&argv[0]);
+	assert(zStr == mem_as_ustr(&argv[0]));	/* No encoding change */
+	zPattern = mem_as_ustr(&argv[1]);
 	if (zPattern == 0) {
-		assert(mem_is_null(argv[1])
+		assert(mem_is_null(&argv[1])
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
-	nPattern = mem_len_unsafe(argv[1]);
+	nPattern = mem_len_unsafe(&argv[1]);
 	if (nPattern == 0) {
-		assert(!mem_is_null(argv[1]));
-		sql_result_value(context, argv[0]);
+		assert(!mem_is_null(&argv[1]));
+		sql_result_value(context, &argv[0]);
 		return;
 	}
-	assert(zPattern == mem_as_ustr(argv[1]));	/* No encoding change */
-	zRep = mem_as_ustr(argv[2]);
+	assert(zPattern == mem_as_ustr(&argv[1]));	/* No encoding change */
+	zRep = mem_as_ustr(&argv[2]);
 	if (zRep == 0)
 		return;
-	nRep = mem_len_unsafe(argv[2]);
-	assert(zRep == mem_as_ustr(argv[2]));
+	nRep = mem_len_unsafe(&argv[2]);
+	assert(zRep == mem_as_ustr(&argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
 	zOut = contextMalloc(context, (i64) nOut);
@@ -1702,14 +1711,14 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
  * implementation depending on the number of arguments.
 */
 static void
-trim_func(struct sql_context *context, int argc, sql_value **argv)
+trim_func(struct sql_context *context, int argc, struct Mem *argv)
 {
 	switch (argc) {
 	case 2:
-		trim_func_two_args(context, argv[0], argv[1]);
+		trim_func_two_args(context, &argv[0], &argv[1]);
 		break;
 	case 3:
-		trim_func_three_args(context, argv[0], argv[1], argv[2]);
+		trim_func_three_args(context, &argv[0], &argv[1], &argv[2]);
 		break;
 	default:
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
@@ -1725,7 +1734,7 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
  * soundex encoding of the string X.
  */
 static void
-soundexFunc(sql_context * context, int argc, sql_value ** argv)
+soundexFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	(void) argc;
 	char zResult[8];
@@ -1742,14 +1751,14 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
 	};
 	assert(argc == 1);
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
-	    mem_is_array(argv[0])) {
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||
+	    mem_is_array(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "string");
+			 mem_str(&argv[0]), "string");
 		context->is_aborted = true;
 		return;
 	}
-	zIn = (u8 *) mem_as_ustr(argv[0]);
+	zIn = (u8 *) mem_as_ustr(&argv[0]);
 	if (zIn == 0)
 		zIn = (u8 *) "";
 	for (i = 0; zIn[i] && !sqlIsalpha(zIn[i]); i++) {
@@ -1805,7 +1814,7 @@ func_sql_builtin_call_stub(struct func *func, struct port *args,
 }
 
 static void
-sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
+sql_builtin_stub(sql_context *ctx, int argc, struct Mem *argv)
 {
 	(void) argc; (void) argv;
 	diag_set(ClientError, ER_SQL_EXECUTE,
@@ -1909,7 +1918,7 @@ struct sql_func_definition {
 	/** Type of the result of the implementation. */
 	enum field_type result;
 	/** Call implementation with given arguments. */
-	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*call)(sql_context *ctx, int argc, struct Mem *argv);
 	/** Call finalization function for this implementation. */
 	int (*finalize)(struct Mem *mem);
 };
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index b0d32ae32..a4247c760 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -221,9 +221,10 @@ setupLookaside(sql * db, void *pBuf, int sz, int cnt)
 }
 
 void
-sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
-	      MAYBE_UNUSED sql_value **unused2)
+sql_row_count(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	sql *db = sql_context_db_handle(context);
 	assert(db->nChange >= 0);
 	sql_result_uint(context, db->nChange);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 2880f8ea0..cdc5d3530 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5638,7 +5638,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 				regHit = ++pParse->nMem;
 			sqlVdbeAddOp1(v, OP_SkipLoad, regHit);
 		}
-		struct sql_context *ctx = sql_context_new(pF->func, nArg, coll);
+		struct sql_context *ctx = sql_context_new(pF->func, coll);
 		if (ctx == NULL) {
 			pParse->is_aborted = true;
 			return;
@@ -6750,12 +6750,9 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
 }
 
 struct sql_context *
-sql_context_new(struct func *func, uint32_t argc, struct coll *coll)
+sql_context_new(struct func *func, struct coll *coll)
 {
-	uint32_t size = sizeof(struct sql_context);
-	if (argc > 1)
-		size += (argc - 1) * sizeof(struct Mem *);
-	struct sql_context *ctx = sqlDbMallocRawNN(sql_get(), size);
+	struct sql_context *ctx = sqlDbMallocRawNN(sql_get(), sizeof(*ctx));
 	if (ctx == NULL)
 		return NULL;
 	ctx->pOut = NULL;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 97e71e82c..d1551e71e 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -481,8 +481,7 @@ 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);
+sql_row_count(struct sql_context *context, int argc, struct Mem *argv);
 
 int
 sql_column_count(sql_stmt * pStmt);
@@ -4130,7 +4129,7 @@ void sqlStrAccumReset(StrAccum *);
 void sqlSelectDestInit(SelectDest *, int, int, int);
 
 struct sql_context *
-sql_context_new(struct func *func, uint32_t argc, struct coll *coll);
+sql_context_new(struct func *func, struct coll *coll);
 
 /*
  * Create an expression to load @a column from datasource
@@ -4366,7 +4365,7 @@ struct func_sql_builtin {
 	 * Access checks are redundant, because all SQL built-ins
 	 * are predefined and are executed on SQL privilege level.
 	 */
-	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*call)(struct sql_context *ctx, int argc, struct Mem *argv);
 	/**
 	 * A VDBE-memory-compatible finalize method
 	 * (is valid only for aggregate function).
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 65bb60d2f..dc1c8009d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1183,35 +1183,26 @@ case OP_SkipLoad: {
  * See also: AggStep, AggFinal
  */
 case OP_BuiltinFunction: {
-	int i;
 	int argc = pOp->p1;
 	sql_context *pCtx;
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
 
-	/* 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
-	 */
 	pOut = vdbe_prepare_null_out(p, pOp->p3);
-	if (pCtx->pOut != pOut) {
+	if (pCtx->pOut != pOut)
 		pCtx->pOut = pOut;
-		for(i = 0; i < argc; ++i)
-			pCtx->argv[i] = &aMem[pOp->p2 + i];
-	}
 
 #ifdef SQL_DEBUG
-	for(i = 0; i < argc; i++) {
-		assert(memIsValid(pCtx->argv[i]));
-		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
+	for(int i = 0; i < argc; i++) {
+		assert(memIsValid(&aMem[pOp->p2 + i]));
+		REGISTER_TRACE(p, pOp->p2 + i, &aMem[pOp->p2 + i]);
 	}
 #endif
 	pCtx->is_aborted = false;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, argc, pCtx->argv);
+	func->call(pCtx, argc, &aMem[pOp->p2]);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -4141,7 +4132,6 @@ case OP_DecrJumpZero: {      /* jump, in1 */
  * successors.
  */
 case OP_AggStep: {
-	int i;
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
@@ -4150,33 +4140,25 @@ case OP_AggStep: {
 	pCtx = pOp->p4.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->pOut != pMem) {
+	if (pCtx->pOut != pMem)
 		pCtx->pOut = pMem;
-		for(i = 0; i < argc; ++i)
-			pCtx->argv[i] = &aMem[pOp->p2 + i];
-	}
 
 #ifdef SQL_DEBUG
-	for(i = 0; i < argc; i++) {
-		assert(memIsValid(pCtx->argv[i]));
-		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
+	for(int i = 0; i < argc; i++) {
+		assert(memIsValid(&aMem[pOp->p2 + i]));
+		REGISTER_TRACE(p, pOp->p2 + i, &aMem[pOp->p2 + i]);
 	}
 #endif
 
 	pCtx->skipFlag = 0;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, argc, pCtx->argv);
+	func->call(pCtx, argc, &aMem[pOp->p2]);
 	if (pCtx->is_aborted)
 		goto abort_due_to_error;
 	if (pCtx->skipFlag) {
 		assert(pOp[-1].opcode == OP_SkipLoad);
-		i = pOp[-1].p1;
+		int i = pOp[-1].p1;
 		if (i) mem_set_bool(&aMem[i], true);
 	}
 	break;
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 5e2692d06..8dbba4908 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -179,7 +179,6 @@ struct sql_context {
 	 */
 	bool is_aborted;
 	u8 skipFlag;		/* Skip accumulator loading if true */
-	sql_value *argv[1];	/* Argument set */
 };
 
 /* A bitfield type for use inside of structures.  Always follow with :N where
-- 
2.25.1


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:47   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 11:13     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:47 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the fixes!

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index a9011fc63..fb304d868 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -217,11 +217,7 @@ mem_create(struct Mem *mem)
>  static inline void
>  mem_clear(struct Mem *mem)
>  {
> -	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
> -	    (mem->flags & MEM_Dyn) != 0) {
> -		if (mem->type == MEM_TYPE_AGG)
> -			sql_vdbemem_finalize(mem, mem->u.func);
> -		assert(mem->type != MEM_TYPE_AGG);
> +	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0) {

It is a single type now, you can use type == MEM_TYPE_FRAME. Also a few lines
below you again check both the conditions:

	if ((mem->flags & MEM_Dyn) != 0) {
		...
	} else if (mem->type == MEM_TYPE_FRAME) {
		...
	}

The 'else' don't need to check type == FRAME. It is true
anyway if this was true:

	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0)

and "(mem->flags & MEM_Dyn) != 0" wasn't.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM() Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 11:17     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:48 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index b4461c6ee..b06955302 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -53,6 +53,29 @@
>  static struct mh_strnptr_t *built_in_functions = NULL;
>  static struct func_sql_builtin **functions;
>  
> +/** Implementation of the SUM() function. */
> +static void
> +step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1);
> +	(void)argc;
> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
> +	if (argv[0]->type == MEM_TYPE_NULL)

Probably we should use mem_is_null() where possible. To avoid
Mem members manual touch out of Mem.h and Mem.c. Here and in other
places, other commits. Sorry I didn't notice it before.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG() Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 11:32     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:48 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the fixes!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 12a6a5a2c..b5f154fb1 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -102,6 +102,54 @@ fin_total(struct sql_context *ctx)
>  		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
>  }
>  
> +/** Implementation of the AVG() function. */
> +static void
> +step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1);
> +	(void)argc;
> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
> +	if (argv[0]->type == MEM_TYPE_NULL)
> +		return;
> +	struct Mem *mem;
> +	uint32_t *count;
> +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> +		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
> +		mem = sqlDbMallocRawNN(sql_get(), size);

1. Where is it deleted? Can't find.

> +		if (mem == NULL) {
> +			ctx->is_aborted = true;
> +			return;
> +		}
> +		count = (uint32_t *)(mem + 1);
> +		mem_create(mem);
> +		*count = 1;
> +		mem_copy_as_ephemeral(mem, argv[0]);
> +		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
> +		return;
> +	}
> diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
> index 507d06549..08a63b86d 100755
> --- a/test/sql-tap/built-in-functions.test.lua
> +++ b/test/sql-tap/built-in-functions.test.lua
> @@ -605,4 +605,33 @@ test:do_execsql_test(
>      }
>  )
>  
> +-- Make sure AVG() accepts and returns DOUBLE by default.
> +test:do_test(
> +    "builtins-4.1.1",
> +    function()
> +        return box.execute([[SELECT AVG(?);]], {1}).metadata
> +    end, {
> +        {name = "COLUMN_1", type = "double"},
> +    })
> +
> +test:do_test(
> +    "builtins-4.1.2",
> +    function()
> +        local res = {pcall(box.execute, [[SELECT AVG(?);]], {-1ULL})}
> +        return {tostring(res[3])}
> +    end, {
> +        "Type mismatch: can not convert integer(18446744073709551615) to double"
> +    })
> +
> +-- Make sure AVG() works with DECIMAL properly.
> +test:do_execsql_test(
> +    "builtins-4.1.3",
> +    [[
> +        SELECT AVG(cast(column_2 as DECIMAL)) from (values(1), (123.432));

2. I don't understand how does it work. Why not column_1? Why does it fail?

tarantool> box.execute('SELECT column_1 from (values(1), (123.432));')
---
- null
- Can’t resolve field 'COLUMN_1'
...

Isn't there 2 tuples [1], and [123.432] with just 1 column each?
And why does it work when I delete one of the values?

tarantool> box.execute('SELECT column_1 from (values(1));')
---
- metadata:
  - name: COLUMN_1
    type: integer
  rows:
  - [1]
...

> +    ]],
> +    {
> +        dec.new(62.216)
> +    }
> +)
> +
>  test:finish_test()
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT() Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:49   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 11:42     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:49 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index f699aa927..001a8641c 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -213,6 +213,52 @@ fin_minmax(struct sql_context *ctx)
>  	mem_copy(ctx->pOut, ctx->pMem);
>  }
>  
> +/** Implementation of the GROUP_CONCAT() function. */
> +static void
> +step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1 || argc == 2);
> +	(void)argc;
> +	if (argv[0]->type == MEM_TYPE_NULL)
> +		return;
> +	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
> +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> +		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)

1. What if the argument is zeroblob with no actual memory allocated yet?

> +			ctx->is_aborted = true;
> +		return;
> +	}
> +	const char *sep = NULL;
> +	int sep_len = 0;
> +	if (argc == 1) {
> +		sep = ",";
> +		sep_len = 1;
> +	} else if (argv[1]->type == MEM_TYPE_NULL) {
> +		sep = "";
> +		sep_len = 0;
> +	} else {
> +		assert(mem_is_same_type(argv[0], argv[0]));
> +		sep = argv[1]->z;
> +		sep_len = argv[1]->n;
> +	}
> +	if (sep_len > 0) {
> +		if (mem_append(ctx->pMem, sep, sep_len) != 0) {

2. Will it work if sep_len == 0? If yes, then I would propose to
drop the len check here and call the append always.

> +			ctx->is_aborted = true;
> +			return;
> +		}

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:50   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 11:47     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:50 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the fixes!

> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 8804e3d18..65bb60d2f 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -4207,24 +4200,10 @@ case OP_AggFinal: {
>  	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
>  	struct Mem *pIn1 = &aMem[pOp->p1];
>  
> -	struct sql_context ctx;
> -	memset(&ctx, 0, sizeof(ctx));
> -	struct Mem t;
> -	memset(&t, 0, sizeof(t));
> -	t.type = MEM_TYPE_NULL;
> -	assert(t.flags == 0);
> -	t.db = pIn1->db;
> -	ctx.pOut = &t;
> -	ctx.pMem = pIn1;
> -	ctx.func = pOp->p4.func;
> -	func->finalize(&ctx);
> -	assert((pIn1->flags & MEM_Dyn) == 0);
> -	if (pIn1->szMalloc > 0)
> -		sqlDbFree(pIn1->db, pIn1->zMalloc);
> -	memcpy(pIn1, &t, sizeof(t));
> -
> -	if (ctx.is_aborted)
> -		goto abort_due_to_error;
> +	if (func->finalize != NULL) {
> +		if (func->finalize(pIn1) != 0)

Such places might look simpler and shorter if you would
use &&:

	if (func->finalize != NULL && func->finalize(...

Up to you.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
@ 2021-09-22 22:51   ` Vladislav Shpilevoy via Tarantool-patches
  2021-09-25 12:03     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-09-22 22:51 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index cdfb020cb..6cbbbac74 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -55,31 +55,31 @@ static struct func_sql_builtin **functions;
>  
>  /** Implementation of the SUM() function. */
>  static void
> -step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
> +step_sum(struct sql_context *ctx, int argc, struct Mem *argv)

Is it a good idea? This way you force to allocate the Mem array.
But what if I have a function call like func(col1, col10, col20),
where colN are columns of a tuple? They are stored in Mems, so previously
it would be possible to pass &col1, &col10, &col20. Now you have no
choice but to allocate 3 * sizeof(Mem) and ephem-copy the mems.

How does func(col1, col10, col20) behave before this patch? Does it
copy the cols even now?

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
@ 2021-09-25 11:06   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:06 UTC (permalink / raw)
  To: v.shpilevoy, tarantool-patches

Thank you for the review! I added check for length of appended string. If it
is 0, than we do not have to do anything. The result can be static or ephemeral
now, but not sure that this should be an issue. On the other hand, we do not
have to allocate memory for static or ephemeral MEMs, when zero-length string is
appended to them. Diff and new patch below.

On Tue, Sep 21, 2021 at 01:59:10PM +0300, Mergen Imeev via Tarantool-patches wrote:
> This patch introduces function mem_append(). This function appends the
> given string to the end of the STRING or VARBINARY contained in MEM.
> In case MEM needs to increase the size of allocated memory, additional
> memory is allocated in an attempt to reduce the total number of
> allocations. The main use of this function is to add multiple strings to
> a single MEM.
> 
> Needed for #4145
> ---
>  src/box/sql/mem.c     | 19 +++++++++++++++++++
>  src/box/sql/mem.h     |  8 ++++++++
>  src/box/sql/vdbeaux.c |  9 ++-------
>  3 files changed, 29 insertions(+), 7 deletions(-)
> 
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index cc0fd836b..a9011fc63 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1953,6 +1953,25 @@ mem_move(struct Mem *to, struct Mem *from)
>  	from->zMalloc = NULL;
>  }
>  
> +int
> +mem_append(struct Mem *mem, const char *value, uint32_t len)
> +{
> +	assert((mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR)) != 0);
> +	int new_size = mem->n + len;
> +	if (((mem->flags & (MEM_Static | MEM_Dyn | MEM_Ephem)) != 0) ||
> +	    mem->szMalloc < new_size) {
> +		/*
> +		 * Force exponential buffer size growth to avoid having to call
> +		 * this routine too often.
> +		 */
> +		if (sqlVdbeMemGrow(mem, new_size + mem->n, 1) != 0)
> +			return -1;
> +	}
> +	memcpy(&mem->z[mem->n], value, len);
> +	mem->n = new_size;
> +	return 0;
> +}
> +
>  int
>  mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  {
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 0da45b8af..54a1e931b 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -597,6 +597,14 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from);
>  void
>  mem_move(struct Mem *to, struct Mem *from);
>  
> +/**
> + * Append the given string to the end of the STRING or VARBINARY contained in
> + * MEM. In case MEM needs to increase the size of allocated memory, additional
> + * memory is allocated in an attempt to reduce the total number of allocations.
> + */
> +int
> +mem_append(struct Mem *mem, const char *value, uint32_t len);
> +
>  /**
>   * Concatenate strings or binaries from the first and the second MEMs and write
>   * to the result MEM. In case the first MEM or the second MEM is NULL, the
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index b7e44a386..6f13d2b03 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1300,14 +1300,9 @@ sqlVdbeList(Vdbe * p)
>  					if (mem_copy_bin(pSub, bin, size) != 0)
>  						return -1;
>  				} else if (j == nSub) {
> -					struct Mem tmp;
> -					mem_create(&tmp);
> -					uint32_t size = sizeof(SubProgram *);
>  					char *bin = (char *)&pOp->p4.pProgram;
> -					mem_set_bin_ephemeral(&tmp, bin, size);
> -					int rc = mem_concat(pSub, &tmp, pSub);
> -					mem_destroy(&tmp);
> -					if (rc != 0)
> +					uint32_t size = sizeof(SubProgram *);
> +					if (mem_append(pSub, bin, size) != 0)
>  						return -1;
>  				}
>  			}
> -- 
> 2.25.1
> 

Diff:


diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index a9011fc63..f64cbe10a 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1957,6 +1957,8 @@ int
 mem_append(struct Mem *mem, const char *value, uint32_t len)
 {
 	assert((mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR)) != 0);
+	if (len == 0)
+		return 0;
 	int new_size = mem->n + len;
 	if (((mem->flags & (MEM_Static | MEM_Dyn | MEM_Ephem)) != 0) ||
 	    mem->szMalloc < new_size) {


New patch:

commit 5025bd6782f2199a120594371e042f99c1ad6c10
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Mon Sep 6 13:49:47 2021 +0300

    sql: introduce mem_append()
    
    This patch introduces function mem_append(). This function appends the
    given string to the end of the STRING or VARBINARY contained in MEM.
    In case MEM needs to increase the size of allocated memory, additional
    memory is allocated in an attempt to reduce the total number of
    allocations. The main use of this function is to add multiple strings to
    a single MEM.
    
    Needed for #4145

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index cc0fd836b..f64cbe10a 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1953,6 +1953,27 @@ mem_move(struct Mem *to, struct Mem *from)
 	from->zMalloc = NULL;
 }
 
+int
+mem_append(struct Mem *mem, const char *value, uint32_t len)
+{
+	assert((mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR)) != 0);
+	if (len == 0)
+		return 0;
+	int new_size = mem->n + len;
+	if (((mem->flags & (MEM_Static | MEM_Dyn | MEM_Ephem)) != 0) ||
+	    mem->szMalloc < new_size) {
+		/*
+		 * Force exponential buffer size growth to avoid having to call
+		 * this routine too often.
+		 */
+		if (sqlVdbeMemGrow(mem, new_size + mem->n, 1) != 0)
+			return -1;
+	}
+	memcpy(&mem->z[mem->n], value, len);
+	mem->n = new_size;
+	return 0;
+}
+
 int
 mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 0da45b8af..54a1e931b 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -597,6 +597,14 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from);
 void
 mem_move(struct Mem *to, struct Mem *from);
 
+/**
+ * Append the given string to the end of the STRING or VARBINARY contained in
+ * MEM. In case MEM needs to increase the size of allocated memory, additional
+ * memory is allocated in an attempt to reduce the total number of allocations.
+ */
+int
+mem_append(struct Mem *mem, const char *value, uint32_t len);
+
 /**
  * Concatenate strings or binaries from the first and the second MEMs and write
  * to the result MEM. In case the first MEM or the second MEM is NULL, the
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 4c2bd11ba..3015760e1 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1294,14 +1294,9 @@ sqlVdbeList(Vdbe * p)
 					if (mem_copy_bin(pSub, bin, size) != 0)
 						return -1;
 				} else if (j == nSub) {
-					struct Mem tmp;
-					mem_create(&tmp);
-					uint32_t size = sizeof(SubProgram *);
 					char *bin = (char *)&pOp->p4.pProgram;
-					mem_set_bin_ephemeral(&tmp, bin, size);
-					int rc = mem_concat(pSub, &tmp, pSub);
-					mem_destroy(&tmp);
-					if (rc != 0)
+					uint32_t size = sizeof(SubProgram *);
+					if (mem_append(pSub, bin, size) != 0)
 						return -1;
 				}
 			}

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize()
  2021-09-22 22:47   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 11:13     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:13 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On 23.09.2021 01:47, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
>
>>>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>>>> index 12dc9126b..dfab86c50 100644
>>>> --- a/src/box/sql/vdbe.c
>>>> +++ b/src/box/sql/vdbe.c
>>>> @@ -4204,16 +4204,32 @@ case OP_AggStep: {
>>>>   * the step function was not previously called.
>>>>   */
>>>>  case OP_AggFinal: {
>>>> -	Mem *pMem;
>>>>  	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
>>>> -	pMem = &aMem[pOp->p1];
>>>> -	assert(mem_is_null(pMem) || mem_is_agg(pMem));
>>>> -	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
>>>> +	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
>>>> +	struct Mem *pIn1 = &aMem[pOp->p1];
>>>> +	assert(mem_is_null(pIn1) || mem_is_agg(pIn1));
>>>> +
>>>> +	struct sql_context ctx;
>>>> +	memset(&ctx, 0, sizeof(ctx));
>>>> +	struct Mem t;
>>>
>>> Please, use mem_create(). Also for the context I would propose to add
>>> sql_context_create(). So as not to touch internals of the struct here
>>> directly.
>>>
>> I suggest to leave it as it is for now, since I plan to completely drop
>> struct sql_context from finalize functions in a few patches in this patch-set.
>> What do you think?
>
> mem_create() would be just -3 and +1 line in the patch. I suggest to
> use it. Previously memset was tolerated because it was done in mem.c,
> where it is allowed to touch Mem internals directly.
>
Fixed.

On Thu, Sep 23, 2021 at 12:47:45AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index a9011fc63..fb304d868 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -217,11 +217,7 @@ mem_create(struct Mem *mem)
> >  static inline void
> >  mem_clear(struct Mem *mem)
> >  {
> > -	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
> > -	    (mem->flags & MEM_Dyn) != 0) {
> > -		if (mem->type == MEM_TYPE_AGG)
> > -			sql_vdbemem_finalize(mem, mem->u.func);
> > -		assert(mem->type != MEM_TYPE_AGG);
> > +	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0) {
> 
> It is a single type now, you can use type == MEM_TYPE_FRAME. Also a few lines
> below you again check both the conditions:
> 
> 	if ((mem->flags & MEM_Dyn) != 0) {
> 		...
> 	} else if (mem->type == MEM_TYPE_FRAME) {
> 		...
> 	}
> 
> The 'else' don't need to check type == FRAME. It is true
> anyway if this was true:
> 
> 	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0)
> 
> and "(mem->flags & MEM_Dyn) != 0" wasn't.
Fixed. In addition, I believe that by the end of the refactoring of all the SQL
built-in functions, we will drop MEM_Dyn as it is mostly, if not only, used in
functions.



Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index bb26da60a..e59935672 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -217,11 +217,11 @@ mem_create(struct Mem *mem)
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if ((mem->type & MEM_TYPE_FRAME) != 0 || (mem->flags & MEM_Dyn) != 0) {
+	if (mem->type == MEM_TYPE_FRAME || (mem->flags & MEM_Dyn) != 0) {
 		if ((mem->flags & MEM_Dyn) != 0) {
 			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
 			mem->xDel((void *)mem->z);
-		} else if (mem->type == MEM_TYPE_FRAME) {
+		} else {
 			struct VdbeFrame *frame = mem->u.pFrame;
 			frame->pParent = frame->v->pDelFrame;
 			frame->v->pDelFrame = frame;
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index dfab86c50..b101fc6c2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4212,10 +4212,7 @@ case OP_AggFinal: {
 	struct sql_context ctx;
 	memset(&ctx, 0, sizeof(ctx));
 	struct Mem t;
-	memset(&t, 0, sizeof(t));
-	t.type = MEM_TYPE_NULL;
-	assert(t.flags == 0);
-	t.db = pIn1->db;
+	mem_create(&t);
 	ctx.pOut = &t;
 	ctx.pMem = pIn1;
 	ctx.func = pOp->p4.func;


New patch:

commit d30d6b78e5c9486fdc1986e74e6340e11a960f7f
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 17:19:03 2021 +0300

    sql: remove sql_vdbemem_finalize()
    
    We don't need this function, since it is easier to call finalizers
    directly. This patch also allows us to make further simplifications.
    
    Needed for #4145

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index f64cbe10a..e59935672 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -217,15 +217,11 @@ mem_create(struct Mem *mem)
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
-	    (mem->flags & MEM_Dyn) != 0) {
-		if (mem->type == MEM_TYPE_AGG)
-			sql_vdbemem_finalize(mem, mem->u.func);
-		assert(mem->type != MEM_TYPE_AGG);
+	if (mem->type == MEM_TYPE_FRAME || (mem->flags & MEM_Dyn) != 0) {
 		if ((mem->flags & MEM_Dyn) != 0) {
 			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
 			mem->xDel((void *)mem->z);
-		} else if (mem->type == MEM_TYPE_FRAME) {
+		} else {
 			struct VdbeFrame *frame = mem->u.pFrame;
 			frame->pParent = frame->v->pDelFrame;
 			frame->v->pDelFrame = frame;
@@ -3067,31 +3063,6 @@ sqlVdbeMemTooBig(Mem * p)
 	return 0;
 }
 
-int
-sql_vdbemem_finalize(struct Mem *mem, struct func *func)
-{
-	assert(func != NULL);
-	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
-	sql_context ctx;
-	memset(&ctx, 0, sizeof(ctx));
-	Mem t;
-	memset(&t, 0, sizeof(t));
-	t.type = MEM_TYPE_NULL;
-	assert(t.flags == 0);
-	t.db = mem->db;
-	ctx.pOut = &t;
-	ctx.pMem = mem;
-	ctx.func = func;
-	((struct func_sql_builtin *)func)->finalize(&ctx);
-	assert((mem->flags & MEM_Dyn) == 0);
-	if (mem->szMalloc > 0)
-		sqlDbFree(mem->db, mem->zMalloc);
-	memcpy(mem, &t, sizeof(t));
-	return ctx.is_aborted ? -1 : 0;
-}
-
 int
 sqlVdbeRecordCompareMsgpack(const void *key1,
 				struct UnpackedRecord *key2)
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 54a1e931b..c0993e573 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -969,18 +969,6 @@ int sqlVdbeMemTooBig(Mem *);
 #define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
 			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
 
-/** MEM manipulate functions. */
-
-/**
- * Memory cell mem contains the context of an aggregate function.
- * This routine calls the finalize method for that function. The
- * result of the aggregate is stored back into mem.
- *
- * Returns -1 if the finalizer reports an error. 0 otherwise.
- */
-int
-sql_vdbemem_finalize(struct Mem *mem, struct func *func);
-
 /**
  * Perform comparison of two tuples: unpacked (key1) and packed (key2)
  *
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 12dc9126b..b101fc6c2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4204,16 +4204,29 @@ case OP_AggStep: {
  * the step function was not previously called.
  */
 case OP_AggFinal: {
-	Mem *pMem;
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
-	pMem = &aMem[pOp->p1];
-	assert(mem_is_null(pMem) || mem_is_agg(pMem));
-	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
+	struct Mem *pIn1 = &aMem[pOp->p1];
+	assert(mem_is_null(pIn1) || mem_is_agg(pIn1));
+
+	struct sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	struct Mem t;
+	mem_create(&t);
+	ctx.pOut = &t;
+	ctx.pMem = pIn1;
+	ctx.func = pOp->p4.func;
+	func->finalize(&ctx);
+	assert((pIn1->flags & MEM_Dyn) == 0);
+	if (pIn1->szMalloc > 0)
+		sqlDbFree(pIn1->db, pIn1->zMalloc);
+	memcpy(pIn1, &t, sizeof(t));
+
+	if (ctx.is_aborted)
 		goto abort_due_to_error;
-	UPDATE_MAX_BLOBSIZE(pMem);
-	if (sqlVdbeMemTooBig(pMem)) {
+	UPDATE_MAX_BLOBSIZE(pIn1);
+	if (sqlVdbeMemTooBig(pIn1) != 0)
 		goto too_big;
-	}
 	break;
 }
 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM()
  2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 11:17     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:17 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below.

On Thu, Sep 23, 2021 at 12:48:13AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index b4461c6ee..b06955302 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -53,6 +53,29 @@
> >  static struct mh_strnptr_t *built_in_functions = NULL;
> >  static struct func_sql_builtin **functions;
> >  
> > +/** Implementation of the SUM() function. */
> > +static void
> > +step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
> > +{
> > +	assert(argc == 1);
> > +	(void)argc;
> > +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
> > +	if (argv[0]->type == MEM_TYPE_NULL)
> 
> Probably we should use mem_is_null() where possible. To avoid
> Mem members manual touch out of Mem.h and Mem.c. Here and in other
> places, other commits. Sorry I didn't notice it before.
Fixed here and in all similar patches. I will try to follow this rule in all
following patches.

Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b06955302..4cc6f378b 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -59,10 +59,10 @@ step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	if (mem_is_null(argv[0]))
 		return;
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	if (mem_is_null(ctx->pMem))
 		return mem_copy_as_ephemeral(ctx->pMem, argv[0]);
 	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
 		ctx->is_aborted = true;
@@ -72,7 +72,7 @@ step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
 static void
 fin_sum(struct sql_context *ctx)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 

New patch:

commit 4e8b6f54349e1cfb53fab5dd39b41e77f9a7350a
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 17:50:21 2021 +0300

    sql: rework SUM()
    
    This patch makes SUM() accept DOUBLE values by default. Also, after this
    patch SUM() will be able to work with DECIMAL values.
    
    Part of #4145
    Part of #6355

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b4461c6ee..4cc6f378b 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -53,6 +53,29 @@
 static struct mh_strnptr_t *built_in_functions = NULL;
 static struct func_sql_builtin **functions;
 
+/** Implementation of the SUM() function. */
+static void
+step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	if (mem_is_null(argv[0]))
+		return;
+	if (mem_is_null(ctx->pMem))
+		return mem_copy_as_ephemeral(ctx->pMem, argv[0]);
+	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the SUM() function. */
+static void
+fin_sum(struct sql_context *ctx)
+{
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1654,17 +1677,6 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 		context->is_aborted = true;
 }
 
-static void
-sumFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0)
-		mem_set_null(context->pOut);
-	else
-		mem_copy_as_ephemeral(context->pOut, &p->mem);
-}
-
 static void
 avgFinalize(sql_context * context)
 {
@@ -2113,8 +2125,9 @@ static struct sql_func_definition definitions[] = {
 	{"SUBSTR", 3,
 	 {FIELD_TYPE_VARBINARY, FIELD_TYPE_INTEGER, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_VARBINARY, substrFunc, NULL},
-	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, sum_step, sumFinalize},
-	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step, sumFinalize},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
 	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, sum_step,
 	 totalFinalize},
 	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b101fc6c2..66ac2c4f3 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4207,7 +4207,6 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
 	struct Mem *pIn1 = &aMem[pOp->p1];
-	assert(mem_is_null(pIn1) || mem_is_agg(pIn1));
 
 	struct sql_context ctx;
 	memset(&ctx, 0, sizeof(ctx));
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 6fae811dc..8daf41c1d 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,8 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(52)
+test:plan(55)
+
+local dec = require('decimal')
 
 --
 -- Make sure that number of arguments check is checked properly for SQL built-in
@@ -455,7 +457,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT SUM(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 test:do_catchsql_test(
@@ -545,4 +547,33 @@ test:do_test(
         {name = "COLUMN_2", type = "scalar"},
     })
 
+-- Make sure SUM() accepts and returns DOUBLE by default.
+test:do_test(
+    "builtins-4.1.1",
+    function()
+        return box.execute([[SELECT SUM(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.1.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT SUM(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure SUM() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.1.3",
+    [[
+        SELECT SUM(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        dec.new(124.432)
+    }
+)
+
 test:finish_test()

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL() Mergen Imeev via Tarantool-patches
@ 2021-09-25 11:20   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:20 UTC (permalink / raw)
  To: v.shpilevoy, tarantool-patches

Thank you for the review! I replaced manual checks of MEM type by mem_is_*()
functions. Diff and new patch below.

On Tue, Sep 21, 2021 at 01:59:18PM +0300, Mergen Imeev via Tarantool-patches wrote:
> This patch makes TOTAL() accept DOUBLE values by default. Also, after
> this patch TOTAL() will be able to work with DECIMAL values.
> 
> Part of #4145
> Part of #6355
> ---
>  src/box/sql/func.c                       | 47 ++++++++++++++++--------
>  test/sql-tap/built-in-functions.test.lua | 33 ++++++++++++++++-
>  2 files changed, 63 insertions(+), 17 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index b06955302..12a6a5a2c 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -76,6 +76,32 @@ fin_sum(struct sql_context *ctx)
>  	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
>  }
>  
> +/** Implementation of the TOTAL() function. */
> +static void
> +step_total(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1);
> +	(void)argc;
> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
> +	if (argv[0]->type == MEM_TYPE_NULL)
> +		return;
> +	if (ctx->pMem->type == MEM_TYPE_NULL)
> +		mem_set_double(ctx->pMem, 0.0);
> +	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
> +		ctx->is_aborted = true;
> +}
> +
> +/** Finalizer for the TOTAL() function. */
> +static void
> +fin_total(struct sql_context *ctx)
> +{
> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_double(ctx->pMem));
> +	if (ctx->pMem->type == MEM_TYPE_NULL)
> +		mem_set_double(ctx->pOut, 0.0);
> +	else
> +		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
> +}
> +
>  static const unsigned char *
>  mem_as_ustr(struct Mem *mem)
>  {
> @@ -1693,17 +1719,6 @@ avgFinalize(sql_context * context)
>  		context->is_aborted = true;
>  }
>  
> -static void
> -totalFinalize(sql_context * context)
> -{
> -	SumCtx *p;
> -	p = sql_aggregate_context(context, 0);
> -	if (p == NULL || p->count == 0)
> -		mem_set_double(context->pOut, 0.0);
> -	else
> -		mem_copy_as_ephemeral(context->pOut, &p->mem);
> -}
> -
>  /*
>   * The following structure keeps track of state information for the
>   * count() aggregate function.
> @@ -2128,10 +2143,12 @@ static struct sql_func_definition definitions[] = {
>  	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
>  	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
>  	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
> -	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, sum_step,
> -	 totalFinalize},
> -	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step,
> -	 totalFinalize},
> +	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
> +	 fin_total},
> +	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
> +	 fin_total},
> +	{"TOTAL", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DOUBLE, step_total,
> +	 fin_total},
>  
>  	{"TRIM", 2, {FIELD_TYPE_STRING, FIELD_TYPE_INTEGER},
>  	 FIELD_TYPE_STRING, trim_func, NULL},
> diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
> index 8daf41c1d..507d06549 100755
> --- a/test/sql-tap/built-in-functions.test.lua
> +++ b/test/sql-tap/built-in-functions.test.lua
> @@ -1,6 +1,6 @@
>  #!/usr/bin/env tarantool
>  local test = require("sqltester")
> -test:plan(55)
> +test:plan(58)
>  
>  local dec = require('decimal')
>  
> @@ -497,7 +497,7 @@ test:do_test(
>          local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {'1'})}
>          return {tostring(res[3])}
>      end, {
> -        "Type mismatch: can not convert string('1') to integer"
> +        "Type mismatch: can not convert string('1') to double"
>      })
>  
>  --
> @@ -576,4 +576,33 @@ test:do_execsql_test(
>      }
>  )
>  
> +-- Make sure TOTAL() accepts DOUBLE by default.
> +test:do_test(
> +    "builtins-4.2.1",
> +    function()
> +        return box.execute([[SELECT TOTAL(?);]], {1}).metadata
> +    end, {
> +        {name = "COLUMN_1", type = "double"},
> +    })
> +
> +test:do_test(
> +    "builtins-4.2.2",
> +    function()
> +        local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {-1ULL})}
> +        return {tostring(res[3])}
> +    end, {
> +        "Type mismatch: can not convert integer(18446744073709551615) to double"
> +    })
> +
> +-- Make sure TOTAL() works with DECIMAL properly.
> +test:do_execsql_test(
> +    "builtins-4.2.3",
> +    [[
> +        SELECT TOTAL(cast(column_2 as DECIMAL)) from (values(1), (123.432));
> +    ]],
> +    {
> +        124.432
> +    }
> +)
> +
>  test:finish_test()
> -- 
> 2.25.1
> 


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6cd884221..d0606744c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -82,10 +82,10 @@ step_total(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_num(ctx->pMem));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	if (mem_is_null(argv[0]))
 		return;
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	if (mem_is_null(ctx->pMem))
 		mem_set_double(ctx->pMem, 0.0);
 	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
 		ctx->is_aborted = true;
@@ -95,8 +95,8 @@ step_total(struct sql_context *ctx, int argc, struct Mem **argv)
 static void
 fin_total(struct sql_context *ctx)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_double(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_double(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
 		mem_set_double(ctx->pOut, 0.0);
 	else
 		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);


New patch:

commit 3d6db454539f77cd9c3d96aacdc8c4ff3a74ef2f
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:02:59 2021 +0300

    sql: rework TOTAL()
    
    This patch makes TOTAL() accept DOUBLE values by default. Also, after
    this patch TOTAL() will be able to work with DECIMAL values.
    
    Part of #4145
    Part of #6355

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 4cc6f378b..d0606744c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -76,6 +76,32 @@ fin_sum(struct sql_context *ctx)
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the TOTAL() function. */
+static void
+step_total(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	if (mem_is_null(argv[0]))
+		return;
+	if (mem_is_null(ctx->pMem))
+		mem_set_double(ctx->pMem, 0.0);
+	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the TOTAL() function. */
+static void
+fin_total(struct sql_context *ctx)
+{
+	assert(mem_is_null(ctx->pMem) || mem_is_double(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
+		mem_set_double(ctx->pOut, 0.0);
+	else
+		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1693,17 +1719,6 @@ avgFinalize(sql_context * context)
 		context->is_aborted = true;
 }
 
-static void
-totalFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0)
-		mem_set_double(context->pOut, 0.0);
-	else
-		mem_copy_as_ephemeral(context->pOut, &p->mem);
-}
-
 /*
  * The following structure keeps track of state information for the
  * count() aggregate function.
@@ -2128,10 +2143,12 @@ static struct sql_func_definition definitions[] = {
 	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
 	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
 	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
-	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, sum_step,
-	 totalFinalize},
-	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step,
-	 totalFinalize},
+	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
+	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
+	{"TOTAL", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
 
 	{"TRIM", 2, {FIELD_TYPE_STRING, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_STRING, trim_func, NULL},
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 8daf41c1d..507d06549 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(55)
+test:plan(58)
 
 local dec = require('decimal')
 
@@ -497,7 +497,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 --
@@ -576,4 +576,33 @@ test:do_execsql_test(
     }
 )
 
+-- Make sure TOTAL() accepts DOUBLE by default.
+test:do_test(
+    "builtins-4.2.1",
+    function()
+        return box.execute([[SELECT TOTAL(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.2.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT TOTAL(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure TOTAL() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.2.3",
+    [[
+        SELECT TOTAL(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        124.432
+    }
+)
+
 test:finish_test()

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG()
  2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 11:32     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:32 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On Thu, Sep 23, 2021 at 12:48:58AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 12a6a5a2c..b5f154fb1 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -102,6 +102,54 @@ fin_total(struct sql_context *ctx)
> >  		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
> >  }
> >  
> > +/** Implementation of the AVG() function. */
> > +static void
> > +step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
> > +{
> > +	assert(argc == 1);
> > +	(void)argc;
> > +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
> > +	if (argv[0]->type == MEM_TYPE_NULL)
> > +		return;
> > +	struct Mem *mem;
> > +	uint32_t *count;
> > +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> > +		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
> > +		mem = sqlDbMallocRawNN(sql_get(), size);
> 
> 1. Where is it deleted? Can't find.
> 
It will be set as allocated to MEM and will be released during mem_destroy().
However, I agree that it is obvious that this new MEM shouldn't be cleared
first. I added mem_destroy() for this mem, though it will work as mem_clear()
due to numbers being scalars.

> > +		if (mem == NULL) {
> > +			ctx->is_aborted = true;
> > +			return;
> > +		}
> > +		count = (uint32_t *)(mem + 1);
> > +		mem_create(mem);
> > +		*count = 1;
> > +		mem_copy_as_ephemeral(mem, argv[0]);
> > +		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
> > +		return;
> > +	}
> > diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
> > index 507d06549..08a63b86d 100755
> > --- a/test/sql-tap/built-in-functions.test.lua
> > +++ b/test/sql-tap/built-in-functions.test.lua
> > @@ -605,4 +605,33 @@ test:do_execsql_test(
> >      }
> >  )
> >  
> > +-- Make sure AVG() accepts and returns DOUBLE by default.
> > +test:do_test(
> > +    "builtins-4.1.1",
> > +    function()
> > +        return box.execute([[SELECT AVG(?);]], {1}).metadata
> > +    end, {
> > +        {name = "COLUMN_1", type = "double"},
> > +    })
> > +
> > +test:do_test(
> > +    "builtins-4.1.2",
> > +    function()
> > +        local res = {pcall(box.execute, [[SELECT AVG(?);]], {-1ULL})}
> > +        return {tostring(res[3])}
> > +    end, {
> > +        "Type mismatch: can not convert integer(18446744073709551615) to double"
> > +    })
> > +
> > +-- Make sure AVG() works with DECIMAL properly.
> > +test:do_execsql_test(
> > +    "builtins-4.1.3",
> > +    [[
> > +        SELECT AVG(cast(column_2 as DECIMAL)) from (values(1), (123.432));
> 
> 2. I don't understand how does it work. Why not column_1? Why does it fail?
> 
> tarantool> box.execute('SELECT column_1 from (values(1), (123.432));')
> ---
> - null
> - Can’t resolve field 'COLUMN_1'
> ...
> 
> Isn't there 2 tuples [1], and [123.432] with just 1 column each?
> And why does it work when I delete one of the values?
> 
> tarantool> box.execute('SELECT column_1 from (values(1));')
> ---
> - metadata:
>   - name: COLUMN_1
>     type: integer
>   rows:
>   - [1]
> ...
> 
I believe this is a bug that was introduced in patch
7bfcf57e44028ea425d0d274b9ca402c196716f9 .

Before patch:

tarantool> box.execute([[values(1), (2), (3);]])
---
- metadata:
  - name: column1
    type: integer
  rows:
  - [1]
  - [2]
  - [3]
...

tarantool> box.execute([[values(1), (2), (3), (4);]])
---
- metadata:
  - name: column1
    type: integer
  rows:
  - [1]
  - [2]
  - [3]
  - [4]
...

After patch:

tarantool> box.execute([[values(1), (2), (3);]])
---
- metadata:
  - name: COLUMN_3
    type: integer
  rows:
  - [1]
  - [2]
  - [3]
...

tarantool> box.execute([[values(1), (2), (3), (4);]])
---
- metadata:
  - name: COLUMN_4
    type: integer
  rows:
  - [1]
  - [2]
  - [3]
  - [4]
...

Not sure if there is an issue for this.

> > +    ]],
> > +    {
> > +        dec.new(62.216)
> > +    }
> > +)
> > +
> >  test:finish_test()
> > 


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 77a8d637a..e436ffbe1 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -108,12 +108,12 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
-	if (argv[0]->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
+	if (mem_is_null(argv[0]))
 		return;
 	struct Mem *mem;
 	uint32_t *count;
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
+	if (mem_is_null(ctx->pMem)) {
 		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
 		mem = sqlDbMallocRawNN(sql_get(), size);
 		if (mem == NULL) {
@@ -138,15 +138,19 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 static void
 fin_avg(struct sql_context *ctx)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_bin(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
 		return mem_set_null(ctx->pOut);
-	struct Mem *mem = (struct Mem *)ctx->pMem->z;
-	uint32_t *count = (uint32_t *)(mem + 1);
-	struct Mem mem_count;
-	mem_create(&mem_count);
-	mem_set_uint(&mem_count, *count);
-	if (mem_div(mem, &mem_count, ctx->pOut) != 0)
+	struct Mem *tmp = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(tmp + 1);
+	struct Mem sum;
+	mem_create(&sum);
+	mem_copy_as_ephemeral(&sum, tmp);
+	mem_destroy(tmp);
+	struct Mem count;
+	mem_create(&count);
+	mem_set_uint(&count, *count_val);
+	if (mem_div(&sum, &count, ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 

New patch:

commit 2b5ae06ceadff5e121e4508ffc2b7913f6d84e79
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:19:53 2021 +0300

    sql: rework AVG()
    
    This patch makes AVG() accept DOUBLE values by default. Also, after this
    patch AVG() will be able to work with DECIMAL values.
    
    Part of #4145
    Part of #6355

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index d0606744c..e436ffbe1 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -102,6 +102,58 @@ fin_total(struct sql_context *ctx)
 		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the AVG() function. */
+static void
+step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
+	if (mem_is_null(argv[0]))
+		return;
+	struct Mem *mem;
+	uint32_t *count;
+	if (mem_is_null(ctx->pMem)) {
+		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
+		mem = sqlDbMallocRawNN(sql_get(), size);
+		if (mem == NULL) {
+			ctx->is_aborted = true;
+			return;
+		}
+		count = (uint32_t *)(mem + 1);
+		mem_create(mem);
+		*count = 1;
+		mem_copy_as_ephemeral(mem, argv[0]);
+		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
+		return;
+	}
+	mem = (struct Mem *)ctx->pMem->z;
+	count = (uint32_t *)(mem + 1);
+	++*count;
+	if (mem_add(mem, argv[0], mem) != 0)
+		ctx->is_aborted = true;
+}
+
+/** Finalizer for the AVG() function. */
+static void
+fin_avg(struct sql_context *ctx)
+{
+	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
+		return mem_set_null(ctx->pOut);
+	struct Mem *tmp = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(tmp + 1);
+	struct Mem sum;
+	mem_create(&sum);
+	mem_copy_as_ephemeral(&sum, tmp);
+	mem_destroy(tmp);
+	struct Mem count;
+	mem_create(&count);
+	mem_set_uint(&count, *count_val);
+	if (mem_div(&sum, &count, ctx->pOut) != 0)
+		ctx->is_aborted = true;
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1656,69 +1708,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * An instance of the following structure holds the context of a
- * sum() or avg() aggregate computation.
- */
-typedef struct SumCtx SumCtx;
-struct SumCtx {
-	struct Mem mem;
-	uint32_t count;
-};
-
-/*
- * Routines used to compute the sum, average, and total.
- *
- * The SUM() function follows the (broken) SQL standard which means
- * that it returns NULL if it sums over no inputs.  TOTAL returns
- * 0.0 in that case.  In addition, TOTAL always returns a float where
- * SUM might return an integer if it never encounters a floating point
- * 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)
-{
-	assert(argc == 1);
-	UNUSED_PARAMETER(argc);
-	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
-	if (p == NULL) {
-		context->is_aborted = true;
-		return;
-	}
-	if (p->count == 0) {
-		mem_create(&p->mem);
-		assert(context->func->def->returns == FIELD_TYPE_INTEGER ||
-		       context->func->def->returns == FIELD_TYPE_DOUBLE);
-		if (context->func->def->returns == FIELD_TYPE_INTEGER)
-			mem_set_uint(&p->mem, 0);
-		else
-			mem_set_double(&p->mem, 0.0);
-	}
-	if (argv[0]->type == MEM_TYPE_NULL)
-		return;
-	++p->count;
-	assert(mem_is_num(argv[0]));
-	if (mem_add(&p->mem, argv[0], &p->mem) != 0)
-		context->is_aborted = true;
-}
-
-static void
-avgFinalize(sql_context * context)
-{
-	SumCtx *p;
-	p = sql_aggregate_context(context, 0);
-	if (p == NULL || p->count == 0) {
-		mem_set_null(context->pOut);
-		return;
-	}
-	struct Mem mem;
-	mem_create(&mem);
-	mem_set_uint(&mem, p->count);
-	if (mem_div(&p->mem, &mem, context->pOut) != 0)
-		context->is_aborted = true;
-}
-
 /*
  * The following structure keeps track of state information for the
  * count() aggregate function.
@@ -2015,8 +2004,9 @@ struct sql_func_definition {
 static struct sql_func_definition definitions[] = {
 	{"ABS", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, absFunc, NULL},
 	{"ABS", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, absFunc, NULL},
-	{"AVG", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, sum_step, avgFinalize},
-	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, sum_step, avgFinalize},
+	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_avg, fin_avg},
 	{"CHAR", -1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_STRING, charFunc, NULL},
 	{"CHAR_LENGTH", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_INTEGER, lengthFunc,
 	 NULL},
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
index 507d06549..08a63b86d 100755
--- a/test/sql-tap/built-in-functions.test.lua
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(58)
+test:plan(61)
 
 local dec = require('decimal')
 
@@ -477,7 +477,7 @@ test:do_test(
         local res = {pcall(box.execute, [[SELECT AVG(?);]], {'1'})}
         return {tostring(res[3])}
     end, {
-        "Type mismatch: can not convert string('1') to integer"
+        "Type mismatch: can not convert string('1') to double"
     })
 
 test:do_catchsql_test(
@@ -605,4 +605,33 @@ test:do_execsql_test(
     }
 )
 
+-- Make sure AVG() accepts and returns DOUBLE by default.
+test:do_test(
+    "builtins-4.1.1",
+    function()
+        return box.execute([[SELECT AVG(?);]], {1}).metadata
+    end, {
+        {name = "COLUMN_1", type = "double"},
+    })
+
+test:do_test(
+    "builtins-4.1.2",
+    function()
+        local res = {pcall(box.execute, [[SELECT AVG(?);]], {-1ULL})}
+        return {tostring(res[3])}
+    end, {
+        "Type mismatch: can not convert integer(18446744073709551615) to double"
+    })
+
+-- Make sure AVG() works with DECIMAL properly.
+test:do_execsql_test(
+    "builtins-4.1.3",
+    [[
+        SELECT AVG(cast(column_2 as DECIMAL)) from (values(1), (123.432));
+    ]],
+    {
+        dec.new(62.216)
+    }
+)
+
 test:finish_test()

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT() Mergen Imeev via Tarantool-patches
@ 2021-09-25 11:34   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:34 UTC (permalink / raw)
  To: v.shpilevoy, tarantool-patches

Thank you for the review! I replaced manual checks of MEM type by mem_is_*()
functions. Diff and new patch below.

On Tue, Sep 21, 2021 at 01:59:23PM +0300, Mergen Imeev via Tarantool-patches wrote:
> This patch does some refactoring for the SQL built-in aggregate function
> COUNT().
> 
> Part of #4145
> ---
>  src/box/sql/func.c | 64 +++++++++++++++++++---------------------------
>  1 file changed, 26 insertions(+), 38 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index b5f154fb1..113df423a 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -150,6 +150,29 @@ fin_avg(struct sql_context *ctx)
>  		ctx->is_aborted = true;
>  }
>  
> +/** Implementation of the COUNT() function. */
> +static void
> +step_count(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 0 || argc == 1);
> +	if (ctx->pMem->type == MEM_TYPE_NULL)
> +		mem_set_uint(ctx->pMem, 0);
> +	if (argc == 1 && argv[0]->type == MEM_TYPE_NULL)
> +		return;
> +	assert(ctx->pMem->type == MEM_TYPE_UINT);
> +	++ctx->pMem->u.u;
> +}
> +
> +/** Finalizer for the COUNT() function. */
> +static void
> +fin_count(struct sql_context *ctx)
> +{
> +	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_uint(ctx->pMem));
> +	if (ctx->pMem->type == MEM_TYPE_NULL)
> +		return mem_set_uint(ctx->pOut, 0);
> +	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
> +}
> +
>  static const unsigned char *
>  mem_as_ustr(struct Mem *mem)
>  {
> @@ -1704,41 +1727,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
>  	}
>  }
>  
> -/*
> - * The following structure keeps track of state information for the
> - * count() aggregate function.
> - */
> -typedef struct CountCtx CountCtx;
> -struct CountCtx {
> -	i64 n;
> -};
> -
> -/*
> - * Routines to implement the count() aggregate function.
> - */
> -static void
> -countStep(sql_context * context, int argc, sql_value ** argv)
> -{
> -	CountCtx *p;
> -	if (argc != 0 && argc != 1) {
> -		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> -			 "COUNT", "0 or 1", argc);
> -		context->is_aborted = true;
> -		return;
> -	}
> -	p = sql_aggregate_context(context, sizeof(*p));
> -	if ((argc == 0 || !mem_is_null(argv[0])) && p != NULL)
> -		p->n++;
> -}
> -
> -static void
> -countFinalize(sql_context * context)
> -{
> -	CountCtx *p;
> -	p = sql_aggregate_context(context, 0);
> -	sql_result_uint(context, p ? p->n : 0);
> -}
> -
>  /*
>   * Routines to implement min() and max() aggregate functions.
>   */
> @@ -2008,9 +1996,9 @@ static struct sql_func_definition definitions[] = {
>  	 NULL},
>  	{"COALESCE", -1, {FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR, sql_builtin_stub,
>  	 NULL},
> -	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, countStep, countFinalize},
> -	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, countStep,
> -	 countFinalize},
> +	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, step_count, fin_count},
> +	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, step_count,
> +	 fin_count},
>  
>  	{"GREATEST", -1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxFunc,
>  	 NULL},
> -- 
> 2.25.1
> 


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b4c25c935..00db6512a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -159,11 +159,11 @@ static void
 step_count(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 0 || argc == 1);
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	if (mem_is_null(ctx->pMem))
 		mem_set_uint(ctx->pMem, 0);
-	if (argc == 1 && argv[0]->type == MEM_TYPE_NULL)
+	if (argc == 1 && mem_is_null(argv[0]))
 		return;
-	assert(ctx->pMem->type == MEM_TYPE_UINT);
+	assert(mem_is_uint(ctx->pMem));
 	++ctx->pMem->u.u;
 }
 
@@ -171,8 +171,8 @@ step_count(struct sql_context *ctx, int argc, struct Mem **argv)
 static void
 fin_count(struct sql_context *ctx)
 {
-	assert(ctx->pMem->type == MEM_TYPE_NULL || mem_is_uint(ctx->pMem));
-	if (ctx->pMem->type == MEM_TYPE_NULL)
+	assert(mem_is_null(ctx->pMem) || mem_is_uint(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
 		return mem_set_uint(ctx->pOut, 0);
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }


New patch:

commit 000ac8430821d33aaa939d426c30c1de2d5a7cab
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:26:04 2021 +0300

    sql: rework COUNT()
    
    This patch does some refactoring for the SQL built-in aggregate function
    COUNT().
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e436ffbe1..00db6512a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -154,6 +154,29 @@ fin_avg(struct sql_context *ctx)
 		ctx->is_aborted = true;
 }
 
+/** Implementation of the COUNT() function. */
+static void
+step_count(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 0 || argc == 1);
+	if (mem_is_null(ctx->pMem))
+		mem_set_uint(ctx->pMem, 0);
+	if (argc == 1 && mem_is_null(argv[0]))
+		return;
+	assert(mem_is_uint(ctx->pMem));
+	++ctx->pMem->u.u;
+}
+
+/** Finalizer for the COUNT() function. */
+static void
+fin_count(struct sql_context *ctx)
+{
+	assert(mem_is_null(ctx->pMem) || mem_is_uint(ctx->pMem));
+	if (mem_is_null(ctx->pMem))
+		return mem_set_uint(ctx->pOut, 0);
+	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1708,41 +1731,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * The following structure keeps track of state information for the
- * count() aggregate function.
- */
-typedef struct CountCtx CountCtx;
-struct CountCtx {
-	i64 n;
-};
-
-/*
- * Routines to implement the count() aggregate function.
- */
-static void
-countStep(sql_context * context, int argc, sql_value ** argv)
-{
-	CountCtx *p;
-	if (argc != 0 && argc != 1) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "COUNT", "0 or 1", argc);
-		context->is_aborted = true;
-		return;
-	}
-	p = sql_aggregate_context(context, sizeof(*p));
-	if ((argc == 0 || !mem_is_null(argv[0])) && p != NULL)
-		p->n++;
-}
-
-static void
-countFinalize(sql_context * context)
-{
-	CountCtx *p;
-	p = sql_aggregate_context(context, 0);
-	sql_result_uint(context, p ? p->n : 0);
-}
-
 /*
  * Routines to implement min() and max() aggregate functions.
  */
@@ -2012,9 +2000,9 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 	{"COALESCE", -1, {FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR, sql_builtin_stub,
 	 NULL},
-	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, countStep, countFinalize},
-	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, countStep,
-	 countFinalize},
+	{"COUNT", 0, {}, FIELD_TYPE_INTEGER, step_count, fin_count},
+	{"COUNT", 1, {FIELD_TYPE_ANY}, FIELD_TYPE_INTEGER, step_count,
+	 fin_count},
 
 	{"GREATEST", -1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxFunc,
 	 NULL},

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX()
  2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX() Mergen Imeev via Tarantool-patches
@ 2021-09-25 11:36   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:36 UTC (permalink / raw)
  To: v.shpilevoy, tarantool-patches

Thank you for the review! I replaced manual checks of MEM type by mem_is_*()
functions. Diff and new patch below.

On Tue, Sep 21, 2021 at 01:59:26PM +0300, Mergen Imeev via Tarantool-patches wrote:
> This patch does some refactoring for the SQL built-in aggregate
> functions MIN() and MAX().
> 
> Part of #4145
> ---
>  src/box/sql/func.c | 162 +++++++++++++++++++--------------------------
>  src/box/sql/vdbe.c |   1 -
>  2 files changed, 69 insertions(+), 94 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 113df423a..f699aa927 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -173,6 +173,46 @@ fin_count(struct sql_context *ctx)
>  	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
>  }
>  
> +/** Implementation of the MIN() and MAX() functions. */
> +static void
> +step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1);
> +	(void)argc;
> +	if (argv[0]->type == MEM_TYPE_NULL) {
> +		if (ctx->pMem->type != MEM_TYPE_NULL)
> +			ctx->skipFlag = 1;
> +		return;
> +	}
> +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> +		if (mem_copy(ctx->pMem, argv[0]) != 0)
> +			ctx->is_aborted = true;
> +		return;
> +	}
> +
> +	uint32_t flags = ((struct func_sql_builtin *)ctx->func)->flags;
> +	bool is_max = (flags & SQL_FUNC_MAX) != 0;
> +	/*
> +	 * 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.
> +	 */
> +	int cmp = mem_cmp_scalar(ctx->pMem, argv[0], ctx->coll);
> +	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
> +		if (mem_copy(ctx->pMem, argv[0]) != 0)
> +			ctx->is_aborted = true;
> +		return;
> +	}
> +	ctx->skipFlag = 1;
> +}
> +
> +/** Finalizer for the MIN() and MAX() functions. */
> +static void
> +fin_minmax(struct sql_context *ctx)
> +{
> +	mem_copy(ctx->pOut, ctx->pMem);
> +}
> +
>  static const unsigned char *
>  mem_as_ustr(struct Mem *mem)
>  {
> @@ -220,16 +260,6 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
>  	mem_set_uuid(ctx->pOut, &uuid);
>  }
>  
> -/*
> - * Indicate that the accumulator load should be skipped on this
> - * iteration of the aggregate loop.
> - */
> -static void
> -sqlSkipAccumulatorLoad(sql_context * context)
> -{
> -	context->skipFlag = 1;
> -}
> -
>  /*
>   * Implementation of the non-aggregate min() and max() functions
>   */
> @@ -1727,60 +1757,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
>  	}
>  }
>  
> -/*
> - * Routines to implement min() and max() aggregate functions.
> - */
> -static void
> -minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> -{
> -	Mem *pArg = (Mem *) argv[0];
> -	Mem *pBest;
> -	UNUSED_PARAMETER(NotUsed);
> -
> -	struct func_sql_builtin *func =
> -		(struct func_sql_builtin *)context->func;
> -	pBest = sql_context_agg_mem(context);
> -	if (!pBest)
> -		return;
> -
> -	if (mem_is_null(argv[0])) {
> -		if (!mem_is_null(pBest))
> -			sqlSkipAccumulatorLoad(context);
> -	} else if (!mem_is_null(pBest)) {
> -		struct coll *pColl = context->coll;
> -		/*
> -		 * 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.
> -		 */
> -		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
> -		int cmp = mem_cmp_scalar(pBest, pArg, pColl);
> -		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
> -			if (mem_copy(pBest, pArg) != 0)
> -				context->is_aborted = true;
> -		} else {
> -			sqlSkipAccumulatorLoad(context);
> -		}
> -	} else {
> -		pBest->db = sql_context_db_handle(context);
> -		if (mem_copy(pBest, pArg) != 0)
> -			context->is_aborted = true;
> -	}
> -}
> -
> -static void
> -minMaxFinalize(sql_context * context)
> -{
> -	struct Mem *mem = context->pMem;
> -	struct Mem *res;
> -	if (mem_get_agg(mem, (void **)&res) != 0)
> -		return;
> -	if (!mem_is_null(res))
> -		sql_result_value(context, res);
> -	mem_destroy(res);
> -}
> -
>  /*
>   * group_concat(EXPR, ?SEPARATOR?)
>   */
> @@ -2052,35 +2028,35 @@ static struct sql_func_definition definitions[] = {
>  	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
>  	 NULL},
>  
> -	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
> -	 minMaxFinalize},
> -	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
> -	 minMaxFinalize},
> -
> -	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
> -	 minMaxFinalize},
> -	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
> -	 minMaxFinalize},
> +	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
> +	 fin_minmax},
> +	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
> +	 fin_minmax},
> +
> +	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
> +	 fin_minmax},
> +	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
> +	 fin_minmax},
>  
>  	{"NULLIF", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
>  	 nullifFunc, NULL},
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index a74ee29ea..8804e3d18 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -4169,7 +4169,6 @@ case OP_AggStep: {
>  	}
>  #endif
>  
> -	pMem->n++;
>  	mem_create(&t);
>  	pCtx->pOut = &t;
>  	pCtx->is_aborted = false;
> -- 
> 2.25.1
> 

Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 59cb9aca8..3708440e3 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -183,12 +183,12 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	if (argv[0]->type == MEM_TYPE_NULL) {
-		if (ctx->pMem->type != MEM_TYPE_NULL)
+	if (mem_is_null(argv[0])) {
+		if (!mem_is_null(ctx->pMem))
 			ctx->skipFlag = 1;
 		return;
 	}
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
+	if (mem_is_null(ctx->pMem)) {
 		if (mem_copy(ctx->pMem, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;



New patch:

commit 898e3104cedbe5f91c04d70a4d8436dbb9dd094d
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:32:19 2021 +0300

    sql: rework MIN() and MAX()
    
    This patch does some refactoring for the SQL built-in aggregate
    functions MIN() and MAX().
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 00db6512a..3708440e3 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -177,6 +177,46 @@ fin_count(struct sql_context *ctx)
 	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the MIN() and MAX() functions. */
+static void
+step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1);
+	(void)argc;
+	if (mem_is_null(argv[0])) {
+		if (!mem_is_null(ctx->pMem))
+			ctx->skipFlag = 1;
+		return;
+	}
+	if (mem_is_null(ctx->pMem)) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+
+	uint32_t flags = ((struct func_sql_builtin *)ctx->func)->flags;
+	bool is_max = (flags & SQL_FUNC_MAX) != 0;
+	/*
+	 * 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.
+	 */
+	int cmp = mem_cmp_scalar(ctx->pMem, argv[0], ctx->coll);
+	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+	ctx->skipFlag = 1;
+}
+
+/** Finalizer for the MIN() and MAX() functions. */
+static void
+fin_minmax(struct sql_context *ctx)
+{
+	mem_copy(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -224,16 +264,6 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
 	mem_set_uuid(ctx->pOut, &uuid);
 }
 
-/*
- * Indicate that the accumulator load should be skipped on this
- * iteration of the aggregate loop.
- */
-static void
-sqlSkipAccumulatorLoad(sql_context * context)
-{
-	context->skipFlag = 1;
-}
-
 /*
  * Implementation of the non-aggregate min() and max() functions
  */
@@ -1731,60 +1761,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * Routines to implement min() and max() aggregate functions.
- */
-static void
-minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
-{
-	Mem *pArg = (Mem *) argv[0];
-	Mem *pBest;
-	UNUSED_PARAMETER(NotUsed);
-
-	struct func_sql_builtin *func =
-		(struct func_sql_builtin *)context->func;
-	pBest = sql_context_agg_mem(context);
-	if (!pBest)
-		return;
-
-	if (mem_is_null(argv[0])) {
-		if (!mem_is_null(pBest))
-			sqlSkipAccumulatorLoad(context);
-	} else if (!mem_is_null(pBest)) {
-		struct coll *pColl = context->coll;
-		/*
-		 * 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.
-		 */
-		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
-		int cmp = mem_cmp_scalar(pBest, pArg, pColl);
-		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-			if (mem_copy(pBest, pArg) != 0)
-				context->is_aborted = true;
-		} else {
-			sqlSkipAccumulatorLoad(context);
-		}
-	} else {
-		pBest->db = sql_context_db_handle(context);
-		if (mem_copy(pBest, pArg) != 0)
-			context->is_aborted = true;
-	}
-}
-
-static void
-minMaxFinalize(sql_context * context)
-{
-	struct Mem *mem = context->pMem;
-	struct Mem *res;
-	if (mem_get_agg(mem, (void **)&res) != 0)
-		return;
-	if (!mem_is_null(res))
-		sql_result_value(context, res);
-	mem_destroy(res);
-}
-
 /*
  * group_concat(EXPR, ?SEPARATOR?)
  */
@@ -2056,35 +2032,35 @@ static struct sql_func_definition definitions[] = {
 	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
 	 NULL},
 
-	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
-	 minMaxFinalize},
-	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
-	 minMaxFinalize},
-
-	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, minmaxStep,
-	 minMaxFinalize},
-	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, minmaxStep,
-	 minMaxFinalize},
+	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
+	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
+	 fin_minmax},
+
+	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
+	 fin_minmax},
+	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
+	 fin_minmax},
 
 	{"NULLIF", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
 	 nullifFunc, NULL},
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 66ac2c4f3..ee07af9c2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4169,7 +4169,6 @@ case OP_AggStep: {
 	}
 #endif
 
-	pMem->n++;
 	mem_create(&t);
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT()
  2021-09-22 22:49   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 11:42     ` Mergen Imeev via Tarantool-patches
  2021-09-29  7:03       ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:42 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On Thu, Sep 23, 2021 at 12:49:52AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index f699aa927..001a8641c 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -213,6 +213,52 @@ fin_minmax(struct sql_context *ctx)
> >  	mem_copy(ctx->pOut, ctx->pMem);
> >  }
> >  
> > +/** Implementation of the GROUP_CONCAT() function. */
> > +static void
> > +step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
> > +{
> > +	assert(argc == 1 || argc == 2);
> > +	(void)argc;
> > +	if (argv[0]->type == MEM_TYPE_NULL)
> > +		return;
> > +	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
> > +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> > +		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
> 
> 1. What if the argument is zeroblob with no actual memory allocated yet?
> 
There will be '', which is wrong. I fixed this and added a test.

> > +			ctx->is_aborted = true;
> > +		return;
> > +	}
> > +	const char *sep = NULL;
> > +	int sep_len = 0;
> > +	if (argc == 1) {
> > +		sep = ",";
> > +		sep_len = 1;
> > +	} else if (argv[1]->type == MEM_TYPE_NULL) {
> > +		sep = "";
> > +		sep_len = 0;
> > +	} else {
> > +		assert(mem_is_same_type(argv[0], argv[0]));
> > +		sep = argv[1]->z;
> > +		sep_len = argv[1]->n;
> > +	}
> > +	if (sep_len > 0) {
> > +		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
> 
> 2. Will it work if sep_len == 0? If yes, then I would propose to
> drop the len check here and call the append always.
> 
Fixed. I moved this check to mem_append().

> > +			ctx->is_aborted = true;
> > +			return;
> > +		}


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 182fb85be..28094e258 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -223,34 +223,43 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1 || argc == 2);
 	(void)argc;
-	if (argv[0]->type == MEM_TYPE_NULL)
+	if (mem_is_null(argv[0]))
 		return;
 	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
-	if (ctx->pMem->type == MEM_TYPE_NULL) {
-		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
+	if (mem_is_null(ctx->pMem)) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
+	assert(!mem_is_zerobin(ctx->pMem));
 	const char *sep = NULL;
 	int sep_len = 0;
 	if (argc == 1) {
 		sep = ",";
 		sep_len = 1;
-	} else if (argv[1]->type == MEM_TYPE_NULL) {
+	} else if (mem_is_null(argv[1])) {
 		sep = "";
 		sep_len = 0;
 	} else {
-		assert(mem_is_same_type(argv[0], argv[0]));
+		assert(mem_is_same_type(argv[0], argv[1]));
 		sep = argv[1]->z;
 		sep_len = argv[1]->n;
 	}
-	if (sep_len > 0) {
-		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
-			ctx->is_aborted = true;
-			return;
-		}
+	if (mem_append(ctx->pMem, sep, sep_len) != 0) {
+		ctx->is_aborted = true;
+		return;
+	}
+	uint32_t size;
+	char *str;
+	if (mem_is_zerobin(argv[0])) {
+		size = argv[0]->u.nZero;
+		str = sqlDbMallocRawNN(sql_get(), size);
+		memset(str, 0, size);
+	} else {
+		size = argv[0]->n;
+		str = argv[0]->z;
 	}
-	if (mem_append(ctx->pMem, argv[0]->z, argv[0]->n) != 0) {
+	if (mem_append(ctx->pMem, str, size) != 0) {
 		ctx->is_aborted = true;
 		return;
 	}
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 416f27d69..bd8a8fe78 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(14680)
+test:plan(14681)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -2142,11 +2142,14 @@ test:do_execsql_test(
         -- </func-24.2>
     })
 
--- do_test func-24.3 {
---   execsql {
---     SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1
---   }
--- } {{this 2 program 3 is 4 free 5 software}}
+test:do_execsql_test(
+    "func-24.3",
+    [[
+        SELECT group_concat(zeroblob(10));
+    ]], {
+        '\0\0\0\0\0\0\0\0\0\0'
+    })
+
 test:do_execsql_test(
     "func-24.4",
     [[


New patch:

commit 5b8563e9b884c86885a3a91fc608fb144afb69a0
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:37:00 2021 +0300

    sql: rework GROUP_CONCAT()
    
    This patch simplifies SQL built-in aggregate function GROUP_CONCAT().
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3708440e3..28094e258 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -217,6 +217,61 @@ fin_minmax(struct sql_context *ctx)
 	mem_copy(ctx->pOut, ctx->pMem);
 }
 
+/** Implementation of the GROUP_CONCAT() function. */
+static void
+step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
+{
+	assert(argc == 1 || argc == 2);
+	(void)argc;
+	if (mem_is_null(argv[0]))
+		return;
+	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
+	if (mem_is_null(ctx->pMem)) {
+		if (mem_copy(ctx->pMem, argv[0]) != 0)
+			ctx->is_aborted = true;
+		return;
+	}
+	assert(!mem_is_zerobin(ctx->pMem));
+	const char *sep = NULL;
+	int sep_len = 0;
+	if (argc == 1) {
+		sep = ",";
+		sep_len = 1;
+	} else if (mem_is_null(argv[1])) {
+		sep = "";
+		sep_len = 0;
+	} else {
+		assert(mem_is_same_type(argv[0], argv[1]));
+		sep = argv[1]->z;
+		sep_len = argv[1]->n;
+	}
+	if (mem_append(ctx->pMem, sep, sep_len) != 0) {
+		ctx->is_aborted = true;
+		return;
+	}
+	uint32_t size;
+	char *str;
+	if (mem_is_zerobin(argv[0])) {
+		size = argv[0]->u.nZero;
+		str = sqlDbMallocRawNN(sql_get(), size);
+		memset(str, 0, size);
+	} else {
+		size = argv[0]->n;
+		str = argv[0]->z;
+	}
+	if (mem_append(ctx->pMem, str, size) != 0) {
+		ctx->is_aborted = true;
+		return;
+	}
+}
+
+/** Finalizer for the GROUP_CONCAT() function. */
+static void
+fin_group_concat(struct sql_context *ctx)
+{
+	mem_copy(ctx->pOut, ctx->pMem);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1761,73 +1816,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
-/*
- * group_concat(EXPR, ?SEPARATOR?)
- */
-static void
-groupConcatStep(sql_context * context, int argc, sql_value ** argv)
-{
-	const char *zVal;
-	StrAccum *pAccum;
-	const char *zSep;
-	int nVal, nSep;
-	if (argc != 1 && argc != 2) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "GROUP_CONCAT", "1 or 2", argc);
-		context->is_aborted = true;
-		return;
-	}
-	if (mem_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 = mem_as_str0(argv[1]);
-				nSep = mem_len_unsafe(argv[1]);
-			} else {
-				zSep = ",";
-				nSep = 1;
-			}
-			if (zSep)
-				sqlStrAccumAppend(pAccum, zSep, nSep);
-		}
-		zVal = mem_as_str0(argv[0]);
-		nVal = mem_len_unsafe(argv[0]);
-		if (zVal)
-			sqlStrAccumAppend(pAccum, zVal, nVal);
-	}
-}
-
-static void
-groupConcatFinalize(sql_context * context)
-{
-	StrAccum *pAccum;
-	pAccum = sql_aggregate_context(context, 0);
-	if (pAccum) {
-		if (pAccum->accError == STRACCUM_TOOBIG) {
-			diag_set(ClientError, ER_SQL_EXECUTE, "string or binary"\
-				 "string is too big");
-			context->is_aborted = true;
-		} else if (pAccum->accError == STRACCUM_NOMEM) {
-			context->is_aborted = true;
-		} else {
-			char *str = sqlStrAccumFinish(pAccum);
-			int len = pAccum->nChar;
-			assert(len >= 0);
-			if (context->func->def->returns == FIELD_TYPE_STRING)
-				mem_set_str_dynamic(context->pOut, str, len);
-			else
-				mem_set_bin_dynamic(context->pOut, str, len);
-		}
-	}
-}
-
 int
 sql_is_like_func(struct Expr *expr)
 {
@@ -1995,13 +1983,13 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING,
-	 groupConcatStep, groupConcatFinalize},
+	 step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_STRING, FIELD_TYPE_STRING},
-	 FIELD_TYPE_STRING, groupConcatStep, groupConcatFinalize},
+	 FIELD_TYPE_STRING, step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY,
-	 groupConcatStep, groupConcatFinalize},
+	 step_group_concat, fin_group_concat},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_VARBINARY, FIELD_TYPE_VARBINARY},
-	 FIELD_TYPE_VARBINARY, groupConcatStep, groupConcatFinalize},
+	 FIELD_TYPE_VARBINARY, step_group_concat, fin_group_concat},
 
 	{"HEX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_STRING, hexFunc, NULL},
 	{"IFNULL", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 416f27d69..bd8a8fe78 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(14680)
+test:plan(14681)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -2142,11 +2142,14 @@ test:do_execsql_test(
         -- </func-24.2>
     })
 
--- do_test func-24.3 {
---   execsql {
---     SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1
---   }
--- } {{this 2 program 3 is 4 free 5 software}}
+test:do_execsql_test(
+    "func-24.3",
+    [[
+        SELECT group_concat(zeroblob(10));
+    ]], {
+        '\0\0\0\0\0\0\0\0\0\0'
+    })
+
 test:do_execsql_test(
     "func-24.4",
     [[

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers
  2021-09-22 22:50   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 11:47     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 11:47 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below. Also, there were
quite a few merge conflicts due to changes in reworked functions, but I didn't
include them into diff, sorry.

On Thu, Sep 23, 2021 at 12:50:40AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 8804e3d18..65bb60d2f 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -4207,24 +4200,10 @@ case OP_AggFinal: {
> >  	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
> >  	struct Mem *pIn1 = &aMem[pOp->p1];
> >  
> > -	struct sql_context ctx;
> > -	memset(&ctx, 0, sizeof(ctx));
> > -	struct Mem t;
> > -	memset(&t, 0, sizeof(t));
> > -	t.type = MEM_TYPE_NULL;
> > -	assert(t.flags == 0);
> > -	t.db = pIn1->db;
> > -	ctx.pOut = &t;
> > -	ctx.pMem = pIn1;
> > -	ctx.func = pOp->p4.func;
> > -	func->finalize(&ctx);
> > -	assert((pIn1->flags & MEM_Dyn) == 0);
> > -	if (pIn1->szMalloc > 0)
> > -		sqlDbFree(pIn1->db, pIn1->zMalloc);
> > -	memcpy(pIn1, &t, sizeof(t));
> > -
> > -	if (ctx.is_aborted)
> > -		goto abort_due_to_error;
> > +	if (func->finalize != NULL) {
> > +		if (func->finalize(pIn1) != 0)
> 
> Such places might look simpler and shorter if you would
> use &&:
> 
> 	if (func->finalize != NULL && func->finalize(...
> 
> Up to you.
Thanks, fixed.


Diff:

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 65bb60d2f..5dd68912c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4200,10 +4200,8 @@ case OP_AggFinal: {
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
 	struct Mem *pIn1 = &aMem[pOp->p1];
 
-	if (func->finalize != NULL) {
-		if (func->finalize(pIn1) != 0)
-			goto abort_due_to_error;
-	}
+	if (func->finalize != NULL && func->finalize(pIn1) != 0)
+		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (sqlVdbeMemTooBig(pIn1) != 0)
 		goto too_big;


New patch:

commit b45f3f91d38a3dba4a5bc1187a05690f14b4173d
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Sep 9 18:50:33 2021 +0300

    sql: remove copying of result in finalizers
    
    This patch removes copying of the result in the finalizers of the SQL
    built-in aggregate functions.
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 28094e258..769caba94 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -59,47 +59,38 @@ step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	assert(mem_is_null(ctx->pOut) || mem_is_num(ctx->pOut));
 	if (mem_is_null(argv[0]))
 		return;
-	if (mem_is_null(ctx->pMem))
-		return mem_copy_as_ephemeral(ctx->pMem, argv[0]);
-	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+	if (mem_is_null(ctx->pOut))
+		return mem_copy_as_ephemeral(ctx->pOut, argv[0]);
+	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
-/** Finalizer for the SUM() function. */
-static void
-fin_sum(struct sql_context *ctx)
-{
-	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
-	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
-}
-
 /** Implementation of the TOTAL() function. */
 static void
 step_total(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(mem_is_null(ctx->pMem) || mem_is_num(ctx->pMem));
+	assert(mem_is_null(ctx->pOut) || mem_is_num(ctx->pOut));
 	if (mem_is_null(argv[0]))
 		return;
-	if (mem_is_null(ctx->pMem))
-		mem_set_double(ctx->pMem, 0.0);
-	if (mem_add(ctx->pMem, argv[0], ctx->pMem) != 0)
+	if (mem_is_null(ctx->pOut))
+		mem_set_double(ctx->pOut, 0.0);
+	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
 /** Finalizer for the TOTAL() function. */
-static void
-fin_total(struct sql_context *ctx)
+static int
+fin_total(struct Mem *mem)
 {
-	assert(mem_is_null(ctx->pMem) || mem_is_double(ctx->pMem));
-	if (mem_is_null(ctx->pMem))
-		mem_set_double(ctx->pOut, 0.0);
-	else
-		mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+	assert(mem_is_null(mem) || mem_is_double(mem));
+	if (mem_is_null(mem))
+		mem_set_double(mem, 0.0);
+	return 0;
 }
 
 /** Implementation of the AVG() function. */
@@ -108,12 +99,12 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
+	assert(mem_is_null(ctx->pOut) || mem_is_bin(ctx->pOut));
 	if (mem_is_null(argv[0]))
 		return;
 	struct Mem *mem;
 	uint32_t *count;
-	if (mem_is_null(ctx->pMem)) {
+	if (mem_is_null(ctx->pOut)) {
 		uint32_t size = sizeof(struct Mem) + sizeof(uint32_t);
 		mem = sqlDbMallocRawNN(sql_get(), size);
 		if (mem == NULL) {
@@ -124,10 +115,10 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 		mem_create(mem);
 		*count = 1;
 		mem_copy_as_ephemeral(mem, argv[0]);
-		mem_set_bin_allocated(ctx->pMem, (char *)mem, size);
+		mem_set_bin_allocated(ctx->pOut, (char *)mem, size);
 		return;
 	}
-	mem = (struct Mem *)ctx->pMem->z;
+	mem = (struct Mem *)ctx->pOut->z;
 	count = (uint32_t *)(mem + 1);
 	++*count;
 	if (mem_add(mem, argv[0], mem) != 0)
@@ -135,13 +126,15 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 }
 
 /** Finalizer for the AVG() function. */
-static void
-fin_avg(struct sql_context *ctx)
+static int
+fin_avg(struct Mem *mem)
 {
-	assert(mem_is_null(ctx->pMem) || mem_is_bin(ctx->pMem));
-	if (mem_is_null(ctx->pMem))
-		return mem_set_null(ctx->pOut);
-	struct Mem *tmp = (struct Mem *)ctx->pMem->z;
+	assert(mem_is_null(mem) || mem_is_bin(mem));
+	if (mem_is_null(mem)) {
+		mem_set_null(mem);
+		return 0;
+	}
+	struct Mem *tmp = (struct Mem *)mem->z;
 	uint32_t *count_val = (uint32_t *)(tmp + 1);
 	struct Mem sum;
 	mem_create(&sum);
@@ -150,8 +143,7 @@ fin_avg(struct sql_context *ctx)
 	struct Mem count;
 	mem_create(&count);
 	mem_set_uint(&count, *count_val);
-	if (mem_div(&sum, &count, ctx->pOut) != 0)
-		ctx->is_aborted = true;
+	return mem_div(&sum, &count, mem);
 }
 
 /** Implementation of the COUNT() function. */
@@ -159,22 +151,22 @@ static void
 step_count(struct sql_context *ctx, int argc, struct Mem **argv)
 {
 	assert(argc == 0 || argc == 1);
-	if (mem_is_null(ctx->pMem))
-		mem_set_uint(ctx->pMem, 0);
+	if (mem_is_null(ctx->pOut))
+		mem_set_uint(ctx->pOut, 0);
 	if (argc == 1 && mem_is_null(argv[0]))
 		return;
-	assert(mem_is_uint(ctx->pMem));
-	++ctx->pMem->u.u;
+	assert(mem_is_uint(ctx->pOut));
+	++ctx->pOut->u.u;
 }
 
 /** Finalizer for the COUNT() function. */
-static void
-fin_count(struct sql_context *ctx)
+static int
+fin_count(struct Mem *mem)
 {
-	assert(mem_is_null(ctx->pMem) || mem_is_uint(ctx->pMem));
-	if (mem_is_null(ctx->pMem))
-		return mem_set_uint(ctx->pOut, 0);
-	mem_copy_as_ephemeral(ctx->pOut, ctx->pMem);
+	assert(mem_is_null(mem) || mem_is_uint(mem));
+	if (mem_is_null(mem))
+		mem_set_uint(mem, 0);
+	return 0;
 }
 
 /** Implementation of the MIN() and MAX() functions. */
@@ -184,12 +176,12 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	assert(argc == 1);
 	(void)argc;
 	if (mem_is_null(argv[0])) {
-		if (!mem_is_null(ctx->pMem))
+		if (!mem_is_null(ctx->pOut))
 			ctx->skipFlag = 1;
 		return;
 	}
-	if (mem_is_null(ctx->pMem)) {
-		if (mem_copy(ctx->pMem, argv[0]) != 0)
+	if (mem_is_null(ctx->pOut)) {
+		if (mem_copy(ctx->pOut, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -201,22 +193,15 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	 * the only difference between the two being that the sense of the
 	 * comparison is inverted.
 	 */
-	int cmp = mem_cmp_scalar(ctx->pMem, argv[0], ctx->coll);
+	int cmp = mem_cmp_scalar(ctx->pOut, argv[0], ctx->coll);
 	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-		if (mem_copy(ctx->pMem, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
 	ctx->skipFlag = 1;
 }
 
-/** Finalizer for the MIN() and MAX() functions. */
-static void
-fin_minmax(struct sql_context *ctx)
-{
-	mem_copy(ctx->pOut, ctx->pMem);
-}
-
 /** Implementation of the GROUP_CONCAT() function. */
 static void
 step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
@@ -226,12 +211,12 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	if (mem_is_null(argv[0]))
 		return;
 	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
-	if (mem_is_null(ctx->pMem)) {
-		if (mem_copy(ctx->pMem, argv[0]) != 0)
+	if (mem_is_null(ctx->pOut)) {
+		if (mem_copy(ctx->pOut, argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
-	assert(!mem_is_zerobin(ctx->pMem));
+	assert(!mem_is_zerobin(ctx->pOut));
 	const char *sep = NULL;
 	int sep_len = 0;
 	if (argc == 1) {
@@ -245,7 +230,7 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 		sep = argv[1]->z;
 		sep_len = argv[1]->n;
 	}
-	if (mem_append(ctx->pMem, sep, sep_len) != 0) {
+	if (mem_append(ctx->pOut, sep, sep_len) != 0) {
 		ctx->is_aborted = true;
 		return;
 	}
@@ -259,19 +244,12 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 		size = argv[0]->n;
 		str = argv[0]->z;
 	}
-	if (mem_append(ctx->pMem, str, size) != 0) {
+	if (mem_append(ctx->pOut, str, size) != 0) {
 		ctx->is_aborted = true;
 		return;
 	}
 }
 
-/** Finalizer for the GROUP_CONCAT() function. */
-static void
-fin_group_concat(struct sql_context *ctx)
-{
-	mem_copy(ctx->pOut, ctx->pMem);
-}
-
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1946,7 +1924,7 @@ struct sql_func_definition {
 	/** Call implementation with given arguments. */
 	void (*call)(sql_context *ctx, int argc, sql_value **argv);
 	/** Call finalization function for this implementation. */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
@@ -1983,13 +1961,13 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING,
-	 step_group_concat, fin_group_concat},
+	 step_group_concat, NULL},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_STRING, FIELD_TYPE_STRING},
-	 FIELD_TYPE_STRING, step_group_concat, fin_group_concat},
+	 FIELD_TYPE_STRING, step_group_concat, NULL},
 	{"GROUP_CONCAT", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY,
-	 step_group_concat, fin_group_concat},
+	 step_group_concat, NULL},
 	{"GROUP_CONCAT", 2, {FIELD_TYPE_VARBINARY, FIELD_TYPE_VARBINARY},
-	 FIELD_TYPE_VARBINARY, step_group_concat, fin_group_concat},
+	 FIELD_TYPE_VARBINARY, step_group_concat, NULL},
 
 	{"HEX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_STRING, hexFunc, NULL},
 	{"IFNULL", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
@@ -2020,35 +1998,23 @@ static struct sql_func_definition definitions[] = {
 	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
 	 NULL},
 
-	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
-	 fin_minmax},
+	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax, NULL},
 	{"MAX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
-	 fin_minmax},
-	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
-	 fin_minmax},
-
-	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax,
-	 fin_minmax},
+	 NULL},
+	{"MAX", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax, NULL},
+	{"MAX", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax, NULL},
+
+	{"MIN", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_NUMBER}, FIELD_TYPE_NUMBER, step_minmax, NULL},
 	{"MIN", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax,
-	 fin_minmax},
-	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax,
-	 fin_minmax},
+	 NULL},
+	{"MIN", 1, {FIELD_TYPE_UUID}, FIELD_TYPE_UUID, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, step_minmax, NULL},
+	{"MIN", 1, {FIELD_TYPE_SCALAR}, FIELD_TYPE_SCALAR, step_minmax, NULL},
 
 	{"NULLIF", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
 	 nullifFunc, NULL},
@@ -2082,9 +2048,9 @@ static struct sql_func_definition definitions[] = {
 	{"SUBSTR", 3,
 	 {FIELD_TYPE_VARBINARY, FIELD_TYPE_INTEGER, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_VARBINARY, substrFunc, NULL},
-	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
-	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
-	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, NULL},
+	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, NULL},
+	{"SUM", 1, {FIELD_TYPE_DECIMAL}, FIELD_TYPE_DECIMAL, step_sum, NULL},
 	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
 	 fin_total},
 	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
@@ -2343,7 +2309,7 @@ sql_built_in_functions_cache_init(void)
 		assert(desc->argc != -1 || dict->argc_min != dict->argc_max);
 		def->param_count = desc->argc;
 		def->returns = desc->result;
-		def->aggregate = desc->finalize == NULL ?
+		def->aggregate = (dict->flags & SQL_FUNC_AGG) == 0 ?
 				 FUNC_AGGREGATE_NONE : FUNC_AGGREGATE_GROUP;
 		def->language = FUNC_LANGUAGE_SQL_BUILTIN;
 		def->name_len = len;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index abcc3bfdf..5bce70eaa 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -484,18 +484,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_aggregate_context(sql_context *,
-			  int nBytes);
-
-/**
- * Allocate or return the aggregate context containing struct MEM for a user
- * function. A new context is allocated on the first call. Subsequent calls
- * return the same context that was returned on prior calls.
- */
-struct Mem *
-sql_context_agg_mem(struct sql_context *context);
-
 int
 sql_column_count(sql_stmt * pStmt);
 
@@ -4386,7 +4374,7 @@ struct func_sql_builtin {
 	 * A VDBE-memory-compatible finalize method
 	 * (is valid only for aggregate function).
 	 */
-	void (*finalize)(sql_context *ctx);
+	int (*finalize)(struct Mem *mem);
 };
 
 /**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index ee07af9c2..5dd68912c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4145,7 +4145,6 @@ case OP_AggStep: {
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
-	Mem t;
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
@@ -4156,8 +4155,8 @@ case OP_AggStep: {
 	 * 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;
+	if (pCtx->pOut != pMem) {
+		pCtx->pOut = pMem;
 		for(i = 0; i < argc; ++i)
 			pCtx->argv[i] = &aMem[pOp->p2 + i];
 	}
@@ -4169,18 +4168,12 @@ case OP_AggStep: {
 	}
 #endif
 
-	mem_create(&t);
-	pCtx->pOut = &t;
-	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
 	func->call(pCtx, argc, pCtx->argv);
-	if (pCtx->is_aborted) {
-		mem_destroy(&t);
+	if (pCtx->is_aborted)
 		goto abort_due_to_error;
-	}
-	assert(mem_is_null(&t));
 	if (pCtx->skipFlag) {
 		assert(pOp[-1].opcode == OP_SkipLoad);
 		i = pOp[-1].p1;
@@ -4207,20 +4200,7 @@ case OP_AggFinal: {
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pOp->p4.func;
 	struct Mem *pIn1 = &aMem[pOp->p1];
 
-	struct sql_context ctx;
-	memset(&ctx, 0, sizeof(ctx));
-	struct Mem t;
-	mem_create(&t);
-	ctx.pOut = &t;
-	ctx.pMem = pIn1;
-	ctx.func = pOp->p4.func;
-	func->finalize(&ctx);
-	assert((pIn1->flags & MEM_Dyn) == 0);
-	if (pIn1->szMalloc > 0)
-		sqlDbFree(pIn1->db, pIn1->zMalloc);
-	memcpy(pIn1, &t, sizeof(t));
-
-	if (ctx.is_aborted)
+	if (func->finalize != NULL && func->finalize(pIn1) != 0)
 		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (sqlVdbeMemTooBig(pIn1) != 0)
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index b9a18f1a1..5e2692d06 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -172,7 +172,6 @@ struct sql_context {
 	Mem *pOut;		/* The return value is stored here */
 	/* A pointer to function implementation. */
 	struct func *func;
-	Mem *pMem;		/* Memory cell used to store aggregate context */
 	struct coll *coll;
 	/*
 	 * True, if an error occurred during the execution of the
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e598513c..39f560c32 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -349,45 +349,6 @@ sql_context_db_handle(sql_context * p)
 	return p->pOut->db;
 }
 
-/*
- * Allocate or return the aggregate context for a user function.  A new
- * context is allocated on the first call.  Subsequent calls return the
- * same context that was returned on prior calls.
- */
-void *
-sql_aggregate_context(sql_context * p, int nByte)
-{
-	assert(p != NULL && p->func != NULL);
-	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	if (!mem_is_agg(p->pMem) && mem_set_agg(p->pMem, p->func, nByte) != 0)
-		return NULL;
-	void *accum;
-	if (mem_get_agg(p->pMem, &accum) != 0)
-		return NULL;
-	return accum;
-}
-
-struct Mem *
-sql_context_agg_mem(struct sql_context *ctx)
-{
-	assert(ctx != NULL && ctx->func != NULL);
-	assert(ctx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	assert(ctx->func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	struct Mem *mem;
-	if (!mem_is_agg(ctx->pMem)) {
-		if (mem_set_agg(ctx->pMem, ctx->func, sizeof(*mem)) != 0)
-			return NULL;
-		if (mem_get_agg(ctx->pMem, (void **)&mem) != 0)
-			return NULL;
-		mem_create(mem);
-		return mem;
-	}
-	if (mem_get_agg(ctx->pMem, (void **)&mem) != 0)
-		return NULL;
-	return mem;
-}
-
 /*
  * Return the number of columns in the result set for the statement pStmt.
  */

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context
  2021-09-22 22:51   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-09-25 12:03     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-25 12:03 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below. There was merge
conflicts due to review fixes, but I didn't include them into diff.

On Thu, Sep 23, 2021 at 12:51:22AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index cdfb020cb..6cbbbac74 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -55,31 +55,31 @@ static struct func_sql_builtin **functions;
> >  
> >  /** Implementation of the SUM() function. */
> >  static void
> > -step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
> > +step_sum(struct sql_context *ctx, int argc, struct Mem *argv)
> 
> Is it a good idea? This way you force to allocate the Mem array.
> But what if I have a function call like func(col1, col10, col20),
> where colN are columns of a tuple? They are stored in Mems, so previously
> it would be possible to pass &col1, &col10, &col20. Now you have no
> choice but to allocate 3 * sizeof(Mem) and ephem-copy the mems.
> 
> How does func(col1, col10, col20) behave before this patch? Does it
> copy the cols even now?
Currently it works this way:
	if (pCtx->pOut != pMem)
		pCtx->pOut = pMem;
		for(i = 0; i < argc; ++i)
			pCtx->argv[i] = &aMem[pOp->p2 + i];
	}

I.e. it allocates an array of addresses and fills it with sequential MEM
addresses. Arguments are copied as needed. There are two ways to go from
here - discard the array or not copy the arguments. I plan on making the
arguments constant, so the second option looks more promising. However, I
believe the change required to implement this option is quite large. I'm not
sure if we will fix this anytime soon. On the other hand, the first option is
fairly easy to implement, and we can move from the first option to the second
quite easily.

We can drop this patch, though. Or create an issue about the second option.


New patch:

commit d26808d1697646d27dd2ee8a55d31851c01a0d91
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Mon Sep 20 19:10:03 2021 +0300

    sql: remove field argv from struct sql_context
    
    Since the function arguments are always sequential in the array of all
    VDBE MEMs, we don't need to store the position of each argument. The
    position of the first MEM and the number of arguments are sufficient to
    describe all the arguments.
    
    Part of #4145

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index ab7d95f7e..db8355f33 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4104,7 +4104,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			}
 			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
 				struct sql_context *ctx =
-					sql_context_new(func, nFarg, coll);
+					sql_context_new(func, coll);
 				if (ctx == NULL) {
 					pParse->is_aborted = true;
 					return -1;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 769caba94..3c54591a6 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -55,31 +55,31 @@ static struct func_sql_builtin **functions;
 
 /** Implementation of the SUM() function. */
 static void
-step_sum(struct sql_context *ctx, int argc, struct Mem **argv)
+step_sum(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(mem_is_null(ctx->pOut) || mem_is_num(ctx->pOut));
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
 	if (mem_is_null(ctx->pOut))
-		return mem_copy_as_ephemeral(ctx->pOut, argv[0]);
-	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
+		return mem_copy_as_ephemeral(ctx->pOut, &argv[0]);
+	if (mem_add(ctx->pOut, &argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
 /** Implementation of the TOTAL() function. */
 static void
-step_total(struct sql_context *ctx, int argc, struct Mem **argv)
+step_total(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(mem_is_null(ctx->pOut) || mem_is_num(ctx->pOut));
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
 	if (mem_is_null(ctx->pOut))
 		mem_set_double(ctx->pOut, 0.0);
-	if (mem_add(ctx->pOut, argv[0], ctx->pOut) != 0)
+	if (mem_add(ctx->pOut, &argv[0], ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
@@ -95,12 +95,12 @@ fin_total(struct Mem *mem)
 
 /** Implementation of the AVG() function. */
 static void
-step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
+step_avg(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
 	assert(mem_is_null(ctx->pOut) || mem_is_bin(ctx->pOut));
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
 	struct Mem *mem;
 	uint32_t *count;
@@ -114,14 +114,14 @@ step_avg(struct sql_context *ctx, int argc, struct Mem **argv)
 		count = (uint32_t *)(mem + 1);
 		mem_create(mem);
 		*count = 1;
-		mem_copy_as_ephemeral(mem, argv[0]);
+		mem_copy_as_ephemeral(mem, &argv[0]);
 		mem_set_bin_allocated(ctx->pOut, (char *)mem, size);
 		return;
 	}
 	mem = (struct Mem *)ctx->pOut->z;
 	count = (uint32_t *)(mem + 1);
 	++*count;
-	if (mem_add(mem, argv[0], mem) != 0)
+	if (mem_add(mem, &argv[0], mem) != 0)
 		ctx->is_aborted = true;
 }
 
@@ -148,12 +148,12 @@ fin_avg(struct Mem *mem)
 
 /** Implementation of the COUNT() function. */
 static void
-step_count(struct sql_context *ctx, int argc, struct Mem **argv)
+step_count(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 0 || argc == 1);
 	if (mem_is_null(ctx->pOut))
 		mem_set_uint(ctx->pOut, 0);
-	if (argc == 1 && mem_is_null(argv[0]))
+	if (argc == 1 && mem_is_null(&argv[0]))
 		return;
 	assert(mem_is_uint(ctx->pOut));
 	++ctx->pOut->u.u;
@@ -171,17 +171,17 @@ fin_count(struct Mem *mem)
 
 /** Implementation of the MIN() and MAX() functions. */
 static void
-step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
+step_minmax(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	(void)argc;
-	if (mem_is_null(argv[0])) {
+	if (mem_is_null(&argv[0])) {
 		if (!mem_is_null(ctx->pOut))
 			ctx->skipFlag = 1;
 		return;
 	}
 	if (mem_is_null(ctx->pOut)) {
-		if (mem_copy(ctx->pOut, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, &argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -193,9 +193,9 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 	 * the only difference between the two being that the sense of the
 	 * comparison is inverted.
 	 */
-	int cmp = mem_cmp_scalar(ctx->pOut, argv[0], ctx->coll);
+	int cmp = mem_cmp_scalar(ctx->pOut, &argv[0], ctx->coll);
 	if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
-		if (mem_copy(ctx->pOut, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, &argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -204,15 +204,15 @@ step_minmax(struct sql_context *ctx, int argc, struct Mem **argv)
 
 /** Implementation of the GROUP_CONCAT() function. */
 static void
-step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
+step_group_concat(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	assert(argc == 1 || argc == 2);
 	(void)argc;
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
-	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
+	assert(mem_is_str(&argv[0]) || mem_is_bin(&argv[0]));
 	if (mem_is_null(ctx->pOut)) {
-		if (mem_copy(ctx->pOut, argv[0]) != 0)
+		if (mem_copy(ctx->pOut, &argv[0]) != 0)
 			ctx->is_aborted = true;
 		return;
 	}
@@ -222,13 +222,13 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	if (argc == 1) {
 		sep = ",";
 		sep_len = 1;
-	} else if (mem_is_null(argv[1])) {
+	} else if (mem_is_null(&argv[1])) {
 		sep = "";
 		sep_len = 0;
 	} else {
-		assert(mem_is_same_type(argv[0], argv[1]));
-		sep = argv[1]->z;
-		sep_len = argv[1]->n;
+		assert(mem_is_same_type(&argv[0], &argv[1]));
+		sep = argv[1].z;
+		sep_len = argv[1].n;
 	}
 	if (mem_append(ctx->pOut, sep, sep_len) != 0) {
 		ctx->is_aborted = true;
@@ -236,13 +236,13 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	}
 	uint32_t size;
 	char *str;
-	if (mem_is_zerobin(argv[0])) {
-		size = argv[0]->u.nZero;
+	if (mem_is_zerobin(&argv[0])) {
+		size = argv[0].u.nZero;
 		str = sqlDbMallocRawNN(sql_get(), size);
 		memset(str, 0, size);
 	} else {
-		size = argv[0]->n;
-		str = argv[0]->z;
+		size = argv[0].n;
+		str = argv[0].z;
 	}
 	if (mem_append(ctx->pOut, str, size) != 0) {
 		ctx->is_aborted = true;
@@ -269,7 +269,7 @@ mem_as_bin(struct Mem *mem)
 }
 
 static void
-sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
+sql_func_uuid(struct sql_context *ctx, int argc, struct Mem *argv)
 {
 	if (argc > 1) {
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UUID",
@@ -279,9 +279,9 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
 	}
 	if (argc == 1) {
 		uint64_t version;
-		if (mem_get_uint(argv[0], &version) != 0) {
+		if (mem_get_uint(&argv[0], &version) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(argv[0]), "integer");
+				 mem_str(&argv[0]), "integer");
 			ctx->is_aborted = true;
 			return;
 		}
@@ -301,7 +301,7 @@ sql_func_uuid(struct sql_context *ctx, int argc, struct Mem **argv)
  * Implementation of the non-aggregate min() and max() functions
  */
 static void
-minmaxFunc(sql_context * context, int argc, sql_value ** argv)
+minmaxFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int i;
 	int iBest;
@@ -317,30 +317,30 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	pColl = context->coll;
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
 	for (i = 1; i < argc; i++) {
-		if (mem_is_null(argv[i]))
+		if (mem_is_null(&argv[i]))
 			return;
-		if ((mem_cmp_scalar(argv[iBest], argv[i], pColl) ^ mask) >= 0)
+		if ((mem_cmp_scalar(&argv[iBest], &argv[i], pColl) ^ mask) >= 0)
 			iBest = i;
 	}
-	sql_result_value(context, argv[iBest]);
+	sql_result_value(context, &argv[iBest]);
 }
 
 /*
  * Return the type of the argument.
  */
 static void
-typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
+typeofFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
 	const char *z = 0;
-	UNUSED_PARAMETER(NotUsed);
-	if ((argv[0]->flags & MEM_Number) != 0)
+	if ((argv[0].flags & MEM_Number) != 0)
 		return mem_set_str0_static(context->pOut, "number");
-	if ((argv[0]->flags & MEM_Scalar) != 0)
+	if ((argv[0].flags & MEM_Scalar) != 0)
 		return mem_set_str0_static(context->pOut, "scalar");
-	switch (argv[0]->type) {
+	switch (argv[0].type) {
 	case MEM_TYPE_INT:
 	case MEM_TYPE_UINT:
 		z = "integer";
@@ -379,13 +379,13 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
  * Implementation of the length() function
  */
 static void
-lengthFunc(sql_context * context, int argc, sql_value ** argv)
+lengthFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int len;
 
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
+	switch (sql_value_type(&argv[0])) {
 	case MP_BIN:
 	case MP_ARRAY:
 	case MP_MAP:
@@ -393,16 +393,16 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_UINT:
 	case MP_BOOL:
 	case MP_DOUBLE:{
-			mem_as_bin(argv[0]);
-			sql_result_uint(context, mem_len_unsafe(argv[0]));
+			mem_as_bin(&argv[0]);
+			sql_result_uint(context, mem_len_unsafe(&argv[0]));
 			break;
 		}
 	case MP_EXT:
 	case MP_STR:{
-			const unsigned char *z = mem_as_ustr(argv[0]);
+			const unsigned char *z = mem_as_ustr(&argv[0]);
 			if (z == 0)
 				return;
-			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(&argv[0]));
 			sql_result_uint(context, len);
 			break;
 		}
@@ -420,17 +420,17 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
  * the numeric argument X.
  */
 static void
-absFunc(sql_context * context, int argc, sql_value ** argv)
+absFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
+	switch (sql_value_type(&argv[0])) {
 	case MP_UINT: {
-		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
+		sql_result_uint(context, mem_get_uint_unsafe(&argv[0]));
 		break;
 	}
 	case MP_INT: {
-		int64_t value = mem_get_int_unsafe(argv[0]);
+		int64_t value = mem_get_int_unsafe(&argv[0]);
 		assert(value < 0);
 		sql_result_uint(context, -value);
 		break;
@@ -446,7 +446,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_ARRAY:
 	case MP_MAP: {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "number",
-			 mem_str(argv[0]));
+			 mem_str(&argv[0]));
 		context->is_aborted = true;
 		return;
 	}
@@ -455,7 +455,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal = mem_get_double_unsafe(argv[0]);
+			double rVal = mem_get_double_unsafe(&argv[0]);
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -476,11 +476,11 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
  * occurrence of needle, or 0 if needle never occurs in haystack.
  */
 static void
-position_func(struct sql_context *context, int argc, struct Mem **argv)
+position_func(struct sql_context *context, int argc, struct Mem *argv)
 {
 	UNUSED_PARAMETER(argc);
-	struct Mem *needle = argv[0];
-	struct Mem *haystack = argv[1];
+	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);
 
@@ -603,7 +603,7 @@ finish:
  * Implementation of the printf() function.
  */
 static void
-printfFunc(sql_context * context, int argc, sql_value ** argv)
+printfFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	PrintfArguments x;
 	StrAccum str;
@@ -611,14 +611,22 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
 	int n;
 	sql *db = sql_context_db_handle(context);
 
-	if (argc >= 1 && (zFormat = mem_as_str0(argv[0])) != NULL) {
+	if (argc >= 1 && (zFormat = mem_as_str0(&argv[0])) != NULL) {
 		x.nArg = argc - 1;
 		x.nUsed = 0;
-		x.apArg = argv + 1;
+		x.apArg = sqlDbMallocRawNN(sql_get(),
+					   (argc - 1) * sizeof(*x.apArg));
+		if (x.apArg == NULL) {
+			context->is_aborted = true;
+			return;
+		}
+		for (int i = 1; i < argc; ++i)
+			x.apArg[i - 1] = &argv[i];
 		sqlStrAccumInit(&str, db, 0, 0,
 				    db->aLimit[SQL_LIMIT_LENGTH]);
 		str.printfFlags = SQL_PRINTF_SQLFUNC;
 		sqlXPrintf(&str, zFormat, &x);
+		sqlDbFree(sql_get(), x.apArg);
 		n = str.nChar;
 		sql_result_text(context, sqlStrAccumFinish(&str), n,
 				    SQL_DYNAMIC);
@@ -638,7 +646,7 @@ 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)
+substrFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	const unsigned char *z;
 	const unsigned char *z2;
@@ -653,26 +661,26 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
+	if (mem_is_null(&argv[1]) || (argc == 3 && mem_is_null(&argv[2])))
 		return;
-	p0type = sql_value_type(argv[0]);
-	p1 = mem_get_int_unsafe(argv[1]);
+	p0type = sql_value_type(&argv[0]);
+	p1 = mem_get_int_unsafe(&argv[1]);
 	if (p0type == MP_BIN) {
-		z = mem_as_bin(argv[0]);
-		len = mem_len_unsafe(argv[0]);
+		z = mem_as_bin(&argv[0]);
+		len = mem_len_unsafe(&argv[0]);
 		if (z == 0)
 			return;
-		assert(len == mem_len_unsafe(argv[0]));
+		assert(len == mem_len_unsafe(&argv[0]));
 	} else {
-		z = mem_as_ustr(argv[0]);
+		z = mem_as_ustr(&argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
 		if (p1 < 0)
-			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(&argv[0]));
 	}
 	if (argc == 3) {
-		p2 = mem_get_int_unsafe(argv[2]);
+		p2 = mem_get_int_unsafe(&argv[2]);
 		if (p2 < 0) {
 			p2 = -p2;
 			negP2 = 1;
@@ -708,7 +716,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 = mem_len_unsafe(argv[0]);
+		int byte_size = mem_len_unsafe(&argv[0]);
 		int n_chars = sql_utf8_char_count(z, byte_size);
 		int cnt = 0;
 		int i = 0;
@@ -739,7 +747,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
  * Implementation of the round() function
  */
 static void
-roundFunc(sql_context * context, int argc, sql_value ** argv)
+roundFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n = 0;
 	double r;
@@ -750,21 +758,21 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	if (argc == 2) {
-		if (mem_is_null(argv[1]))
+		if (mem_is_null(&argv[1]))
 			return;
-		n = mem_get_int_unsafe(argv[1]);
+		n = mem_get_int_unsafe(&argv[1]);
 		if (n < 0)
 			n = 0;
 	}
-	if (mem_is_null(argv[0]))
+	if (mem_is_null(&argv[0]))
 		return;
-	if (!mem_is_num(argv[0]) && !mem_is_str(argv[0])) {
+	if (!mem_is_num(&argv[0]) && !mem_is_str(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "number");
+			 mem_str(&argv[0]), "number");
 		context->is_aborted = true;
 		return;
 	}
-	r = mem_get_double_unsafe(argv[0]);
+	r = mem_get_double_unsafe(&argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -787,7 +795,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
  * NULL.
  */
 static void *
-contextMalloc(sql_context * context, i64 nByte)
+contextMalloc(struct sql_context *context, i64 nByte)
 {
 	char *z;
 	sql *db = sql_context_db_handle(context);
@@ -812,26 +820,26 @@ 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)   \
+case_type##ICUFunc(sql_context *context, int argc, struct Mem *argv)           \
 {                                                                              \
 	char *z1;                                                              \
 	const char *z2;                                                        \
 	int n;                                                                 \
 	UNUSED_PARAMETER(argc);                                                \
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||                      \
-	    mem_is_array(argv[0])) {                                           \
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||                    \
+	    mem_is_array(&argv[0])) {                                          \
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "string",         \
-			 mem_str(argv[0]));                                    \
+			 mem_str(&argv[0]));                                   \
 		context->is_aborted = true;                                    \
 		return;                                                        \
 	}                                                                      \
-	z2 = mem_as_str0(argv[0]);                                             \
-	n = mem_len_unsafe(argv[0]);                                           \
+	z2 = mem_as_str0(&argv[0]);                                            \
+	n = mem_len_unsafe(&argv[0]);                                          \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
 	 */                                                                    \
-	assert(z2 == mem_as_str0(argv[0]));                                    \
+	assert(z2 == mem_as_str0(&argv[0]));                                   \
 	if (!z2)                                                               \
 		return;                                                        \
 	z1 = contextMalloc(context, ((i64) n) + 1);                            \
@@ -881,10 +889,11 @@ ICU_CASE_CONVERT(Upper);
  * Implementation of random().  Return a random integer.
  */
 static void
-randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
+randomFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	int64_t r;
-	UNUSED_PARAMETER2(NotUsed, NotUsed2);
 	sql_randomness(sizeof(r), &r);
 	sql_result_int(context, r);
 }
@@ -894,20 +903,20 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
  * that is N bytes long.
  */
 static void
-randomBlob(sql_context * context, int argc, sql_value ** argv)
+randomBlob(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n;
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
-	    mem_is_array(argv[0])) {
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||
+	    mem_is_array(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "number");
+			 mem_str(&argv[0]), "number");
 		context->is_aborted = true;
 		return;
 	}
-	n = mem_get_int_unsafe(argv[0]);
+	n = mem_get_int_unsafe(&argv[0]);
 	if (n < 1)
 		return;
 	p = contextMalloc(context, n);
@@ -1137,7 +1146,7 @@ sql_utf8_pattern_compare(const char *pattern,
  * is NULL then result is NULL as well.
  */
 static void
-likeFunc(sql_context *context, int argc, sql_value **argv)
+likeFunc(sql_context *context, int argc, struct Mem *argv)
 {
 	u32 escape = SQL_END_OF_STRING;
 	int nPat;
@@ -1148,29 +1157,29 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		return;
 	}
 	sql *db = sql_context_db_handle(context);
-	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;
 		const char *str = rhs_type != MP_STR ?
-				  mem_str(argv[0]) : mem_str(argv[1]);
+				  mem_str(&argv[0]) : mem_str(&argv[1]);
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "string", str);
 		context->is_aborted = true;
 		return;
 	}
-	const char *zB = mem_as_str0(argv[0]);
-	const char *zA = mem_as_str0(argv[1]);
-	const char *zB_end = zB + mem_len_unsafe(argv[0]);
-	const char *zA_end = zA + mem_len_unsafe(argv[1]);
+	const char *zB = mem_as_str0(&argv[0]);
+	const char *zA = mem_as_str0(&argv[1]);
+	const char *zB_end = zB + mem_len_unsafe(&argv[0]);
+	const char *zA_end = zA + mem_len_unsafe(&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 = mem_len_unsafe(argv[0]);
+	nPat = mem_len_unsafe(&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]) {
@@ -1180,7 +1189,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		return;
 	}
 	/* Encoding did not change */
-	assert(zB == mem_as_str0(argv[0]));
+	assert(zB == mem_as_str0(&argv[0]));
 
 	if (argc == 3) {
 		/*
@@ -1188,10 +1197,10 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		 * single UTF-8 character. Otherwise, return an
 		 * error.
 		 */
-		const unsigned char *zEsc = mem_as_ustr(argv[2]);
+		const unsigned char *zEsc = mem_as_ustr(&argv[2]);
 		if (zEsc == 0)
 			return;
-		if (sql_utf8_char_count(zEsc, mem_len_unsafe(argv[2])) != 1) {
+		if (sql_utf8_char_count(zEsc, mem_len_unsafe(&argv[2])) != 1) {
 			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
 				 "expression must be a single character");
 			context->is_aborted = true;
@@ -1221,12 +1230,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
  * arguments are equal to each other.
  */
 static void
-nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
+nullifFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
 	struct coll *pColl = context->coll;
-	UNUSED_PARAMETER(NotUsed);
-	if (mem_cmp_scalar(argv[0], argv[1], pColl) != 0)
-		sql_result_value(context, argv[0]);
+	if (mem_cmp_scalar(&argv[0], &argv[1], pColl) != 0)
+		sql_result_value(context, &argv[0]);
 }
 
 /**
@@ -1238,10 +1247,10 @@ nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
  * @param unused2 Unused.
  */
 static void
-sql_func_version(struct sql_context *context,
-		 MAYBE_UNUSED int unused1,
-		 MAYBE_UNUSED sql_value **unused2)
+sql_func_version(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	sql_result_text(context, tarantool_version(), -1, SQL_STATIC);
 }
 
@@ -1261,14 +1270,14 @@ static const char hexdigits[] = {
  * single-quote escapes.
  */
 static void
-quoteFunc(sql_context * context, int argc, sql_value ** argv)
+quoteFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (argv[0]->type) {
+	switch (argv[0].type) {
 	case MEM_TYPE_UUID: {
 		char buf[UUID_STR_LEN + 1];
-		tt_uuid_to_string(&argv[0]->u.uuid, &buf[0]);
+		tt_uuid_to_string(&argv[0].u.uuid, &buf[0]);
 		sql_result_text(context, buf, UUID_STR_LEN, SQL_TRANSIENT);
 		break;
 	}
@@ -1276,16 +1285,16 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MEM_TYPE_DEC:
 	case MEM_TYPE_UINT:
 	case MEM_TYPE_INT: {
-			sql_result_value(context, argv[0]);
+			sql_result_value(context, &argv[0]);
 			break;
 		}
 	case MEM_TYPE_BIN:
 	case MEM_TYPE_ARRAY:
 	case MEM_TYPE_MAP: {
 			char *zText = 0;
-			char const *zBlob = mem_as_bin(argv[0]);
-			int nBlob = mem_len_unsafe(argv[0]);
-			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
+			char const *zBlob = mem_as_bin(&argv[0]);
+			int nBlob = mem_len_unsafe(&argv[0]);
+			assert(zBlob == mem_as_bin(&argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
 						  (2 * (i64) nBlob) + 4);
@@ -1310,7 +1319,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MEM_TYPE_STR: {
 			int i, j;
 			u64 n;
-			const unsigned char *zArg = mem_as_ustr(argv[0]);
+			const unsigned char *zArg = mem_as_ustr(&argv[0]);
 			char *z;
 
 			if (zArg == 0)
@@ -1337,12 +1346,12 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 		}
 	case MEM_TYPE_BOOL: {
 		sql_result_text(context,
-				SQL_TOKEN_BOOLEAN(mem_get_bool_unsafe(argv[0])),
+				SQL_TOKEN_BOOLEAN(argv[0].u.b),
 				-1, SQL_TRANSIENT);
 		break;
 	}
 	default:{
-			assert(mem_is_null(argv[0]));
+			assert(mem_is_null(&argv[0]));
 			sql_result_text(context, "NULL", 4, SQL_STATIC);
 			break;
 		}
@@ -1354,9 +1363,9 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
  * for the first character of the input string.
  */
 static void
-unicodeFunc(sql_context * context, int argc, sql_value ** argv)
+unicodeFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
-	const unsigned char *z = mem_as_ustr(argv[0]);
+	const unsigned char *z = mem_as_ustr(&argv[0]);
 	(void)argc;
 	if (z && z[0])
 		sql_result_uint(context, sqlUtf8Read(&z));
@@ -1368,7 +1377,7 @@ unicodeFunc(sql_context * context, int argc, sql_value ** argv)
  * is the unicode character for the corresponding integer argument.
  */
 static void
-charFunc(sql_context * context, int argc, sql_value ** argv)
+charFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	unsigned char *z, *zOut;
 	int i;
@@ -1380,10 +1389,10 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 	for (i = 0; i < argc; i++) {
 		uint64_t x;
 		unsigned c;
-		if (sql_value_type(argv[i]) == MP_INT)
+		if (sql_value_type(&argv[i]) == MP_INT)
 			x = 0xfffd;
 		else
-			x = mem_get_uint_unsafe(argv[i]);
+			x = mem_get_uint_unsafe(&argv[i]);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
@@ -1411,16 +1420,16 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
  * a hexadecimal rendering as text.
  */
 static void
-hexFunc(sql_context * context, int argc, sql_value ** argv)
+hexFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int i, n;
 	const unsigned char *pBlob;
 	char *zHex, *z;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	pBlob = mem_as_bin(argv[0]);
-	n = mem_len_unsafe(argv[0]);
-	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
+	pBlob = mem_as_bin(&argv[0]);
+	n = mem_len_unsafe(&argv[0]);
+	assert(pBlob == mem_as_bin(&argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
 		for (i = 0; i < n; i++, pBlob++) {
@@ -1437,12 +1446,12 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
  * The zeroblob(N) function returns a zero-filled blob of size N bytes.
  */
 static void
-zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
+zeroblobFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	int64_t n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	n = mem_get_int_unsafe(argv[0]);
+	n = mem_get_int_unsafe(&argv[0]);
 	if (n < 0)
 		n = 0;
 	if (sql_result_zeroblob64(context, n) != 0) {
@@ -1459,7 +1468,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
  * must be exact.  Collating sequences are not used.
  */
 static void
-replaceFunc(sql_context * context, int argc, sql_value ** argv)
+replaceFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	const unsigned char *zStr;	/* The input string A */
 	const unsigned char *zPattern;	/* The pattern string B */
@@ -1474,29 +1483,29 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 
 	assert(argc == 3);
 	UNUSED_PARAMETER(argc);
-	zStr = mem_as_ustr(argv[0]);
+	zStr = mem_as_ustr(&argv[0]);
 	if (zStr == 0)
 		return;
-	nStr = mem_len_unsafe(argv[0]);
-	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
-	zPattern = mem_as_ustr(argv[1]);
+	nStr = mem_len_unsafe(&argv[0]);
+	assert(zStr == mem_as_ustr(&argv[0]));	/* No encoding change */
+	zPattern = mem_as_ustr(&argv[1]);
 	if (zPattern == 0) {
-		assert(mem_is_null(argv[1])
+		assert(mem_is_null(&argv[1])
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
-	nPattern = mem_len_unsafe(argv[1]);
+	nPattern = mem_len_unsafe(&argv[1]);
 	if (nPattern == 0) {
-		assert(!mem_is_null(argv[1]));
-		sql_result_value(context, argv[0]);
+		assert(!mem_is_null(&argv[1]));
+		sql_result_value(context, &argv[0]);
 		return;
 	}
-	assert(zPattern == mem_as_ustr(argv[1]));	/* No encoding change */
-	zRep = mem_as_ustr(argv[2]);
+	assert(zPattern == mem_as_ustr(&argv[1]));	/* No encoding change */
+	zRep = mem_as_ustr(&argv[2]);
 	if (zRep == 0)
 		return;
-	nRep = mem_len_unsafe(argv[2]);
-	assert(zRep == mem_as_ustr(argv[2]));
+	nRep = mem_len_unsafe(&argv[2]);
+	assert(zRep == mem_as_ustr(&argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
 	zOut = contextMalloc(context, (i64) nOut);
@@ -1715,14 +1724,14 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
  * implementation depending on the number of arguments.
 */
 static void
-trim_func(struct sql_context *context, int argc, sql_value **argv)
+trim_func(struct sql_context *context, int argc, struct Mem *argv)
 {
 	switch (argc) {
 	case 2:
-		trim_func_two_args(context, argv[0], argv[1]);
+		trim_func_two_args(context, &argv[0], &argv[1]);
 		break;
 	case 3:
-		trim_func_three_args(context, argv[0], argv[1], argv[2]);
+		trim_func_three_args(context, &argv[0], &argv[1], &argv[2]);
 		break;
 	default:
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
@@ -1738,7 +1747,7 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
  * soundex encoding of the string X.
  */
 static void
-soundexFunc(sql_context * context, int argc, sql_value ** argv)
+soundexFunc(struct sql_context *context, int argc, struct Mem *argv)
 {
 	(void) argc;
 	char zResult[8];
@@ -1755,14 +1764,14 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
 	};
 	assert(argc == 1);
-	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
-	    mem_is_array(argv[0])) {
+	if (mem_is_bin(&argv[0]) || mem_is_map(&argv[0]) ||
+	    mem_is_array(&argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[0]), "string");
+			 mem_str(&argv[0]), "string");
 		context->is_aborted = true;
 		return;
 	}
-	zIn = (u8 *) mem_as_ustr(argv[0]);
+	zIn = (u8 *) mem_as_ustr(&argv[0]);
 	if (zIn == 0)
 		zIn = (u8 *) "";
 	for (i = 0; zIn[i] && !sqlIsalpha(zIn[i]); i++) {
@@ -1818,7 +1827,7 @@ func_sql_builtin_call_stub(struct func *func, struct port *args,
 }
 
 static void
-sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
+sql_builtin_stub(sql_context *ctx, int argc, struct Mem *argv)
 {
 	(void) argc; (void) argv;
 	diag_set(ClientError, ER_SQL_EXECUTE,
@@ -1922,7 +1931,7 @@ struct sql_func_definition {
 	/** Type of the result of the implementation. */
 	enum field_type result;
 	/** Call implementation with given arguments. */
-	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*call)(sql_context *ctx, int argc, struct Mem *argv);
 	/** Call finalization function for this implementation. */
 	int (*finalize)(struct Mem *mem);
 };
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index b0d32ae32..a4247c760 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -221,9 +221,10 @@ setupLookaside(sql * db, void *pBuf, int sz, int cnt)
 }
 
 void
-sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
-	      MAYBE_UNUSED sql_value **unused2)
+sql_row_count(struct sql_context *context, int argc, struct Mem *argv)
 {
+	(void)argc;
+	(void)argv;
 	sql *db = sql_context_db_handle(context);
 	assert(db->nChange >= 0);
 	sql_result_uint(context, db->nChange);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 6269c2868..8ca967108 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5638,7 +5638,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 				regHit = ++pParse->nMem;
 			sqlVdbeAddOp1(v, OP_SkipLoad, regHit);
 		}
-		struct sql_context *ctx = sql_context_new(pF->func, nArg, coll);
+		struct sql_context *ctx = sql_context_new(pF->func, coll);
 		if (ctx == NULL) {
 			pParse->is_aborted = true;
 			return;
@@ -6750,12 +6750,9 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
 }
 
 struct sql_context *
-sql_context_new(struct func *func, uint32_t argc, struct coll *coll)
+sql_context_new(struct func *func, struct coll *coll)
 {
-	uint32_t size = sizeof(struct sql_context);
-	if (argc > 1)
-		size += (argc - 1) * sizeof(struct Mem *);
-	struct sql_context *ctx = sqlDbMallocRawNN(sql_get(), size);
+	struct sql_context *ctx = sqlDbMallocRawNN(sql_get(), sizeof(*ctx));
 	if (ctx == NULL)
 		return NULL;
 	ctx->pOut = NULL;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 5bce70eaa..7c28c34e1 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -481,8 +481,7 @@ 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);
+sql_row_count(struct sql_context *context, int argc, struct Mem *argv);
 
 int
 sql_column_count(sql_stmt * pStmt);
@@ -4130,7 +4129,7 @@ void sqlStrAccumReset(StrAccum *);
 void sqlSelectDestInit(SelectDest *, int, int, int);
 
 struct sql_context *
-sql_context_new(struct func *func, uint32_t argc, struct coll *coll);
+sql_context_new(struct func *func, struct coll *coll);
 
 void
 sql_context_delete(struct sql_context *ctx);
@@ -4369,7 +4368,7 @@ struct func_sql_builtin {
 	 * Access checks are redundant, because all SQL built-ins
 	 * are predefined and are executed on SQL privilege level.
 	 */
-	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*call)(struct sql_context *ctx, int argc, struct Mem *argv);
 	/**
 	 * A VDBE-memory-compatible finalize method
 	 * (is valid only for aggregate function).
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 5dd68912c..43ce4fdfc 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1183,35 +1183,26 @@ case OP_SkipLoad: {
  * See also: AggStep, AggFinal
  */
 case OP_BuiltinFunction: {
-	int i;
 	int argc = pOp->p1;
 	sql_context *pCtx;
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
 
-	/* 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
-	 */
 	pOut = vdbe_prepare_null_out(p, pOp->p3);
-	if (pCtx->pOut != pOut) {
+	if (pCtx->pOut != pOut)
 		pCtx->pOut = pOut;
-		for(i = 0; i < argc; ++i)
-			pCtx->argv[i] = &aMem[pOp->p2 + i];
-	}
 
 #ifdef SQL_DEBUG
-	for(i = 0; i < argc; i++) {
-		assert(memIsValid(pCtx->argv[i]));
-		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
+	for(int i = 0; i < argc; i++) {
+		assert(memIsValid(&aMem[pOp->p2 + i]));
+		REGISTER_TRACE(p, pOp->p2 + i, &aMem[pOp->p2 + i]);
 	}
 #endif
 	pCtx->is_aborted = false;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, argc, pCtx->argv);
+	func->call(pCtx, argc, &aMem[pOp->p2]);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -4141,7 +4132,6 @@ case OP_DecrJumpZero: {      /* jump, in1 */
  * successors.
  */
 case OP_AggStep: {
-	int i;
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
@@ -4150,33 +4140,25 @@ case OP_AggStep: {
 	pCtx = pOp->p4.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->pOut != pMem) {
+	if (pCtx->pOut != pMem)
 		pCtx->pOut = pMem;
-		for(i = 0; i < argc; ++i)
-			pCtx->argv[i] = &aMem[pOp->p2 + i];
-	}
 
 #ifdef SQL_DEBUG
-	for(i = 0; i < argc; i++) {
-		assert(memIsValid(pCtx->argv[i]));
-		REGISTER_TRACE(p, pOp->p2+i, pCtx->argv[i]);
+	for(int i = 0; i < argc; i++) {
+		assert(memIsValid(&aMem[pOp->p2 + i]));
+		REGISTER_TRACE(p, pOp->p2 + i, &aMem[pOp->p2 + i]);
 	}
 #endif
 
 	pCtx->skipFlag = 0;
 	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
-	func->call(pCtx, argc, pCtx->argv);
+	func->call(pCtx, argc, &aMem[pOp->p2]);
 	if (pCtx->is_aborted)
 		goto abort_due_to_error;
 	if (pCtx->skipFlag) {
 		assert(pOp[-1].opcode == OP_SkipLoad);
-		i = pOp[-1].p1;
+		int i = pOp[-1].p1;
 		if (i) mem_set_bool(&aMem[i], true);
 	}
 	break;
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 5e2692d06..8dbba4908 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -179,7 +179,6 @@ struct sql_context {
 	 */
 	bool is_aborted;
 	u8 skipFlag;		/* Skip accumulator loading if true */
-	sql_value *argv[1];	/* Argument set */
 };
 
 /* A bitfield type for use inside of structures.  Always follow with :N where

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT()
  2021-09-25 11:42     ` Mergen Imeev via Tarantool-patches
@ 2021-09-29  7:03       ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 31+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-09-29  7:03 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

I found out that I forgot to free allocated memory in zeroblob case. Fixed. Diff
in this patch and changed diffs in "sql: remove copying of result in finalizers"
and "sql: remove field argv from struct sql_context" below. Also I rebased this
branch to current master.

On Sat, Sep 25, 2021 at 02:42:36PM +0300, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answers, diff and new patch below.
> 
> On Thu, Sep 23, 2021 at 12:49:52AM +0200, Vladislav Shpilevoy wrote:
> > Thanks for the patch!
> > 
> > > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > > index f699aa927..001a8641c 100644
> > > --- a/src/box/sql/func.c
> > > +++ b/src/box/sql/func.c
> > > @@ -213,6 +213,52 @@ fin_minmax(struct sql_context *ctx)
> > >  	mem_copy(ctx->pOut, ctx->pMem);
> > >  }
> > >  
> > > +/** Implementation of the GROUP_CONCAT() function. */
> > > +static void
> > > +step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
> > > +{
> > > +	assert(argc == 1 || argc == 2);
> > > +	(void)argc;
> > > +	if (argv[0]->type == MEM_TYPE_NULL)
> > > +		return;
> > > +	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
> > > +	if (ctx->pMem->type == MEM_TYPE_NULL) {
> > > +		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
> > 
> > 1. What if the argument is zeroblob with no actual memory allocated yet?
> > 
> There will be '', which is wrong. I fixed this and added a test.
> 
> > > +			ctx->is_aborted = true;
> > > +		return;
> > > +	}
> > > +	const char *sep = NULL;
> > > +	int sep_len = 0;
> > > +	if (argc == 1) {
> > > +		sep = ",";
> > > +		sep_len = 1;
> > > +	} else if (argv[1]->type == MEM_TYPE_NULL) {
> > > +		sep = "";
> > > +		sep_len = 0;
> > > +	} else {
> > > +		assert(mem_is_same_type(argv[0], argv[0]));
> > > +		sep = argv[1]->z;
> > > +		sep_len = argv[1]->n;
> > > +	}
> > > +	if (sep_len > 0) {
> > > +		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
> > 
> > 2. Will it work if sep_len == 0? If yes, then I would propose to
> > drop the len check here and call the append always.
> > 
> Fixed. I moved this check to mem_append().
> 
> > > +			ctx->is_aborted = true;
> > > +			return;
> > > +		}
> 
> 
> Diff:
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 182fb85be..28094e258 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -223,34 +223,43 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
>  {
>  	assert(argc == 1 || argc == 2);
>  	(void)argc;
> -	if (argv[0]->type == MEM_TYPE_NULL)
> +	if (mem_is_null(argv[0]))
>  		return;
>  	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
> -	if (ctx->pMem->type == MEM_TYPE_NULL) {
> -		if (mem_copy_str(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
> +	if (mem_is_null(ctx->pMem)) {
> +		if (mem_copy(ctx->pMem, argv[0]) != 0)
>  			ctx->is_aborted = true;
>  		return;
>  	}
> +	assert(!mem_is_zerobin(ctx->pMem));
>  	const char *sep = NULL;
>  	int sep_len = 0;
>  	if (argc == 1) {
>  		sep = ",";
>  		sep_len = 1;
> -	} else if (argv[1]->type == MEM_TYPE_NULL) {
> +	} else if (mem_is_null(argv[1])) {
>  		sep = "";
>  		sep_len = 0;
>  	} else {
> -		assert(mem_is_same_type(argv[0], argv[0]));
> +		assert(mem_is_same_type(argv[0], argv[1]));
>  		sep = argv[1]->z;
>  		sep_len = argv[1]->n;
>  	}
> -	if (sep_len > 0) {
> -		if (mem_append(ctx->pMem, sep, sep_len) != 0) {
> -			ctx->is_aborted = true;
> -			return;
> -		}
> +	if (mem_append(ctx->pMem, sep, sep_len) != 0) {
> +		ctx->is_aborted = true;
> +		return;
> +	}
> +	uint32_t size;
> +	char *str;
> +	if (mem_is_zerobin(argv[0])) {
> +		size = argv[0]->u.nZero;
> +		str = sqlDbMallocRawNN(sql_get(), size);
> +		memset(str, 0, size);
> +	} else {
> +		size = argv[0]->n;
> +		str = argv[0]->z;
>  	}
> -	if (mem_append(ctx->pMem, argv[0]->z, argv[0]->n) != 0) {
> +	if (mem_append(ctx->pMem, str, size) != 0) {
>  		ctx->is_aborted = true;
>  		return;
>  	}
> diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
> index 416f27d69..bd8a8fe78 100755
> --- a/test/sql-tap/func.test.lua
> +++ b/test/sql-tap/func.test.lua
> @@ -1,6 +1,6 @@
>  #!/usr/bin/env tarantool
>  local test = require("sqltester")
> -test:plan(14680)
> +test:plan(14681)
>  
>  --!./tcltestrunner.lua
>  -- 2001 September 15
> @@ -2142,11 +2142,14 @@ test:do_execsql_test(
>          -- </func-24.2>
>      })
>  
> --- do_test func-24.3 {
> ---   execsql {
> ---     SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1
> ---   }
> --- } {{this 2 program 3 is 4 free 5 software}}
> +test:do_execsql_test(
> +    "func-24.3",
> +    [[
> +        SELECT group_concat(zeroblob(10));
> +    ]], {
> +        '\0\0\0\0\0\0\0\0\0\0'
> +    })
> +
>  test:do_execsql_test(
>      "func-24.4",
>      [[
> 
> 
> New patch:
> 
> commit 5b8563e9b884c86885a3a91fc608fb144afb69a0
> Author: Mergen Imeev <imeevma@gmail.com>
> Date:   Thu Sep 9 18:37:00 2021 +0300
> 
>     sql: rework GROUP_CONCAT()
>     
>     This patch simplifies SQL built-in aggregate function GROUP_CONCAT().
>     
>     Part of #4145
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 3708440e3..28094e258 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -217,6 +217,61 @@ fin_minmax(struct sql_context *ctx)
>  	mem_copy(ctx->pOut, ctx->pMem);
>  }
>  
> +/** Implementation of the GROUP_CONCAT() function. */
> +static void
> +step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
> +{
> +	assert(argc == 1 || argc == 2);
> +	(void)argc;
> +	if (mem_is_null(argv[0]))
> +		return;
> +	assert(mem_is_str(argv[0]) || mem_is_bin(argv[0]));
> +	if (mem_is_null(ctx->pMem)) {
> +		if (mem_copy(ctx->pMem, argv[0]) != 0)
> +			ctx->is_aborted = true;
> +		return;
> +	}
> +	assert(!mem_is_zerobin(ctx->pMem));
> +	const char *sep = NULL;
> +	int sep_len = 0;
> +	if (argc == 1) {
> +		sep = ",";
> +		sep_len = 1;
> +	} else if (mem_is_null(argv[1])) {
> +		sep = "";
> +		sep_len = 0;
> +	} else {
> +		assert(mem_is_same_type(argv[0], argv[1]));
> +		sep = argv[1]->z;
> +		sep_len = argv[1]->n;
> +	}
> +	if (mem_append(ctx->pMem, sep, sep_len) != 0) {
> +		ctx->is_aborted = true;
> +		return;
> +	}
> +	uint32_t size;
> +	char *str;
> +	if (mem_is_zerobin(argv[0])) {
> +		size = argv[0]->u.nZero;
> +		str = sqlDbMallocRawNN(sql_get(), size);
> +		memset(str, 0, size);
> +	} else {
> +		size = argv[0]->n;
> +		str = argv[0]->z;
> +	}
> +	if (mem_append(ctx->pMem, str, size) != 0) {
> +		ctx->is_aborted = true;
> +		return;
> +	}
> +}
> +
> +/** Finalizer for the GROUP_CONCAT() function. */
> +static void
> +fin_group_concat(struct sql_context *ctx)
> +{
> +	mem_copy(ctx->pOut, ctx->pMem);
> +}
> +
>  static const unsigned char *
>  mem_as_ustr(struct Mem *mem)
>  {
> @@ -1761,73 +1816,6 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
>  	}
>  }
>  
> -/*
> - * group_concat(EXPR, ?SEPARATOR?)
> - */
> -static void
> -groupConcatStep(sql_context * context, int argc, sql_value ** argv)
> -{
> -	const char *zVal;
> -	StrAccum *pAccum;
> -	const char *zSep;
> -	int nVal, nSep;
> -	if (argc != 1 && argc != 2) {
> -		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> -			 "GROUP_CONCAT", "1 or 2", argc);
> -		context->is_aborted = true;
> -		return;
> -	}
> -	if (mem_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 = mem_as_str0(argv[1]);
> -				nSep = mem_len_unsafe(argv[1]);
> -			} else {
> -				zSep = ",";
> -				nSep = 1;
> -			}
> -			if (zSep)
> -				sqlStrAccumAppend(pAccum, zSep, nSep);
> -		}
> -		zVal = mem_as_str0(argv[0]);
> -		nVal = mem_len_unsafe(argv[0]);
> -		if (zVal)
> -			sqlStrAccumAppend(pAccum, zVal, nVal);
> -	}
> -}
> -
> -static void
> -groupConcatFinalize(sql_context * context)
> -{
> -	StrAccum *pAccum;
> -	pAccum = sql_aggregate_context(context, 0);
> -	if (pAccum) {
> -		if (pAccum->accError == STRACCUM_TOOBIG) {
> -			diag_set(ClientError, ER_SQL_EXECUTE, "string or binary"\
> -				 "string is too big");
> -			context->is_aborted = true;
> -		} else if (pAccum->accError == STRACCUM_NOMEM) {
> -			context->is_aborted = true;
> -		} else {
> -			char *str = sqlStrAccumFinish(pAccum);
> -			int len = pAccum->nChar;
> -			assert(len >= 0);
> -			if (context->func->def->returns == FIELD_TYPE_STRING)
> -				mem_set_str_dynamic(context->pOut, str, len);
> -			else
> -				mem_set_bin_dynamic(context->pOut, str, len);
> -		}
> -	}
> -}
> -
>  int
>  sql_is_like_func(struct Expr *expr)
>  {
> @@ -1995,13 +1983,13 @@ static struct sql_func_definition definitions[] = {
>  	 NULL},
>  
>  	{"GROUP_CONCAT", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING,
> -	 groupConcatStep, groupConcatFinalize},
> +	 step_group_concat, fin_group_concat},
>  	{"GROUP_CONCAT", 2, {FIELD_TYPE_STRING, FIELD_TYPE_STRING},
> -	 FIELD_TYPE_STRING, groupConcatStep, groupConcatFinalize},
> +	 FIELD_TYPE_STRING, step_group_concat, fin_group_concat},
>  	{"GROUP_CONCAT", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_VARBINARY,
> -	 groupConcatStep, groupConcatFinalize},
> +	 step_group_concat, fin_group_concat},
>  	{"GROUP_CONCAT", 2, {FIELD_TYPE_VARBINARY, FIELD_TYPE_VARBINARY},
> -	 FIELD_TYPE_VARBINARY, groupConcatStep, groupConcatFinalize},
> +	 FIELD_TYPE_VARBINARY, step_group_concat, fin_group_concat},
>  
>  	{"HEX", 1, {FIELD_TYPE_VARBINARY}, FIELD_TYPE_STRING, hexFunc, NULL},
>  	{"IFNULL", 2, {FIELD_TYPE_ANY, FIELD_TYPE_ANY}, FIELD_TYPE_SCALAR,
> diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
> index 416f27d69..bd8a8fe78 100755
> --- a/test/sql-tap/func.test.lua
> +++ b/test/sql-tap/func.test.lua
> @@ -1,6 +1,6 @@
>  #!/usr/bin/env tarantool
>  local test = require("sqltester")
> -test:plan(14680)
> +test:plan(14681)
>  
>  --!./tcltestrunner.lua
>  -- 2001 September 15
> @@ -2142,11 +2142,14 @@ test:do_execsql_test(
>          -- </func-24.2>
>      })
>  
> --- do_test func-24.3 {
> ---   execsql {
> ---     SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1
> ---   }
> --- } {{this 2 program 3 is 4 free 5 software}}
> +test:do_execsql_test(
> +    "func-24.3",
> +    [[
> +        SELECT group_concat(zeroblob(10));
> +    ]], {
> +        '\0\0\0\0\0\0\0\0\0\0'
> +    })
> +
>  test:do_execsql_test(
>      "func-24.4",
>      [[



Changes in "sql: rework GROUP_CONCAT()":

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 28094e258..e1566e150 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -259,10 +259,10 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 		size = argv[0]->n;
 		str = argv[0]->z;
 	}
-	if (mem_append(ctx->pMem, str, size) != 0) {
+	if (mem_append(ctx->pMem, str, size) != 0)
 		ctx->is_aborted = true;
-		return;
-	}
+	if (mem_is_zerobin(argv[0]))
+		sqlDbFree(sql_get(), str);
 }
 
 /** Finalizer for the GROUP_CONCAT() function. */


Changed diff in "sql: remove copying of result in finalizers":

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e1566e150..cd256eef2 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -259,19 +244,12 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 		size = argv[0]->n;
 		str = argv[0]->z;
 	}
-	if (mem_append(ctx->pMem, str, size) != 0)
+	if (mem_append(ctx->pOut, str, size) != 0)
 		ctx->is_aborted = true;
 	if (mem_is_zerobin(argv[0]))
 		sqlDbFree(sql_get(), str);
 }
 
-/** Finalizer for the GROUP_CONCAT() function. */
-static void
-fin_group_concat(struct sql_context *ctx)
-{
-	mem_copy(ctx->pOut, ctx->pMem);
-}
-
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {


Changed diff in "sql: remove field argv from struct sql_context":

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index cd256eef2..41b3de89b 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -236,17 +236,17 @@ step_group_concat(struct sql_context *ctx, int argc, struct Mem **argv)
 	}
 	uint32_t size;
 	char *str;
-	if (mem_is_zerobin(argv[0])) {
-		size = argv[0]->u.nZero;
+	if (mem_is_zerobin(&argv[0])) {
+		size = argv[0].u.nZero;
 		str = sqlDbMallocRawNN(sql_get(), size);
 		memset(str, 0, size);
 	} else {
-		size = argv[0]->n;
-		str = argv[0]->z;
+		size = argv[0].n;
+		str = argv[0].z;
 	}
 	if (mem_append(ctx->pOut, str, size) != 0)
 		ctx->is_aborted = true;
-	if (mem_is_zerobin(argv[0]))
+	if (mem_is_zerobin(&argv[0]))
 		sqlDbFree(sql_get(), str);
 }
 

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2021-09-29  7:03 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <cover.1632220375.git.imeevma@gmail.com>
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 01/15] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 02/15] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 04/15] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 05/15] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
2021-09-25 11:06   ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 06/15] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
2021-09-22 22:47   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 11:13     ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 07/15] sql: rework SUM() Mergen Imeev via Tarantool-patches
2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 11:17     ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 08/15] sql: rework TOTAL() Mergen Imeev via Tarantool-patches
2021-09-25 11:20   ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 09/15] sql: rework AVG() Mergen Imeev via Tarantool-patches
2021-09-22 22:48   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 11:32     ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 10/15] sql: rework COUNT() Mergen Imeev via Tarantool-patches
2021-09-25 11:34   ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 11/15] sql: rework MIN() and MAX() Mergen Imeev via Tarantool-patches
2021-09-25 11:36   ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 12/15] sql: rework GROUP_CONCAT() Mergen Imeev via Tarantool-patches
2021-09-22 22:49   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 11:42     ` Mergen Imeev via Tarantool-patches
2021-09-29  7:03       ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 13/15] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
2021-09-22 22:50   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 11:47     ` Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 14/15] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
2021-09-21 10:59 ` [Tarantool-patches] [PATCH v2 15/15] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
2021-09-22 22:51   ` Vladislav Shpilevoy via Tarantool-patches
2021-09-25 12:03     ` Mergen Imeev via Tarantool-patches

Tarantool development patches archive

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://lists.tarantool.org/tarantool-patches/0 tarantool-patches/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 tarantool-patches tarantool-patches/ https://lists.tarantool.org/tarantool-patches \
		tarantool-patches@dev.tarantool.org.
	public-inbox-index tarantool-patches

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git