Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions
@ 2021-10-01 12:48 Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM Mergen Imeev via Tarantool-patches
                   ` (16 more replies)
  0 siblings, 17 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch-set refactor the aggregate functions and does some refactoring of the
SQL function infrastructure as a whole.

https://github.com/tarantool/tarantool/issues/4145
https://github.com/tarantool/tarantool/tree/imeevma/gh-4145-aggregate-functions

Changes in v2:
 - Added commit to remove possible UB in int-to-double implicit cast function.
 - Added commit to drop argv from struct sql_context.
 - Review fixes.

Changes in v3:
 - All functional changes were carried over from this patch-set. All that's left
   is refactoring.

Changes in v4:
 - Zeroblob optimization was removed, which allowed to simplify code and avoid
   some errors.

Mergen Imeev (16):
  sql: remove MEM_Zero flag from struct MEM
  sql: fix possible undefined behavior during cast
  sql: use register P1 for number of arguments
  sql: remove AggStep0 and OP_BuiltinFunction0
  sql: move collation to struct sql_context
  sql: introduce mem_append()
  sql: remove sql_vdbemem_finalize()
  sql: refactor SUM() function
  sql: refactor TOTAL() function
  sql: refactor AVG() function
  sql: refactor COUNT() function
  sql: refactor MIN() and MAX() functions
  sql: refactor GROUP_CONCAT() function
  sql: remove copying of result in finalizers
  sql: remove MEM_TYPE_AGG
  sql: remove field argv from struct sql_context

 src/box/sql/date.c    |  43 ---
 src/box/sql/expr.c    |  19 +-
 src/box/sql/func.c    | 812 +++++++++++++++++++-----------------------
 src/box/sql/main.c    |   5 +-
 src/box/sql/mem.c     | 200 ++---------
 src/box/sql/mem.h     |  51 +--
 src/box/sql/select.c  |  35 +-
 src/box/sql/sqlInt.h  |  40 +--
 src/box/sql/vdbe.c    | 202 ++---------
 src/box/sql/vdbeInt.h |   7 +-
 src/box/sql/vdbeapi.c | 108 ------
 src/box/sql/vdbeaux.c |  21 +-
 12 files changed, 504 insertions(+), 1039 deletions(-)

-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:51   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removes zeroblob optimization from SQL code. This
optimization complicates the code, and there is almost no profit from
it.
---
 src/box/sql/func.c    |  10 +++-
 src/box/sql/mem.c     | 119 ++++--------------------------------------
 src/box/sql/mem.h     |  22 +-------
 src/box/sql/sqlInt.h  |  14 -----
 src/box/sql/vdbe.c    |  10 +---
 src/box/sql/vdbeapi.c |  42 ---------------
 6 files changed, 20 insertions(+), 197 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 29d713fd0..ff0c461ce 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1272,11 +1272,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 	n = mem_get_int_unsafe(argv[0]);
 	if (n < 0)
 		n = 0;
-	if (sql_result_zeroblob64(context, n) != 0) {
+	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
 			 "is too big");
 		context->is_aborted = true;
+		return;
+	}
+	char *str = sqlDbMallocRawNN(sql_get(), n);
+	memset(str, 0, n);
+	if (str == NULL) {
+		context->is_aborted = true;
+		return;
 	}
+	mem_set_bin_allocated(context->pOut, str, n);
 }
 
 /*
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 5e23c901c..24d6d7dbf 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -503,19 +503,6 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 	return 0;
 }
 
-void
-mem_set_zerobin(struct Mem *mem, int n)
-{
-	mem_destroy(mem);
-	if (n < 0)
-		n = 0;
-	mem->u.nZero = n;
-	mem->z = NULL;
-	mem->n = 0;
-	mem->type = MEM_TYPE_BIN;
-	mem->flags = MEM_Zero;
-}
-
 static inline void
 set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
 		  enum mem_type type)
@@ -806,8 +793,6 @@ static inline int
 bin_to_str(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	mem->type = MEM_TYPE_STR;
 	mem->flags &= ~MEM_Scalar;
 	return 0;
@@ -817,8 +802,6 @@ static inline int
 bin_to_str0(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
 		return -1;
 	mem->z[mem->n] = '\0';
@@ -831,8 +814,6 @@ static inline int
 bin_to_uuid(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (mem->n != UUID_LEN ||
 	    tt_uuid_validate((struct tt_uuid *)mem->z) != 0)
 		return -1;
@@ -1869,8 +1850,6 @@ mem_get_bin(const struct Mem *mem, const char **s)
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
-	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
-		return -1;
 	*s = mem->z;
 	return 0;
 }
@@ -1880,11 +1859,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
 {
 	if (!mem_is_bytes(mem))
 		return -1;
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	if ((mem->flags & MEM_Zero) != 0)
-		*len = mem->n + mem->u.nZero;
-	else
-		*len = mem->n;
+	*len = mem->n;
 	return 0;
 }
 
@@ -1910,9 +1885,6 @@ mem_copy(struct Mem *to, const struct Mem *from)
 		return 0;
 	if ((to->flags & MEM_Static) != 0)
 		return 0;
-	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
-	if ((to->flags & MEM_Zero) != 0)
-		return sqlVdbeMemExpandBlob(to);
 	to->zMalloc = sqlDbRealloc(to->db, to->zMalloc, MAX(32, to->n));
 	assert(to->zMalloc != NULL || sql_get()->mallocFailed != 0);
 	if (to->zMalloc == NULL)
@@ -1937,7 +1909,7 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
 		return;
 	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
 		return;
-	to->flags &= MEM_Term | MEM_Zero;
+	to->flags &= MEM_Term;
 	to->flags |= MEM_Ephem;
 	return;
 }
@@ -1954,7 +1926,7 @@ mem_move(struct Mem *to, struct Mem *from)
 }
 
 int
-mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
 	if (mem_is_any_null(a, b)) {
 		mem_set_null(result);
@@ -1981,9 +1953,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		return -1;
 	}
 
-	if (ExpandBlob(a) != 0 || ExpandBlob(b) != 0)
-		return -1;
-
 	uint32_t size = a->n + b->n;
 	if ((int)size > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
@@ -2330,37 +2299,8 @@ static int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b)
 {
 	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
-	int an = a->n;
-	int bn = b->n;
-	int minlen = MIN(an, bn);
-
-	/*
-	 * It is possible to have a Blob value that has some non-zero content
-	 * followed by zero content.  But that only comes up for Blobs formed
-	 * by the OP_MakeRecord opcode, and such Blobs never get passed into
-	 * mem_compare().
-	 */
-	assert((a->flags & MEM_Zero) == 0 || an == 0);
-	assert((b->flags & MEM_Zero) == 0 || bn == 0);
-
-	if ((a->flags & b->flags & MEM_Zero) != 0)
-		return a->u.nZero - b->u.nZero;
-	if ((a->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (b->z[i] != 0)
-				return -1;
-		}
-		return a->u.nZero - bn;
-	}
-	if ((b->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (a->z[i] != 0)
-				return 1;
-		}
-		return b->u.nZero - an;
-	}
-	int res = memcmp(a->z, b->z, minlen);
-	return res != 0 ? res : an - bn;
+	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
+	return res != 0 ? res : a->n - b->n;
 }
 
 static int
@@ -2797,10 +2737,6 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 		}
 		sql_snprintf(100, zCsr, "]%s", "(8)");
 		zCsr += sqlStrlen30(zCsr);
-		if (f & MEM_Zero) {
-			sql_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
-			zCsr += sqlStrlen30(zCsr);
-		}
 		*zCsr = '\0';
 	} else if (pMem->type == MEM_TYPE_STR) {
 		int j, k;
@@ -2886,32 +2822,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * If the given Mem* has a zero-filled tail, turn it into an ordinary
- * blob stored in dynamically allocated space.
- */
-int
-sqlVdbeMemExpandBlob(Mem * pMem)
-{
-	int nByte;
-	assert(pMem->flags & MEM_Zero);
-	assert(pMem->type == MEM_TYPE_BIN);
-
-	/* Set nByte to the number of bytes required to store the expanded blob. */
-	nByte = pMem->n + pMem->u.nZero;
-	if (nByte <= 0) {
-		nByte = 1;
-	}
-	if (sqlVdbeMemGrow(pMem, nByte, 1)) {
-		return -1;
-	}
-
-	memset(&pMem->z[pMem->n], 0, pMem->u.nZero);
-	pMem->n += pMem->u.nZero;
-	pMem->flags &= ~(MEM_Zero | MEM_Term);
-	return 0;
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
@@ -3036,13 +2946,8 @@ int
 sqlVdbeMemTooBig(Mem * p)
 {
 	assert(p->db != 0);
-	if (mem_is_bytes(p)) {
-		int n = p->n;
-		if (p->flags & MEM_Zero) {
-			n += p->u.nZero;
-		}
-		return n > p->db->aLimit[SQL_LIMIT_LENGTH];
-	}
+	if (mem_is_bytes(p))
+		return p->n > p->db->aLimit[SQL_LIMIT_LENGTH];
 	return 0;
 }
 
@@ -3251,14 +3156,8 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 		mpstream_encode_double(stream, var->u.r);
 		return;
 	case MEM_TYPE_BIN:
-		if ((var->flags & MEM_Zero) != 0) {
-			mpstream_encode_binl(stream, var->n + var->u.nZero);
-			mpstream_memcpy(stream, var->z, var->n);
-			mpstream_memset(stream, 0, var->u.nZero);
-		} else {
-			mpstream_encode_binl(stream, var->n);
-			mpstream_memcpy(stream, var->z, var->n);
-		}
+		mpstream_encode_binl(stream, var->n);
+		mpstream_memcpy(stream, var->z, var->n);
 		return;
 	case MEM_TYPE_ARRAY:
 	case MEM_TYPE_MAP:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 0da45b8af..1574da22d 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -68,7 +68,6 @@ struct Mem {
 		i64 i;		/* Integer value used when MEM_Int is set in flags */
 		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
 		bool b;         /* Boolean value used when MEM_Bool is set in flags */
-		int nZero;	/* Used when bit MEM_Zero is set in flags */
 		void *p;	/* Generic pointer */
 		/**
 		 * A pointer to function implementation.
@@ -111,7 +110,6 @@ struct Mem {
 #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
 #define MEM_Static    0x1000	/* Mem.z points to a static string */
 #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
-#define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
 
 static inline bool
 mem_is_null(const struct Mem *mem)
@@ -246,13 +244,6 @@ mem_is_cleared(const struct Mem *mem)
 	return (mem->flags & MEM_Cleared) != 0;
 }
 
-static inline bool
-mem_is_zerobin(const struct Mem *mem)
-{
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	return (mem->flags & MEM_Zero) != 0;
-}
-
 static inline bool
 mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 {
@@ -474,12 +465,6 @@ mem_set_binl(struct Mem *mem, char *value, uint32_t size,
 		return mem_set_bin_dynamic(mem, value, size);
 }
 
-/**
- * Clear MEM and set it to VARBINARY. The binary value consist of n zero bytes.
- */
-void
-mem_set_zerobin(struct Mem *mem, int n);
-
 /**
  * Copy binary value to a newly allocated memory. The MEM type becomes
  * VARBINARY.
@@ -603,7 +588,7 @@ mem_move(struct Mem *to, struct Mem *from);
  * result MEM is set to NULL even if the result MEM is actually the first MEM.
  */
 int
-mem_concat(struct Mem *left, struct Mem *right, struct Mem *result);
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result);
 
 /**
  * Add the first MEM to the second MEM and write the result to the third MEM.
@@ -935,11 +920,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
 #endif
 
-int sqlVdbeMemExpandBlob(struct Mem *);
-#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
-
-/** Setters = Change MEM value. */
-
 int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
 
 void sqlValueFree(struct Mem *);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 2e250dc29..eac6bcec8 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -394,13 +394,6 @@ void
 sql_result_value(sql_context *,
 		     sql_value *);
 
-void
-sql_result_zeroblob(sql_context *, int n);
-
-int
-sql_result_zeroblob64(sql_context *,
-			  sql_uint64 n);
-
 char *
 sql_mprintf(const char *, ...);
 char *
@@ -631,13 +624,6 @@ int
 sql_bind_text64(sql_stmt *, int, const char *,
 		    sql_uint64, void (*)(void *));
 
-int
-sql_bind_zeroblob(sql_stmt *, int, int n);
-
-int
-sql_bind_zeroblob64(sql_stmt *, int,
-			sql_uint64);
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 44533fb3e..86550541b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1462,8 +1462,6 @@ case OP_MustBeInt: {            /* jump, in1 */
  */
 case OP_Cast: {                  /* in1 */
 	pIn1 = &aMem[pOp->p1];
-	if (ExpandBlob(pIn1) != 0)
-		goto abort_due_to_error;
 	rc = mem_cast_explicit(pIn1, pOp->p2);
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (rc == 0)
@@ -2772,8 +2770,6 @@ case OP_Found: {        /* jump, in3 */
 #ifdef SQL_DEBUG
 		for(ii=0; ii<r.nField; ii++) {
 			assert(memIsValid(&r.aMem[ii]));
-			assert(!mem_is_zerobin(&r.aMem[ii]) ||
-			       r.aMem[ii].n == 0);
 			if (ii != 0)
 				REGISTER_TRACE(p, pOp->p3+ii, &r.aMem[ii]);
 		}
@@ -2784,7 +2780,6 @@ case OP_Found: {        /* jump, in3 */
 		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(mem_is_bin(pIn3));
-		(void)ExpandBlob(pIn3);
 		sqlVdbeRecordUnpackMsgpack(pC->key_def,
 					       pIn3->z, pIdxKey);
 	}
@@ -3411,8 +3406,7 @@ case OP_SorterInsert: {      /* in2 */
 	assert(isSorter(cursor));
 	pIn2 = &aMem[pOp->p2];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0 ||
-	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
+	if (sqlVdbeSorterWrite(cursor, pIn2) != 0)
 		goto abort_due_to_error;
 	break;
 }
@@ -3445,8 +3439,6 @@ case OP_IdxReplace:
 case OP_IdxInsert: {
 	pIn2 = &aMem[pOp->p1];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0)
-		goto abort_due_to_error;
 	struct space *space;
 	if (pOp->p4type == P4_SPACEPTR)
 		space = pOp->p4.space;
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 115940227..97bd19863 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -236,25 +236,6 @@ sql_result_value(sql_context * pCtx, sql_value * pValue)
 		pCtx->is_aborted = true;
 }
 
-void
-sql_result_zeroblob(sql_context * pCtx, int n)
-{
-	mem_set_zerobin(pCtx->pOut, n);
-}
-
-int
-sql_result_zeroblob64(sql_context * pCtx, u64 n)
-{
-	Mem *pOut = pCtx->pOut;
-	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	mem_set_zerobin(pCtx->pOut, (int)n);
-	return 0;
-}
-
 /*
  * Execute the statement pStmt, either until a row of data is ready, the
  * statement is completely executed or an error occurs.
@@ -818,29 +799,6 @@ sql_bind_text64(sql_stmt * pStmt,
 	}
 }
 
-int
-sql_bind_zeroblob(sql_stmt * pStmt, int i, int n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (vdbeUnbind(p, i) != 0)
-		return -1;
-	mem_set_zerobin(&p->aVar[i - 1], n);
-	return 0;
-}
-
-int
-sql_bind_zeroblob64(sql_stmt * pStmt, int i, sql_uint64 n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (n > (u64) p->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	assert((n & 0x7FFFFFFF) == n);
-	return sql_bind_zeroblob(pStmt, i, n);
-}
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid)
 {
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
                   ` (14 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 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 24d6d7dbf..079083fa1 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -682,7 +682,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 04/16] sql: remove AggStep0 and OP_BuiltinFunction0 Mergen Imeev via Tarantool-patches
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 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 86550541b..af97b0499 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;
 
@@ -4177,26 +4170,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,
@@ -4220,7 +4213,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;
@@ -4229,6 +4221,7 @@ case OP_AggStep0: {
 }
 case OP_AggStep: {
 	int i;
+	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
 	Mem t;
@@ -4244,11 +4237,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]);
 	}
@@ -4261,7 +4255,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 04/16] sql: remove AggStep0 and OP_BuiltinFunction0
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (2 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 05/16] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch moves the initialization of sql_context out of the VDBE. This
allows us to remove the opcodes OP_BuiltinFunction0 and OP_AggStep0,
which work in a rather strange way. Moreover, due to the changes these
opcodes make to the VDBEs, it is possible that the estimated size of the
VDBE may change during execution of VDBE, which could lead to various
problems.

Needed for #4145
---
 src/box/sql/expr.c    | 12 +++++--
 src/box/sql/select.c  | 33 ++++++++++++++++--
 src/box/sql/sqlInt.h  |  6 ++++
 src/box/sql/vdbe.c    | 81 ++-----------------------------------------
 src/box/sql/vdbeaux.c | 12 ++-----
 5 files changed, 51 insertions(+), 93 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 3b5df5292..6446ef091 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4107,9 +4107,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 						  (char *)coll, P4_COLLSEQ);
 			}
 			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
-				sqlVdbeAddOp4(v, OP_BuiltinFunction0, nFarg,
-					      r1, target, (char *)func,
-					      P4_FUNC);
+				struct sql_context *ctx =
+					sql_context_new(v, func, nFarg);
+				if (ctx == NULL) {
+					pParse->is_aborted = true;
+					return -1;
+				}
+				sqlVdbeAddOp4(v, OP_BuiltinFunction, nFarg,
+					      r1, target, (char *)ctx,
+					      P4_FUNCCTX);
 			} else {
 				sqlVdbeAddOp4(v, OP_FunctionByName, nFarg,
 					      r1, target,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 92e40aef6..5b57f57a3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5639,8 +5639,13 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			sqlVdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
 					  (char *)coll, P4_COLLSEQ);
 		}
-		sqlVdbeAddOp3(v, OP_AggStep0, nArg, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
+		struct sql_context *ctx = sql_context_new(v, pF->func, nArg);
+		if (ctx == NULL) {
+			pParse->is_aborted = true;
+			return;
+		}
+		sqlVdbeAddOp3(v, OP_AggStep, nArg, regAgg, pF->iMem);
+		sqlVdbeAppendP4(v, ctx, P4_FUNCCTX);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
 		if (addrNext) {
@@ -6744,3 +6749,27 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
 	parser->parsed_ast.expr =
 		sqlExprDup(parser->db, expr_list->a->pExpr, 0);
 }
+
+struct sql_context *
+sql_context_new(struct Vdbe *vdbe, struct func *func, uint32_t argc)
+{
+	uint32_t size = sizeof(struct sql_context);
+	if (argc > 1)
+		size += (argc - 1) * sizeof(struct Mem *);
+	struct sql_context *ctx = sqlDbMallocRawNN(sql_get(), size);
+	if (ctx == NULL)
+		return NULL;
+	ctx->pOut = NULL;
+	ctx->func = func;
+	ctx->is_aborted = false;
+	ctx->skipFlag = 0;
+	ctx->pVdbe = vdbe;
+	ctx->iOp = 0;
+	return ctx;
+}
+
+void
+sql_context_delete(struct sql_context *ctx)
+{
+	sqlDbFree(sql_get(), ctx);
+}
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index eac6bcec8..b944b357b 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4127,6 +4127,12 @@ char *sqlStrAccumFinish(StrAccum *);
 void sqlStrAccumReset(StrAccum *);
 void sqlSelectDestInit(SelectDest *, int, int, int);
 
+struct sql_context *
+sql_context_new(struct Vdbe *vdbe, struct func *func, uint32_t argc);
+
+void
+sql_context_delete(struct sql_context *ctx);
+
 /*
  * Create an expression to load @a column from datasource
  * @a src_idx in @a src_list.
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index af97b0499..11e418e53 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1182,16 +1182,6 @@ case OP_CollSeq: {
 	break;
 }
 
-/* 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 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.
- *
- * See also: BuiltinFunction, AggStep, AggFinal
- */
 /* Opcode: BuiltinFunction P1 P2 P3 P4 *
  * Synopsis: r[P3]=func(r[P2@P1])
  *
@@ -1200,37 +1190,8 @@ case OP_CollSeq: {
  * 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.
  *
- * 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
- * object and the operation changed to this OP_BuiltinFunction
- * opcode.  In this way, the initialization of the sql_context
- * object occurs only once, rather than once for each evaluation
- * of the function.
- *
- * See also: BuiltinFunction0, AggStep, AggFinal
+ * See also: AggStep, AggFinal
  */
-case OP_BuiltinFunction0: {
-	int n;
-	sql_context *pCtx;
-
-	assert(pOp->p4type == P4_FUNC);
-	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);
-	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
-	if (pCtx==0) goto no_mem;
-	pCtx->pOut = 0;
-	pCtx->func = pOp->p4.func;
-	pCtx->iOp = (int)(pOp - aOp);
-	pCtx->pVdbe = p;
-	pOp->p4type = P4_FUNCCTX;
-	pOp->p4.pCtx = pCtx;
-	pOp->opcode = OP_BuiltinFunction;
-	/* Fall through into OP_BuiltinFunction */
-	FALLTHROUGH;
-}
 case OP_BuiltinFunction: {
 	int i;
 	int argc = pOp->p1;
@@ -1238,6 +1199,7 @@ 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
@@ -4170,17 +4132,6 @@ case OP_DecrJumpZero: {      /* jump, in1 */
 }
 
 
-/* Opcode: AggStep0 P1 P2 P3 P4 *
- * Synopsis: accum=r[P3] step(r[P2@P1])
- *
- * Execute the step function for an aggregate.  The
- * function has P1 arguments.   P4 is a pointer to the FuncDef
- * structure that specifies the function.  Register P3 is the
- * accumulator.
- *
- * The P1 arguments are taken from register P2 and its
- * successors.
- */
 /* Opcode: AggStep P1 P2 P3 P4 *
  * Synopsis: accum=r[P3] step(r[P2@P1])
  *
@@ -4191,34 +4142,7 @@ case OP_DecrJumpZero: {      /* jump, in1 */
  *
  * The P1 arguments are taken from register P2 and its
  * successors.
- *
- * This opcode is initially coded as OP_AggStep0.  On first evaluation,
- * the FuncDef stored in P4 is converted into an sql_context and
- * the opcode is changed.  In this way, the initialization of the
- * sql_context only happens once, instead of on each call to the
- * step function.
  */
-case OP_AggStep0: {
-	int n;
-	sql_context *pCtx;
-
-	assert(pOp->p4type == P4_FUNC);
-	n = pOp->p5;
-	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
-	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
-	assert(pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n);
-	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
-	if (pCtx==0) goto no_mem;
-	pCtx->pMem = 0;
-	pCtx->func = pOp->p4.func;
-	pCtx->iOp = (int)(pOp - aOp);
-	pCtx->pVdbe = p;
-	pOp->p4type = P4_FUNCCTX;
-	pOp->p4.pCtx = pCtx;
-	pOp->opcode = OP_AggStep;
-	/* Fall through into OP_AggStep */
-	FALLTHROUGH;
-}
 case OP_AggStep: {
 	int i;
 	int argc = pOp->p1;
@@ -4228,6 +4152,7 @@ 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[]
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 8148ed8b0..4c2bd11ba 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -590,19 +590,13 @@ sqlVdbeJumpHere(Vdbe * p, int addr)
 
 static void vdbeFreeOpArray(sql *, Op *, int);
 
-static SQL_NOINLINE void
-freeP4FuncCtx(sql * db, sql_context * p)
-{
-	sqlDbFree(db, p);
-}
-
 static void
 freeP4(sql * db, int p4type, void *p4)
 {
 	assert(db);
 	switch (p4type) {
 	case P4_FUNCCTX:{
-			freeP4FuncCtx(db, (sql_context *) p4);
+			sql_context_delete(p4);
 			break;
 		}
 	case P4_REAL:
@@ -1068,14 +1062,12 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 				   func->def->param_count);
 			break;
 		}
-#if defined(SQL_DEBUG) || defined(VDBE_PROFILE)
 	case P4_FUNCCTX:{
-			struct func *func = pOp->p4.func;
+			struct func *func = pOp->p4.pCtx->func;
 			sqlXPrintf(&x, "%s(%d)", func->def->name,
 				   func->def->param_count);
 			break;
 		}
-#endif
 	case P4_BOOL:
 			sqlXPrintf(&x, "%d", pOp->p4.b);
 			break;
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 05/16] sql: move collation to struct sql_context
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (3 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 04/16] sql: remove AggStep0 and OP_BuiltinFunction0 Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 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 ff0c461ce..bc2d69c49 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]);
@@ -1769,7 +1755,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 5b57f57a3..6269c2868 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,8 +6762,7 @@ 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 b944b357b..87ba9e067 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4128,7 +4128,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);
 
 void
 sql_context_delete(struct sql_context *ctx);
@@ -4193,7 +4193,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 11e418e53..7f1965492 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
@@ -4152,7 +4141,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[]
@@ -4187,7 +4175,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 97bd19863..65f51c21c 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -330,33 +330,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append()
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (4 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 05/16] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 07/16] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
                   ` (10 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch introduces the mem_append() function. This function appends
the specified string to the end of the STRING or VARBINARY contained in
MEM. In case MEM needs to increase the size of allocated memory,
extra memory is allocated in an attempt to reduce the total number of
allocations.

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

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 079083fa1..b8ead8f41 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1925,6 +1925,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(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 1574da22d..b567aea50 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -582,6 +582,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;
 				}
 			}
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 07/16] sql: remove sql_vdbemem_finalize()
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (5 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 08/16] sql: refactor SUM() function Mergen Imeev via Tarantool-patches
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 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  | 33 ++-------------------------------
 src/box/sql/mem.h  | 12 ------------
 src/box/sql/vdbe.c | 27 ++++++++++++++++++++-------
 3 files changed, 22 insertions(+), 50 deletions(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index b8ead8f41..853f9efe9 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;
@@ -2972,31 +2968,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 b567aea50..7d5a750f5 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -949,18 +949,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 7f1965492..067065a97 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4196,16 +4196,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;
 }
 
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 08/16] sql: refactor SUM() function
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (6 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 07/16] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 09/16] sql: refactor TOTAL() function Mergen Imeev via Tarantool-patches
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

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

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index bc2d69c49..ccad647c1 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)
 {
@@ -1662,17 +1685,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)
 {
@@ -2121,8 +2133,8 @@ 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_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, 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 067065a97..a3d403176 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4199,7 +4199,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));
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 09/16] sql: refactor TOTAL() function
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (7 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 08/16] sql: refactor SUM() function Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function Mergen Imeev via Tarantool-patches
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Part of #4145
---
 src/box/sql/func.c | 45 ++++++++++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index ccad647c1..8a9de1254 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)
 {
@@ -1701,17 +1727,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.
@@ -2135,10 +2150,10 @@ static struct sql_func_definition definitions[] = {
 	 FIELD_TYPE_VARBINARY, substrFunc, NULL},
 	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, fin_sum},
 	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, 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_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
+	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
+	 fin_total},
 
 	{"TRIM", 2, {FIELD_TYPE_STRING, FIELD_TYPE_INTEGER},
 	 FIELD_TYPE_STRING, trim_func, NULL},
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (8 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 09/16] sql: refactor TOTAL() function Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function Mergen Imeev via Tarantool-patches
                   ` (6 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Part of #4145
---
 src/box/sql/func.c | 119 ++++++++++++++++++++-------------------------
 1 file changed, 54 insertions(+), 65 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 8a9de1254..94ec811ef 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)
 {
@@ -1664,69 +1716,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.
@@ -2023,8 +2012,8 @@ 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_INTEGER}, FIELD_TYPE_INTEGER, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, 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},
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (9 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions Mergen Imeev via Tarantool-patches
                   ` (5 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

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 94ec811ef..384c68be8 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)
 {
@@ -1716,41 +1739,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.
  */
@@ -2019,9 +2007,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (10 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-04 21:54   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 13/16] sql: refactor GROUP_CONCAT() function Mergen Imeev via Tarantool-patches
                   ` (4 subsequent siblings)
  16 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

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 384c68be8..51693d47b 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
  */
@@ -1739,60 +1769,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?)
  */
@@ -2063,35 +2039,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 a3d403176..96eb76244 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4161,7 +4161,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 13/16] sql: refactor GROUP_CONCAT() function
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (11 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 14/16] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

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

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 51693d47b..b669402bb 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -217,6 +217,48 @@ 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;
+	}
+	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;
+	}
+	if (mem_append(ctx->pMem, argv[0]->z, argv[0]->n) != 0)
+		ctx->is_aborted = true;
+}
+
+/** 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)
 {
@@ -1769,73 +1811,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)
 {
@@ -2002,13 +1977,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 14/16] sql: remove copying of result in finalizers
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (12 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 13/16] sql: refactor GROUP_CONCAT() function Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 15/16] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removes copying of the result in the finalizers of the SQL
built-in aggregate functions.

Part of #4145
---
 src/box/sql/func.c    | 172 +++++++++++++++++-------------------------
 src/box/sql/sqlInt.h  |  14 +---
 src/box/sql/vdbe.c    |  28 +------
 src/box/sql/vdbeInt.h |   1 -
 src/box/sql/vdbeapi.c |  39 ----------
 5 files changed, 74 insertions(+), 180 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b669402bb..f49d777cc 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,8 +211,8 @@ 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;
 	}
@@ -244,21 +229,14 @@ 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;
 	}
-	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;
 }
 
-/** 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)
 {
@@ -1941,7 +1919,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);
 };
 
 /**
@@ -1977,13 +1955,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,
@@ -2014,35 +1992,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},
@@ -2076,8 +2042,8 @@ 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, step_sum, fin_sum},
-	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, fin_sum},
+	{"SUM", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_sum, NULL},
+	{"SUM", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_sum, NULL},
 	{"TOTAL", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_DOUBLE, step_total,
 	 fin_total},
 	{"TOTAL", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_total,
@@ -2328,7 +2294,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 87ba9e067..5fcf5e5a3 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -477,18 +477,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);
 
@@ -4372,7 +4360,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 96eb76244..386430b3b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4137,7 +4137,6 @@ case OP_AggStep: {
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
-	Mem t;
 
 	assert(pOp->p4type==P4_FUNCCTX);
 	pCtx = pOp->p4.pCtx;
@@ -4148,8 +4147,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];
 	}
@@ -4161,18 +4160,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;
@@ -4199,20 +4192,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 65f51c21c..8f7e28ffb 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -330,45 +330,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.
  */
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v4 15/16] sql: remove MEM_TYPE_AGG
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (13 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 14/16] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:48 ` Mergen Imeev via Tarantool-patches
  2021-10-01 12:49 ` [Tarantool-patches] [PATCH v4 16/16] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
  2021-10-25 20:58 ` [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Vladislav Shpilevoy via Tarantool-patches
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:48 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 853f9efe9..ec25bc565 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -592,22 +592,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)
 {
@@ -1859,15 +1843,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 7d5a750f5..7f7382961 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,
 };
 
 /*
@@ -185,12 +184,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)
 {
@@ -947,7 +940,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] 39+ messages in thread

* [Tarantool-patches] [PATCH v4 16/16] sql: remove field argv from struct sql_context
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (14 preceding siblings ...)
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 15/16] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
@ 2021-10-01 12:49 ` Mergen Imeev via Tarantool-patches
  2021-10-25 20:58 ` [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Vladislav Shpilevoy via Tarantool-patches
  16 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-01 12:49 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 f49d777cc..b23636d7e 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;
 	}
@@ -221,19 +221,19 @@ 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;
 		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;
 }
 
@@ -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 (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
@@ -1454,7 +1463,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 */
@@ -1469,29 +1478,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);
@@ -1710,14 +1719,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",
@@ -1733,7 +1742,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];
@@ -1750,14 +1759,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++) {
@@ -1813,7 +1822,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,
@@ -1917,7 +1926,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 5fcf5e5a3..cfdf71f1f 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -474,8 +474,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);
@@ -4116,7 +4115,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);
@@ -4355,7 +4354,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 386430b3b..628fcb7be 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)
@@ -4133,7 +4124,6 @@ case OP_DecrJumpZero: {      /* jump, in1 */
  * successors.
  */
 case OP_AggStep: {
-	int i;
 	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
@@ -4142,33 +4132,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] 39+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:51   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05  8:46     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:51 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Hi! Thanks for the patch!

See 2 comments below.

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 29d713fd0..ff0c461ce 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1272,11 +1272,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>  	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 0)
>  		n = 0;
> -	if (sql_result_zeroblob64(context, n) != 0) {
> +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
>  			 "is too big");
>  		context->is_aborted = true;
> +		return;
> +	}
> +	char *str = sqlDbMallocRawNN(sql_get(), n);
> +	memset(str, 0, n);

1. There is sqlDbMallocZero().

> +	if (str == NULL) {
> +		context->is_aborted = true;
> +		return;
>  	}
> +	mem_set_bin_allocated(context->pOut, str, n);
>  }
>  
>  /*
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 5e23c901c..24d6d7dbf 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1869,8 +1850,6 @@ mem_get_bin(const struct Mem *mem, const char **s)
>  		*s = mem->n > 0 ? mem->z : NULL;
>  		return 0;
>  	}
> -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> -		return -1;

2. If the type is not MEM_TYPE_BIN, you will return garbage. Maybe
keep return -1 for other types?

>  	*s = mem->z;
>  	return 0;
>  }

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

* Re: [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05  9:00     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:52 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the fixes!

On 01.10.2021 14:48, imeevma@tarantool.org wrote:
> 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 24d6d7dbf..079083fa1 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -682,7 +682,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)

What if uint really was UINT64_MAX? Then you treat its conversion
into UINT64_MAX double as an error?

Maybe we could simply refuse to convert all ints > 2^53 and <-2^53
(or whatever is the precise int limit in double). All ints above
these values will convert 'from time to time' depending on exact
values, which does not look reliable anyway.

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

* Re: [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append()
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05  9:32     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:52 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for working on this!

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 079083fa1..b8ead8f41 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1925,6 +1925,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)

Looks like you could call sqlVdbeMemGrow() without all these checks
above. The grow function does them already.

> +			return -1;
> +	}
> +	memcpy(&mem->z[mem->n], value, len);
> +	mem->n = new_size;
> +	return 0;
> +}

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

* Re: [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05  9:48     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:53 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 8a9de1254..94ec811ef 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -102,6 +102,58 @@ fin_total(struct sql_context *ctx)

<...>

> +
> +/** 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;

Consider this:

====================
@@ -141,17 +141,14 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(sum + 1);
 	struct Mem count;
 	mem_create(&count);
 	mem_set_uint(&count, *count_val);
 	if (mem_div(&sum, &count, ctx->pOut) != 0)
 		ctx->is_aborted = true;
+	mem_destroy(sum);
 }

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

* Re: [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05  9:55     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:53 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Nicely done!

On 01.10.2021 14:48, imeevma@tarantool.org wrote:
> 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 94ec811ef..384c68be8 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);

Would be nice to have a 'begin' step for the aggregation
functions. This would allow to eliminate these 'if is null'
ifs in some step functions in favor of having +1 virtual 'begin'
call in the beginning.

Do you think it would simplify/speed up things? If you agree,
could you please create a ticket for that? As 'good first issue'
even.

If don't agree, then ignore this comment.

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

* Re: [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions
  2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions Mergen Imeev via Tarantool-patches
@ 2021-10-04 21:54   ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-05 10:07     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-04 21:54 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Good job on the fixes!

> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index a3d403176..96eb76244 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -4161,7 +4161,6 @@ case OP_AggStep: {
>  	}
>  #endif
>  
> -	pMem->n++;

Could you please remind me why is it dropped in this commit?

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

* Re: [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM
  2021-10-04 21:51   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05  8:46     ` Mergen Imeev via Tarantool-patches
  2021-10-05  9:42       ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  8:46 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

On Mon, Oct 04, 2021 at 11:51:42PM +0200, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch!
> 
> See 2 comments below.
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 29d713fd0..ff0c461ce 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -1272,11 +1272,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> >  	n = mem_get_int_unsafe(argv[0]);
> >  	if (n < 0)
> >  		n = 0;
> > -	if (sql_result_zeroblob64(context, n) != 0) {
> > +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> >  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> >  			 "is too big");
> >  		context->is_aborted = true;
> > +		return;
> > +	}
> > +	char *str = sqlDbMallocRawNN(sql_get(), n);
> > +	memset(str, 0, n);
> 
> 1. There is sqlDbMallocZero().
> 
Thanks, fixed.

> > +	if (str == NULL) {
> > +		context->is_aborted = true;
> > +		return;
> >  	}
> > +	mem_set_bin_allocated(context->pOut, str, n);
> >  }
> >  
> >  /*
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 5e23c901c..24d6d7dbf 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1869,8 +1850,6 @@ mem_get_bin(const struct Mem *mem, const char **s)
> >  		*s = mem->n > 0 ? mem->z : NULL;
> >  		return 0;
> >  	}
> > -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> > -		return -1;
> 
> 2. If the type is not MEM_TYPE_BIN, you will return garbage. Maybe
> keep return -1 for other types?
> 
True, fixed. There is a case where the behavior will be different - I
mean the case where new_size < szMalloc and new_size + n > szMalloc,
but that doesn't seem to be a very common case.

> >  	*s = mem->z;
> >  	return 0;
> >  }


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index ff0c461ce..187456d2c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1278,8 +1278,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	char *str = sqlDbMallocRawNN(sql_get(), n);
-	memset(str, 0, n);
+	char *str = sqlDbMallocZero(sql_get(), n);
 	if (str == NULL) {
 		context->is_aborted = true;
 		return;
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 24d6d7dbf..117a44314 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1850,6 +1850,8 @@ mem_get_bin(const struct Mem *mem, const char **s)
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
+	if (mem->type != MEM_TYPE_BIN)
+		return -1;
 	*s = mem->z;
 	return 0;
 }


New patch:

commit 37ccef0873f7d62271d0c46352f8f9cbfb73c22f
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Fri Oct 1 14:05:23 2021 +0300

    sql: remove MEM_Zero flag from struct MEM
    
    This patch removes zeroblob optimization from SQL code. This
    optimization complicates the code, and there is almost no profit from
    it.

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 29d713fd0..187456d2c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1272,11 +1272,18 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 	n = mem_get_int_unsafe(argv[0]);
 	if (n < 0)
 		n = 0;
-	if (sql_result_zeroblob64(context, n) != 0) {
+	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
 			 "is too big");
 		context->is_aborted = true;
+		return;
+	}
+	char *str = sqlDbMallocZero(sql_get(), n);
+	if (str == NULL) {
+		context->is_aborted = true;
+		return;
 	}
+	mem_set_bin_allocated(context->pOut, str, n);
 }
 
 /*
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 5e23c901c..117a44314 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -503,19 +503,6 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 	return 0;
 }
 
-void
-mem_set_zerobin(struct Mem *mem, int n)
-{
-	mem_destroy(mem);
-	if (n < 0)
-		n = 0;
-	mem->u.nZero = n;
-	mem->z = NULL;
-	mem->n = 0;
-	mem->type = MEM_TYPE_BIN;
-	mem->flags = MEM_Zero;
-}
-
 static inline void
 set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
 		  enum mem_type type)
@@ -806,8 +793,6 @@ static inline int
 bin_to_str(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	mem->type = MEM_TYPE_STR;
 	mem->flags &= ~MEM_Scalar;
 	return 0;
@@ -817,8 +802,6 @@ static inline int
 bin_to_str0(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
 		return -1;
 	mem->z[mem->n] = '\0';
@@ -831,8 +814,6 @@ static inline int
 bin_to_uuid(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (mem->n != UUID_LEN ||
 	    tt_uuid_validate((struct tt_uuid *)mem->z) != 0)
 		return -1;
@@ -1869,7 +1850,7 @@ mem_get_bin(const struct Mem *mem, const char **s)
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
-	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
+	if (mem->type != MEM_TYPE_BIN)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1880,11 +1861,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
 {
 	if (!mem_is_bytes(mem))
 		return -1;
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	if ((mem->flags & MEM_Zero) != 0)
-		*len = mem->n + mem->u.nZero;
-	else
-		*len = mem->n;
+	*len = mem->n;
 	return 0;
 }
 
@@ -1910,9 +1887,6 @@ mem_copy(struct Mem *to, const struct Mem *from)
 		return 0;
 	if ((to->flags & MEM_Static) != 0)
 		return 0;
-	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
-	if ((to->flags & MEM_Zero) != 0)
-		return sqlVdbeMemExpandBlob(to);
 	to->zMalloc = sqlDbRealloc(to->db, to->zMalloc, MAX(32, to->n));
 	assert(to->zMalloc != NULL || sql_get()->mallocFailed != 0);
 	if (to->zMalloc == NULL)
@@ -1937,7 +1911,7 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
 		return;
 	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
 		return;
-	to->flags &= MEM_Term | MEM_Zero;
+	to->flags &= MEM_Term;
 	to->flags |= MEM_Ephem;
 	return;
 }
@@ -1954,7 +1928,7 @@ mem_move(struct Mem *to, struct Mem *from)
 }
 
 int
-mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
 	if (mem_is_any_null(a, b)) {
 		mem_set_null(result);
@@ -1981,9 +1955,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		return -1;
 	}
 
-	if (ExpandBlob(a) != 0 || ExpandBlob(b) != 0)
-		return -1;
-
 	uint32_t size = a->n + b->n;
 	if ((int)size > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
@@ -2330,37 +2301,8 @@ static int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b)
 {
 	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
-	int an = a->n;
-	int bn = b->n;
-	int minlen = MIN(an, bn);
-
-	/*
-	 * It is possible to have a Blob value that has some non-zero content
-	 * followed by zero content.  But that only comes up for Blobs formed
-	 * by the OP_MakeRecord opcode, and such Blobs never get passed into
-	 * mem_compare().
-	 */
-	assert((a->flags & MEM_Zero) == 0 || an == 0);
-	assert((b->flags & MEM_Zero) == 0 || bn == 0);
-
-	if ((a->flags & b->flags & MEM_Zero) != 0)
-		return a->u.nZero - b->u.nZero;
-	if ((a->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (b->z[i] != 0)
-				return -1;
-		}
-		return a->u.nZero - bn;
-	}
-	if ((b->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (a->z[i] != 0)
-				return 1;
-		}
-		return b->u.nZero - an;
-	}
-	int res = memcmp(a->z, b->z, minlen);
-	return res != 0 ? res : an - bn;
+	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
+	return res != 0 ? res : a->n - b->n;
 }
 
 static int
@@ -2797,10 +2739,6 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 		}
 		sql_snprintf(100, zCsr, "]%s", "(8)");
 		zCsr += sqlStrlen30(zCsr);
-		if (f & MEM_Zero) {
-			sql_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
-			zCsr += sqlStrlen30(zCsr);
-		}
 		*zCsr = '\0';
 	} else if (pMem->type == MEM_TYPE_STR) {
 		int j, k;
@@ -2886,32 +2824,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * If the given Mem* has a zero-filled tail, turn it into an ordinary
- * blob stored in dynamically allocated space.
- */
-int
-sqlVdbeMemExpandBlob(Mem * pMem)
-{
-	int nByte;
-	assert(pMem->flags & MEM_Zero);
-	assert(pMem->type == MEM_TYPE_BIN);
-
-	/* Set nByte to the number of bytes required to store the expanded blob. */
-	nByte = pMem->n + pMem->u.nZero;
-	if (nByte <= 0) {
-		nByte = 1;
-	}
-	if (sqlVdbeMemGrow(pMem, nByte, 1)) {
-		return -1;
-	}
-
-	memset(&pMem->z[pMem->n], 0, pMem->u.nZero);
-	pMem->n += pMem->u.nZero;
-	pMem->flags &= ~(MEM_Zero | MEM_Term);
-	return 0;
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
@@ -3036,13 +2948,8 @@ int
 sqlVdbeMemTooBig(Mem * p)
 {
 	assert(p->db != 0);
-	if (mem_is_bytes(p)) {
-		int n = p->n;
-		if (p->flags & MEM_Zero) {
-			n += p->u.nZero;
-		}
-		return n > p->db->aLimit[SQL_LIMIT_LENGTH];
-	}
+	if (mem_is_bytes(p))
+		return p->n > p->db->aLimit[SQL_LIMIT_LENGTH];
 	return 0;
 }
 
@@ -3251,14 +3158,8 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 		mpstream_encode_double(stream, var->u.r);
 		return;
 	case MEM_TYPE_BIN:
-		if ((var->flags & MEM_Zero) != 0) {
-			mpstream_encode_binl(stream, var->n + var->u.nZero);
-			mpstream_memcpy(stream, var->z, var->n);
-			mpstream_memset(stream, 0, var->u.nZero);
-		} else {
-			mpstream_encode_binl(stream, var->n);
-			mpstream_memcpy(stream, var->z, var->n);
-		}
+		mpstream_encode_binl(stream, var->n);
+		mpstream_memcpy(stream, var->z, var->n);
 		return;
 	case MEM_TYPE_ARRAY:
 	case MEM_TYPE_MAP:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 0da45b8af..1574da22d 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -68,7 +68,6 @@ struct Mem {
 		i64 i;		/* Integer value used when MEM_Int is set in flags */
 		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
 		bool b;         /* Boolean value used when MEM_Bool is set in flags */
-		int nZero;	/* Used when bit MEM_Zero is set in flags */
 		void *p;	/* Generic pointer */
 		/**
 		 * A pointer to function implementation.
@@ -111,7 +110,6 @@ struct Mem {
 #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
 #define MEM_Static    0x1000	/* Mem.z points to a static string */
 #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
-#define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
 
 static inline bool
 mem_is_null(const struct Mem *mem)
@@ -246,13 +244,6 @@ mem_is_cleared(const struct Mem *mem)
 	return (mem->flags & MEM_Cleared) != 0;
 }
 
-static inline bool
-mem_is_zerobin(const struct Mem *mem)
-{
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	return (mem->flags & MEM_Zero) != 0;
-}
-
 static inline bool
 mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 {
@@ -474,12 +465,6 @@ mem_set_binl(struct Mem *mem, char *value, uint32_t size,
 		return mem_set_bin_dynamic(mem, value, size);
 }
 
-/**
- * Clear MEM and set it to VARBINARY. The binary value consist of n zero bytes.
- */
-void
-mem_set_zerobin(struct Mem *mem, int n);
-
 /**
  * Copy binary value to a newly allocated memory. The MEM type becomes
  * VARBINARY.
@@ -603,7 +588,7 @@ mem_move(struct Mem *to, struct Mem *from);
  * result MEM is set to NULL even if the result MEM is actually the first MEM.
  */
 int
-mem_concat(struct Mem *left, struct Mem *right, struct Mem *result);
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result);
 
 /**
  * Add the first MEM to the second MEM and write the result to the third MEM.
@@ -935,11 +920,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
 #endif
 
-int sqlVdbeMemExpandBlob(struct Mem *);
-#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
-
-/** Setters = Change MEM value. */
-
 int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
 
 void sqlValueFree(struct Mem *);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 2e250dc29..eac6bcec8 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -394,13 +394,6 @@ void
 sql_result_value(sql_context *,
 		     sql_value *);
 
-void
-sql_result_zeroblob(sql_context *, int n);
-
-int
-sql_result_zeroblob64(sql_context *,
-			  sql_uint64 n);
-
 char *
 sql_mprintf(const char *, ...);
 char *
@@ -631,13 +624,6 @@ int
 sql_bind_text64(sql_stmt *, int, const char *,
 		    sql_uint64, void (*)(void *));
 
-int
-sql_bind_zeroblob(sql_stmt *, int, int n);
-
-int
-sql_bind_zeroblob64(sql_stmt *, int,
-			sql_uint64);
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 44533fb3e..86550541b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1462,8 +1462,6 @@ case OP_MustBeInt: {            /* jump, in1 */
  */
 case OP_Cast: {                  /* in1 */
 	pIn1 = &aMem[pOp->p1];
-	if (ExpandBlob(pIn1) != 0)
-		goto abort_due_to_error;
 	rc = mem_cast_explicit(pIn1, pOp->p2);
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (rc == 0)
@@ -2772,8 +2770,6 @@ case OP_Found: {        /* jump, in3 */
 #ifdef SQL_DEBUG
 		for(ii=0; ii<r.nField; ii++) {
 			assert(memIsValid(&r.aMem[ii]));
-			assert(!mem_is_zerobin(&r.aMem[ii]) ||
-			       r.aMem[ii].n == 0);
 			if (ii != 0)
 				REGISTER_TRACE(p, pOp->p3+ii, &r.aMem[ii]);
 		}
@@ -2784,7 +2780,6 @@ case OP_Found: {        /* jump, in3 */
 		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(mem_is_bin(pIn3));
-		(void)ExpandBlob(pIn3);
 		sqlVdbeRecordUnpackMsgpack(pC->key_def,
 					       pIn3->z, pIdxKey);
 	}
@@ -3411,8 +3406,7 @@ case OP_SorterInsert: {      /* in2 */
 	assert(isSorter(cursor));
 	pIn2 = &aMem[pOp->p2];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0 ||
-	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
+	if (sqlVdbeSorterWrite(cursor, pIn2) != 0)
 		goto abort_due_to_error;
 	break;
 }
@@ -3445,8 +3439,6 @@ case OP_IdxReplace:
 case OP_IdxInsert: {
 	pIn2 = &aMem[pOp->p1];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0)
-		goto abort_due_to_error;
 	struct space *space;
 	if (pOp->p4type == P4_SPACEPTR)
 		space = pOp->p4.space;
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 115940227..97bd19863 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -236,25 +236,6 @@ sql_result_value(sql_context * pCtx, sql_value * pValue)
 		pCtx->is_aborted = true;
 }
 
-void
-sql_result_zeroblob(sql_context * pCtx, int n)
-{
-	mem_set_zerobin(pCtx->pOut, n);
-}
-
-int
-sql_result_zeroblob64(sql_context * pCtx, u64 n)
-{
-	Mem *pOut = pCtx->pOut;
-	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	mem_set_zerobin(pCtx->pOut, (int)n);
-	return 0;
-}
-
 /*
  * Execute the statement pStmt, either until a row of data is ready, the
  * statement is completely executed or an error occurs.
@@ -818,29 +799,6 @@ sql_bind_text64(sql_stmt * pStmt,
 	}
 }
 
-int
-sql_bind_zeroblob(sql_stmt * pStmt, int i, int n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (vdbeUnbind(p, i) != 0)
-		return -1;
-	mem_set_zerobin(&p->aVar[i - 1], n);
-	return 0;
-}
-
-int
-sql_bind_zeroblob64(sql_stmt * pStmt, int i, sql_uint64 n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (n > (u64) p->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	assert((n & 0x7FFFFFFF) == n);
-	return sql_bind_zeroblob(pStmt, i, n);
-}
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid)
 {

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

* Re: [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast
  2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05  9:00     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  9:00 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers below.

On Mon, Oct 04, 2021 at 11:52:18PM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> On 01.10.2021 14:48, imeevma@tarantool.org wrote:
> > 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 24d6d7dbf..079083fa1 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -682,7 +682,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)
> 
> What if uint really was UINT64_MAX? Then you treat its conversion
> into UINT64_MAX double as an error?
> 
The UINT64_MAX value is 2^64-1, which will change to 2^64 when INTEGER is cast
to DOUBLE. Since the cast is not exact, an error will be thrown anyway. The
question is what we will get when DOUBLE 2^64 is cast to INTEGER? Since it is
not defined, I decided to avoid this by adding a new restriction.

> Maybe we could simply refuse to convert all ints > 2^53 and <-2^53
> (or whatever is the precise int limit in double). All ints above
> these values will convert 'from time to time' depending on exact
> values, which does not look reliable anyway.
I don't think we should be doing this. One reason is that in this case
we must decide what to do with casts from DOUBLE to INTEGER, DOUBLE to
DECIMAL, DECIMAL to DOUBLE, and DECIMAL to INTEGER. I don't see simple
enough rules to give us an easy way to determine when we should allow
these casts and when we shouldn't. The current rule of "allow when cast
is exact" seems easier to understand and implement.

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

* Re: [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append()
  2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05  9:32     ` Mergen Imeev via Tarantool-patches
  2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  9:32 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

On Mon, Oct 04, 2021 at 11:52:43PM +0200, Vladislav Shpilevoy wrote:
> Thanks for working on this!
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 079083fa1..b8ead8f41 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1925,6 +1925,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)
> 
> Looks like you could call sqlVdbeMemGrow() without all these checks
> above. The grow function does them already.
> 
True, fixed. There is a case where the behavior will be different - I
mean the case where new_size < szMalloc and new_size + n > szMalloc,
but that doesn't seem to be a very common case.

Sorry, I wrote this comment in the wrong place first (in the first patch).
> > +			return -1;
> > +	}
> > +	memcpy(&mem->z[mem->n], value, len);
> > +	mem->n = new_size;
> > +	return 0;
> > +}

Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index de4c41ccf..343a730b3 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1934,15 +1934,12 @@ mem_append(struct Mem *mem, const char *value, uint32_t len)
 	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;
-	}
+	/*
+	 * 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;


New patch:


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

    sql: introduce mem_append()
    
    This patch introduces the mem_append() function. This function appends
    the specified string to the end of the STRING or VARBINARY contained in
    MEM. In case MEM needs to increase the size of allocated memory,
    extra memory is allocated in an attempt to reduce the total number of
    allocations.
    
    Needed for #4145

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index f1b61dec6..343a730b3 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1927,6 +1927,24 @@ 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;
+	/*
+	 * 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(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 1574da22d..b567aea50 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -582,6 +582,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] 39+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM
  2021-10-05  8:46     ` Mergen Imeev via Tarantool-patches
@ 2021-10-05  9:42       ` Mergen Imeev via Tarantool-patches
  2021-10-05 12:28         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  9:42 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

Sorry, I wrote wrong answer. Fixed, see below.

On Tue, Oct 05, 2021 at 11:46:57AM +0300, Mergen Imeev via Tarantool-patches wrote:
> Hi! Thank you for the review! my answers, diff and new patch below.
> 
> On Mon, Oct 04, 2021 at 11:51:42PM +0200, Vladislav Shpilevoy wrote:
> > Hi! Thanks for the patch!
> > 
> > See 2 comments below.
> > 
> > > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > > index 29d713fd0..ff0c461ce 100644
> > > --- a/src/box/sql/func.c
> > > +++ b/src/box/sql/func.c
> > > @@ -1272,11 +1272,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> > >  	n = mem_get_int_unsafe(argv[0]);
> > >  	if (n < 0)
> > >  		n = 0;
> > > -	if (sql_result_zeroblob64(context, n) != 0) {
> > > +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> > >  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> > >  			 "is too big");
> > >  		context->is_aborted = true;
> > > +		return;
> > > +	}
> > > +	char *str = sqlDbMallocRawNN(sql_get(), n);
> > > +	memset(str, 0, n);
> > 
> > 1. There is sqlDbMallocZero().
> > 
> Thanks, fixed.
> 
> > > +	if (str == NULL) {
> > > +		context->is_aborted = true;
> > > +		return;
> > >  	}
> > > +	mem_set_bin_allocated(context->pOut, str, n);
> > >  }
> > >  
> > >  /*
> > > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > > index 5e23c901c..24d6d7dbf 100644
> > > --- a/src/box/sql/mem.c
> > > +++ b/src/box/sql/mem.c
> > > @@ -1869,8 +1850,6 @@ mem_get_bin(const struct Mem *mem, const char **s)
> > >  		*s = mem->n > 0 ? mem->z : NULL;
> > >  		return 0;
> > >  	}
> > > -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> > > -		return -1;
> > 
> > 2. If the type is not MEM_TYPE_BIN, you will return garbage. Maybe
> > keep return -1 for other types?
> > 
Thanks, fixed.

> True, fixed. There is a case where the behavior will be different - I
> mean the case where new_size < szMalloc and new_size + n > szMalloc,
> but that doesn't seem to be a very common case.
> 
> > >  	*s = mem->z;
> > >  	return 0;
> > >  }
> 
> 
> Diff:
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index ff0c461ce..187456d2c 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1278,8 +1278,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>  		context->is_aborted = true;
>  		return;
>  	}
> -	char *str = sqlDbMallocRawNN(sql_get(), n);
> -	memset(str, 0, n);
> +	char *str = sqlDbMallocZero(sql_get(), n);
>  	if (str == NULL) {
>  		context->is_aborted = true;
>  		return;
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 24d6d7dbf..117a44314 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1850,6 +1850,8 @@ mem_get_bin(const struct Mem *mem, const char **s)
>  		*s = mem->n > 0 ? mem->z : NULL;
>  		return 0;
>  	}
> +	if (mem->type != MEM_TYPE_BIN)
> +		return -1;
>  	*s = mem->z;
>  	return 0;
>  }
> 
> 
> New patch:
> 
> commit 37ccef0873f7d62271d0c46352f8f9cbfb73c22f
> Author: Mergen Imeev <imeevma@gmail.com>
> Date:   Fri Oct 1 14:05:23 2021 +0300
> 
>     sql: remove MEM_Zero flag from struct MEM
>     
>     This patch removes zeroblob optimization from SQL code. This
>     optimization complicates the code, and there is almost no profit from
>     it.
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 29d713fd0..187456d2c 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1272,11 +1272,18 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>  	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 0)
>  		n = 0;
> -	if (sql_result_zeroblob64(context, n) != 0) {
> +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
>  			 "is too big");
>  		context->is_aborted = true;
> +		return;
> +	}
> +	char *str = sqlDbMallocZero(sql_get(), n);
> +	if (str == NULL) {
> +		context->is_aborted = true;
> +		return;
>  	}
> +	mem_set_bin_allocated(context->pOut, str, n);
>  }
>  
>  /*
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 5e23c901c..117a44314 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -503,19 +503,6 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
>  	return 0;
>  }
>  
> -void
> -mem_set_zerobin(struct Mem *mem, int n)
> -{
> -	mem_destroy(mem);
> -	if (n < 0)
> -		n = 0;
> -	mem->u.nZero = n;
> -	mem->z = NULL;
> -	mem->n = 0;
> -	mem->type = MEM_TYPE_BIN;
> -	mem->flags = MEM_Zero;
> -}
> -
>  static inline void
>  set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
>  		  enum mem_type type)
> @@ -806,8 +793,6 @@ static inline int
>  bin_to_str(struct Mem *mem)
>  {
>  	assert(mem->type == MEM_TYPE_BIN);
> -	if (ExpandBlob(mem) != 0)
> -		return -1;
>  	mem->type = MEM_TYPE_STR;
>  	mem->flags &= ~MEM_Scalar;
>  	return 0;
> @@ -817,8 +802,6 @@ static inline int
>  bin_to_str0(struct Mem *mem)
>  {
>  	assert(mem->type == MEM_TYPE_BIN);
> -	if (ExpandBlob(mem) != 0)
> -		return -1;
>  	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
>  		return -1;
>  	mem->z[mem->n] = '\0';
> @@ -831,8 +814,6 @@ static inline int
>  bin_to_uuid(struct Mem *mem)
>  {
>  	assert(mem->type == MEM_TYPE_BIN);
> -	if (ExpandBlob(mem) != 0)
> -		return -1;
>  	if (mem->n != UUID_LEN ||
>  	    tt_uuid_validate((struct tt_uuid *)mem->z) != 0)
>  		return -1;
> @@ -1869,7 +1850,7 @@ mem_get_bin(const struct Mem *mem, const char **s)
>  		*s = mem->n > 0 ? mem->z : NULL;
>  		return 0;
>  	}
> -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> +	if (mem->type != MEM_TYPE_BIN)
>  		return -1;
>  	*s = mem->z;
>  	return 0;
> @@ -1880,11 +1861,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
>  {
>  	if (!mem_is_bytes(mem))
>  		return -1;
> -	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
> -	if ((mem->flags & MEM_Zero) != 0)
> -		*len = mem->n + mem->u.nZero;
> -	else
> -		*len = mem->n;
> +	*len = mem->n;
>  	return 0;
>  }
>  
> @@ -1910,9 +1887,6 @@ mem_copy(struct Mem *to, const struct Mem *from)
>  		return 0;
>  	if ((to->flags & MEM_Static) != 0)
>  		return 0;
> -	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
> -	if ((to->flags & MEM_Zero) != 0)
> -		return sqlVdbeMemExpandBlob(to);
>  	to->zMalloc = sqlDbRealloc(to->db, to->zMalloc, MAX(32, to->n));
>  	assert(to->zMalloc != NULL || sql_get()->mallocFailed != 0);
>  	if (to->zMalloc == NULL)
> @@ -1937,7 +1911,7 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
>  		return;
>  	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
>  		return;
> -	to->flags &= MEM_Term | MEM_Zero;
> +	to->flags &= MEM_Term;
>  	to->flags |= MEM_Ephem;
>  	return;
>  }
> @@ -1954,7 +1928,7 @@ mem_move(struct Mem *to, struct Mem *from)
>  }
>  
>  int
> -mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
> +mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result)
>  {
>  	if (mem_is_any_null(a, b)) {
>  		mem_set_null(result);
> @@ -1981,9 +1955,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  		return -1;
>  	}
>  
> -	if (ExpandBlob(a) != 0 || ExpandBlob(b) != 0)
> -		return -1;
> -
>  	uint32_t size = a->n + b->n;
>  	if ((int)size > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> @@ -2330,37 +2301,8 @@ static int
>  mem_cmp_bin(const struct Mem *a, const struct Mem *b)
>  {
>  	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
> -	int an = a->n;
> -	int bn = b->n;
> -	int minlen = MIN(an, bn);
> -
> -	/*
> -	 * It is possible to have a Blob value that has some non-zero content
> -	 * followed by zero content.  But that only comes up for Blobs formed
> -	 * by the OP_MakeRecord opcode, and such Blobs never get passed into
> -	 * mem_compare().
> -	 */
> -	assert((a->flags & MEM_Zero) == 0 || an == 0);
> -	assert((b->flags & MEM_Zero) == 0 || bn == 0);
> -
> -	if ((a->flags & b->flags & MEM_Zero) != 0)
> -		return a->u.nZero - b->u.nZero;
> -	if ((a->flags & MEM_Zero) != 0) {
> -		for (int i = 0; i < minlen; ++i) {
> -			if (b->z[i] != 0)
> -				return -1;
> -		}
> -		return a->u.nZero - bn;
> -	}
> -	if ((b->flags & MEM_Zero) != 0) {
> -		for (int i = 0; i < minlen; ++i) {
> -			if (a->z[i] != 0)
> -				return 1;
> -		}
> -		return b->u.nZero - an;
> -	}
> -	int res = memcmp(a->z, b->z, minlen);
> -	return res != 0 ? res : an - bn;
> +	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
> +	return res != 0 ? res : a->n - b->n;
>  }
>  
>  static int
> @@ -2797,10 +2739,6 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
>  		}
>  		sql_snprintf(100, zCsr, "]%s", "(8)");
>  		zCsr += sqlStrlen30(zCsr);
> -		if (f & MEM_Zero) {
> -			sql_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
> -			zCsr += sqlStrlen30(zCsr);
> -		}
>  		*zCsr = '\0';
>  	} else if (pMem->type == MEM_TYPE_STR) {
>  		int j, k;
> @@ -2886,32 +2824,6 @@ registerTrace(int iReg, Mem *p) {
>  }
>  #endif
>  
> -/*
> - * If the given Mem* has a zero-filled tail, turn it into an ordinary
> - * blob stored in dynamically allocated space.
> - */
> -int
> -sqlVdbeMemExpandBlob(Mem * pMem)
> -{
> -	int nByte;
> -	assert(pMem->flags & MEM_Zero);
> -	assert(pMem->type == MEM_TYPE_BIN);
> -
> -	/* Set nByte to the number of bytes required to store the expanded blob. */
> -	nByte = pMem->n + pMem->u.nZero;
> -	if (nByte <= 0) {
> -		nByte = 1;
> -	}
> -	if (sqlVdbeMemGrow(pMem, nByte, 1)) {
> -		return -1;
> -	}
> -
> -	memset(&pMem->z[pMem->n], 0, pMem->u.nZero);
> -	pMem->n += pMem->u.nZero;
> -	pMem->flags &= ~(MEM_Zero | MEM_Term);
> -	return 0;
> -}
> -
>  static int
>  sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
>  {
> @@ -3036,13 +2948,8 @@ int
>  sqlVdbeMemTooBig(Mem * p)
>  {
>  	assert(p->db != 0);
> -	if (mem_is_bytes(p)) {
> -		int n = p->n;
> -		if (p->flags & MEM_Zero) {
> -			n += p->u.nZero;
> -		}
> -		return n > p->db->aLimit[SQL_LIMIT_LENGTH];
> -	}
> +	if (mem_is_bytes(p))
> +		return p->n > p->db->aLimit[SQL_LIMIT_LENGTH];
>  	return 0;
>  }
>  
> @@ -3251,14 +3158,8 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
>  		mpstream_encode_double(stream, var->u.r);
>  		return;
>  	case MEM_TYPE_BIN:
> -		if ((var->flags & MEM_Zero) != 0) {
> -			mpstream_encode_binl(stream, var->n + var->u.nZero);
> -			mpstream_memcpy(stream, var->z, var->n);
> -			mpstream_memset(stream, 0, var->u.nZero);
> -		} else {
> -			mpstream_encode_binl(stream, var->n);
> -			mpstream_memcpy(stream, var->z, var->n);
> -		}
> +		mpstream_encode_binl(stream, var->n);
> +		mpstream_memcpy(stream, var->z, var->n);
>  		return;
>  	case MEM_TYPE_ARRAY:
>  	case MEM_TYPE_MAP:
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 0da45b8af..1574da22d 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -68,7 +68,6 @@ struct Mem {
>  		i64 i;		/* Integer value used when MEM_Int is set in flags */
>  		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
>  		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> -		int nZero;	/* Used when bit MEM_Zero is set in flags */
>  		void *p;	/* Generic pointer */
>  		/**
>  		 * A pointer to function implementation.
> @@ -111,7 +110,6 @@ struct Mem {
>  #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
>  #define MEM_Static    0x1000	/* Mem.z points to a static string */
>  #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
> -#define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
>  
>  static inline bool
>  mem_is_null(const struct Mem *mem)
> @@ -246,13 +244,6 @@ mem_is_cleared(const struct Mem *mem)
>  	return (mem->flags & MEM_Cleared) != 0;
>  }
>  
> -static inline bool
> -mem_is_zerobin(const struct Mem *mem)
> -{
> -	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
> -	return (mem->flags & MEM_Zero) != 0;
> -}
> -
>  static inline bool
>  mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
>  {
> @@ -474,12 +465,6 @@ mem_set_binl(struct Mem *mem, char *value, uint32_t size,
>  		return mem_set_bin_dynamic(mem, value, size);
>  }
>  
> -/**
> - * Clear MEM and set it to VARBINARY. The binary value consist of n zero bytes.
> - */
> -void
> -mem_set_zerobin(struct Mem *mem, int n);
> -
>  /**
>   * Copy binary value to a newly allocated memory. The MEM type becomes
>   * VARBINARY.
> @@ -603,7 +588,7 @@ mem_move(struct Mem *to, struct Mem *from);
>   * result MEM is set to NULL even if the result MEM is actually the first MEM.
>   */
>  int
> -mem_concat(struct Mem *left, struct Mem *right, struct Mem *result);
> +mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result);
>  
>  /**
>   * Add the first MEM to the second MEM and write the result to the third MEM.
> @@ -935,11 +920,6 @@ registerTrace(int iReg, Mem *p);
>  #define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
>  #endif
>  
> -int sqlVdbeMemExpandBlob(struct Mem *);
> -#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
> -
> -/** Setters = Change MEM value. */
> -
>  int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
>  
>  void sqlValueFree(struct Mem *);
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 2e250dc29..eac6bcec8 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -394,13 +394,6 @@ void
>  sql_result_value(sql_context *,
>  		     sql_value *);
>  
> -void
> -sql_result_zeroblob(sql_context *, int n);
> -
> -int
> -sql_result_zeroblob64(sql_context *,
> -			  sql_uint64 n);
> -
>  char *
>  sql_mprintf(const char *, ...);
>  char *
> @@ -631,13 +624,6 @@ int
>  sql_bind_text64(sql_stmt *, int, const char *,
>  		    sql_uint64, void (*)(void *));
>  
> -int
> -sql_bind_zeroblob(sql_stmt *, int, int n);
> -
> -int
> -sql_bind_zeroblob64(sql_stmt *, int,
> -			sql_uint64);
> -
>  int
>  sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid);
>  
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 44533fb3e..86550541b 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -1462,8 +1462,6 @@ case OP_MustBeInt: {            /* jump, in1 */
>   */
>  case OP_Cast: {                  /* in1 */
>  	pIn1 = &aMem[pOp->p1];
> -	if (ExpandBlob(pIn1) != 0)
> -		goto abort_due_to_error;
>  	rc = mem_cast_explicit(pIn1, pOp->p2);
>  	UPDATE_MAX_BLOBSIZE(pIn1);
>  	if (rc == 0)
> @@ -2772,8 +2770,6 @@ case OP_Found: {        /* jump, in3 */
>  #ifdef SQL_DEBUG
>  		for(ii=0; ii<r.nField; ii++) {
>  			assert(memIsValid(&r.aMem[ii]));
> -			assert(!mem_is_zerobin(&r.aMem[ii]) ||
> -			       r.aMem[ii].n == 0);
>  			if (ii != 0)
>  				REGISTER_TRACE(p, pOp->p3+ii, &r.aMem[ii]);
>  		}
> @@ -2784,7 +2780,6 @@ case OP_Found: {        /* jump, in3 */
>  		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
>  		if (pIdxKey==0) goto no_mem;
>  		assert(mem_is_bin(pIn3));
> -		(void)ExpandBlob(pIn3);
>  		sqlVdbeRecordUnpackMsgpack(pC->key_def,
>  					       pIn3->z, pIdxKey);
>  	}
> @@ -3411,8 +3406,7 @@ case OP_SorterInsert: {      /* in2 */
>  	assert(isSorter(cursor));
>  	pIn2 = &aMem[pOp->p2];
>  	assert(mem_is_bin(pIn2));
> -	if (ExpandBlob(pIn2) != 0 ||
> -	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
> +	if (sqlVdbeSorterWrite(cursor, pIn2) != 0)
>  		goto abort_due_to_error;
>  	break;
>  }
> @@ -3445,8 +3439,6 @@ case OP_IdxReplace:
>  case OP_IdxInsert: {
>  	pIn2 = &aMem[pOp->p1];
>  	assert(mem_is_bin(pIn2));
> -	if (ExpandBlob(pIn2) != 0)
> -		goto abort_due_to_error;
>  	struct space *space;
>  	if (pOp->p4type == P4_SPACEPTR)
>  		space = pOp->p4.space;
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index 115940227..97bd19863 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -236,25 +236,6 @@ sql_result_value(sql_context * pCtx, sql_value * pValue)
>  		pCtx->is_aborted = true;
>  }
>  
> -void
> -sql_result_zeroblob(sql_context * pCtx, int n)
> -{
> -	mem_set_zerobin(pCtx->pOut, n);
> -}
> -
> -int
> -sql_result_zeroblob64(sql_context * pCtx, u64 n)
> -{
> -	Mem *pOut = pCtx->pOut;
> -	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
> -		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> -			 "is too big");
> -		return -1;
> -	}
> -	mem_set_zerobin(pCtx->pOut, (int)n);
> -	return 0;
> -}
> -
>  /*
>   * Execute the statement pStmt, either until a row of data is ready, the
>   * statement is completely executed or an error occurs.
> @@ -818,29 +799,6 @@ sql_bind_text64(sql_stmt * pStmt,
>  	}
>  }
>  
> -int
> -sql_bind_zeroblob(sql_stmt * pStmt, int i, int n)
> -{
> -	Vdbe *p = (Vdbe *) pStmt;
> -	if (vdbeUnbind(p, i) != 0)
> -		return -1;
> -	mem_set_zerobin(&p->aVar[i - 1], n);
> -	return 0;
> -}
> -
> -int
> -sql_bind_zeroblob64(sql_stmt * pStmt, int i, sql_uint64 n)
> -{
> -	Vdbe *p = (Vdbe *) pStmt;
> -	if (n > (u64) p->db->aLimit[SQL_LIMIT_LENGTH]) {
> -		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> -			 "is too big");
> -		return -1;
> -	}
> -	assert((n & 0x7FFFFFFF) == n);
> -	return sql_bind_zeroblob(pStmt, i, n);
> -}
> -
>  int
>  sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid)
>  {

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

* Re: [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function
  2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05  9:48     ` Mergen Imeev via Tarantool-patches
  2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  9:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer below.

On Mon, Oct 04, 2021 at 11:53:19PM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 8a9de1254..94ec811ef 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -102,6 +102,58 @@ fin_total(struct sql_context *ctx)
> 
> <...>
> 
> > +
> > +/** 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;
> 
> Consider this:
> 
> ====================
> @@ -141,17 +141,14 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
> +	uint32_t *count_val = (uint32_t *)(sum + 1);
>  	struct Mem count;
>  	mem_create(&count);
>  	mem_set_uint(&count, *count_val);
>  	if (mem_div(&sum, &count, ctx->pOut) != 0)
>  		ctx->is_aborted = true;
> +	mem_destroy(sum);
>  }
This will work, however, I think it will create some unnecessary restrictions
due to changes with pMem and pOut in a few patches. I suggest to apply part of
you diff with exception of mem_destroy(), which I sugget to replace by assert().
We have full control over this tmp/sum mem and we know, that there will be no
memory to free, so assert should be enough.

What do you think of this diff?

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index a811e55f9..8213704e9 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -141,16 +141,13 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(sum + 1);
+	assert(!VdbeMemDynamic(sum) && sum->szMalloc == 0);
 	struct Mem count;
 	mem_create(&count);
 	mem_set_uint(&count, *count_val);
-	if (mem_div(&sum, &count, ctx->pOut) != 0)
+	if (mem_div(sum, &count, ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 

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

* Re: [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function
  2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05  9:55     ` Mergen Imeev via Tarantool-patches
  2021-10-11 21:51       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05  9:55 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer below.

On Mon, Oct 04, 2021 at 11:53:50PM +0200, Vladislav Shpilevoy wrote:
> Nicely done!
> 
> On 01.10.2021 14:48, imeevma@tarantool.org wrote:
> > 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 94ec811ef..384c68be8 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);
> 
> Would be nice to have a 'begin' step for the aggregation
> functions. This would allow to eliminate these 'if is null'
> ifs in some step functions in favor of having +1 virtual 'begin'
> call in the beginning.
> 
> Do you think it would simplify/speed up things? If you agree,
> could you please create a ticket for that? As 'good first issue'
> even.
> 
> If don't agree, then ignore this comment.
I think this is a good idea, but I don't see a suitable way to implement it. The
problem is that in case we receive NULL as an argument, we will have NULL in
pMem/pOut after the first step, and we still have to call begin() again or check
with 'if'. And there is no way to determine how much NULLs will there be.


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

* Re: [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions
  2021-10-04 21:54   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-05 10:07     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05 10:07 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My asnwer below.

On Mon, Oct 04, 2021 at 11:54:10PM +0200, Vladislav Shpilevoy wrote:
> Good job on the fixes!
> 
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index a3d403176..96eb76244 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -4161,7 +4161,6 @@ case OP_AggStep: {
> >  	}
> >  #endif
> >  
> > -	pMem->n++;
> 
> Could you please remind me why is it dropped in this commit?
I dropped this because it affects the result of the MIN()/MAX() functions. Prior
to this patch-set, each aggregate MEM contained one special structure. They each
have a constant size, so mem->n could have been and was used for something else.
However, I have no idea why it was here or what it was used for.


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

* Re: [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM
  2021-10-05  9:42       ` Mergen Imeev via Tarantool-patches
@ 2021-10-05 12:28         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-05 12:28 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

Sorry again, there will be one more change. If you remember, there were two
patches named "sql: fix a segfault in hex() on receiving zeroblob", one for the
master and one for 2.8. I got the LGTM from you, but I was not able to get the
LGTM from Timur as we had different opinions about the use of some of the
variables. Around this time, I started refactoring of the built-in functions and
decided to leave that patch for later, as it also needed to be refactored.
However, today I realized that this patch fixes the root of the problem with
segfault. Because of this, I moved the changelog (and reformulated it a bit) and
the test from this patch.

On Tue, Oct 05, 2021 at 12:42:43PM +0300, Mergen Imeev via Tarantool-patches wrote:
> Sorry, I wrote wrong answer. Fixed, see below.
> 
> On Tue, Oct 05, 2021 at 11:46:57AM +0300, Mergen Imeev via Tarantool-patches wrote:
> > Hi! Thank you for the review! my answers, diff and new patch below.
> > 
> > On Mon, Oct 04, 2021 at 11:51:42PM +0200, Vladislav Shpilevoy wrote:
> > > Hi! Thanks for the patch!
> > > 
> > > See 2 comments below.
> > > 
> > > > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > > > index 29d713fd0..ff0c461ce 100644
> > > > --- a/src/box/sql/func.c
> > > > +++ b/src/box/sql/func.c
> > > > @@ -1272,11 +1272,19 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> > > >  	n = mem_get_int_unsafe(argv[0]);
> > > >  	if (n < 0)
> > > >  		n = 0;
> > > > -	if (sql_result_zeroblob64(context, n) != 0) {
> > > > +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> > > >  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> > > >  			 "is too big");
> > > >  		context->is_aborted = true;
> > > > +		return;
> > > > +	}
> > > > +	char *str = sqlDbMallocRawNN(sql_get(), n);
> > > > +	memset(str, 0, n);
> > > 
> > > 1. There is sqlDbMallocZero().
> > > 
> > Thanks, fixed.
> > 
> > > > +	if (str == NULL) {
> > > > +		context->is_aborted = true;
> > > > +		return;
> > > >  	}
> > > > +	mem_set_bin_allocated(context->pOut, str, n);
> > > >  }
> > > >  
> > > >  /*
> > > > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > > > index 5e23c901c..24d6d7dbf 100644
> > > > --- a/src/box/sql/mem.c
> > > > +++ b/src/box/sql/mem.c
> > > > @@ -1869,8 +1850,6 @@ mem_get_bin(const struct Mem *mem, const char **s)
> > > >  		*s = mem->n > 0 ? mem->z : NULL;
> > > >  		return 0;
> > > >  	}
> > > > -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> > > > -		return -1;
> > > 
> > > 2. If the type is not MEM_TYPE_BIN, you will return garbage. Maybe
> > > keep return -1 for other types?
> > > 
> Thanks, fixed.
> 
> > True, fixed. There is a case where the behavior will be different - I
> > mean the case where new_size < szMalloc and new_size + n > szMalloc,
> > but that doesn't seem to be a very common case.
> > 
> > > >  	*s = mem->z;
> > > >  	return 0;
> > > >  }
> > 
> > 
> > Diff:
> > 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index ff0c461ce..187456d2c 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -1278,8 +1278,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> >  		context->is_aborted = true;
> >  		return;
> >  	}
> > -	char *str = sqlDbMallocRawNN(sql_get(), n);
> > -	memset(str, 0, n);
> > +	char *str = sqlDbMallocZero(sql_get(), n);
> >  	if (str == NULL) {
> >  		context->is_aborted = true;
> >  		return;
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 24d6d7dbf..117a44314 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1850,6 +1850,8 @@ mem_get_bin(const struct Mem *mem, const char **s)
> >  		*s = mem->n > 0 ? mem->z : NULL;
> >  		return 0;
> >  	}
> > +	if (mem->type != MEM_TYPE_BIN)
> > +		return -1;
> >  	*s = mem->z;
> >  	return 0;
> >  }
> > 
> > 
> > New patch:
> > 
> > commit 37ccef0873f7d62271d0c46352f8f9cbfb73c22f
> > Author: Mergen Imeev <imeevma@gmail.com>
> > Date:   Fri Oct 1 14:05:23 2021 +0300
> > 
> >     sql: remove MEM_Zero flag from struct MEM
> >     
> >     This patch removes zeroblob optimization from SQL code. This
> >     optimization complicates the code, and there is almost no profit from
> >     it.
> > 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 29d713fd0..187456d2c 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -1272,11 +1272,18 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> >  	n = mem_get_int_unsafe(argv[0]);
> >  	if (n < 0)
> >  		n = 0;
> > -	if (sql_result_zeroblob64(context, n) != 0) {
> > +	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> >  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> >  			 "is too big");
> >  		context->is_aborted = true;
> > +		return;
> > +	}
> > +	char *str = sqlDbMallocZero(sql_get(), n);
> > +	if (str == NULL) {
> > +		context->is_aborted = true;
> > +		return;
> >  	}
> > +	mem_set_bin_allocated(context->pOut, str, n);
> >  }
> >  
> >  /*
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 5e23c901c..117a44314 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -503,19 +503,6 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
> >  	return 0;
> >  }
> >  
> > -void
> > -mem_set_zerobin(struct Mem *mem, int n)
> > -{
> > -	mem_destroy(mem);
> > -	if (n < 0)
> > -		n = 0;
> > -	mem->u.nZero = n;
> > -	mem->z = NULL;
> > -	mem->n = 0;
> > -	mem->type = MEM_TYPE_BIN;
> > -	mem->flags = MEM_Zero;
> > -}
> > -
> >  static inline void
> >  set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
> >  		  enum mem_type type)
> > @@ -806,8 +793,6 @@ static inline int
> >  bin_to_str(struct Mem *mem)
> >  {
> >  	assert(mem->type == MEM_TYPE_BIN);
> > -	if (ExpandBlob(mem) != 0)
> > -		return -1;
> >  	mem->type = MEM_TYPE_STR;
> >  	mem->flags &= ~MEM_Scalar;
> >  	return 0;
> > @@ -817,8 +802,6 @@ static inline int
> >  bin_to_str0(struct Mem *mem)
> >  {
> >  	assert(mem->type == MEM_TYPE_BIN);
> > -	if (ExpandBlob(mem) != 0)
> > -		return -1;
> >  	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
> >  		return -1;
> >  	mem->z[mem->n] = '\0';
> > @@ -831,8 +814,6 @@ static inline int
> >  bin_to_uuid(struct Mem *mem)
> >  {
> >  	assert(mem->type == MEM_TYPE_BIN);
> > -	if (ExpandBlob(mem) != 0)
> > -		return -1;
> >  	if (mem->n != UUID_LEN ||
> >  	    tt_uuid_validate((struct tt_uuid *)mem->z) != 0)
> >  		return -1;
> > @@ -1869,7 +1850,7 @@ mem_get_bin(const struct Mem *mem, const char **s)
> >  		*s = mem->n > 0 ? mem->z : NULL;
> >  		return 0;
> >  	}
> > -	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> > +	if (mem->type != MEM_TYPE_BIN)
> >  		return -1;
> >  	*s = mem->z;
> >  	return 0;
> > @@ -1880,11 +1861,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
> >  {
> >  	if (!mem_is_bytes(mem))
> >  		return -1;
> > -	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
> > -	if ((mem->flags & MEM_Zero) != 0)
> > -		*len = mem->n + mem->u.nZero;
> > -	else
> > -		*len = mem->n;
> > +	*len = mem->n;
> >  	return 0;
> >  }
> >  
> > @@ -1910,9 +1887,6 @@ mem_copy(struct Mem *to, const struct Mem *from)
> >  		return 0;
> >  	if ((to->flags & MEM_Static) != 0)
> >  		return 0;
> > -	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
> > -	if ((to->flags & MEM_Zero) != 0)
> > -		return sqlVdbeMemExpandBlob(to);
> >  	to->zMalloc = sqlDbRealloc(to->db, to->zMalloc, MAX(32, to->n));
> >  	assert(to->zMalloc != NULL || sql_get()->mallocFailed != 0);
> >  	if (to->zMalloc == NULL)
> > @@ -1937,7 +1911,7 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
> >  		return;
> >  	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
> >  		return;
> > -	to->flags &= MEM_Term | MEM_Zero;
> > +	to->flags &= MEM_Term;
> >  	to->flags |= MEM_Ephem;
> >  	return;
> >  }
> > @@ -1954,7 +1928,7 @@ mem_move(struct Mem *to, struct Mem *from)
> >  }
> >  
> >  int
> > -mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
> > +mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result)
> >  {
> >  	if (mem_is_any_null(a, b)) {
> >  		mem_set_null(result);
> > @@ -1981,9 +1955,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
> >  		return -1;
> >  	}
> >  
> > -	if (ExpandBlob(a) != 0 || ExpandBlob(b) != 0)
> > -		return -1;
> > -
> >  	uint32_t size = a->n + b->n;
> >  	if ((int)size > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
> >  		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
> > @@ -2330,37 +2301,8 @@ static int
> >  mem_cmp_bin(const struct Mem *a, const struct Mem *b)
> >  {
> >  	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
> > -	int an = a->n;
> > -	int bn = b->n;
> > -	int minlen = MIN(an, bn);
> > -
> > -	/*
> > -	 * It is possible to have a Blob value that has some non-zero content
> > -	 * followed by zero content.  But that only comes up for Blobs formed
> > -	 * by the OP_MakeRecord opcode, and such Blobs never get passed into
> > -	 * mem_compare().
> > -	 */
> > -	assert((a->flags & MEM_Zero) == 0 || an == 0);
> > -	assert((b->flags & MEM_Zero) == 0 || bn == 0);
> > -
> > -	if ((a->flags & b->flags & MEM_Zero) != 0)
> > -		return a->u.nZero - b->u.nZero;
> > -	if ((a->flags & MEM_Zero) != 0) {
> > -		for (int i = 0; i < minlen; ++i) {
> > -			if (b->z[i] != 0)
> > -				return -1;
> > -		}
> > -		return a->u.nZero - bn;
> > -	}
> > -	if ((b->flags & MEM_Zero) != 0) {
> > -		for (int i = 0; i < minlen; ++i) {
> > -			if (a->z[i] != 0)
> > -				return 1;
> > -		}
> > -		return b->u.nZero - an;
> > -	}
> > -	int res = memcmp(a->z, b->z, minlen);
> > -	return res != 0 ? res : an - bn;
> > +	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
> > +	return res != 0 ? res : a->n - b->n;
> >  }
> >  
> >  static int
> > @@ -2797,10 +2739,6 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
> >  		}
> >  		sql_snprintf(100, zCsr, "]%s", "(8)");
> >  		zCsr += sqlStrlen30(zCsr);
> > -		if (f & MEM_Zero) {
> > -			sql_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
> > -			zCsr += sqlStrlen30(zCsr);
> > -		}
> >  		*zCsr = '\0';
> >  	} else if (pMem->type == MEM_TYPE_STR) {
> >  		int j, k;
> > @@ -2886,32 +2824,6 @@ registerTrace(int iReg, Mem *p) {
> >  }
> >  #endif
> >  
> > -/*
> > - * If the given Mem* has a zero-filled tail, turn it into an ordinary
> > - * blob stored in dynamically allocated space.
> > - */
> > -int
> > -sqlVdbeMemExpandBlob(Mem * pMem)
> > -{
> > -	int nByte;
> > -	assert(pMem->flags & MEM_Zero);
> > -	assert(pMem->type == MEM_TYPE_BIN);
> > -
> > -	/* Set nByte to the number of bytes required to store the expanded blob. */
> > -	nByte = pMem->n + pMem->u.nZero;
> > -	if (nByte <= 0) {
> > -		nByte = 1;
> > -	}
> > -	if (sqlVdbeMemGrow(pMem, nByte, 1)) {
> > -		return -1;
> > -	}
> > -
> > -	memset(&pMem->z[pMem->n], 0, pMem->u.nZero);
> > -	pMem->n += pMem->u.nZero;
> > -	pMem->flags &= ~(MEM_Zero | MEM_Term);
> > -	return 0;
> > -}
> > -
> >  static int
> >  sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
> >  {
> > @@ -3036,13 +2948,8 @@ int
> >  sqlVdbeMemTooBig(Mem * p)
> >  {
> >  	assert(p->db != 0);
> > -	if (mem_is_bytes(p)) {
> > -		int n = p->n;
> > -		if (p->flags & MEM_Zero) {
> > -			n += p->u.nZero;
> > -		}
> > -		return n > p->db->aLimit[SQL_LIMIT_LENGTH];
> > -	}
> > +	if (mem_is_bytes(p))
> > +		return p->n > p->db->aLimit[SQL_LIMIT_LENGTH];
> >  	return 0;
> >  }
> >  
> > @@ -3251,14 +3158,8 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
> >  		mpstream_encode_double(stream, var->u.r);
> >  		return;
> >  	case MEM_TYPE_BIN:
> > -		if ((var->flags & MEM_Zero) != 0) {
> > -			mpstream_encode_binl(stream, var->n + var->u.nZero);
> > -			mpstream_memcpy(stream, var->z, var->n);
> > -			mpstream_memset(stream, 0, var->u.nZero);
> > -		} else {
> > -			mpstream_encode_binl(stream, var->n);
> > -			mpstream_memcpy(stream, var->z, var->n);
> > -		}
> > +		mpstream_encode_binl(stream, var->n);
> > +		mpstream_memcpy(stream, var->z, var->n);
> >  		return;
> >  	case MEM_TYPE_ARRAY:
> >  	case MEM_TYPE_MAP:
> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index 0da45b8af..1574da22d 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -68,7 +68,6 @@ struct Mem {
> >  		i64 i;		/* Integer value used when MEM_Int is set in flags */
> >  		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
> >  		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> > -		int nZero;	/* Used when bit MEM_Zero is set in flags */
> >  		void *p;	/* Generic pointer */
> >  		/**
> >  		 * A pointer to function implementation.
> > @@ -111,7 +110,6 @@ struct Mem {
> >  #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
> >  #define MEM_Static    0x1000	/* Mem.z points to a static string */
> >  #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
> > -#define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
> >  
> >  static inline bool
> >  mem_is_null(const struct Mem *mem)
> > @@ -246,13 +244,6 @@ mem_is_cleared(const struct Mem *mem)
> >  	return (mem->flags & MEM_Cleared) != 0;
> >  }
> >  
> > -static inline bool
> > -mem_is_zerobin(const struct Mem *mem)
> > -{
> > -	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
> > -	return (mem->flags & MEM_Zero) != 0;
> > -}
> > -
> >  static inline bool
> >  mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
> >  {
> > @@ -474,12 +465,6 @@ mem_set_binl(struct Mem *mem, char *value, uint32_t size,
> >  		return mem_set_bin_dynamic(mem, value, size);
> >  }
> >  
> > -/**
> > - * Clear MEM and set it to VARBINARY. The binary value consist of n zero bytes.
> > - */
> > -void
> > -mem_set_zerobin(struct Mem *mem, int n);
> > -
> >  /**
> >   * Copy binary value to a newly allocated memory. The MEM type becomes
> >   * VARBINARY.
> > @@ -603,7 +588,7 @@ mem_move(struct Mem *to, struct Mem *from);
> >   * result MEM is set to NULL even if the result MEM is actually the first MEM.
> >   */
> >  int
> > -mem_concat(struct Mem *left, struct Mem *right, struct Mem *result);
> > +mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result);
> >  
> >  /**
> >   * Add the first MEM to the second MEM and write the result to the third MEM.
> > @@ -935,11 +920,6 @@ registerTrace(int iReg, Mem *p);
> >  #define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
> >  #endif
> >  
> > -int sqlVdbeMemExpandBlob(struct Mem *);
> > -#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
> > -
> > -/** Setters = Change MEM value. */
> > -
> >  int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
> >  
> >  void sqlValueFree(struct Mem *);
> > diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> > index 2e250dc29..eac6bcec8 100644
> > --- a/src/box/sql/sqlInt.h
> > +++ b/src/box/sql/sqlInt.h
> > @@ -394,13 +394,6 @@ void
> >  sql_result_value(sql_context *,
> >  		     sql_value *);
> >  
> > -void
> > -sql_result_zeroblob(sql_context *, int n);
> > -
> > -int
> > -sql_result_zeroblob64(sql_context *,
> > -			  sql_uint64 n);
> > -
> >  char *
> >  sql_mprintf(const char *, ...);
> >  char *
> > @@ -631,13 +624,6 @@ int
> >  sql_bind_text64(sql_stmt *, int, const char *,
> >  		    sql_uint64, void (*)(void *));
> >  
> > -int
> > -sql_bind_zeroblob(sql_stmt *, int, int n);
> > -
> > -int
> > -sql_bind_zeroblob64(sql_stmt *, int,
> > -			sql_uint64);
> > -
> >  int
> >  sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid);
> >  
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 44533fb3e..86550541b 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -1462,8 +1462,6 @@ case OP_MustBeInt: {            /* jump, in1 */
> >   */
> >  case OP_Cast: {                  /* in1 */
> >  	pIn1 = &aMem[pOp->p1];
> > -	if (ExpandBlob(pIn1) != 0)
> > -		goto abort_due_to_error;
> >  	rc = mem_cast_explicit(pIn1, pOp->p2);
> >  	UPDATE_MAX_BLOBSIZE(pIn1);
> >  	if (rc == 0)
> > @@ -2772,8 +2770,6 @@ case OP_Found: {        /* jump, in3 */
> >  #ifdef SQL_DEBUG
> >  		for(ii=0; ii<r.nField; ii++) {
> >  			assert(memIsValid(&r.aMem[ii]));
> > -			assert(!mem_is_zerobin(&r.aMem[ii]) ||
> > -			       r.aMem[ii].n == 0);
> >  			if (ii != 0)
> >  				REGISTER_TRACE(p, pOp->p3+ii, &r.aMem[ii]);
> >  		}
> > @@ -2784,7 +2780,6 @@ case OP_Found: {        /* jump, in3 */
> >  		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
> >  		if (pIdxKey==0) goto no_mem;
> >  		assert(mem_is_bin(pIn3));
> > -		(void)ExpandBlob(pIn3);
> >  		sqlVdbeRecordUnpackMsgpack(pC->key_def,
> >  					       pIn3->z, pIdxKey);
> >  	}
> > @@ -3411,8 +3406,7 @@ case OP_SorterInsert: {      /* in2 */
> >  	assert(isSorter(cursor));
> >  	pIn2 = &aMem[pOp->p2];
> >  	assert(mem_is_bin(pIn2));
> > -	if (ExpandBlob(pIn2) != 0 ||
> > -	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
> > +	if (sqlVdbeSorterWrite(cursor, pIn2) != 0)
> >  		goto abort_due_to_error;
> >  	break;
> >  }
> > @@ -3445,8 +3439,6 @@ case OP_IdxReplace:
> >  case OP_IdxInsert: {
> >  	pIn2 = &aMem[pOp->p1];
> >  	assert(mem_is_bin(pIn2));
> > -	if (ExpandBlob(pIn2) != 0)
> > -		goto abort_due_to_error;
> >  	struct space *space;
> >  	if (pOp->p4type == P4_SPACEPTR)
> >  		space = pOp->p4.space;
> > diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> > index 115940227..97bd19863 100644
> > --- a/src/box/sql/vdbeapi.c
> > +++ b/src/box/sql/vdbeapi.c
> > @@ -236,25 +236,6 @@ sql_result_value(sql_context * pCtx, sql_value * pValue)
> >  		pCtx->is_aborted = true;
> >  }
> >  
> > -void
> > -sql_result_zeroblob(sql_context * pCtx, int n)
> > -{
> > -	mem_set_zerobin(pCtx->pOut, n);
> > -}
> > -
> > -int
> > -sql_result_zeroblob64(sql_context * pCtx, u64 n)
> > -{
> > -	Mem *pOut = pCtx->pOut;
> > -	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
> > -		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> > -			 "is too big");
> > -		return -1;
> > -	}
> > -	mem_set_zerobin(pCtx->pOut, (int)n);
> > -	return 0;
> > -}
> > -
> >  /*
> >   * Execute the statement pStmt, either until a row of data is ready, the
> >   * statement is completely executed or an error occurs.
> > @@ -818,29 +799,6 @@ sql_bind_text64(sql_stmt * pStmt,
> >  	}
> >  }
> >  
> > -int
> > -sql_bind_zeroblob(sql_stmt * pStmt, int i, int n)
> > -{
> > -	Vdbe *p = (Vdbe *) pStmt;
> > -	if (vdbeUnbind(p, i) != 0)
> > -		return -1;
> > -	mem_set_zerobin(&p->aVar[i - 1], n);
> > -	return 0;
> > -}
> > -
> > -int
> > -sql_bind_zeroblob64(sql_stmt * pStmt, int i, sql_uint64 n)
> > -{
> > -	Vdbe *p = (Vdbe *) pStmt;
> > -	if (n > (u64) p->db->aLimit[SQL_LIMIT_LENGTH]) {
> > -		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> > -			 "is too big");
> > -		return -1;
> > -	}
> > -	assert((n & 0x7FFFFFFF) == n);
> > -	return sql_bind_zeroblob(pStmt, i, n);
> > -}
> > -
> >  int
> >  sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid)
> >  {


Diff:

diff --git a/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md b/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md
new file mode 100644
index 000000000..4b51e15ae
--- /dev/null
+++ b/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md
@@ -0,0 +1,4 @@
+## bugfix/sql
+* The HEX() SQL built-in function no longer throw an assert when its argument
+  consist of zero-bytes (gh-6113).
diff --git a/test/sql-tap/engine.cfg b/test/sql-tap/engine.cfg
index a6f03307f..dea5a42cf 100644
--- a/test/sql-tap/engine.cfg
+++ b/test/sql-tap/engine.cfg
@@ -39,6 +39,7 @@
     "gh-6376-wrong-double-to-dec-cmp.test.lua": {},
     "gh-4077-iproto-execute-no-bind.test.lua": {},
     "gh-6375-assert-on-unsupported-ext.test.lua": {},
+    "gh-6113-assert-in-hex-on-zeroblob.test.lua": {},
     "*": {
         "memtx": {"engine": "memtx"},
         "vinyl": {"engine": "vinyl"}
diff --git a/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua b/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua
new file mode 100755
index 000000000..91a29a5b4
--- /dev/null
+++ b/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua
@@ -0,0 +1,13 @@
+#!/usr/bin/env tarantool
+local test = require("sqltester")
+test:plan(1)
+
+test:do_execsql_test(
+    "gh-6113",
+    [[
+        SELECT hex(zeroblob(0)), hex(zeroblob(10));
+    ]], {
+        '', '00000000000000000000'
+    })
+
+test:finish_test()


New patch:

commit e5cba5128550f51a2ef939c22eb909eeac1128df
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Fri Oct 1 14:05:23 2021 +0300

    sql: remove MEM_Zero flag from struct MEM
    
    This patch removes zeroblob optimization from SQL code. This
    optimization complicates the code, and there is almost no profit from
    it.
    
    Closes #6113
    Needed for #4145

diff --git a/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md b/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md
new file mode 100644
index 000000000..d9bd9e279
--- /dev/null
+++ b/changelogs/unreleased/gh-6113-fix-segfault-in-hex-func.md
@@ -0,0 +1,4 @@
+## bugfix/sql
+
+* The HEX() SQL built-in function no longer throw an assert when its argument
+  consist of zero-bytes (gh-6113).
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 29d713fd0..187456d2c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1272,11 +1272,18 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 	n = mem_get_int_unsafe(argv[0]);
 	if (n < 0)
 		n = 0;
-	if (sql_result_zeroblob64(context, n) != 0) {
+	if (n > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
 			 "is too big");
 		context->is_aborted = true;
+		return;
+	}
+	char *str = sqlDbMallocZero(sql_get(), n);
+	if (str == NULL) {
+		context->is_aborted = true;
+		return;
 	}
+	mem_set_bin_allocated(context->pOut, str, n);
 }
 
 /*
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 5e23c901c..117a44314 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -503,19 +503,6 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 	return 0;
 }
 
-void
-mem_set_zerobin(struct Mem *mem, int n)
-{
-	mem_destroy(mem);
-	if (n < 0)
-		n = 0;
-	mem->u.nZero = n;
-	mem->z = NULL;
-	mem->n = 0;
-	mem->type = MEM_TYPE_BIN;
-	mem->flags = MEM_Zero;
-}
-
 static inline void
 set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
 		  enum mem_type type)
@@ -806,8 +793,6 @@ static inline int
 bin_to_str(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	mem->type = MEM_TYPE_STR;
 	mem->flags &= ~MEM_Scalar;
 	return 0;
@@ -817,8 +802,6 @@ static inline int
 bin_to_str0(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
 		return -1;
 	mem->z[mem->n] = '\0';
@@ -831,8 +814,6 @@ static inline int
 bin_to_uuid(struct Mem *mem)
 {
 	assert(mem->type == MEM_TYPE_BIN);
-	if (ExpandBlob(mem) != 0)
-		return -1;
 	if (mem->n != UUID_LEN ||
 	    tt_uuid_validate((struct tt_uuid *)mem->z) != 0)
 		return -1;
@@ -1869,7 +1850,7 @@ mem_get_bin(const struct Mem *mem, const char **s)
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
-	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
+	if (mem->type != MEM_TYPE_BIN)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1880,11 +1861,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
 {
 	if (!mem_is_bytes(mem))
 		return -1;
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	if ((mem->flags & MEM_Zero) != 0)
-		*len = mem->n + mem->u.nZero;
-	else
-		*len = mem->n;
+	*len = mem->n;
 	return 0;
 }
 
@@ -1910,9 +1887,6 @@ mem_copy(struct Mem *to, const struct Mem *from)
 		return 0;
 	if ((to->flags & MEM_Static) != 0)
 		return 0;
-	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
-	if ((to->flags & MEM_Zero) != 0)
-		return sqlVdbeMemExpandBlob(to);
 	to->zMalloc = sqlDbRealloc(to->db, to->zMalloc, MAX(32, to->n));
 	assert(to->zMalloc != NULL || sql_get()->mallocFailed != 0);
 	if (to->zMalloc == NULL)
@@ -1937,7 +1911,7 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
 		return;
 	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
 		return;
-	to->flags &= MEM_Term | MEM_Zero;
+	to->flags &= MEM_Term;
 	to->flags |= MEM_Ephem;
 	return;
 }
@@ -1954,7 +1928,7 @@ mem_move(struct Mem *to, struct Mem *from)
 }
 
 int
-mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
 	if (mem_is_any_null(a, b)) {
 		mem_set_null(result);
@@ -1981,9 +1955,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		return -1;
 	}
 
-	if (ExpandBlob(a) != 0 || ExpandBlob(b) != 0)
-		return -1;
-
 	uint32_t size = a->n + b->n;
 	if ((int)size > sql_get()->aLimit[SQL_LIMIT_LENGTH]) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or blob too big");
@@ -2330,37 +2301,8 @@ static int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b)
 {
 	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
-	int an = a->n;
-	int bn = b->n;
-	int minlen = MIN(an, bn);
-
-	/*
-	 * It is possible to have a Blob value that has some non-zero content
-	 * followed by zero content.  But that only comes up for Blobs formed
-	 * by the OP_MakeRecord opcode, and such Blobs never get passed into
-	 * mem_compare().
-	 */
-	assert((a->flags & MEM_Zero) == 0 || an == 0);
-	assert((b->flags & MEM_Zero) == 0 || bn == 0);
-
-	if ((a->flags & b->flags & MEM_Zero) != 0)
-		return a->u.nZero - b->u.nZero;
-	if ((a->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (b->z[i] != 0)
-				return -1;
-		}
-		return a->u.nZero - bn;
-	}
-	if ((b->flags & MEM_Zero) != 0) {
-		for (int i = 0; i < minlen; ++i) {
-			if (a->z[i] != 0)
-				return 1;
-		}
-		return b->u.nZero - an;
-	}
-	int res = memcmp(a->z, b->z, minlen);
-	return res != 0 ? res : an - bn;
+	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
+	return res != 0 ? res : a->n - b->n;
 }
 
 static int
@@ -2797,10 +2739,6 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 		}
 		sql_snprintf(100, zCsr, "]%s", "(8)");
 		zCsr += sqlStrlen30(zCsr);
-		if (f & MEM_Zero) {
-			sql_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
-			zCsr += sqlStrlen30(zCsr);
-		}
 		*zCsr = '\0';
 	} else if (pMem->type == MEM_TYPE_STR) {
 		int j, k;
@@ -2886,32 +2824,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * If the given Mem* has a zero-filled tail, turn it into an ordinary
- * blob stored in dynamically allocated space.
- */
-int
-sqlVdbeMemExpandBlob(Mem * pMem)
-{
-	int nByte;
-	assert(pMem->flags & MEM_Zero);
-	assert(pMem->type == MEM_TYPE_BIN);
-
-	/* Set nByte to the number of bytes required to store the expanded blob. */
-	nByte = pMem->n + pMem->u.nZero;
-	if (nByte <= 0) {
-		nByte = 1;
-	}
-	if (sqlVdbeMemGrow(pMem, nByte, 1)) {
-		return -1;
-	}
-
-	memset(&pMem->z[pMem->n], 0, pMem->u.nZero);
-	pMem->n += pMem->u.nZero;
-	pMem->flags &= ~(MEM_Zero | MEM_Term);
-	return 0;
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
@@ -3036,13 +2948,8 @@ int
 sqlVdbeMemTooBig(Mem * p)
 {
 	assert(p->db != 0);
-	if (mem_is_bytes(p)) {
-		int n = p->n;
-		if (p->flags & MEM_Zero) {
-			n += p->u.nZero;
-		}
-		return n > p->db->aLimit[SQL_LIMIT_LENGTH];
-	}
+	if (mem_is_bytes(p))
+		return p->n > p->db->aLimit[SQL_LIMIT_LENGTH];
 	return 0;
 }
 
@@ -3251,14 +3158,8 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 		mpstream_encode_double(stream, var->u.r);
 		return;
 	case MEM_TYPE_BIN:
-		if ((var->flags & MEM_Zero) != 0) {
-			mpstream_encode_binl(stream, var->n + var->u.nZero);
-			mpstream_memcpy(stream, var->z, var->n);
-			mpstream_memset(stream, 0, var->u.nZero);
-		} else {
-			mpstream_encode_binl(stream, var->n);
-			mpstream_memcpy(stream, var->z, var->n);
-		}
+		mpstream_encode_binl(stream, var->n);
+		mpstream_memcpy(stream, var->z, var->n);
 		return;
 	case MEM_TYPE_ARRAY:
 	case MEM_TYPE_MAP:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 0da45b8af..1574da22d 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -68,7 +68,6 @@ struct Mem {
 		i64 i;		/* Integer value used when MEM_Int is set in flags */
 		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
 		bool b;         /* Boolean value used when MEM_Bool is set in flags */
-		int nZero;	/* Used when bit MEM_Zero is set in flags */
 		void *p;	/* Generic pointer */
 		/**
 		 * A pointer to function implementation.
@@ -111,7 +110,6 @@ struct Mem {
 #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
 #define MEM_Static    0x1000	/* Mem.z points to a static string */
 #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
-#define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
 
 static inline bool
 mem_is_null(const struct Mem *mem)
@@ -246,13 +244,6 @@ mem_is_cleared(const struct Mem *mem)
 	return (mem->flags & MEM_Cleared) != 0;
 }
 
-static inline bool
-mem_is_zerobin(const struct Mem *mem)
-{
-	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
-	return (mem->flags & MEM_Zero) != 0;
-}
-
 static inline bool
 mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 {
@@ -474,12 +465,6 @@ mem_set_binl(struct Mem *mem, char *value, uint32_t size,
 		return mem_set_bin_dynamic(mem, value, size);
 }
 
-/**
- * Clear MEM and set it to VARBINARY. The binary value consist of n zero bytes.
- */
-void
-mem_set_zerobin(struct Mem *mem, int n);
-
 /**
  * Copy binary value to a newly allocated memory. The MEM type becomes
  * VARBINARY.
@@ -603,7 +588,7 @@ mem_move(struct Mem *to, struct Mem *from);
  * result MEM is set to NULL even if the result MEM is actually the first MEM.
  */
 int
-mem_concat(struct Mem *left, struct Mem *right, struct Mem *result);
+mem_concat(const struct Mem *a, const struct Mem *b, struct Mem *result);
 
 /**
  * Add the first MEM to the second MEM and write the result to the third MEM.
@@ -935,11 +920,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
 #endif
 
-int sqlVdbeMemExpandBlob(struct Mem *);
-#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
-
-/** Setters = Change MEM value. */
-
 int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
 
 void sqlValueFree(struct Mem *);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 2e250dc29..eac6bcec8 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -394,13 +394,6 @@ void
 sql_result_value(sql_context *,
 		     sql_value *);
 
-void
-sql_result_zeroblob(sql_context *, int n);
-
-int
-sql_result_zeroblob64(sql_context *,
-			  sql_uint64 n);
-
 char *
 sql_mprintf(const char *, ...);
 char *
@@ -631,13 +624,6 @@ int
 sql_bind_text64(sql_stmt *, int, const char *,
 		    sql_uint64, void (*)(void *));
 
-int
-sql_bind_zeroblob(sql_stmt *, int, int n);
-
-int
-sql_bind_zeroblob64(sql_stmt *, int,
-			sql_uint64);
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 44533fb3e..86550541b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1462,8 +1462,6 @@ case OP_MustBeInt: {            /* jump, in1 */
  */
 case OP_Cast: {                  /* in1 */
 	pIn1 = &aMem[pOp->p1];
-	if (ExpandBlob(pIn1) != 0)
-		goto abort_due_to_error;
 	rc = mem_cast_explicit(pIn1, pOp->p2);
 	UPDATE_MAX_BLOBSIZE(pIn1);
 	if (rc == 0)
@@ -2772,8 +2770,6 @@ case OP_Found: {        /* jump, in3 */
 #ifdef SQL_DEBUG
 		for(ii=0; ii<r.nField; ii++) {
 			assert(memIsValid(&r.aMem[ii]));
-			assert(!mem_is_zerobin(&r.aMem[ii]) ||
-			       r.aMem[ii].n == 0);
 			if (ii != 0)
 				REGISTER_TRACE(p, pOp->p3+ii, &r.aMem[ii]);
 		}
@@ -2784,7 +2780,6 @@ case OP_Found: {        /* jump, in3 */
 		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(mem_is_bin(pIn3));
-		(void)ExpandBlob(pIn3);
 		sqlVdbeRecordUnpackMsgpack(pC->key_def,
 					       pIn3->z, pIdxKey);
 	}
@@ -3411,8 +3406,7 @@ case OP_SorterInsert: {      /* in2 */
 	assert(isSorter(cursor));
 	pIn2 = &aMem[pOp->p2];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0 ||
-	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
+	if (sqlVdbeSorterWrite(cursor, pIn2) != 0)
 		goto abort_due_to_error;
 	break;
 }
@@ -3445,8 +3439,6 @@ case OP_IdxReplace:
 case OP_IdxInsert: {
 	pIn2 = &aMem[pOp->p1];
 	assert(mem_is_bin(pIn2));
-	if (ExpandBlob(pIn2) != 0)
-		goto abort_due_to_error;
 	struct space *space;
 	if (pOp->p4type == P4_SPACEPTR)
 		space = pOp->p4.space;
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 115940227..97bd19863 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -236,25 +236,6 @@ sql_result_value(sql_context * pCtx, sql_value * pValue)
 		pCtx->is_aborted = true;
 }
 
-void
-sql_result_zeroblob(sql_context * pCtx, int n)
-{
-	mem_set_zerobin(pCtx->pOut, n);
-}
-
-int
-sql_result_zeroblob64(sql_context * pCtx, u64 n)
-{
-	Mem *pOut = pCtx->pOut;
-	if (n > (u64) pOut->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	mem_set_zerobin(pCtx->pOut, (int)n);
-	return 0;
-}
-
 /*
  * Execute the statement pStmt, either until a row of data is ready, the
  * statement is completely executed or an error occurs.
@@ -818,29 +799,6 @@ sql_bind_text64(sql_stmt * pStmt,
 	}
 }
 
-int
-sql_bind_zeroblob(sql_stmt * pStmt, int i, int n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (vdbeUnbind(p, i) != 0)
-		return -1;
-	mem_set_zerobin(&p->aVar[i - 1], n);
-	return 0;
-}
-
-int
-sql_bind_zeroblob64(sql_stmt * pStmt, int i, sql_uint64 n)
-{
-	Vdbe *p = (Vdbe *) pStmt;
-	if (n > (u64) p->db->aLimit[SQL_LIMIT_LENGTH]) {
-		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-			 "is too big");
-		return -1;
-	}
-	assert((n & 0x7FFFFFFF) == n);
-	return sql_bind_zeroblob(pStmt, i, n);
-}
-
 int
 sql_bind_uuid(struct sql_stmt *stmt, int i, const struct tt_uuid *uuid)
 {
diff --git a/test/sql-tap/engine.cfg b/test/sql-tap/engine.cfg
index a6f03307f..dea5a42cf 100644
--- a/test/sql-tap/engine.cfg
+++ b/test/sql-tap/engine.cfg
@@ -39,6 +39,7 @@
     "gh-6376-wrong-double-to-dec-cmp.test.lua": {},
     "gh-4077-iproto-execute-no-bind.test.lua": {},
     "gh-6375-assert-on-unsupported-ext.test.lua": {},
+    "gh-6113-assert-in-hex-on-zeroblob.test.lua": {},
     "*": {
         "memtx": {"engine": "memtx"},
         "vinyl": {"engine": "vinyl"}
diff --git a/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua b/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua
new file mode 100755
index 000000000..91a29a5b4
--- /dev/null
+++ b/test/sql-tap/gh-6113-assert-in-hex-on-zeroblob.test.lua
@@ -0,0 +1,13 @@
+#!/usr/bin/env tarantool
+local test = require("sqltester")
+test:plan(1)
+
+test:do_execsql_test(
+    "gh-6113",
+    [[
+        SELECT hex(zeroblob(0)), hex(zeroblob(10));
+    ]], {
+        '', '00000000000000000000'
+    })
+
+test:finish_test()

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

* Re: [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append()
  2021-10-05  9:32     ` Mergen Imeev via Tarantool-patches
@ 2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-19 10:49         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-11 21:50 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Hi! Thanks for the fixes!

>>> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
>>> index 079083fa1..b8ead8f41 100644
>>> --- a/src/box/sql/mem.c
>>> +++ b/src/box/sql/mem.c
>>> @@ -1925,6 +1925,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)
>>
>> Looks like you could call sqlVdbeMemGrow() without all these checks
>> above. The grow function does them already.
>>
> True, fixed. There is a case where the behavior will be different - I
> mean the case where new_size < szMalloc and new_size + n > szMalloc,
> but that doesn't seem to be a very common case.

Wait, this does not look correct either. I thought sqlVdbeMemGrow()
does the exponential grow. But if it does not, we need to either patch
it, or keep some checks above. The 'standard' way is to grow only when
necessary, but at least x2. How this new way works I can't tell. It
might be worse. You will always have unused free space. Lets either
stick to the old way or patch the grow function.

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

* Re: [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function
  2021-10-05  9:48     ` Mergen Imeev via Tarantool-patches
@ 2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-19 11:14         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-11 21:50 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the fixes!

>> @@ -141,17 +141,14 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
>> +	uint32_t *count_val = (uint32_t *)(sum + 1);
>>  	struct Mem count;
>>  	mem_create(&count);
>>  	mem_set_uint(&count, *count_val);
>>  	if (mem_div(&sum, &count, ctx->pOut) != 0)
>>  		ctx->is_aborted = true;
>> +	mem_destroy(sum);
>>  }
> This will work, however, I think it will create some unnecessary restrictions
> due to changes with pMem and pOut in a few patches. I suggest to apply part of
> you diff with exception of mem_destroy(), which I sugget to replace by assert().
> We have full control over this tmp/sum mem and we know, that there will be no
> memory to free, so assert should be enough.
> 
> What do you think of this diff?

It looks the same as mine except you didn't call the destroy. I am fine with it,
but when I propose to wrap the check about a mem not needing a destroy into a
function. We should not use mem members as is when possible. It is a too
complicated structure. So far.

Something like mem_is_trivial(). If it returns true, you don't need to
call mem_clear()/mem_destroy() and nothing will leak.

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

* Re: [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function
  2021-10-05  9:55     ` Mergen Imeev via Tarantool-patches
@ 2021-10-11 21:51       ` Vladislav Shpilevoy via Tarantool-patches
  2021-10-19 11:17         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-11 21:51 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the fixes!

>>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>>> index 94ec811ef..384c68be8 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);
>>
>> Would be nice to have a 'begin' step for the aggregation
>> functions. This would allow to eliminate these 'if is null'
>> ifs in some step functions in favor of having +1 virtual 'begin'
>> call in the beginning.
>>
>> Do you think it would simplify/speed up things? If you agree,
>> could you please create a ticket for that? As 'good first issue'
>> even.
>>
>> If don't agree, then ignore this comment.
> I think this is a good idea, but I don't see a suitable way to implement it. The
> problem is that in case we receive NULL as an argument, we will have NULL in
> pMem/pOut after the first step, and we still have to call begin() again or check
> with 'if'. And there is no way to determine how much NULLs will there be.

There are functions where it does not matter. For example,
TOTAL() never returns NULL. You could initialize its
accumulator with 0 from the beginning and drop

	mem_set_double(ctx->pMem, 0.0);

from step_total() and fin_total().

The same for COUNT() and mem_set_uint(ctx->pMem, 0). We have
more functions like these AFAIR.

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

* Re: [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append()
  2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-19 10:49         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-19 10:49 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

On Mon, Oct 11, 2021 at 11:50:08PM +0200, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> >>> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> >>> index 079083fa1..b8ead8f41 100644
> >>> --- a/src/box/sql/mem.c
> >>> +++ b/src/box/sql/mem.c
> >>> @@ -1925,6 +1925,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)
> >>
> >> Looks like you could call sqlVdbeMemGrow() without all these checks
> >> above. The grow function does them already.
> >>
> > True, fixed. There is a case where the behavior will be different - I
> > mean the case where new_size < szMalloc and new_size + n > szMalloc,
> > but that doesn't seem to be a very common case.
> 
> Wait, this does not look correct either. I thought sqlVdbeMemGrow()
> does the exponential grow. But if it does not, we need to either patch
> it, or keep some checks above. The 'standard' way is to grow only when
> necessary, but at least x2. How this new way works I can't tell. It
> might be worse. You will always have unused free space. Lets either
> stick to the old way or patch the grow function.
I think it's better to use the old way, because in all other cases we only need
to reallocate the memory once, and it would be in vain to double it every time.


Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 343a730b3..de4c41ccf 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1934,12 +1934,15 @@ mem_append(struct Mem *mem, const char *value, uint32_t len)
 	if (len == 0)
 		return 0;
 	int new_size = mem->n + len;
-	/*
-	 * 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;
+	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;


New patch:

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

    sql: introduce mem_append()
    
    This patch introduces the mem_append() function. This function appends
    the specified string to the end of the STRING or VARBINARY contained in
    MEM. In case MEM needs to increase the size of allocated memory,
    extra memory is allocated in an attempt to reduce the total number of
    allocations.
    
    Needed for #4145

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index f1b61dec6..de4c41ccf 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1927,6 +1927,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(const struct Mem *a, const struct Mem *b, struct Mem *result)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 1574da22d..b567aea50 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -582,6 +582,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] 39+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function
  2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-19 11:14         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-19 11:14 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

On Mon, Oct 11, 2021 at 11:50:39PM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> >> @@ -141,17 +141,14 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
> >> +	uint32_t *count_val = (uint32_t *)(sum + 1);
> >>  	struct Mem count;
> >>  	mem_create(&count);
> >>  	mem_set_uint(&count, *count_val);
> >>  	if (mem_div(&sum, &count, ctx->pOut) != 0)
> >>  		ctx->is_aborted = true;
> >> +	mem_destroy(sum);
> >>  }
> > This will work, however, I think it will create some unnecessary restrictions
> > due to changes with pMem and pOut in a few patches. I suggest to apply part of
> > you diff with exception of mem_destroy(), which I sugget to replace by assert().
> > We have full control over this tmp/sum mem and we know, that there will be no
> > memory to free, so assert should be enough.
> > 
> > What do you think of this diff?
> 
> It looks the same as mine except you didn't call the destroy. I am fine with it,
> but when I propose to wrap the check about a mem not needing a destroy into a
> function. We should not use mem members as is when possible. It is a too
> complicated structure. So far.
> 
> Something like mem_is_trivial(). If it returns true, you don't need to
> call mem_clear()/mem_destroy() and nothing will leak.
Added mem_is_trivial().


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index a811e55f9..8be553110 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -141,16 +141,13 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(sum + 1);
+	assert(mem_is_trivial(sum));
 	struct Mem count;
 	mem_create(&count);
 	mem_set_uint(&count, *count_val);
-	if (mem_div(&sum, &count, ctx->pOut) != 0)
+	if (mem_div(sum, &count, ctx->pOut) != 0)
 		ctx->is_aborted = true;
 }
 
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 7d5a750f5..52a63949a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -237,6 +237,14 @@ mem_is_allocated(const struct Mem *mem)
 	return mem_is_bytes(mem) && mem->z == mem->zMalloc;
 }
 
+/** Return TRUE if MEM does not need to be freed or destroyed. */
+static inline bool
+mem_is_trivial(const struct Mem *mem)
+{
+	return mem->szMalloc == 0 && (mem->flags & MEM_Dyn) == 0 &&
+	       (mem->type & (MEM_TYPE_FRAME | MEM_TYPE_AGG)) == 0;
+}
+
 static inline bool
 mem_is_cleared(const struct Mem *mem)
 {


New patch:

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

    sql: refactor AVG() function
    
    Part of #4145

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index c3c7ebec0..8be553110 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -102,6 +102,55 @@ 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 *sum = (struct Mem *)ctx->pMem->z;
+	uint32_t *count_val = (uint32_t *)(sum + 1);
+	assert(mem_is_trivial(sum));
+	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)
 {
@@ -1663,69 +1712,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.
@@ -2022,8 +2008,8 @@ 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_INTEGER}, FIELD_TYPE_INTEGER, step_avg, fin_avg},
+	{"AVG", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, 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/src/box/sql/mem.h b/src/box/sql/mem.h
index 7d5a750f5..52a63949a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -237,6 +237,14 @@ mem_is_allocated(const struct Mem *mem)
 	return mem_is_bytes(mem) && mem->z == mem->zMalloc;
 }
 
+/** Return TRUE if MEM does not need to be freed or destroyed. */
+static inline bool
+mem_is_trivial(const struct Mem *mem)
+{
+	return mem->szMalloc == 0 && (mem->flags & MEM_Dyn) == 0 &&
+	       (mem->type & (MEM_TYPE_FRAME | MEM_TYPE_AGG)) == 0;
+}
+
 static inline bool
 mem_is_cleared(const struct Mem *mem)
 {

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

* Re: [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function
  2021-10-11 21:51       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-10-19 11:17         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-19 11:17 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer below.

On Mon, Oct 11, 2021 at 11:51:07PM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> >>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> >>> index 94ec811ef..384c68be8 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);
> >>
> >> Would be nice to have a 'begin' step for the aggregation
> >> functions. This would allow to eliminate these 'if is null'
> >> ifs in some step functions in favor of having +1 virtual 'begin'
> >> call in the beginning.
> >>
> >> Do you think it would simplify/speed up things? If you agree,
> >> could you please create a ticket for that? As 'good first issue'
> >> even.
> >>
> >> If don't agree, then ignore this comment.
> > I think this is a good idea, but I don't see a suitable way to implement it. The
> > problem is that in case we receive NULL as an argument, we will have NULL in
> > pMem/pOut after the first step, and we still have to call begin() again or check
> > with 'if'. And there is no way to determine how much NULLs will there be.
> 
> There are functions where it does not matter. For example,
> TOTAL() never returns NULL. You could initialize its
> accumulator with 0 from the beginning and drop
> 
> 	mem_set_double(ctx->pMem, 0.0);
> 
> from step_total() and fin_total().
> 
> The same for COUNT() and mem_set_uint(ctx->pMem, 0). We have
> more functions like these AFAIR.
Ok, understood. I filled an issue:
https://github.com/tarantool/tarantool/issues/6533


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

* Re: [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions
  2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
                   ` (15 preceding siblings ...)
  2021-10-01 12:49 ` [Tarantool-patches] [PATCH v4 16/16] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
@ 2021-10-25 20:58 ` Vladislav Shpilevoy via Tarantool-patches
  16 siblings, 0 replies; 39+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-10-25 20:58 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Hi! Thanks for working on this and delivering all the fixes!

LGTM.

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

* [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments
  2021-10-26 10:34 Mergen Imeev via Tarantool-patches
@ 2021-10-26 10:34 ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 39+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-10-26 10:34 UTC (permalink / raw)
  To: kyukhin; +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 86550541b..af97b0499 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;
 
@@ -4177,26 +4170,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,
@@ -4220,7 +4213,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;
@@ -4229,6 +4221,7 @@ case OP_AggStep0: {
 }
 case OP_AggStep: {
 	int i;
+	int argc = pOp->p1;
 	sql_context *pCtx;
 	Mem *pMem;
 	Mem t;
@@ -4244,11 +4237,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]);
 	}
@@ -4261,7 +4255,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] 39+ messages in thread

end of thread, other threads:[~2021-10-26 10:35 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-01 12:48 [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 01/16] sql: remove MEM_Zero flag from struct MEM Mergen Imeev via Tarantool-patches
2021-10-04 21:51   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05  8:46     ` Mergen Imeev via Tarantool-patches
2021-10-05  9:42       ` Mergen Imeev via Tarantool-patches
2021-10-05 12:28         ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 02/16] sql: fix possible undefined behavior during cast Mergen Imeev via Tarantool-patches
2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05  9:00     ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 04/16] sql: remove AggStep0 and OP_BuiltinFunction0 Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 05/16] sql: move collation to struct sql_context Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 06/16] sql: introduce mem_append() Mergen Imeev via Tarantool-patches
2021-10-04 21:52   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05  9:32     ` Mergen Imeev via Tarantool-patches
2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
2021-10-19 10:49         ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 07/16] sql: remove sql_vdbemem_finalize() Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 08/16] sql: refactor SUM() function Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 09/16] sql: refactor TOTAL() function Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 10/16] sql: refactor AVG() function Mergen Imeev via Tarantool-patches
2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05  9:48     ` Mergen Imeev via Tarantool-patches
2021-10-11 21:50       ` Vladislav Shpilevoy via Tarantool-patches
2021-10-19 11:14         ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 11/16] sql: refactor COUNT() function Mergen Imeev via Tarantool-patches
2021-10-04 21:53   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05  9:55     ` Mergen Imeev via Tarantool-patches
2021-10-11 21:51       ` Vladislav Shpilevoy via Tarantool-patches
2021-10-19 11:17         ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 12/16] sql: refactor MIN() and MAX() functions Mergen Imeev via Tarantool-patches
2021-10-04 21:54   ` Vladislav Shpilevoy via Tarantool-patches
2021-10-05 10:07     ` Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 13/16] sql: refactor GROUP_CONCAT() function Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 14/16] sql: remove copying of result in finalizers Mergen Imeev via Tarantool-patches
2021-10-01 12:48 ` [Tarantool-patches] [PATCH v4 15/16] sql: remove MEM_TYPE_AGG Mergen Imeev via Tarantool-patches
2021-10-01 12:49 ` [Tarantool-patches] [PATCH v4 16/16] sql: remove field argv from struct sql_context Mergen Imeev via Tarantool-patches
2021-10-25 20:58 ` [Tarantool-patches] [PATCH v4 00/16] sql: refactor aggregate functions Vladislav Shpilevoy via Tarantool-patches
2021-10-26 10:34 Mergen Imeev via Tarantool-patches
2021-10-26 10:34 ` [Tarantool-patches] [PATCH v4 03/16] sql: use register P1 for number of arguments 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