[tarantool-patches] Re: [PATCH 2/2] sql: make aggregate functions types more strict
i.koptelov
ivan.koptelov at tarantool.org
Mon May 6 16:24:10 MSK 2019
> On 24 Apr 2019, at 20:37, n.pettik <korablev at tarantool.org> wrote:
>
>
>>>> + if (sql_type != ref_sql_type) {
>>>> + is_compatible = false;
>>>> + if ((sql_type == SQL_INTEGER || sql_type == SQL_FLOAT) &&
>>>> + (ref_sql_type == SQL_INTEGER ||
>>>> + ref_sql_type == SQL_FLOAT)) {
>>>> + is_compatible = true;
>>>
>>> This is a very hot path and doing so much work to check
>>> compatibility is a) clumsy when reading b) slow c) hard to
>>> maintain.
>>>
>>> Please use a compatibility matrix statically defined as a 8x8
>>> bitmap.
>> Fixed:
>
> It is not what was suggested. Look at field_type1_contains_type2() and
> field_type_compatibility as examples.
Thank you for noticing.
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0738f44e6..d98c60914 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1605,6 +1599,25 @@ countFinalize(sql_context * context)
sql_result_int64(context, p ? p->n : 0);
}
+/**
+ * Table of aggregate functions args type compatibility.
+ */
+static const bool scalar_type_compatibility[] = {
+ /* SQL_INTEGER SQL_FLOAT SQL_TEXT SQL_BLOB SQL_NULL */
+/* SQL_INTEGER */ true, true, false, false, false,
+/* SQL_FLOAT */ true, true, false, false, false,
+/* SQL_TEXT */ false, false, true, false, false,
+/* SQL_BLOB */ false, false, false, true, false,
+/* SQL_NULL */ false, false, false, false, true,
+};
+
+static bool
+are_scalar_types_compatible(enum sql_type type1, enum sql_type type2)
+{
+ int idx = (type2 - 1) * (SQL_TYPE_MAX - 1) + (type1 - 1);
+ return scalar_type_compatibility[idx];
+}
+
>
>> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
>> index c84f22caf..553c4f225 100644
>> --- a/src/box/sql/vdbeInt.h
>> +++ b/src/box/sql/vdbeInt.h
>> @@ -228,6 +228,7 @@ struct Mem {
>> #define MEM_Str 0x0002 /* Value is a string */
>> #define MEM_Int 0x0004 /* Value is an integer */
>> #define MEM_Real 0x0008 /* Value is a real number */
>> +#define MEM_NumMask 0x000c /* Value is integer or real (mask) */
>
> This mask covers neither Blob nor Bool values.
Can you please explain why it should? Blob and Bool are not numeric as far as I understand.
>
>> #define MEM_Blob 0x0010 /* Value is a BLOB */
>> #define MEM_Bool 0x0020 /* Value is a bool */
>> #define MEM_Ptr 0x0040 /* Value is a generic pointer */
>> @@ -262,11 +263,25 @@ enum {
>> MEM_PURE_TYPE_MASK = 0x1f
>> };
>>
>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>> index 624083b22..dd87c5b70 100644
>> --- a/src/box/sql/select.c
>> +++ b/src/box/sql/select.c
>> @@ -4351,7 +4351,7 @@ pushDownWhereTerms(Parse * pParse, /* Parse context (for malloc() and error repo
>> * The requirement of column type not being SCALAR follows from
>> * the purpose of the function. The purpose of the function is
>> * to answer the question: "Should MIN/MAX call be optimised by
>> - * using ORDER ON clause code?" If the type of column is SCALAR
>> + * using ORDER BY clause code?" If the type of column is SCALAR
>> * then we have to iterate over all rows to check if their types
>> * are compatible. Hence, the optimisation should not be used.
>>
>>>
>>>> + * then we have to iterate over all rows to check if their types
>>>> + * are compatible. Hence, the optimisation should not be used.
>>>> + * For details please see: https://github.com/tarantool/tarantool/issues/4032
>>>> + *
>>>> * Or, if the conditions above are not met, *ppMinMax is set to 0 and
>>>> * WHERE_ORDERBY_NORMAL is returned.
>>>> */
>>>> @@ -4364,6 +4373,8 @@ minMaxQuery(AggInfo * pAggInfo, ExprList ** ppMinMax)
>>>> if (pEList && pEList->nExpr == 1
>>>> && pEList->a[0].pExpr->op == TK_AGG_COLUMN) {
>>>> const char *zFunc = pExpr->u.zToken;
>>>> + if (sql_expr_type(pEList->a[0].pExpr) == FIELD_TYPE_SCALAR)
>>>> + return eRet;
>>>
>>> I see no difference between byte code generated for SCALAR and other
>>> columns (SELECT MIN(a) FROM t;). Investigate this case please.
>>>
>> I am not quite understand what is the problem with this.
>> If the following conditions are true, then the result of MIN/MAX is retrieved without
>> usage of corresponding function in func:
>>
>> * the query contains just a single aggregate function,
>> * the aggregate function is either min() or max(),
>> * the argument to the aggregate function is a column value,
>> * the type of column is not SCALAR.
>>
>> And the changes above disables this optimisation if column type is SCALAR.
>> I also added test for this case.
>
> This optimisation disables index search for min/max queries and SCALAR.
> I’m not sure that it is what we want. Nevertheless it makes usage of min/max
> uniform (in both cases error is raised), it dramatically slows down execution.
> I’ve asked server team, and we decided to allow all comparisons with SCALAR
> field type. Now we can’t tell scalar from non-scalar fields. To achieve this we should
> add field_type member to struct Mem and check field_type before comparisons.
>
> Theoretically speaking, these checks introduced in minmaxStep still may occur
> useful: if user register function, which may return data of different types and use
> this function as argument of min/max functions.
>
> Should current patch be pushed before handling of scalars is introduced - IDK.
>
> Overall, I suggest following diff. Note that you don’t need even
> ‘reference_type’ in minmax context.
>
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 0738f44e6..cc63f8b5f 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -51,13 +51,7 @@
> */
> struct minmax_context {
> /** Value being aggregated i.e. current MAX or MIN. */
> - Mem best;
> - /**
> - * Reference type to keep track of previous argument's types.
> - * One of MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob
> - * or MEM_Bool.
> - */
> - int reference_type;
> + struct Mem best;
> };
>
> static UConverter* pUtf8conv;
> @@ -1618,12 +1612,10 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
> return;
>
> sql_value *arg = argv[0];
> - sql_value *best = &(minmax_context->best);
> - if (best == NULL)
> - return;
> + sql_value *best = &minmax_context->best;
>
> - int sql_type = mem_type(arg);
> - if (sql_type == MEM_Null) {
> + int mem_type_current = arg->flags & MEM_PURE_TYPE_MASK;
> + if (mem_type_current == MEM_Null) {
> if (best->flags != 0)
> sqlSkipAccumulatorLoad(context);
> } else if (best->flags != 0) {
> @@ -1636,25 +1628,20 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
> * any other type). In the later case an error
> * is raised.
> */
> - if (minmax_context->reference_type == 0)
> - minmax_context->reference_type = sql_type;
> - int ref_sql_type = minmax_context->reference_type;
> -
> - if (sql_type != ref_sql_type) {
> - bool types_are_compatible = (sql_type & MEM_NumMask) &&
> - (ref_sql_type & MEM_NumMask);
> + int mem_type_best = best->flags & MEM_PURE_TYPE_MASK;
> + if (mem_type_best != mem_type_current) {
> + bool types_are_compatible = (mem_type_best & MEM_NumMask) &&
> + (mem_type_current & MEM_NumMask);
> if (!types_are_compatible) {
> diag_set(ClientError, ER_INCONSISTENT_TYPES,
> - type_to_str(ref_sql_type),
> - type_to_str(sql_type));
> + mem_type_to_str(best),
> + mem_type_to_str(arg));
> context->fErrorOrAux = 1;
> context->isError = SQL_TARANTOOL_ERROR;
> return;
> }
> }
>
> - int max;
> - int cmp;
> struct coll *coll = sqlGetFuncCollSeq(context);
> /* This step function is used for both the min() and max() aggregates,
> * the only difference between the two being that the sense of the
> @@ -1664,9 +1651,9 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
> * Therefore the next statement sets variable 'max' to 1 for the max()
> * aggregate, or 0 for min().
> */
> - max = sql_user_data(context) != 0;
> - cmp = sqlMemCompare(best, arg, coll);
> - if ((max != 0 && cmp < 0) || (max == 0 && cmp > 0)) {
> + bool max = sql_user_data(context) != 0;
> + int cmp_res = sqlMemCompare(best, arg, coll);
> + if ((max && cmp_res < 0) || (! max && cmp_res > 0)) {
> sqlVdbeMemCopy(best, arg);
> } else {
> sqlSkipAccumulatorLoad(context);
> @@ -1682,12 +1669,13 @@ minMaxFinalize(sql_context * context)
> {
> struct minmax_context *minmax_context = (struct minmax_context *)
> sql_aggregate_context(context, sizeof(*minmax_context));
> - sql_value *res = &(minmax_context->best);
> + sql_value *res = &minmax_context->best;
>
> if (res != NULL) {
> if (res->flags != 0) {
> sql_result_value(context, res);
> }
> + sqlVdbeMemRelease(res);
> }
> }
>
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index dd87c5b70..b1ec8c758 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -4340,22 +4340,13 @@ pushDownWhereTerms(Parse * pParse, /* Parse context (for malloc() and error repo
> * argument, this function checks if the following are true:
> *
> * * the query contains just a single aggregate function,
> - * * the aggregate function is either min() or max(),
> - * * the argument to the aggregate function is a column value,
> - * * the type of column is not SCALAR.
> + * * the aggregate function is either min() or max(), and
> + * * the argument to the aggregate function is a column value.
> *
> * If all of the above are true, then WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX
> * is returned as appropriate. Also, *ppMinMax is set to point to the
> * list of arguments passed to the aggregate before returning.
> *
> - * The requirement of column type not being SCALAR follows from
> - * the purpose of the function. The purpose of the function is
> - * to answer the question: "Should MIN/MAX call be optimised by
> - * using ORDER BY clause code?" If the type of column is SCALAR
> - * then we have to iterate over all rows to check if their types
> - * are compatible. Hence, the optimisation should not be used.
> - * For details please see: https://github.com/tarantool/tarantool/issues/4032
> - *
> * Or, if the conditions above are not met, *ppMinMax is set to 0 and
> * WHERE_ORDERBY_NORMAL is returned.
> */
> @@ -4373,8 +4364,6 @@ minMaxQuery(AggInfo * pAggInfo, ExprList ** ppMinMax)
> if (pEList && pEList->nExpr == 1
> && pEList->a[0].pExpr->op == TK_AGG_COLUMN) {
> const char *zFunc = pExpr->u.zToken;
> - if (sql_expr_type(pEList->a[0].pExpr) == FIELD_TYPE_SCALAR)
> - return eRet;
> if (sqlStrICmp(zFunc, "min") == 0) {
> eRet = WHERE_ORDERBY_MIN;
> *ppMinMax = pEList;
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 960030b52..ed7bf8870 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -618,9 +618,10 @@ vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id)
> }
>
> char *
> -type_to_str(int type)
> +mem_type_to_str(const struct Mem *p)
> {
> - switch (type) {
> + assert(p != NULL);
> + switch (p->flags & MEM_PURE_TYPE_MASK) {
> case MEM_Null:
> return "NULL";
> case MEM_Str:
> @@ -638,19 +639,6 @@ type_to_str(int type)
> }
> }
>
> -int
> -mem_type(const struct Mem *p)
> -{
> - assert(p != NULL);
> - return p->flags & MEM_PURE_TYPE_MASK;
> -}
> -
> -char *
> -mem_type_to_str(const struct Mem *p)
> -{
> - return type_to_str(mem_type(p));
> -}
> -
> /*
> * Execute as much of a VDBE program as we can.
> * This is the core of sql_step().
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 553c4f225..2b60bd6b5 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -263,25 +263,11 @@ enum {
> MEM_PURE_TYPE_MASK = 0x1f
> };
>
> -/**
> - * Returns one of MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob
> - * or MEM_Bool. Used for error detection and reporting.
> - */
> -int
> -mem_type(const struct Mem *p);
> -
> /**
> * Simple type to str convertor. It is used to simplify
> * error reporting.
> */
> char *
> -type_to_str(int type);
> -
> -/**
> - * Returns string representing type of the given Mem. It is used
> - * to simplify error reporting.
> - */
> -char *
> mem_type_to_str(const struct Mem *p);
Thank you very much, I have checked the diff and applied it with little changes.
Full diff:
---
src/box/sql/func.c | 62 ++++++++++++++++++++---------------
src/box/sql/select.c | 15 ++-------
src/box/sql/sqlInt.h | 1 +
src/box/sql/vdbe.c | 18 ++--------
src/box/sql/vdbeInt.h | 15 ---------
test/sql-tap/minmax4.test.lua | 13 +++++---
6 files changed, 49 insertions(+), 75 deletions(-)
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0738f44e6..d98c60914 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -51,13 +51,7 @@
*/
struct minmax_context {
/** Value being aggregated i.e. current MAX or MIN. */
- Mem best;
- /**
- * Reference type to keep track of previous argument's types.
- * One of MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob
- * or MEM_Bool.
- */
- int reference_type;
+ struct Mem best;
};
static UConverter* pUtf8conv;
@@ -1605,6 +1599,25 @@ countFinalize(sql_context * context)
sql_result_int64(context, p ? p->n : 0);
}
+/**
+ * Table of aggregate functions args type compatibility.
+ */
+static const bool scalar_type_compatibility[] = {
+ /* SQL_INTEGER SQL_FLOAT SQL_TEXT SQL_BLOB SQL_NULL */
+/* SQL_INTEGER */ true, true, false, false, false,
+/* SQL_FLOAT */ true, true, false, false, false,
+/* SQL_TEXT */ false, false, true, false, false,
+/* SQL_BLOB */ false, false, false, true, false,
+/* SQL_NULL */ false, false, false, false, true,
+};
+
+static bool
+are_scalar_types_compatible(enum sql_type type1, enum sql_type type2)
+{
+ int idx = (type2 - 1) * (SQL_TYPE_MAX - 1) + (type1 - 1);
+ return scalar_type_compatibility[idx];
+}
+
/*
* Routines to implement min() and max() aggregate functions.
*/
@@ -1618,12 +1631,10 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
return;
sql_value *arg = argv[0];
- sql_value *best = &(minmax_context->best);
- if (best == NULL)
- return;
+ sql_value *best = &minmax_context->best;
- int sql_type = mem_type(arg);
- if (sql_type == MEM_Null) {
+ enum sql_type sql_type_current = sql_value_type(arg);
+ if (sql_type_current == SQL_NULL) {
if (best->flags != 0)
sqlSkipAccumulatorLoad(context);
} else if (best->flags != 0) {
@@ -1636,25 +1647,21 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
* any other type). In the later case an error
* is raised.
*/
- if (minmax_context->reference_type == 0)
- minmax_context->reference_type = sql_type;
- int ref_sql_type = minmax_context->reference_type;
-
- if (sql_type != ref_sql_type) {
- bool types_are_compatible = (sql_type & MEM_NumMask) &&
- (ref_sql_type & MEM_NumMask);
+ enum sql_type sql_type_best = sql_value_type(best);
+ if (sql_type_best != sql_type_current) {
+ bool types_are_compatible =
+ are_scalar_types_compatible(sql_type_best,
+ sql_type_current);
if (!types_are_compatible) {
diag_set(ClientError, ER_INCONSISTENT_TYPES,
- type_to_str(ref_sql_type),
- type_to_str(sql_type));
+ mem_type_to_str(best),
+ mem_type_to_str(arg));
context->fErrorOrAux = 1;
context->isError = SQL_TARANTOOL_ERROR;
return;
}
}
- int max;
- int cmp;
struct coll *coll = sqlGetFuncCollSeq(context);
/* This step function is used for both the min() and max() aggregates,
* the only difference between the two being that the sense of the
@@ -1664,9 +1671,9 @@ minmaxStep(sql_context *context, int not_used, sql_value **argv)
* Therefore the next statement sets variable 'max' to 1 for the max()
* aggregate, or 0 for min().
*/
- max = sql_user_data(context) != 0;
- cmp = sqlMemCompare(best, arg, coll);
- if ((max != 0 && cmp < 0) || (max == 0 && cmp > 0)) {
+ bool max = sql_user_data(context) != 0;
+ int cmp_res = sqlMemCompare(best, arg, coll);
+ if ((max && cmp_res < 0) || (! max && cmp_res > 0)) {
sqlVdbeMemCopy(best, arg);
} else {
sqlSkipAccumulatorLoad(context);
@@ -1682,12 +1689,13 @@ minMaxFinalize(sql_context * context)
{
struct minmax_context *minmax_context = (struct minmax_context *)
sql_aggregate_context(context, sizeof(*minmax_context));
- sql_value *res = &(minmax_context->best);
+ sql_value *res = &minmax_context->best;
if (res != NULL) {
if (res->flags != 0) {
sql_result_value(context, res);
}
+ sqlVdbeMemRelease(res);
}
}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index dd87c5b70..b1ec8c758 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4340,22 +4340,13 @@ pushDownWhereTerms(Parse * pParse, /* Parse context (for malloc() and error repo
* argument, this function checks if the following are true:
*
* * the query contains just a single aggregate function,
- * * the aggregate function is either min() or max(),
- * * the argument to the aggregate function is a column value,
- * * the type of column is not SCALAR.
+ * * the aggregate function is either min() or max(), and
+ * * the argument to the aggregate function is a column value.
*
* If all of the above are true, then WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX
* is returned as appropriate. Also, *ppMinMax is set to point to the
* list of arguments passed to the aggregate before returning.
*
- * The requirement of column type not being SCALAR follows from
- * the purpose of the function. The purpose of the function is
- * to answer the question: "Should MIN/MAX call be optimised by
- * using ORDER BY clause code?" If the type of column is SCALAR
- * then we have to iterate over all rows to check if their types
- * are compatible. Hence, the optimisation should not be used.
- * For details please see: https://github.com/tarantool/tarantool/issues/4032
- *
* Or, if the conditions above are not met, *ppMinMax is set to 0 and
* WHERE_ORDERBY_NORMAL is returned.
*/
@@ -4373,8 +4364,6 @@ minMaxQuery(AggInfo * pAggInfo, ExprList ** ppMinMax)
if (pEList && pEList->nExpr == 1
&& pEList->a[0].pExpr->op == TK_AGG_COLUMN) {
const char *zFunc = pExpr->u.zToken;
- if (sql_expr_type(pEList->a[0].pExpr) == FIELD_TYPE_SCALAR)
- return eRet;
if (sqlStrICmp(zFunc, "min") == 0) {
eRet = WHERE_ORDERBY_MIN;
*ppMinMax = pEList;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index b322602dc..a96033db5 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -641,6 +641,7 @@ enum sql_type {
SQL_TEXT = 3,
SQL_BLOB = 4,
SQL_NULL = 5,
+ SQL_TYPE_MAX = 6,
};
/**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 960030b52..ed7bf8870 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -618,9 +618,10 @@ vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id)
}
char *
-type_to_str(int type)
+mem_type_to_str(const struct Mem *p)
{
- switch (type) {
+ assert(p != NULL);
+ switch (p->flags & MEM_PURE_TYPE_MASK) {
case MEM_Null:
return "NULL";
case MEM_Str:
@@ -638,19 +639,6 @@ type_to_str(int type)
}
}
-int
-mem_type(const struct Mem *p)
-{
- assert(p != NULL);
- return p->flags & MEM_PURE_TYPE_MASK;
-}
-
-char *
-mem_type_to_str(const struct Mem *p)
-{
- return type_to_str(mem_type(p));
-}
-
/*
* Execute as much of a VDBE program as we can.
* This is the core of sql_step().
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 553c4f225..c84f22caf 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -228,7 +228,6 @@ struct Mem {
#define MEM_Str 0x0002 /* Value is a string */
#define MEM_Int 0x0004 /* Value is an integer */
#define MEM_Real 0x0008 /* Value is a real number */
-#define MEM_NumMask 0x000c /* Value is integer or real (mask) */
#define MEM_Blob 0x0010 /* Value is a BLOB */
#define MEM_Bool 0x0020 /* Value is a bool */
#define MEM_Ptr 0x0040 /* Value is a generic pointer */
@@ -263,25 +262,11 @@ enum {
MEM_PURE_TYPE_MASK = 0x1f
};
-/**
- * Returns one of MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob
- * or MEM_Bool. Used for error detection and reporting.
- */
-int
-mem_type(const struct Mem *p);
-
/**
* Simple type to str convertor. It is used to simplify
* error reporting.
*/
char *
-type_to_str(int type);
-
-/**
- * Returns string representing type of the given Mem. It is used
- * to simplify error reporting.
- */
-char *
mem_type_to_str(const struct Mem *p);
/* Return TRUE if Mem X contains dynamically allocated content - anything
diff --git a/test/sql-tap/minmax4.test.lua b/test/sql-tap/minmax4.test.lua
index 7476051f1..7e6e309ad 100755
--- a/test/sql-tap/minmax4.test.lua
+++ b/test/sql-tap/minmax4.test.lua
@@ -353,13 +353,16 @@ test:do_test(
SELECT MAX(b) FROM t4;
]]
end, {
- 1, "Inconsistent types: expected REAL got TEXT"
+ 1, "Inconsistent types: expected INTEGER got TEXT"
})
-- Cases when we call aggregate MIN/MAX functions on column with
-- index (e.g. PRIMARY KEY index) deserves it's own test
-- because in this case MIN/MAX is implemented not with
-- dedicated function, but with usage of corresponding index.
+-- The behavior is different: in such cases MIN/MAX are less
+-- type-strict, for example it's possible to compare numeri
+-- values with text values.
test:do_test(
"minmax4-3.5",
function()
@@ -386,22 +389,22 @@ test:do_test(
test:do_test(
"minmax4-3.7",
function()
- return test:catchsql [[
+ return test:execsql [[
INSERT INTO t5 VALUES ('abc');
SELECT MIN(a) FROM t5;
]]
end, {
- 1, "Inconsistent types: expected INTEGER got TEXT"
+ 1.5
})
test:do_test(
"minmax4-3.8",
function()
- return test:catchsql [[
+ return test:execsql [[
SELECT MAX(a) FROM t5;
]]
end, {
- 1, "Inconsistent types: expected INTEGER got TEXT"
+ 'abc'
})
test:finish_test()
--
2.20.1
More information about the Tarantool-patches
mailing list