From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 146A16EC5D; Fri, 9 Apr 2021 19:53:53 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 146A16EC5D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1617987233; bh=8SBk+VbP5fZkfiNFG1jRzVgvWYF69UFNgl7DR9s0WSs=; h=To:Cc:Date:In-Reply-To:References:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=iieKMfglcEqI980FwB6JlinrIraVCdNfJkK39NzvLJZzruY2V+iG6wLw+sSMsZVr6 wQMN4QJZQW9vcpW2fTpb31/wOZlD/Z2q5EGp8q9XifkdM2e3kmIulEJLyDNoYJj3rt us/ZAK92OPIAI7dt4ZxyhgCBAZVRFmAY8nxy2b30= Received: from smtpng2.m.smailru.net (smtpng2.m.smailru.net [94.100.179.3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 02CEF6EC63 for ; Fri, 9 Apr 2021 19:51:38 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 02CEF6EC63 Received: by smtpng2.m.smailru.net with esmtpa (envelope-from ) id 1lUuM4-00061s-CY; Fri, 09 Apr 2021 19:51:36 +0300 To: v.shpilevoy@tarantool.org, tsafin@tarantool.org Cc: tarantool-patches@dev.tarantool.org Date: Fri, 9 Apr 2021 19:51:36 +0300 Message-Id: <16b3a31eb6de710a3512d38a40ca8fd52bb50bca.1617984948.git.imeevma@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-7564579A: 78E4E2B564C1792B X-77F55803: 4F1203BC0FB41BD92FFCB8E6708E748094FADAEB10E66ADA4C48BE3C291E66DA182A05F538085040F95D779B39231F6ECEEBB25B763657A4AE690B800E8B44A0D0F85933A76E85ED X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE78EB5DF72B5A7B6ADEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F790063790C8302959DAAF098638F802B75D45FF914D58D5BE9E6BC1A93B80C6DEB9DEE97C6FB206A91F05B2C0F00D449081B2648F546820332A7FF850D7F87C0C4C56F2D2E47CDBA5A96583C09775C1D3CA48CFCA5A41EBD8A3A0199FA2833FD35BB23D2EF20D2F80756B5F868A13BD56FB6657A471835C12D1D977725E5C173C3A84C3CA5A41EBD8A3A0199FA2833FD35BB23DF004C906525384302BEBFE083D3B9BA73A03B725D353964B0B7D0EA88DDEDAC722CA9DD8327EE4930A3850AC1BE2E735C6EABA9B74D0DA47B5C8C57E37DE458BEDA766A37F9254B7 X-C1DE0DAB: C20DE7B7AB408E4181F030C43753B8186998911F362727C414F749A5E30D975CD0035DD76F8A8A4F973BAA8C6214C2CFFEF0D540A134B9189C2B6934AE262D3EE7EAB7254005DCED7532B743992DF240BDC6A1CF3F042BAD6DF99611D93F60EF0417BEADF48D1460699F904B3F4130E343918A1A30D5E7FCCB5012B2E24CD356 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D34A533D6DF7731C664DE8B3A9787AC7E41AE97DBAD7E8561F259C453AE1F079D0F6BCB058ACF92D1001D7E09C32AA3244C49C19041CCC949DC8221C48E189245519CA7333006C390A0FACE5A9C96DEB163 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojyO2lHpuZu4R2izOV7V9umw== X-Mailru-Sender: 689FA8AB762F73936BC43F508A063822081F1E422FE9A1AE6695B19DB163AE4783D72C36FC87018B9F80AB2734326CD2FB559BB5D741EB96352A0ABBE4FDA4210A04DAD6CC59E33667EA787935ED9F1B X-Mras: Ok Subject: [Tarantool-patches] [PATCH v5 05/52] sql: move MEM-related functions to mem.c/mem.h X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Mergen Imeev via Tarantool-patches Reply-To: imeevma@tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Thaks you for the review! My answers and new patch below. On 30.03.2021 01:58, Vladislav Shpilevoy wrote: > Thanks for the patch! > > I didn't compare each line of each moved function with its > original code, so I trust you didn't make any functional > changes. > > See 2 comments below. > >> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c >> new file mode 100644 >> index 000000000..62338e1db >> --- /dev/null >> +++ b/src/box/sql/mem.c >> @@ -0,0 +1,2376 @@ > > <...> > >> + >> + >> +#ifdef SQL_DEBUG >> +/* >> + * This routine prepares a memory cell for modification by breaking >> + * its link to a shallow copy and by marking any current shallow >> + * copies of this cell as invalid. >> + * >> + * This is used for testing and debugging only - to make sure shallow >> + * copies are not misused. >> + */ >> +void >> +sqlVdbeMemAboutToChange(Vdbe * pVdbe, Mem * pMem) > > 1. This is a vdbe method, not mem. It takes mem as an argument, but > is a method of vdbe struct. Lets keep it in the old place. > Fixed. Moved it back and in patch "sql: disable unused code in sql/vdbemem.c" moved it to vdbe.c. >> +{ >> + int i; >> + Mem *pX; >> + for (i = 0, pX = pVdbe->aMem; i < pVdbe->nMem; i++, pX++) { >> + if (pX->pScopyFrom == pMem) { >> + pX->flags |= MEM_Undefined; >> + pX->pScopyFrom = 0; >> + } >> + } >> + pMem->pScopyFrom = 0; >> +} > > <...> > >> + >> +/* >> + * Print the SQL that was used to generate a VDBE program. >> + */ >> +void >> +sqlVdbePrintSql(Vdbe * p) > > 2. Ditto. Such functions should be moved to vdbe.h/vdbe.c eventually. > And ideally mem.h/mem.c should not know about vdbe anything (probably > can't be done now). > Fixed. Moved it back to vdbe.c. I think mem.c need vdbeInt.h and sqlInt.h right now, so we can't make it forget about struct Vdbe. I hope this will change in future. >> +{ >> + const char *z = 0; >> + if (p->zSql) { >> + z = p->zSql; >> + } else if (p->nOp >= 1) { >> + const VdbeOp *pOp = &p->aOp[0]; >> + if (pOp->opcode == OP_Init && pOp->p4.z != 0) { >> + z = pOp->p4.z; >> + while (sqlIsspace(*z)) >> + z++; >> + } >> + } >> + if (z) >> + printf("SQL: [%s]\n", z); >> +} New patch: commit 16b3a31eb6de710a3512d38a40ca8fd52bb50bca Author: Mergen Imeev Date: Mon Mar 1 18:13:06 2021 +0300 sql: move MEM-related functions to mem.c/mem.h This patch moves all MEM-related functions in mem.c/mem.h. Part of #5818 diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 19203f770..41c32468f 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -42,6 +42,7 @@ set(sql_sources sql/legacy.c sql/main.c sql/malloc.c + sql/mem.c sql/os.c sql/os_unix.c sql/parse_def.c diff --git a/src/box/sql.c b/src/box/sql.c index aa91f003f..790ca7f70 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -34,6 +34,7 @@ #include "sql.h" #include "sql/sqlInt.h" #include "sql/tarantoolInt.h" +#include "sql/mem.h" #include "sql/vdbeInt.h" #include "index.h" diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c index a015f70cb..5a95caa39 100644 --- a/src/box/sql/analyze.c +++ b/src/box/sql/analyze.c @@ -114,6 +114,7 @@ #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" #if 0 diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 521cc2622..6470e0300 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -45,6 +45,7 @@ */ #include #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "tarantoolInt.h" #include "box/ck_constraint.h" diff --git a/src/box/sql/func.c b/src/box/sql/func.c index 9b6179f3a..46814f341 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -35,6 +35,7 @@ * time functions, are implemented separately.) */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "version.h" #include "coll/coll.h" @@ -46,12 +47,6 @@ #include #include "box/coll_id_cache.h" #include "box/schema.h" -#include "box/func.h" -#include "box/port.h" -#include "box/tuple.h" -#include "lua/msgpack.h" -#include "lua/utils.h" -#include "mpstream/mpstream.h" /* * Return the collating function associated with a function. @@ -77,308 +72,6 @@ sqlSkipAccumulatorLoad(sql_context * context) context->skipFlag = 1; } -/** - * Allocate a sequence of initialized vdbe memory registers - * on region. - */ -static struct Mem * -vdbemem_alloc_on_region(uint32_t count) -{ - struct region *region = &fiber()->gc; - size_t size; - struct Mem *ret = region_alloc_array(region, typeof(*ret), count, - &size); - if (ret == NULL) { - diag_set(OutOfMemory, size, "region_alloc_array", "ret"); - return NULL; - } - memset(ret, 0, count * sizeof(*ret)); - for (uint32_t i = 0; i < count; i++) { - sqlVdbeMemInit(&ret[i], sql_get(), MEM_Null); - assert(memIsValid(&ret[i])); - } - return ret; -} - -static void -port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat) -{ - (void) is_flat; - struct port_vdbemem *port = (struct port_vdbemem *) base; - assert(is_flat == true); - for (uint32_t i = 0; i < port->mem_count; i++) { - sql_value *param = - (sql_value *)((struct Mem *)port->mem + i); - switch (sql_value_type(param)) { - case MP_INT: - luaL_pushint64(L, sql_value_int64(param)); - break; - case MP_UINT: - luaL_pushuint64(L, sql_value_uint64(param)); - break; - case MP_DOUBLE: - lua_pushnumber(L, sql_value_double(param)); - break; - case MP_STR: - lua_pushlstring(L, (const char *)sql_value_text(param), - (size_t)sql_value_bytes(param)); - break; - case MP_BIN: - case MP_ARRAY: - case MP_MAP: - lua_pushlstring(L, sql_value_blob(param), - (size_t) sql_value_bytes(param)); - break; - case MP_NIL: - lua_pushnil(L); - break; - case MP_BOOL: - lua_pushboolean(L, sql_value_boolean(param)); - break; - default: - unreachable(); - } - } -} - -static const char * -port_vdbemem_get_msgpack(struct port *base, uint32_t *size) -{ - struct port_vdbemem *port = (struct port_vdbemem *) base; - struct region *region = &fiber()->gc; - size_t region_svp = region_used(region); - bool is_error = false; - struct mpstream stream; - mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, - set_encode_error, &is_error); - mpstream_encode_array(&stream, port->mem_count); - for (uint32_t i = 0; i < port->mem_count && !is_error; i++) { - sql_value *param = - (sql_value *)((struct Mem *)port->mem + i); - switch (sql_value_type(param)) { - case MP_INT: { - sql_int64 val = sql_value_int64(param); - if (val < 0) { - mpstream_encode_int(&stream, val); - break; - } - FALLTHROUGH; - } - case MP_UINT: { - sql_uint64 val = sql_value_uint64(param); - mpstream_encode_uint(&stream, val); - break; - } - case MP_DOUBLE: { - mpstream_encode_double(&stream, - sql_value_double(param)); - break; - } - case MP_STR: { - const char *str = (const char *) sql_value_text(param); - mpstream_encode_strn(&stream, str, - sql_value_bytes(param)); - break; - } - case MP_BIN: - case MP_ARRAY: - case MP_MAP: { - mpstream_encode_binl(&stream, sql_value_bytes(param)); - mpstream_memcpy(&stream, sql_value_blob(param), - sql_value_bytes(param)); - break; - } - case MP_NIL: { - mpstream_encode_nil(&stream); - break; - } - case MP_BOOL: { - mpstream_encode_bool(&stream, sql_value_boolean(param)); - break; - } - default: - unreachable(); - } - } - mpstream_flush(&stream); - *size = region_used(region) - region_svp; - if (is_error) - goto error; - const char *ret = (char *)region_join(region, *size); - if (ret == NULL) - goto error; - return ret; -error: - diag_set(OutOfMemory, *size, "region", "ret"); - return NULL; -} - -static const struct port_vtab port_vdbemem_vtab; - -void -port_vdbemem_create(struct port *base, struct sql_value *mem, - uint32_t mem_count) -{ - struct port_vdbemem *port = (struct port_vdbemem *) base; - port->vtab = &port_vdbemem_vtab; - port->mem = mem; - port->mem_count = mem_count; -} - -static struct sql_value * -port_vdbemem_get_vdbemem(struct port *base, uint32_t *mem_count) -{ - struct port_vdbemem *port = (struct port_vdbemem *) base; - assert(port->vtab == &port_vdbemem_vtab); - *mem_count = port->mem_count; - return port->mem; -} - -static const struct port_vtab port_vdbemem_vtab = { - .dump_msgpack = NULL, - .dump_msgpack_16 = NULL, - .dump_lua = port_vdbemem_dump_lua, - .dump_plain = NULL, - .get_msgpack = port_vdbemem_get_msgpack, - .get_vdbemem = port_vdbemem_get_vdbemem, - .destroy = NULL, -}; - -struct sql_value * -port_lua_get_vdbemem(struct port *base, uint32_t *size) -{ - struct port_lua *port = (struct port_lua *) base; - struct lua_State *L = port->L; - int argc = lua_gettop(L); - if (argc == 0 || argc > 1) { - diag_set(ClientError, ER_SQL_FUNC_WRONG_RET_COUNT, "Lua", argc); - return NULL; - } - *size = argc; - /** FIXME: Implement an ability to return a vector. */ - assert(*size == 1); - struct region *region = &fiber()->gc; - size_t region_svp = region_used(region); - struct Mem *val = vdbemem_alloc_on_region(argc); - if (val == NULL) - return NULL; - for (int i = 0; i < argc; i++) { - struct luaL_field field; - if (luaL_tofield(L, luaL_msgpack_default, - NULL, -1 - i, &field) < 0) { - goto error; - } - switch (field.type) { - case MP_BOOL: - mem_set_bool(&val[i], field.bval); - break; - case MP_FLOAT: - mem_set_double(&val[i], field.fval); - break; - case MP_DOUBLE: - mem_set_double(&val[i], field.dval); - break; - case MP_INT: - mem_set_i64(&val[i], field.ival); - break; - case MP_UINT: - mem_set_u64(&val[i], field.ival); - break; - case MP_STR: - if (sqlVdbeMemSetStr(&val[i], field.sval.data, - field.sval.len, 1, - SQL_TRANSIENT) != 0) - goto error; - break; - case MP_NIL: - sqlVdbeMemSetNull(&val[i]); - break; - default: - diag_set(ClientError, ER_SQL_EXECUTE, - "Unsupported type passed from Lua"); - goto error; - } - } - return (struct sql_value *)val; -error: - for (int i = 0; i < argc; i++) - sqlVdbeMemRelease(&val[i]); - region_truncate(region, region_svp); - return NULL; -} - -struct sql_value * -port_c_get_vdbemem(struct port *base, uint32_t *size) -{ - struct port_c *port = (struct port_c *)base; - *size = port->size; - if (*size == 0 || *size > 1) { - diag_set(ClientError, ER_SQL_FUNC_WRONG_RET_COUNT, "C", *size); - return NULL; - } - /** FIXME: Implement an ability to return a vector. */ - assert(*size == 1); - struct region *region = &fiber()->gc; - size_t region_svp = region_used(region); - struct Mem *val = vdbemem_alloc_on_region(port->size); - if (val == NULL) - return NULL; - int i = 0; - const char *data; - struct port_c_entry *pe; - for (pe = port->first; pe != NULL; pe = pe->next) { - if (pe->mp_size == 0) { - data = tuple_data(pe->tuple); - if (mp_decode_array(&data) != 1) { - diag_set(ClientError, ER_SQL_EXECUTE, - "Unsupported type passed from C"); - goto error; - } - } else { - data = pe->mp; - } - uint32_t len; - const char *str; - switch (mp_typeof(*data)) { - case MP_BOOL: - mem_set_bool(&val[i], mp_decode_bool(&data)); - break; - case MP_FLOAT: - mem_set_double(&val[i], mp_decode_float(&data)); - break; - case MP_DOUBLE: - mem_set_double(&val[i], mp_decode_double(&data)); - break; - case MP_INT: - mem_set_i64(&val[i], mp_decode_int(&data)); - break; - case MP_UINT: - mem_set_u64(&val[i], mp_decode_uint(&data)); - break; - case MP_STR: - str = mp_decode_str(&data, &len); - if (sqlVdbeMemSetStr(&val[i], str, len, - 1, SQL_TRANSIENT) != 0) - goto error; - break; - case MP_NIL: - sqlVdbeMemSetNull(&val[i]); - break; - default: - diag_set(ClientError, ER_SQL_EXECUTE, - "Unsupported type passed from C"); - goto error; - } - i++; - } - return (struct sql_value *) val; -error: - for (int i = 0; i < port->size; i++) - sqlVdbeMemRelease(&val[i]); - region_truncate(region, region_svp); - return NULL; -} - /* * Implementation of the non-aggregate min() and max() functions */ diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 588e142d2..02e9f9673 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -35,6 +35,7 @@ */ #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" #include "box/ck_constraint.h" #include "bit/bit.h" diff --git a/src/box/sql/main.c b/src/box/sql/main.c index 0b20f2132..b0d32ae32 100644 --- a/src/box/sql/main.c +++ b/src/box/sql/main.c @@ -36,6 +36,7 @@ * accessed by users of the library. */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "version.h" #include "box/session.h" diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c new file mode 100644 index 000000000..d28b9a958 --- /dev/null +++ b/src/box/sql/mem.c @@ -0,0 +1,2638 @@ +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "sqlInt.h" +#include "mem.h" +#include "vdbeInt.h" +#include "coll/coll.h" +#include "tarantoolInt.h" +#include "box/schema.h" +#include "box/tuple.h" +#include "mpstream/mpstream.h" +#include "box/port.h" +#include "lua/utils.h" +#include "lua/msgpack.h" + +static inline bool +mem_has_msgpack_subtype(struct Mem *mem) +{ + return (mem->flags & MEM_Subtype) != 0 && + mem->subtype == SQL_SUBTYPE_MSGPACK; +} + +/* + * The pVal argument is known to be a value other than NULL. + * Convert it into a string with encoding enc and return a pointer + * to a zero-terminated version of that string. + */ +static SQL_NOINLINE const void * +valueToText(sql_value * pVal) +{ + assert(pVal != 0); + assert((pVal->flags & (MEM_Null)) == 0); + if ((pVal->flags & (MEM_Blob | MEM_Str)) && + !mem_has_msgpack_subtype(pVal)) { + if (ExpandBlob(pVal)) + return 0; + pVal->flags |= MEM_Str; + sqlVdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */ + } else { + sqlVdbeMemStringify(pVal); + assert(0 == (1 & SQL_PTR_TO_INT(pVal->z))); + } + return pVal->z; +} + +/** + * According to ANSI SQL string value can be converted to boolean + * type if string consists of literal "true" or "false" and + * number of leading and trailing spaces. + * + * For instance, " tRuE " can be successfully converted to + * boolean value true. + * + * @param str String to be converted to boolean. Assumed to be + * null terminated. + * @param[out] result Resulting value of cast. + * @retval 0 If string satisfies conditions above. + * @retval -1 Otherwise. + */ +static int +str_cast_to_boolean(const char *str, bool *result) +{ + assert(str != NULL); + for (; *str == ' '; str++); + if (strncasecmp(str, SQL_TOKEN_TRUE, strlen(SQL_TOKEN_TRUE)) == 0) { + *result = true; + str += 4; + } else if (strncasecmp(str, SQL_TOKEN_FALSE, + strlen(SQL_TOKEN_FALSE)) == 0) { + *result = false; + str += 5; + } else { + return -1; + } + for (; *str != '\0'; ++str) { + if (*str != ' ') + return -1; + } + return 0; +} + +/* + * Convert a 64-bit IEEE double into a 64-bit signed integer. + * If the double is out of range of a 64-bit signed integer then + * return the closest available 64-bit signed integer. + */ +static int +doubleToInt64(double r, int64_t *i) +{ + /* + * Many compilers we encounter do not define constants for the + * minimum and maximum 64-bit integers, or they define them + * inconsistently. And many do not understand the "LL" notation. + * So we define our own static constants here using nothing + * larger than a 32-bit integer constant. + */ + static const int64_t maxInt = LARGEST_INT64; + static const int64_t minInt = SMALLEST_INT64; + if (r <= (double)minInt) { + *i = minInt; + return -1; + } else if (r >= (double)maxInt) { + *i = maxInt; + return -1; + } else { + *i = (int64_t) r; + return *i != r; + } +} + +/* + * It is already known that pMem contains an unterminated string. + * Add the zero terminator. + */ +static SQL_NOINLINE int +vdbeMemAddTerminator(Mem * pMem) +{ + if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) { + return -1; + } + pMem->z[pMem->n] = 0; + pMem->z[pMem->n + 1] = 0; + pMem->flags |= MEM_Term; + return 0; +} + +/* + * If the memory cell contains a value that must be freed by + * invoking the external callback in Mem.xDel, then this routine + * will free that value. It also sets Mem.flags to MEM_Null. + * + * This is a helper routine for sqlVdbeMemSetNull() and + * for sqlVdbeMemRelease(). Use those other routines as the + * entry point for releasing Mem resources. + */ +static SQL_NOINLINE void +vdbeMemClearExternAndSetNull(Mem * p) +{ + assert(VdbeMemDynamic(p)); + if (p->flags & MEM_Agg) { + sql_vdbemem_finalize(p, p->u.func); + assert((p->flags & MEM_Agg) == 0); + testcase(p->flags & MEM_Dyn); + } + if (p->flags & MEM_Dyn) { + assert(p->xDel != SQL_DYNAMIC && p->xDel != 0); + p->xDel((void *)p->z); + } else if (p->flags & MEM_Frame) { + VdbeFrame *pFrame = p->u.pFrame; + pFrame->pParent = pFrame->v->pDelFrame; + pFrame->v->pDelFrame = pFrame; + } + p->flags = MEM_Null; +} + +/* + * Release memory held by the Mem p, both external memory cleared + * by p->xDel and memory in p->zMalloc. + * + * This is a helper routine invoked by sqlVdbeMemRelease() in + * the unusual case where there really is memory in p that needs + * to be freed. + */ +static SQL_NOINLINE void +vdbeMemClear(Mem * p) +{ + if (VdbeMemDynamic(p)) { + vdbeMemClearExternAndSetNull(p); + } + if (p->szMalloc) { + sqlDbFree(p->db, p->zMalloc); + p->szMalloc = 0; + } + p->z = 0; +} + +/* + * Make an shallow copy of pFrom into pTo. Prior contents of + * pTo are freed. The pFrom->z field is not duplicated. If + * pFrom->z is used, then pTo->z points to the same thing as pFrom->z + * and flags gets srcType (either MEM_Ephem or MEM_Static). + */ +static SQL_NOINLINE void +vdbeClrCopy(Mem * pTo, const Mem * pFrom, int eType) +{ + vdbeMemClearExternAndSetNull(pTo); + assert(!VdbeMemDynamic(pTo)); + sqlVdbeMemShallowCopy(pTo, pFrom, eType); +} + +/* + * Both *pMem1 and *pMem2 contain string values. Compare the two values + * using the collation sequence pColl. As usual, return a negative , zero + * or positive value if *pMem1 is less than, equal to or greater than + * *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". + * + * Strungs assume to be UTF-8 encoded + */ +static int +vdbeCompareMemString(const Mem * pMem1, const Mem * pMem2, + const struct coll * pColl) +{ + return pColl->cmp(pMem1->z, (size_t)pMem1->n, + pMem2->z, (size_t)pMem2->n, pColl); +} + +/* + * The input pBlob is guaranteed to be a Blob that is not marked + * with MEM_Zero. Return true if it could be a zero-blob. + */ +static int +isAllZero(const char *z, int n) +{ + int i; + for (i = 0; i < n; i++) { + if (z[i]) + return 0; + } + return 1; +} + +char * +mem_type_to_str(const struct Mem *p) +{ + assert(p != NULL); + switch (p->flags & MEM_PURE_TYPE_MASK) { + case MEM_Null: + return "NULL"; + case MEM_Str: + return "text"; + case MEM_Int: + return "integer"; + case MEM_UInt: + return "unsigned"; + case MEM_Real: + return "real"; + case MEM_Blob: + return "varbinary"; + case MEM_Bool: + return "boolean"; + default: + unreachable(); + } +} + +enum mp_type +mem_mp_type(struct Mem *mem) +{ + switch (mem->flags & MEM_PURE_TYPE_MASK) { + case MEM_Int: + return MP_INT; + case MEM_UInt: + return MP_UINT; + case MEM_Real: + return MP_DOUBLE; + case MEM_Str: + return MP_STR; + case MEM_Blob: + if ((mem->flags & MEM_Subtype) == 0 || + mem->subtype != SQL_SUBTYPE_MSGPACK) + return MP_BIN; + assert(mp_typeof(*mem->z) == MP_MAP || + mp_typeof(*mem->z) == MP_ARRAY); + return mp_typeof(*mem->z); + case MEM_Bool: + return MP_BOOL; + case MEM_Null: + return MP_NIL; + default: unreachable(); + } +} + +/* EVIDENCE-OF: R-12793-43283 Every value in sql has one of five + * fundamental datatypes: 64-bit signed integer 64-bit IEEE floating + * point number string BLOB NULL + */ +enum mp_type +sql_value_type(sql_value *pVal) +{ + struct Mem *mem = (struct Mem *) pVal; + return mem_mp_type(mem); +} + + +/* + * pMem currently only holds a string type (or maybe a BLOB that we can + * interpret as a string if we want to). Compute its corresponding + * numeric type, if has one. Set the pMem->u.r and pMem->u.i fields + * accordingly. + */ +static u16 SQL_NOINLINE +computeNumericType(Mem *pMem) +{ + assert((pMem->flags & (MEM_Int | MEM_UInt | MEM_Real)) == 0); + assert((pMem->flags & (MEM_Str|MEM_Blob))!=0); + if (sqlAtoF(pMem->z, &pMem->u.r, pMem->n)==0) + return 0; + bool is_neg; + if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, pMem->n) == 0) + return is_neg ? MEM_Int : MEM_UInt; + return MEM_Real; +} + +/* + * Return the numeric type for pMem, either MEM_Int or MEM_Real or both or + * none. + * + * Unlike mem_apply_numeric_type(), this routine does not modify pMem->flags. + * But it does set pMem->u.r and pMem->u.i appropriately. + */ +u16 +numericType(Mem *pMem) +{ + if ((pMem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) + return pMem->flags & (MEM_Int | MEM_UInt | MEM_Real); + if (pMem->flags & (MEM_Str|MEM_Blob)) { + return computeNumericType(pMem); + } + return 0; +} + +/* + * The sqlValueBytes() routine returns the number of bytes in the + * sql_value object assuming that it uses the encoding "enc". + * The valueBytes() routine is a helper function. + */ +static SQL_NOINLINE int +valueBytes(sql_value * pVal) +{ + return valueToText(pVal) != 0 ? pVal->n : 0; +} + +int +sqlValueBytes(sql_value * pVal) +{ + Mem *p = (Mem *) pVal; + assert((p->flags & MEM_Null) == 0 + || (p->flags & (MEM_Str | MEM_Blob)) == 0); + if ((p->flags & MEM_Str) != 0) { + return p->n; + } + if ((p->flags & MEM_Blob) != 0) { + if (p->flags & MEM_Zero) { + return p->n + p->u.nZero; + } else { + return p->n; + } + } + if (p->flags & MEM_Null) + return 0; + return valueBytes(pVal); +} + + +#ifdef SQL_DEBUG +/* + * Check invariants on a Mem object. + * + * This routine is intended for use inside of assert() statements, like + * this: assert( sqlVdbeCheckMemInvariants(pMem) ); + */ +int +sqlVdbeCheckMemInvariants(Mem * p) +{ + /* If MEM_Dyn is set then Mem.xDel!=0. + * Mem.xDel is might not be initialized if MEM_Dyn is clear. + */ + assert((p->flags & MEM_Dyn) == 0 || p->xDel != 0); + + /* MEM_Dyn may only be set if Mem.szMalloc==0. In this way we + * ensure that if Mem.szMalloc>0 then it is safe to do + * Mem.z = Mem.zMalloc without having to check Mem.flags&MEM_Dyn. + * That saves a few cycles in inner loops. + */ + assert((p->flags & MEM_Dyn) == 0 || p->szMalloc == 0); + + /* Cannot be both MEM_Int and MEM_Real at the same time */ + assert((p->flags & (MEM_Int | MEM_Real)) != (MEM_Int | MEM_Real)); + /* Can't be both UInt and Int at the same time. */ + assert((p->flags & (MEM_Int | MEM_UInt)) != (MEM_Int | MEM_UInt)); + + /* The szMalloc field holds the correct memory allocation size */ + assert(p->szMalloc == 0 || + p->szMalloc == sqlDbMallocSize(p->db, p->zMalloc)); + + /* If p holds a string or blob, the Mem.z must point to exactly + * one of the following: + * + * (1) Memory in Mem.zMalloc and managed by the Mem object + * (2) Memory to be freed using Mem.xDel + * (3) An ephemeral string or blob + * (4) A static string or blob + */ + if ((p->flags & (MEM_Str | MEM_Blob)) && p->n > 0) { + assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) + + ((p->flags & MEM_Dyn) != 0 ? 1 : 0) + + ((p->flags & MEM_Ephem) != 0 ? 1 : 0) + + ((p->flags & MEM_Static) != 0 ? 1 : 0) == 1); + } + return 1; +} + +/* + * Write a nice string representation of the contents of cell pMem + * into buffer zBuf, length nBuf. + */ +void +sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf) +{ + char *zCsr = zBuf; + int f = pMem->flags; + + if (f&MEM_Blob) { + int i; + char c; + if (f & MEM_Dyn) { + c = 'z'; + assert((f & (MEM_Static|MEM_Ephem))==0); + } else if (f & MEM_Static) { + c = 't'; + assert((f & (MEM_Dyn|MEM_Ephem))==0); + } else if (f & MEM_Ephem) { + c = 'e'; + assert((f & (MEM_Static|MEM_Dyn))==0); + } else { + c = 's'; + } + + sql_snprintf(100, zCsr, "%c", c); + zCsr += sqlStrlen30(zCsr); + sql_snprintf(100, zCsr, "%d[", pMem->n); + zCsr += sqlStrlen30(zCsr); + for(i=0; i<16 && in; i++) { + sql_snprintf(100, zCsr, "%02X", ((int)pMem->z[i] & 0xFF)); + zCsr += sqlStrlen30(zCsr); + } + for(i=0; i<16 && in; i++) { + char z = pMem->z[i]; + if (z<32 || z>126) *zCsr++ = '.'; + else *zCsr++ = z; + } + 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 (f & MEM_Str) { + int j, k; + zBuf[0] = ' '; + if (f & MEM_Dyn) { + zBuf[1] = 'z'; + assert((f & (MEM_Static|MEM_Ephem))==0); + } else if (f & MEM_Static) { + zBuf[1] = 't'; + assert((f & (MEM_Dyn|MEM_Ephem))==0); + } else if (f & MEM_Ephem) { + zBuf[1] = 'e'; + assert((f & (MEM_Static|MEM_Dyn))==0); + } else { + zBuf[1] = 's'; + } + k = 2; + sql_snprintf(100, &zBuf[k], "%d", pMem->n); + k += sqlStrlen30(&zBuf[k]); + zBuf[k++] = '['; + for(j=0; j<15 && jn; j++) { + u8 c = pMem->z[j]; + if (c>=0x20 && c<0x7f) { + zBuf[k++] = c; + } else { + zBuf[k++] = '.'; + } + } + zBuf[k++] = ']'; + sql_snprintf(100,&zBuf[k],"(8)"); + k += sqlStrlen30(&zBuf[k]); + zBuf[k++] = 0; + } +} + +/* + * Print the value of a register for tracing purposes: + */ +static void +memTracePrint(Mem *p) +{ + if (p->flags & MEM_Undefined) { + printf(" undefined"); + } else if (p->flags & MEM_Null) { + printf(" NULL"); + } else if ((p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str)) { + printf(" si:%lld", p->u.i); + } else if (p->flags & MEM_Int) { + printf(" i:%lld", p->u.i); + } else if (p->flags & MEM_UInt) { + printf(" u:%"PRIu64"", p->u.u); + } else if (p->flags & MEM_Real) { + printf(" r:%g", p->u.r); + } else if (p->flags & MEM_Bool) { + printf(" bool:%s", SQL_TOKEN_BOOLEAN(p->u.b)); + } else { + char zBuf[200]; + sqlVdbeMemPrettyPrint(p, zBuf); + printf(" %s", zBuf); + } + if (p->flags & MEM_Subtype) printf(" subtype=0x%02x", p->subtype); +} + +void +registerTrace(int iReg, Mem *p) { + printf("REG[%d] = ", iReg); + memTracePrint(p); + printf("\n"); +} +#endif + +int +mem_apply_numeric_type(struct Mem *record) +{ + if ((record->flags & MEM_Str) == 0) + return -1; + int64_t integer_value; + bool is_neg; + if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) { + mem_set_int(record, integer_value, is_neg); + return 0; + } + double float_value; + if (sqlAtoF(record->z, &float_value, record->n) == 0) + return -1; + mem_set_double(record, float_value); + return 0; +} + +/* + * Convert pMem so that it is of type MEM_Real. + * Invalidate any prior representations. + */ +int +sqlVdbeMemRealify(Mem * pMem) +{ + assert(EIGHT_BYTE_ALIGNMENT(pMem)); + double v; + if (sqlVdbeRealValue(pMem, &v)) + return -1; + mem_set_double(pMem, v); + return 0; +} + +int +vdbe_mem_numerify(struct Mem *mem) +{ + if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real | MEM_Null)) != 0) + return 0; + if ((mem->flags & MEM_Bool) != 0) { + mem->u.u = mem->u.b; + MemSetTypeFlag(mem, MEM_UInt); + return 0; + } + assert((mem->flags & (MEM_Blob | MEM_Str)) != 0); + bool is_neg; + int64_t i; + if (sql_atoi64(mem->z, &i, &is_neg, mem->n) == 0) { + mem_set_int(mem, i, is_neg); + } else { + double d; + if (sqlAtoF(mem->z, &d, mem->n) == 0) + return -1; + mem_set_double(mem, d); + } + return 0; +} + +/* + * Cast the datatype of the value in pMem according to the type + * @type. Casting is different from applying type in that a cast + * is forced. In other words, the value is converted into the desired + * type even if that results in loss of data. This routine is + * used (for example) to implement the SQL "cast()" operator. + */ +int +sqlVdbeMemCast(Mem * pMem, enum field_type type) +{ + assert(type < field_type_MAX); + if (pMem->flags & MEM_Null) + return 0; + switch (type) { + case FIELD_TYPE_SCALAR: + return 0; + case FIELD_TYPE_BOOLEAN: + if ((pMem->flags & MEM_Int) != 0) { + mem_set_bool(pMem, pMem->u.i); + return 0; + } + if ((pMem->flags & MEM_UInt) != 0) { + mem_set_bool(pMem, pMem->u.u); + return 0; + } + if ((pMem->flags & MEM_Real) != 0) { + mem_set_bool(pMem, pMem->u.r); + return 0; + } + if ((pMem->flags & MEM_Str) != 0) { + bool value; + if (str_cast_to_boolean(pMem->z, &value) != 0) + return -1; + mem_set_bool(pMem, value); + return 0; + } + if ((pMem->flags & MEM_Bool) != 0) + return 0; + return -1; + case FIELD_TYPE_INTEGER: + case FIELD_TYPE_UNSIGNED: + if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) { + bool is_neg; + int64_t val; + if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0) + return -1; + if (type == FIELD_TYPE_UNSIGNED && is_neg) + return -1; + mem_set_int(pMem, val, is_neg); + return 0; + } + if ((pMem->flags & MEM_Bool) != 0) { + pMem->u.u = pMem->u.b; + MemSetTypeFlag(pMem, MEM_UInt); + return 0; + } + if ((pMem->flags & MEM_Real) != 0) { + double d; + if (sqlVdbeRealValue(pMem, &d) != 0) + return -1; + if (d < (double)INT64_MAX && d >= (double)INT64_MIN) { + mem_set_int(pMem, d, d <= -1); + return 0; + } + if (d >= (double)INT64_MAX && d < (double)UINT64_MAX) { + mem_set_u64(pMem, d); + return 0; + } + return -1; + } + if (type == FIELD_TYPE_UNSIGNED && + (pMem->flags & MEM_UInt) == 0) + return -1; + return 0; + case FIELD_TYPE_DOUBLE: + return sqlVdbeMemRealify(pMem); + case FIELD_TYPE_NUMBER: + return vdbe_mem_numerify(pMem); + case FIELD_TYPE_VARBINARY: + if ((pMem->flags & MEM_Blob) != 0) + return 0; + if ((pMem->flags & MEM_Str) != 0) { + MemSetTypeFlag(pMem, MEM_Str); + return 0; + } + return -1; + default: + assert(type == FIELD_TYPE_STRING); + assert(MEM_Str == (MEM_Blob >> 3)); + if ((pMem->flags & MEM_Bool) != 0) { + const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b); + sqlVdbeMemSetStr(pMem, str_bool, strlen(str_bool), 1, + SQL_TRANSIENT); + return 0; + } + pMem->flags |= (pMem->flags & MEM_Blob) >> 3; + sql_value_apply_type(pMem, FIELD_TYPE_STRING); + assert(pMem->flags & MEM_Str || pMem->db->mallocFailed); + pMem->flags &= + ~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero); + return 0; + } +} + +/* + * The MEM structure is already a MEM_Real. Try to also make it a + * MEM_Int if we can. + */ +int +mem_apply_integer_type(Mem *pMem) +{ + int rc; + i64 ix; + assert(pMem->flags & MEM_Real); + assert(EIGHT_BYTE_ALIGNMENT(pMem)); + + if ((rc = doubleToInt64(pMem->u.r, (int64_t *) &ix)) == 0) + mem_set_int(pMem, ix, pMem->u.r <= -1); + return rc; +} + +/* + * Add MEM_Str to the set of representations for the given Mem. Numbers + * are converted using sql_snprintf(). Converting a BLOB to a string + * is a no-op. + * + * Existing representations MEM_Int and MEM_Real are invalidated if + * bForce is true but are retained if bForce is false. + * + * A MEM_Null value will never be passed to this function. This function is + * used for converting values to text for returning to the user (i.e. via + * sql_value_text()), or for ensuring that values to be used as btree + * keys are strings. In the former case a NULL pointer is returned the + * user and the latter is an internal programming error. + */ +int +sqlVdbeMemStringify(Mem * pMem) +{ + int fg = pMem->flags; + int nByte = 32; + + if ((fg & (MEM_Null | MEM_Str | MEM_Blob)) != 0 && + !mem_has_msgpack_subtype(pMem)) + return 0; + + assert(!(fg & MEM_Zero)); + assert((fg & (MEM_Int | MEM_UInt | MEM_Real | MEM_Bool | + MEM_Blob)) != 0); + assert(EIGHT_BYTE_ALIGNMENT(pMem)); + + /* + * In case we have ARRAY/MAP we should save decoded value + * before clearing pMem->z. + */ + char *value = NULL; + if (mem_has_msgpack_subtype(pMem)) { + const char *value_str = mp_str(pMem->z); + nByte = strlen(value_str) + 1; + value = region_alloc(&fiber()->gc, nByte); + memcpy(value, value_str, nByte); + } + + if (sqlVdbeMemClearAndResize(pMem, nByte)) { + return -1; + } + if (fg & MEM_Int) { + sql_snprintf(nByte, pMem->z, "%lld", pMem->u.i); + pMem->flags &= ~MEM_Int; + } else if ((fg & MEM_UInt) != 0) { + sql_snprintf(nByte, pMem->z, "%llu", pMem->u.u); + pMem->flags &= ~MEM_UInt; + } else if ((fg & MEM_Bool) != 0) { + sql_snprintf(nByte, pMem->z, "%s", + SQL_TOKEN_BOOLEAN(pMem->u.b)); + pMem->flags &= ~MEM_Bool; + } else if (mem_has_msgpack_subtype(pMem)) { + sql_snprintf(nByte, pMem->z, "%s", value); + pMem->flags &= ~MEM_Subtype; + pMem->subtype = SQL_SUBTYPE_NO; + } else { + assert(fg & MEM_Real); + sql_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r); + pMem->flags &= ~MEM_Real; + } + pMem->n = sqlStrlen30(pMem->z); + pMem->flags |= MEM_Str | MEM_Term; + return 0; +} + +/* + * Make sure the given Mem is \u0000 terminated. + */ +int +sqlVdbeMemNulTerminate(Mem * pMem) +{ + testcase((pMem->flags & (MEM_Term | MEM_Str)) == (MEM_Term | MEM_Str)); + testcase((pMem->flags & (MEM_Term | MEM_Str)) == 0); + if ((pMem->flags & (MEM_Term | MEM_Str)) != MEM_Str) { + return 0; /* Nothing to do */ + } else { + return vdbeMemAddTerminator(pMem); + } +} + +/* + * 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->flags & MEM_Blob); + + /* 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; +} + +/* + * Exported version of mem_apply_type(). This one works on sql_value*, + * not the internal Mem* type. + */ +void +sql_value_apply_type( + sql_value *pVal, + enum field_type type) +{ + mem_apply_type((Mem *) pVal, type); +} + +int +mem_apply_type(struct Mem *record, enum field_type type) +{ + if ((record->flags & MEM_Null) != 0) + return 0; + assert(type < field_type_MAX); + switch (type) { + case FIELD_TYPE_INTEGER: + case FIELD_TYPE_UNSIGNED: + if ((record->flags & (MEM_Bool | MEM_Blob)) != 0) + return -1; + if ((record->flags & MEM_UInt) == MEM_UInt) + return 0; + if ((record->flags & MEM_Real) == MEM_Real) { + double d = record->u.r; + if (d >= 0) { + if (double_compare_uint64(d, UINT64_MAX, + 1) > 0) + return 0; + if ((double)(uint64_t)d == d) + mem_set_u64(record, (uint64_t)d); + } else { + if (double_compare_nint64(d, INT64_MIN, 1) < 0) + return 0; + if ((double)(int64_t)d == d) + mem_set_int(record, (int64_t)d, true); + } + return 0; + } + if ((record->flags & MEM_Str) != 0) { + bool is_neg; + int64_t i; + if (sql_atoi64(record->z, &i, &is_neg, record->n) != 0) + return -1; + mem_set_int(record, i, is_neg); + } + if ((record->flags & MEM_Int) == MEM_Int) { + if (type == FIELD_TYPE_UNSIGNED) + return -1; + return 0; + } + return 0; + case FIELD_TYPE_BOOLEAN: + if ((record->flags & MEM_Bool) == MEM_Bool) + return 0; + return -1; + case FIELD_TYPE_NUMBER: + if ((record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0) + return 0; + return sqlVdbeMemRealify(record); + case FIELD_TYPE_DOUBLE: + if ((record->flags & MEM_Real) != 0) + return 0; + return sqlVdbeMemRealify(record); + case FIELD_TYPE_STRING: + /* + * Only attempt the conversion to TEXT if there is + * an integer or real representation (BLOB and + * NULL do not get converted). + */ + if ((record->flags & MEM_Str) == 0 && + (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0) + sqlVdbeMemStringify(record); + record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt); + return 0; + case FIELD_TYPE_VARBINARY: + if ((record->flags & MEM_Blob) == 0) + return -1; + return 0; + case FIELD_TYPE_SCALAR: + /* Can't cast MAP and ARRAY to scalar types. */ + if ((record->flags & MEM_Subtype) != 0 && + record->subtype == SQL_SUBTYPE_MSGPACK) { + assert(mp_typeof(*record->z) == MP_MAP || + mp_typeof(*record->z) == MP_ARRAY); + return -1; + } + return 0; + case FIELD_TYPE_MAP: + if ((record->flags & MEM_Subtype) != 0 && + record->subtype == SQL_SUBTYPE_MSGPACK && + mp_typeof(*record->z) == MP_MAP) + return 0; + return -1; + case FIELD_TYPE_ARRAY: + if ((record->flags & MEM_Subtype) != 0 && + record->subtype == SQL_SUBTYPE_MSGPACK && + mp_typeof(*record->z) == MP_ARRAY) + return 0; + return -1; + case FIELD_TYPE_ANY: + return 0; + default: + return -1; + } +} + +/** + * Convert the numeric value contained in MEM to double. + * + * @param mem The MEM that contains the numeric value. + * @retval 0 if the conversion was successful, -1 otherwise. + */ +static int +mem_convert_to_double(struct Mem *mem) +{ + if ((mem->flags & MEM_Real) != 0) + return 0; + if ((mem->flags & (MEM_Int | MEM_UInt)) == 0) + return -1; + double d; + if ((mem->flags & MEM_Int) != 0) + d = (double)mem->u.i; + else + d = (double)mem->u.u; + mem_set_double(mem, d); + return 0; +} + +/** + * Convert the numeric value contained in MEM to unsigned. + * + * @param mem The MEM that contains the numeric value. + * @retval 0 if the conversion was successful, -1 otherwise. + */ +static int +mem_convert_to_unsigned(struct Mem *mem) +{ + if ((mem->flags & MEM_UInt) != 0) + return 0; + if ((mem->flags & MEM_Int) != 0) + return -1; + if ((mem->flags & MEM_Real) == 0) + return -1; + double d = mem->u.r; + if (d < 0.0 || d >= (double)UINT64_MAX) + return -1; + mem_set_u64(mem, (uint64_t) d); + return 0; +} + +/** + * Convert the numeric value contained in MEM to integer. + * + * @param mem The MEM that contains the numeric value. + * @retval 0 if the conversion was successful, -1 otherwise. + */ +static int +mem_convert_to_integer(struct Mem *mem) +{ + if ((mem->flags & (MEM_UInt | MEM_Int)) != 0) + return 0; + if ((mem->flags & MEM_Real) == 0) + return -1; + double d = mem->u.r; + if (d >= (double)UINT64_MAX || d < (double)INT64_MIN) + return -1; + if (d < (double)INT64_MAX) + mem_set_int(mem, (int64_t) d, d < 0); + else + mem_set_int(mem, (uint64_t) d, false); + return 0; +} + +int +mem_convert_to_numeric(struct Mem *mem, enum field_type type) +{ + assert(mp_type_is_numeric(mem_mp_type(mem)) && + sql_type_is_numeric(type)); + assert(type != FIELD_TYPE_NUMBER); + if (type == FIELD_TYPE_DOUBLE) + return mem_convert_to_double(mem); + if (type == FIELD_TYPE_UNSIGNED) + return mem_convert_to_unsigned(mem); + assert(type == FIELD_TYPE_INTEGER); + return mem_convert_to_integer(mem); +} + +/* + * Make sure pMem->z points to a writable allocation of at least + * min(n,32) bytes. + * + * If the bPreserve argument is true, then copy of the content of + * pMem->z into the new allocation. pMem must be either a string or + * blob if bPreserve is true. If bPreserve is false, any prior content + * in pMem->z is discarded. + */ +SQL_NOINLINE int +sqlVdbeMemGrow(Mem * pMem, int n, int bPreserve) +{ + assert(sqlVdbeCheckMemInvariants(pMem)); + testcase(pMem->db == 0); + + /* If the bPreserve flag is set to true, then the memory cell must already + * contain a valid string or blob value. + */ + assert(bPreserve == 0 || pMem->flags & (MEM_Blob | MEM_Str)); + testcase(bPreserve && pMem->z == 0); + + assert(pMem->szMalloc == 0 || + pMem->szMalloc == sqlDbMallocSize(pMem->db, pMem->zMalloc)); + if (pMem->szMalloc < n) { + if (n < 32) + n = 32; + if (bPreserve && pMem->szMalloc > 0 && pMem->z == pMem->zMalloc) { + pMem->z = pMem->zMalloc = + sqlDbReallocOrFree(pMem->db, pMem->z, n); + bPreserve = 0; + } else { + if (pMem->szMalloc > 0) + sqlDbFree(pMem->db, pMem->zMalloc); + pMem->zMalloc = sqlDbMallocRaw(pMem->db, n); + } + if (pMem->zMalloc == 0) { + sqlVdbeMemSetNull(pMem); + pMem->z = 0; + pMem->szMalloc = 0; + return -1; + } else { + pMem->szMalloc = sqlDbMallocSize(pMem->db, + pMem->zMalloc); + } + } + + if (bPreserve && pMem->z && pMem->z != pMem->zMalloc) { + memcpy(pMem->zMalloc, pMem->z, pMem->n); + } + if ((pMem->flags & MEM_Dyn) != 0) { + assert(pMem->xDel != 0 && pMem->xDel != SQL_DYNAMIC); + pMem->xDel((void *)(pMem->z)); + } + + pMem->z = pMem->zMalloc; + pMem->flags &= ~(MEM_Dyn | MEM_Ephem | MEM_Static); + return 0; +} + +/* + * Change the pMem->zMalloc allocation to be at least szNew bytes. + * If pMem->zMalloc already meets or exceeds the requested size, this + * routine is a no-op. + * + * Any prior string or blob content in the pMem object may be discarded. + * The pMem->xDel destructor is called, if it exists. Though MEM_Str + * and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null + * values are preserved. + * + * Return 0 on success or -1 if unable to complete the resizing. + */ +int +sqlVdbeMemClearAndResize(Mem * pMem, int szNew) +{ + assert(szNew > 0); + assert((pMem->flags & MEM_Dyn) == 0 || pMem->szMalloc == 0); + if (pMem->szMalloc < szNew) { + return sqlVdbeMemGrow(pMem, szNew, 0); + } + assert((pMem->flags & MEM_Dyn) == 0); + pMem->z = pMem->zMalloc; + pMem->flags &= (MEM_Null | MEM_Int | MEM_Real); + return 0; +} + +void +mem_set_bool(struct Mem *mem, bool value) +{ + sqlVdbeMemSetNull(mem); + mem->u.b = value; + mem->flags = MEM_Bool; + mem->field_type = FIELD_TYPE_BOOLEAN; +} + +void +mem_set_ptr(struct Mem *mem, void *ptr) +{ + sqlVdbeMemRelease(mem); + mem->flags = MEM_Ptr; + mem->u.p = ptr; +} + +void +mem_set_i64(struct Mem *mem, int64_t value) +{ + if (VdbeMemDynamic(mem)) + sqlVdbeMemSetNull(mem); + mem->u.i = value; + int flag = value < 0 ? MEM_Int : MEM_UInt; + MemSetTypeFlag(mem, flag); + mem->field_type = FIELD_TYPE_INTEGER; +} + +void +mem_set_u64(struct Mem *mem, uint64_t value) +{ + if (VdbeMemDynamic(mem)) + sqlVdbeMemSetNull(mem); + mem->u.u = value; + MemSetTypeFlag(mem, MEM_UInt); + mem->field_type = FIELD_TYPE_UNSIGNED; +} + +void +mem_set_int(struct Mem *mem, int64_t value, bool is_neg) +{ + if (VdbeMemDynamic(mem)) + sqlVdbeMemSetNull(mem); + if (is_neg) { + assert(value < 0); + mem->u.i = value; + MemSetTypeFlag(mem, MEM_Int); + } else { + mem->u.u = value; + MemSetTypeFlag(mem, MEM_UInt); + } + mem->field_type = FIELD_TYPE_INTEGER; +} + +void +mem_set_double(struct Mem *mem, double value) +{ + sqlVdbeMemSetNull(mem); + if (sqlIsNaN(value)) + return; + mem->u.r = value; + MemSetTypeFlag(mem, MEM_Real); + mem->field_type = FIELD_TYPE_DOUBLE; +} + +/* + * Change the value of a Mem to be a string or a BLOB. + * + * The memory management strategy depends on the value of the xDel + * parameter. If the value passed is SQL_TRANSIENT, then the + * string is copied into a (possibly existing) buffer managed by the + * Mem structure. Otherwise, any existing buffer is freed and the + * pointer copied. + * + * If the string is too large (if it exceeds the SQL_LIMIT_LENGTH + * size limit) then no memory allocation occurs. If the string can be + * stored without allocating memory, then it is. If a memory allocation + * is required to store the string, then value of pMem is unchanged. In + * either case, error is returned. + */ +int +sqlVdbeMemSetStr(Mem * pMem, /* Memory cell to set to string value */ + const char *z, /* String pointer */ + int n, /* Bytes in string, or negative */ + u8 not_blob, /* Encoding of z. 0 for BLOBs */ + void (*xDel) (void *) /* Destructor function */ + ) +{ + int nByte = n; /* New value for pMem->n */ + int iLimit; /* Maximum allowed string or blob size */ + u16 flags = 0; /* New value for pMem->flags */ + + /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ + if (!z) { + sqlVdbeMemSetNull(pMem); + return 0; + } + + if (pMem->db) { + iLimit = pMem->db->aLimit[SQL_LIMIT_LENGTH]; + } else { + iLimit = SQL_MAX_LENGTH; + } + flags = (not_blob == 0 ? MEM_Blob : MEM_Str); + if (nByte < 0) { + assert(not_blob != 0); + nByte = sqlStrlen30(z); + if (nByte > iLimit) + nByte = iLimit + 1; + flags |= MEM_Term; + } + + /* The following block sets the new values of Mem.z and Mem.xDel. It + * also sets a flag in local variable "flags" to indicate the memory + * management (one of MEM_Dyn or MEM_Static). + */ + if (xDel == SQL_TRANSIENT) { + int nAlloc = nByte; + if (flags & MEM_Term) { + nAlloc += 1; //SQL_UTF8 + } + if (nByte > iLimit) { + diag_set(ClientError, ER_SQL_EXECUTE, "string or binary"\ + "string is too big"); + return -1; + } + testcase(nAlloc == 0); + testcase(nAlloc == 31); + testcase(nAlloc == 32); + if (sqlVdbeMemClearAndResize(pMem, MAX(nAlloc, 32))) { + return -1; + } + memcpy(pMem->z, z, nAlloc); + } else if (xDel == SQL_DYNAMIC) { + sqlVdbeMemRelease(pMem); + pMem->zMalloc = pMem->z = (char *)z; + pMem->szMalloc = sqlDbMallocSize(pMem->db, pMem->zMalloc); + } else { + sqlVdbeMemRelease(pMem); + pMem->z = (char *)z; + pMem->xDel = xDel; + flags |= ((xDel == SQL_STATIC) ? MEM_Static : MEM_Dyn); + } + + pMem->n = nByte; + pMem->flags = flags; + assert((pMem->flags & (MEM_Str | MEM_Blob)) != 0); + if ((pMem->flags & MEM_Str) != 0) + pMem->field_type = FIELD_TYPE_STRING; + else + pMem->field_type = FIELD_TYPE_VARBINARY; + + if (nByte > iLimit) { + diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\ + "is too big"); + return -1; + } + + return 0; +} + +/* + * Initialize bulk memory to be a consistent Mem object. + * + * The minimum amount of initialization feasible is performed. + */ +void +sqlVdbeMemInit(Mem * pMem, sql * db, u32 flags) +{ + assert((flags & ~MEM_TypeMask) == 0); + pMem->flags = flags; + pMem->db = db; + pMem->szMalloc = 0; + pMem->field_type = field_type_MAX; +} + +/* + * Delete any previous value and set the value stored in *pMem to NULL. + * + * This routine calls the Mem.xDel destructor to dispose of values that + * require the destructor. But it preserves the Mem.zMalloc memory allocation. + * To free all resources, use sqlVdbeMemRelease(), which both calls this + * routine to invoke the destructor and deallocates Mem.zMalloc. + * + * Use this routine to reset the Mem prior to insert a new value. + * + * Use sqlVdbeMemRelease() to complete erase the Mem prior to abandoning it. + */ +void +sqlVdbeMemSetNull(Mem * pMem) +{ + if (VdbeMemDynamic(pMem)) { + vdbeMemClearExternAndSetNull(pMem); + } else { + pMem->flags = MEM_Null; + } +} + +/* + * Delete any previous value and set the value to be a BLOB of length + * n containing all zeros. + */ +void +sqlVdbeMemSetZeroBlob(Mem * pMem, int n) +{ + sqlVdbeMemRelease(pMem); + pMem->flags = MEM_Blob | MEM_Zero; + pMem->n = 0; + if (n < 0) + n = 0; + pMem->u.nZero = n; + pMem->z = 0; +} + +/* + * Change the string value of an sql_value object + */ +void +sqlValueSetStr(sql_value * v, /* Value to be set */ + int n, /* Length of string z */ + const void *z, /* Text of the new string */ + void (*xDel) (void *) /* Destructor for the string */ + ) +{ + if (v) + sqlVdbeMemSetStr((Mem *) v, z, n, 1, xDel); +} + +void +sqlValueSetNull(sql_value * p) +{ + sqlVdbeMemSetNull((Mem *) p); +} + +/* + * Free an sql_value object + */ +void +sqlValueFree(sql_value * v) +{ + if (!v) + return; + sqlVdbeMemRelease((Mem *) v); + sqlDbFree(((Mem *) v)->db, v); +} + +/* + * Create a new sql_value object. + */ +sql_value * +sqlValueNew(sql * db) +{ + Mem *p = sqlDbMallocZero(db, sizeof(*p)); + if (p) { + p->flags = MEM_Null; + p->db = db; + } + return p; +} + +void +initMemArray(Mem * p, int N, sql * db, u32 flags) +{ + while ((N--) > 0) { + p->db = db; + p->flags = flags; + p->szMalloc = 0; + p->field_type = field_type_MAX; +#ifdef SQL_DEBUG + p->pScopyFrom = 0; +#endif + p++; + } +} + +void +releaseMemArray(Mem * p, int N) +{ + if (p && N) { + Mem *pEnd = &p[N]; + sql *db = p->db; + do { + assert((&p[1]) == pEnd || p[0].db == p[1].db); + assert(sqlVdbeCheckMemInvariants(p)); + + /* This block is really an inlined version of sqlVdbeMemRelease() + * that takes advantage of the fact that the memory cell value is + * being set to NULL after releasing any dynamic resources. + * + * The justification for duplicating code is that according to + * callgrind, this causes a certain test case to hit the CPU 4.7 + * percent less (x86 linux, gcc version 4.1.2, -O6) than if + * sqlMemRelease() were called from here. With -O2, this jumps + * to 6.6 percent. The test case is inserting 1000 rows into a table + * with no indexes using a single prepared INSERT statement, bind() + * and reset(). Inserts are grouped into a transaction. + */ + testcase(p->flags & MEM_Agg); + testcase(p->flags & MEM_Dyn); + testcase(p->flags & MEM_Frame); + if (p-> + flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) { + sqlVdbeMemRelease(p); + } else if (p->szMalloc) { + sqlDbFree(db, p->zMalloc); + p->szMalloc = 0; + } + + p->flags = MEM_Undefined; + } while ((++p) < pEnd); + } +} + +int +mem_value_bool(const struct Mem *mem, bool *b) +{ + if ((mem->flags & MEM_Bool) != 0) { + *b = mem->u.b; + return 0; + } + return -1; +} + +/* + * Return some kind of integer value which is the best we can do + * at representing the value that *pMem describes as an integer. + * If pMem is an integer, then the value is exact. If pMem is + * a floating-point then the value returned is the integer part. + * If pMem is a string or blob, then we make an attempt to convert + * it into an integer and return that. If pMem represents an + * an SQL-NULL value, return 0. + * + * If pMem represents a string value, its encoding might be changed. + */ +int +sqlVdbeIntValue(Mem * pMem, int64_t *i, bool *is_neg) +{ + int flags; + assert(EIGHT_BYTE_ALIGNMENT(pMem)); + flags = pMem->flags; + if (flags & MEM_Int) { + *i = pMem->u.i; + *is_neg = true; + return 0; + } else if (flags & MEM_UInt) { + *i = pMem->u.u; + *is_neg = false; + return 0; + } else if (flags & MEM_Real) { + *is_neg = pMem->u.r < 0; + return doubleToInt64(pMem->u.r, i); + } else if (flags & (MEM_Str)) { + assert(pMem->z || pMem->n == 0); + if (sql_atoi64(pMem->z, i, is_neg, pMem->n) == 0) + return 0; + } + return -1; +} + +/* + * Return the best representation of pMem that we can get into a + * double. If pMem is already a double or an integer, return its + * value. If it is a string or blob, try to convert it to a double. + * If it is a NULL, return 0.0. + */ +int +sqlVdbeRealValue(Mem * pMem, double *v) +{ + assert(EIGHT_BYTE_ALIGNMENT(pMem)); + if (pMem->flags & MEM_Real) { + *v = pMem->u.r; + return 0; + } else if (pMem->flags & MEM_Int) { + *v = (double)pMem->u.i; + return 0; + } else if ((pMem->flags & MEM_UInt) != 0) { + *v = (double)pMem->u.u; + return 0; + } else if (pMem->flags & MEM_Str) { + if (sqlAtoF(pMem->z, v, pMem->n)) + return 0; + } + return -1; +} + +/**************************** sql_value_ ****************************** + * The following routines extract information from a Mem or sql_value + * structure. + */ +const void * +sql_value_blob(sql_value * pVal) +{ + Mem *p = (Mem *) pVal; + if (p->flags & (MEM_Blob | MEM_Str)) { + if (ExpandBlob(p) != 0) { + assert(p->flags == MEM_Null && p->z == 0); + return 0; + } + p->flags |= MEM_Blob; + return p->n ? p->z : 0; + } else { + return sql_value_text(pVal); + } +} + +int +sql_value_bytes(sql_value * pVal) +{ + return sqlValueBytes(pVal); +} + +double +sql_value_double(sql_value * pVal) +{ + double v = 0.0; + sqlVdbeRealValue((Mem *) pVal, &v); + return v; +} + +bool +sql_value_boolean(sql_value *val) +{ + bool b = false; + int rc = mem_value_bool((struct Mem *) val, &b); + assert(rc == 0); + (void) rc; + return b; +} + +int +sql_value_int(sql_value * pVal) +{ + int64_t i = 0; + bool is_neg; + sqlVdbeIntValue((Mem *) pVal, &i, &is_neg); + return (int)i; +} + +sql_int64 +sql_value_int64(sql_value * pVal) +{ + int64_t i = 0; + bool unused; + sqlVdbeIntValue((Mem *) pVal, &i, &unused); + return i; +} + +uint64_t +sql_value_uint64(sql_value *val) +{ + int64_t i = 0; + bool is_neg; + sqlVdbeIntValue((struct Mem *) val, &i, &is_neg); + assert(!is_neg); + return i; +} + +const unsigned char * +sql_value_text(sql_value * pVal) +{ + return (const unsigned char *)sqlValueText(pVal); +} + +/* This function is only available internally, it is not part of the + * external API. It works in a similar way to sql_value_text(), + * except the data returned is in the encoding specified by the second + * parameter, which must be one of SQL_UTF16BE, SQL_UTF16LE or + * SQL_UTF8. + * + * (2006-02-16:) The enc value can be or-ed with SQL_UTF16_ALIGNED. + * If that is the case, then the result must be aligned on an even byte + * boundary. + */ +const void * +sqlValueText(sql_value * pVal) +{ + if (!pVal) + return 0; + if ((pVal->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term)) { + return pVal->z; + } + if (pVal->flags & MEM_Null) { + return 0; + } + return valueToText(pVal); +} + +const char * +sql_value_to_diag_str(sql_value *value) +{ + enum mp_type mp_type = sql_value_type(value); + if (mp_type_is_bloblike(mp_type)) { + if (mem_has_msgpack_subtype(value)) + return sqlValueText(value); + return "varbinary"; + } + return sqlValueText(value); +} + +enum sql_subtype +sql_value_subtype(sql_value * pVal) +{ + return (pVal->flags & MEM_Subtype) != 0 ? pVal->subtype : SQL_SUBTYPE_NO; +} + +/* + * Return a pointer to static memory containing an SQL NULL value. + */ +const Mem * +columnNullValue(void) +{ + /* Even though the Mem structure contains an element + * of type i64, on certain architectures (x86) with certain compiler + * switches (-Os), gcc may align this Mem object on a 4-byte boundary + * instead of an 8-byte one. This all works fine, except that when + * running with SQL_DEBUG defined the sql code sometimes assert()s + * that a Mem structure is located on an 8-byte boundary. To prevent + * these assert()s from failing, when building with SQL_DEBUG defined + * using gcc, we force nullMem to be 8-byte aligned using the magical + * __attribute__((aligned(8))) macro. + */ + static const Mem nullMem +#if defined(SQL_DEBUG) && defined(__GNUC__) + __attribute__ ((aligned(8))) +#endif + = { + /* .u = */ { + 0}, + /* .flags = */ (u16) MEM_Null, + /* .eSubtype = */ (u8) 0, + /* .field_type = */ field_type_MAX, + /* .n = */ (int)0, + /* .z = */ (char *)0, + /* .zMalloc = */ (char *)0, + /* .szMalloc = */ (int)0, + /* .uTemp = */ (u32) 0, + /* .db = */ (sql *) 0, + /* .xDel = */ (void (*)(void *))0, +#ifdef SQL_DEBUG + /* .pScopyFrom = */ (Mem *) 0, + /* .pFiller = */ (void *)0, +#endif + }; + return &nullMem; +} + +/* + * Return true if the Mem object contains a TEXT or BLOB that is + * too large - whose size exceeds SQL_MAX_LENGTH. + */ +int +sqlVdbeMemTooBig(Mem * p) +{ + assert(p->db != 0); + if (p->flags & (MEM_Str | MEM_Blob)) { + int n = p->n; + if (p->flags & MEM_Zero) { + n += p->u.nZero; + } + return n > p->db->aLimit[SQL_LIMIT_LENGTH]; + } + return 0; +} + +/* + * Compare two blobs. Return negative, zero, or positive if the first + * is less than, equal to, or greater than the second, respectively. + * If one blob is a prefix of the other, then the shorter is the lessor. + */ +static SQL_NOINLINE int +sqlBlobCompare(const Mem * pB1, const Mem * pB2) +{ + int c; + int n1 = pB1->n; + int n2 = pB2->n; + + /* 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 + * sqlMemCompare(). + */ + assert((pB1->flags & MEM_Zero) == 0 || n1 == 0); + assert((pB2->flags & MEM_Zero) == 0 || n2 == 0); + + if ((pB1->flags | pB2->flags) & MEM_Zero) { + if (pB1->flags & pB2->flags & MEM_Zero) { + return pB1->u.nZero - pB2->u.nZero; + } else if (pB1->flags & MEM_Zero) { + if (!isAllZero(pB2->z, pB2->n)) + return -1; + return pB1->u.nZero - n2; + } else { + if (!isAllZero(pB1->z, pB1->n)) + return +1; + return n1 - pB2->u.nZero; + } + } + c = memcmp(pB1->z, pB2->z, n1 > n2 ? n2 : n1); + if (c) + return c; + return n1 - n2; +} + +/* + * Compare the values contained by the two memory cells, returning + * negative, zero or positive if pMem1 is less than, equal to, or greater + * than pMem2. Sorting order is NULL's first, followed by numbers (integers + * and reals) sorted numerically, followed by text ordered by the collating + * sequence pColl and finally blob's ordered by memcmp(). + * + * Two NULL values are considered equal by this function. + */ +int +sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl) +{ + int f1, f2; + int combined_flags; + + f1 = pMem1->flags; + f2 = pMem2->flags; + combined_flags = f1 | f2; + + /* If one value is NULL, it is less than the other. If both values + * are NULL, return 0. + */ + if (combined_flags & MEM_Null) { + return (f2 & MEM_Null) - (f1 & MEM_Null); + } + + if ((combined_flags & MEM_Bool) != 0) { + if ((f1 & f2 & MEM_Bool) != 0) { + if (pMem1->u.b == pMem2->u.b) + return 0; + if (pMem1->u.b) + return 1; + return -1; + } + if ((f2 & MEM_Bool) != 0) + return +1; + return -1; + } + + /* At least one of the two values is a number + */ + if ((combined_flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) { + if ((f1 & f2 & MEM_Int) != 0) { + if (pMem1->u.i < pMem2->u.i) + return -1; + if (pMem1->u.i > pMem2->u.i) + return +1; + return 0; + } + if ((f1 & f2 & MEM_UInt) != 0) { + if (pMem1->u.u < pMem2->u.u) + return -1; + if (pMem1->u.u > pMem2->u.u) + return +1; + return 0; + } + if ((f1 & f2 & MEM_Real) != 0) { + if (pMem1->u.r < pMem2->u.r) + return -1; + if (pMem1->u.r > pMem2->u.r) + return +1; + return 0; + } + if ((f1 & MEM_Int) != 0) { + if ((f2 & MEM_Real) != 0) { + return double_compare_nint64(pMem2->u.r, + pMem1->u.i, -1); + } else { + return -1; + } + } + if ((f1 & MEM_UInt) != 0) { + if ((f2 & MEM_Real) != 0) { + return double_compare_uint64(pMem2->u.r, + pMem1->u.u, -1); + } else if ((f2 & MEM_Int) != 0) { + return +1; + } else { + return -1; + } + } + if ((f1 & MEM_Real) != 0) { + if ((f2 & MEM_Int) != 0) { + return double_compare_nint64(pMem1->u.r, + pMem2->u.i, 1); + } else if ((f2 & MEM_UInt) != 0) { + return double_compare_uint64(pMem1->u.r, + pMem2->u.u, 1); + } else { + return -1; + } + } + return +1; + } + + /* If one value is a string and the other is a blob, the string is less. + * If both are strings, compare using the collating functions. + */ + if (combined_flags & MEM_Str) { + if ((f1 & MEM_Str) == 0) { + return 1; + } + if ((f2 & MEM_Str) == 0) { + return -1; + } + /* The collation sequence must be defined at this point, even if + * the user deletes the collation sequence after the vdbe program is + * compiled (this was not always the case). + */ + if (pColl) { + return vdbeCompareMemString(pMem1, pMem2, pColl); + } else { + size_t n = pMem1->n < pMem2->n ? pMem1->n : pMem2->n; + int res; + res = memcmp(pMem1->z, pMem2->z, n); + if (res == 0) + res = (int)pMem1->n - (int)pMem2->n; + return res; + } + /* If a NULL pointer was passed as the collate function, fall through + * to the blob case and use memcmp(). + */ + } + + /* Both values must be blobs. Compare using memcmp(). */ + return sqlBlobCompare(pMem1, pMem2); +} + +bool +mem_is_type_compatible(struct Mem *mem, enum field_type type) +{ + enum mp_type mp_type = mem_mp_type(mem); + assert(mp_type < MP_EXT); + return field_mp_plain_type_is_compatible(type, mp_type, true); +} + +/* Allocate memory for internal VDBE structure on region. */ +int +vdbe_mem_alloc_blob_region(struct Mem *vdbe_mem, uint32_t size) +{ + vdbe_mem->n = size; + vdbe_mem->z = region_alloc(&fiber()->gc, size); + if (vdbe_mem->z == NULL) + return -1; + vdbe_mem->flags = MEM_Ephem | MEM_Blob; + assert(sqlVdbeCheckMemInvariants(vdbe_mem)); + return 0; +} + +/* + * Make a full copy of pFrom into pTo. Prior contents of pTo are + * freed before the copy is made. + */ +int +sqlVdbeMemCopy(Mem * pTo, const Mem * pFrom) +{ + int rc = 0; + + if (VdbeMemDynamic(pTo)) + vdbeMemClearExternAndSetNull(pTo); + memcpy(pTo, pFrom, MEMCELLSIZE); + pTo->flags &= ~MEM_Dyn; + if (pTo->flags & (MEM_Str | MEM_Blob)) { + if (0 == (pFrom->flags & MEM_Static)) { + pTo->flags |= MEM_Ephem; + rc = sqlVdbeMemMakeWriteable(pTo); + } + } + + return rc; +} + +void +sqlVdbeMemShallowCopy(Mem * pTo, const Mem * pFrom, int srcType) +{ + assert(pTo->db == pFrom->db); + if (VdbeMemDynamic(pTo)) { + vdbeClrCopy(pTo, pFrom, srcType); + return; + } + memcpy(pTo, pFrom, MEMCELLSIZE); + if ((pFrom->flags & MEM_Static) == 0) { + pTo->flags &= ~(MEM_Dyn | MEM_Static | MEM_Ephem); + assert(srcType == MEM_Ephem || srcType == MEM_Static); + pTo->flags |= srcType; + } +} + +/* + * Transfer the contents of pFrom to pTo. Any existing value in pTo is + * freed. If pFrom contains ephemeral data, a copy is made. + * + * pFrom contains an SQL NULL when this routine returns. + */ +void +sqlVdbeMemMove(Mem * pTo, Mem * pFrom) +{ + assert(pFrom->db == 0 || pTo->db == 0 || pFrom->db == pTo->db); + + sqlVdbeMemRelease(pTo); + memcpy(pTo, pFrom, sizeof(Mem)); + pFrom->flags = MEM_Null; + pFrom->szMalloc = 0; +} + +/* + * Change pMem so that its MEM_Str or MEM_Blob value is stored in + * MEM.zMalloc, where it can be safely written. + * + * Return 0 on success or -1 if malloc fails. + */ +int +sqlVdbeMemMakeWriteable(Mem * pMem) +{ + if ((pMem->flags & (MEM_Str | MEM_Blob)) != 0) { + if (ExpandBlob(pMem)) + return -1; + if (pMem->szMalloc == 0 || pMem->z != pMem->zMalloc) { + if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) { + return -1; + } + pMem->z[pMem->n] = 0; + pMem->z[pMem->n + 1] = 0; + pMem->flags |= MEM_Term; + } + } + pMem->flags &= ~MEM_Ephem; +#ifdef SQL_DEBUG + pMem->pScopyFrom = 0; +#endif + + return 0; +} + +/* + * Release any memory resources held by the Mem. Both the memory that is + * free by Mem.xDel and the Mem.zMalloc allocation are freed. + * + * Use this routine prior to clean up prior to abandoning a Mem, or to + * reset a Mem back to its minimum memory utilization. + * + * Use sqlVdbeMemSetNull() to release just the Mem.xDel space + * prior to inserting new content into the Mem. + */ +void +sqlVdbeMemRelease(Mem * p) +{ + assert(sqlVdbeCheckMemInvariants(p)); + if (VdbeMemDynamic(p) || p->szMalloc) { + vdbeMemClear(p); + } +} + +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->flags & MEM_Null) != 0 || func == mem->u.func); + sql_context ctx; + memset(&ctx, 0, sizeof(ctx)); + Mem t; + memset(&t, 0, sizeof(t)); + t.flags = MEM_Null; + t.db = mem->db; + t.field_type = field_type_MAX; + 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 +sqlVdbeCompareMsgpack(const char **key1, + struct UnpackedRecord *unpacked, int key2_idx) +{ + const char *aKey1 = *key1; + Mem *pKey2 = unpacked->aMem + key2_idx; + Mem mem1; + int rc = 0; + switch (mp_typeof(*aKey1)) { + default:{ + /* FIXME */ + rc = -1; + break; + } + case MP_NIL:{ + rc = -((pKey2->flags & MEM_Null) == 0); + mp_decode_nil(&aKey1); + break; + } + case MP_BOOL:{ + mem1.u.b = mp_decode_bool(&aKey1); + if ((pKey2->flags & MEM_Bool) != 0) { + if (mem1.u.b != pKey2->u.b) + rc = mem1.u.b ? 1 : -1; + } else { + rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1; + } + break; + } + case MP_UINT:{ + mem1.u.u = mp_decode_uint(&aKey1); + if ((pKey2->flags & MEM_Int) != 0) { + rc = +1; + } else if ((pKey2->flags & MEM_UInt) != 0) { + if (mem1.u.u < pKey2->u.u) + rc = -1; + else if (mem1.u.u > pKey2->u.u) + rc = +1; + } else if ((pKey2->flags & MEM_Real) != 0) { + rc = double_compare_uint64(pKey2->u.r, + mem1.u.u, -1); + } else if ((pKey2->flags & MEM_Null) != 0) { + rc = 1; + } else if ((pKey2->flags & MEM_Bool) != 0) { + rc = 1; + } else { + rc = -1; + } + break; + } + case MP_INT:{ + mem1.u.i = mp_decode_int(&aKey1); + if ((pKey2->flags & MEM_UInt) != 0) { + rc = -1; + } else if ((pKey2->flags & MEM_Int) != 0) { + if (mem1.u.i < pKey2->u.i) { + rc = -1; + } else if (mem1.u.i > pKey2->u.i) { + rc = +1; + } + } else if (pKey2->flags & MEM_Real) { + rc = double_compare_nint64(pKey2->u.r, mem1.u.i, + -1); + } else if ((pKey2->flags & MEM_Null) != 0) { + rc = 1; + } else if ((pKey2->flags & MEM_Bool) != 0) { + rc = 1; + } else { + rc = -1; + } + break; + } + case MP_FLOAT:{ + mem1.u.r = mp_decode_float(&aKey1); + goto do_float; + } + case MP_DOUBLE:{ + mem1.u.r = mp_decode_double(&aKey1); + do_float: + if ((pKey2->flags & MEM_Int) != 0) { + rc = double_compare_nint64(mem1.u.r, pKey2->u.i, + 1); + } else if (pKey2->flags & MEM_UInt) { + rc = double_compare_uint64(mem1.u.r, + pKey2->u.u, 1); + } else if (pKey2->flags & MEM_Real) { + if (mem1.u.r < pKey2->u.r) { + rc = -1; + } else if (mem1.u.r > pKey2->u.r) { + rc = +1; + } + } else if ((pKey2->flags & MEM_Null) != 0) { + rc = 1; + } else if ((pKey2->flags & MEM_Bool) != 0) { + rc = 1; + } else { + rc = -1; + } + break; + } + case MP_STR:{ + if (pKey2->flags & MEM_Str) { + struct key_def *key_def = unpacked->key_def; + mem1.n = mp_decode_strl(&aKey1); + mem1.z = (char *)aKey1; + aKey1 += mem1.n; + struct coll *coll = + key_def->parts[key2_idx].coll; + if (coll != NULL) { + mem1.flags = MEM_Str; + rc = vdbeCompareMemString(&mem1, pKey2, + coll); + } else { + goto do_bin_cmp; + } + } else { + rc = (pKey2->flags & MEM_Blob) ? -1 : +1; + } + break; + } + case MP_BIN:{ + mem1.n = mp_decode_binl(&aKey1); + mem1.z = (char *)aKey1; + aKey1 += mem1.n; + do_blob: + if (pKey2->flags & MEM_Blob) { + if (pKey2->flags & MEM_Zero) { + if (!isAllZero + ((const char *)mem1.z, mem1.n)) { + rc = 1; + } else { + rc = mem1.n - pKey2->u.nZero; + } + } else { + int nCmp; + do_bin_cmp: + nCmp = MIN(mem1.n, pKey2->n); + rc = memcmp(mem1.z, pKey2->z, nCmp); + if (rc == 0) + rc = mem1.n - pKey2->n; + } + } else { + rc = 1; + } + break; + } + case MP_ARRAY: + case MP_MAP: + case MP_EXT:{ + mem1.z = (char *)aKey1; + mp_next(&aKey1); + mem1.n = aKey1 - (char *)mem1.z; + goto do_blob; + } + } + *key1 = aKey1; + return rc; +} + +int +sqlVdbeRecordCompareMsgpack(const void *key1, + struct UnpackedRecord *key2) +{ + int rc = 0; + u32 i, n = mp_decode_array((const char**)&key1); + + n = MIN(n, key2->nField); + + for (i = 0; i != n; i++) { + rc = sqlVdbeCompareMsgpack((const char**)&key1, key2, i); + if (rc != 0) { + if (key2->key_def->parts[i].sort_order != + SORT_ORDER_ASC) { + rc = -rc; + } + return rc; + } + } + + key2->eqSeen = 1; + return key2->default_rc; +} + +int +vdbe_decode_msgpack_into_mem(const char *buf, struct Mem *mem, uint32_t *len) +{ + const char *start_buf = buf; + switch (mp_typeof(*buf)) { + case MP_ARRAY: { + mem->z = (char *)buf; + mp_next(&buf); + mem->n = buf - mem->z; + mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype; + mem->subtype = SQL_SUBTYPE_MSGPACK; + mem->field_type = FIELD_TYPE_ARRAY; + break; + } + case MP_MAP: { + mem->z = (char *)buf; + mp_next(&buf); + mem->n = buf - mem->z; + mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype; + mem->subtype = SQL_SUBTYPE_MSGPACK; + mem->field_type = FIELD_TYPE_MAP; + break; + } + case MP_EXT: { + mem->z = (char *)buf; + mp_next(&buf); + mem->n = buf - mem->z; + mem->flags = MEM_Blob | MEM_Ephem; + mem->field_type = FIELD_TYPE_VARBINARY; + break; + } + case MP_NIL: { + mp_decode_nil(&buf); + mem->flags = MEM_Null; + mem->field_type = field_type_MAX; + break; + } + case MP_BOOL: { + mem->u.b = mp_decode_bool(&buf); + mem->flags = MEM_Bool; + mem->field_type = FIELD_TYPE_BOOLEAN; + break; + } + case MP_UINT: { + uint64_t v = mp_decode_uint(&buf); + mem->u.u = v; + mem->flags = MEM_UInt; + mem->field_type = FIELD_TYPE_INTEGER; + break; + } + case MP_INT: { + mem->u.i = mp_decode_int(&buf); + mem->flags = MEM_Int; + mem->field_type = FIELD_TYPE_INTEGER; + break; + } + case MP_STR: { + /* XXX u32->int */ + mem->n = (int) mp_decode_strl(&buf); + mem->flags = MEM_Str | MEM_Ephem; + mem->field_type = FIELD_TYPE_STRING; +install_blob: + mem->z = (char *)buf; + buf += mem->n; + break; + } + case MP_BIN: { + /* XXX u32->int */ + mem->n = (int) mp_decode_binl(&buf); + mem->flags = MEM_Blob | MEM_Ephem; + mem->field_type = FIELD_TYPE_VARBINARY; + goto install_blob; + } + case MP_FLOAT: { + mem->u.r = mp_decode_float(&buf); + if (sqlIsNaN(mem->u.r)) { + mem->flags = MEM_Null; + mem->field_type = FIELD_TYPE_DOUBLE; + } else { + mem->flags = MEM_Real; + mem->field_type = FIELD_TYPE_DOUBLE; + } + break; + } + case MP_DOUBLE: { + mem->u.r = mp_decode_double(&buf); + if (sqlIsNaN(mem->u.r)) { + mem->flags = MEM_Null; + mem->field_type = FIELD_TYPE_DOUBLE; + } else { + mem->flags = MEM_Real; + mem->field_type = FIELD_TYPE_DOUBLE; + } + break; + } + default: + unreachable(); + } + *len = (uint32_t)(buf - start_buf); + return 0; +} + +void +mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var) +{ + assert(memIsValid(var)); + int64_t i; + if (var->flags & MEM_Null) { + mpstream_encode_nil(stream); + } else if (var->flags & MEM_Real) { + mpstream_encode_double(stream, var->u.r); + } else if (var->flags & MEM_Int) { + i = var->u.i; + mpstream_encode_int(stream, i); + } else if (var->flags & MEM_UInt) { + i = var->u.u; + mpstream_encode_uint(stream, i); + } else if (var->flags & MEM_Str) { + mpstream_encode_strn(stream, var->z, var->n); + } else if (var->flags & MEM_Bool) { + mpstream_encode_bool(stream, var->u.b); + } else { + /* + * Emit BIN header iff the BLOB doesn't store + * MsgPack content. + */ + if (!mem_has_msgpack_subtype(var)) { + uint32_t binl = var->n + + ((var->flags & MEM_Zero) ? + var->u.nZero : 0); + mpstream_encode_binl(stream, binl); + } + mpstream_memcpy(stream, var->z, var->n); + if (var->flags & MEM_Zero) + mpstream_memset(stream, 0, var->u.nZero); + } +} + +char * +sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count, + uint32_t *tuple_size, struct region *region) +{ + size_t used = region_used(region); + bool is_error = false; + struct mpstream stream; + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, + set_encode_error, &is_error); + mpstream_encode_array(&stream, field_count); + for (struct Mem *field = fields; field < fields + field_count; field++) + mpstream_encode_vdbe_mem(&stream, field); + mpstream_flush(&stream); + if (is_error) { + diag_set(OutOfMemory, stream.pos - stream.buf, + "mpstream_flush", "stream"); + return NULL; + } + *tuple_size = region_used(region) - used; + char *tuple = region_join(region, *tuple_size); + if (tuple == NULL) { + diag_set(OutOfMemory, *tuple_size, "region_join", "tuple"); + return NULL; + } + mp_tuple_assert(tuple, tuple + *tuple_size); + return tuple; +} + +/** + * Allocate a sequence of initialized vdbe memory registers + * on region. + */ +static struct Mem * +vdbemem_alloc_on_region(uint32_t count) +{ + struct region *region = &fiber()->gc; + size_t size; + struct Mem *ret = region_alloc_array(region, typeof(*ret), count, + &size); + if (ret == NULL) { + diag_set(OutOfMemory, size, "region_alloc_array", "ret"); + return NULL; + } + memset(ret, 0, count * sizeof(*ret)); + for (uint32_t i = 0; i < count; i++) { + sqlVdbeMemInit(&ret[i], sql_get(), MEM_Null); + assert(memIsValid(&ret[i])); + } + return ret; +} + +static void +port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat) +{ + (void) is_flat; + struct port_vdbemem *port = (struct port_vdbemem *) base; + assert(is_flat == true); + for (uint32_t i = 0; i < port->mem_count; i++) { + sql_value *param = + (sql_value *)((struct Mem *)port->mem + i); + switch (sql_value_type(param)) { + case MP_INT: + luaL_pushint64(L, sql_value_int64(param)); + break; + case MP_UINT: + luaL_pushuint64(L, sql_value_uint64(param)); + break; + case MP_DOUBLE: + lua_pushnumber(L, sql_value_double(param)); + break; + case MP_STR: + lua_pushlstring(L, (const char *)sql_value_text(param), + (size_t)sql_value_bytes(param)); + break; + case MP_BIN: + case MP_ARRAY: + case MP_MAP: + lua_pushlstring(L, sql_value_blob(param), + (size_t) sql_value_bytes(param)); + break; + case MP_NIL: + lua_pushnil(L); + break; + case MP_BOOL: + lua_pushboolean(L, sql_value_boolean(param)); + break; + default: + unreachable(); + } + } +} + +static const char * +port_vdbemem_get_msgpack(struct port *base, uint32_t *size) +{ + struct port_vdbemem *port = (struct port_vdbemem *) base; + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + bool is_error = false; + struct mpstream stream; + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, + set_encode_error, &is_error); + mpstream_encode_array(&stream, port->mem_count); + for (uint32_t i = 0; i < port->mem_count && !is_error; i++) { + sql_value *param = + (sql_value *)((struct Mem *)port->mem + i); + switch (sql_value_type(param)) { + case MP_INT: { + sql_int64 val = sql_value_int64(param); + if (val < 0) { + mpstream_encode_int(&stream, val); + break; + } + FALLTHROUGH; + } + case MP_UINT: { + sql_uint64 val = sql_value_uint64(param); + mpstream_encode_uint(&stream, val); + break; + } + case MP_DOUBLE: { + mpstream_encode_double(&stream, + sql_value_double(param)); + break; + } + case MP_STR: { + const char *str = (const char *) sql_value_text(param); + mpstream_encode_strn(&stream, str, + sql_value_bytes(param)); + break; + } + case MP_BIN: + case MP_ARRAY: + case MP_MAP: { + mpstream_encode_binl(&stream, sql_value_bytes(param)); + mpstream_memcpy(&stream, sql_value_blob(param), + sql_value_bytes(param)); + break; + } + case MP_NIL: { + mpstream_encode_nil(&stream); + break; + } + case MP_BOOL: { + mpstream_encode_bool(&stream, sql_value_boolean(param)); + break; + } + default: + unreachable(); + } + } + mpstream_flush(&stream); + *size = region_used(region) - region_svp; + if (is_error) + goto error; + const char *ret = (char *)region_join(region, *size); + if (ret == NULL) + goto error; + return ret; +error: + diag_set(OutOfMemory, *size, "region", "ret"); + return NULL; +} + +static const struct port_vtab port_vdbemem_vtab; + +void +port_vdbemem_create(struct port *base, struct sql_value *mem, + uint32_t mem_count) +{ + struct port_vdbemem *port = (struct port_vdbemem *) base; + port->vtab = &port_vdbemem_vtab; + port->mem = mem; + port->mem_count = mem_count; +} + +static struct sql_value * +port_vdbemem_get_vdbemem(struct port *base, uint32_t *mem_count) +{ + struct port_vdbemem *port = (struct port_vdbemem *) base; + assert(port->vtab == &port_vdbemem_vtab); + *mem_count = port->mem_count; + return port->mem; +} + +static const struct port_vtab port_vdbemem_vtab = { + .dump_msgpack = NULL, + .dump_msgpack_16 = NULL, + .dump_lua = port_vdbemem_dump_lua, + .dump_plain = NULL, + .get_msgpack = port_vdbemem_get_msgpack, + .get_vdbemem = port_vdbemem_get_vdbemem, + .destroy = NULL, +}; + +struct sql_value * +port_lua_get_vdbemem(struct port *base, uint32_t *size) +{ + struct port_lua *port = (struct port_lua *) base; + struct lua_State *L = port->L; + int argc = lua_gettop(L); + if (argc == 0 || argc > 1) { + diag_set(ClientError, ER_SQL_FUNC_WRONG_RET_COUNT, "Lua", argc); + return NULL; + } + *size = argc; + /** FIXME: Implement an ability to return a vector. */ + assert(*size == 1); + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + struct Mem *val = vdbemem_alloc_on_region(argc); + if (val == NULL) + return NULL; + for (int i = 0; i < argc; i++) { + struct luaL_field field; + if (luaL_tofield(L, luaL_msgpack_default, + NULL, -1 - i, &field) < 0) { + goto error; + } + switch (field.type) { + case MP_BOOL: + mem_set_bool(&val[i], field.bval); + break; + case MP_FLOAT: + mem_set_double(&val[i], field.fval); + break; + case MP_DOUBLE: + mem_set_double(&val[i], field.dval); + break; + case MP_INT: + mem_set_i64(&val[i], field.ival); + break; + case MP_UINT: + mem_set_u64(&val[i], field.ival); + break; + case MP_STR: + if (sqlVdbeMemSetStr(&val[i], field.sval.data, + field.sval.len, 1, + SQL_TRANSIENT) != 0) + goto error; + break; + case MP_NIL: + sqlVdbeMemSetNull(&val[i]); + break; + default: + diag_set(ClientError, ER_SQL_EXECUTE, + "Unsupported type passed from Lua"); + goto error; + } + } + return (struct sql_value *)val; +error: + for (int i = 0; i < argc; i++) + sqlVdbeMemRelease(&val[i]); + region_truncate(region, region_svp); + return NULL; +} + +struct sql_value * +port_c_get_vdbemem(struct port *base, uint32_t *size) +{ + struct port_c *port = (struct port_c *)base; + *size = port->size; + if (*size == 0 || *size > 1) { + diag_set(ClientError, ER_SQL_FUNC_WRONG_RET_COUNT, "C", *size); + return NULL; + } + /** FIXME: Implement an ability to return a vector. */ + assert(*size == 1); + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + struct Mem *val = vdbemem_alloc_on_region(port->size); + if (val == NULL) + return NULL; + int i = 0; + const char *data; + struct port_c_entry *pe; + for (pe = port->first; pe != NULL; pe = pe->next) { + if (pe->mp_size == 0) { + data = tuple_data(pe->tuple); + if (mp_decode_array(&data) != 1) { + diag_set(ClientError, ER_SQL_EXECUTE, + "Unsupported type passed from C"); + goto error; + } + } else { + data = pe->mp; + } + uint32_t len; + const char *str; + switch (mp_typeof(*data)) { + case MP_BOOL: + mem_set_bool(&val[i], mp_decode_bool(&data)); + break; + case MP_FLOAT: + mem_set_double(&val[i], mp_decode_float(&data)); + break; + case MP_DOUBLE: + mem_set_double(&val[i], mp_decode_double(&data)); + break; + case MP_INT: + mem_set_i64(&val[i], mp_decode_int(&data)); + break; + case MP_UINT: + mem_set_u64(&val[i], mp_decode_uint(&data)); + break; + case MP_STR: + str = mp_decode_str(&data, &len); + if (sqlVdbeMemSetStr(&val[i], str, len, + 1, SQL_TRANSIENT) != 0) + goto error; + break; + case MP_NIL: + sqlVdbeMemSetNull(&val[i]); + break; + default: + diag_set(ClientError, ER_SQL_EXECUTE, + "Unsupported type passed from C"); + goto error; + } + i++; + } + return (struct sql_value *) val; +error: + for (int i = 0; i < port->size; i++) + sqlVdbeMemRelease(&val[i]); + region_truncate(region, region_svp); + return NULL; +} diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h new file mode 100644 index 000000000..acc8ce054 --- /dev/null +++ b/src/box/sql/mem.h @@ -0,0 +1,485 @@ +#pragma once +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "box/field_def.h" + +struct sql; +struct Vdbe; +struct region; +struct mpstream; +struct VdbeFrame; + +/* + * Internally, the vdbe manipulates nearly all SQL values as Mem + * structures. Each Mem struct may cache multiple representations (string, + * integer etc.) of the same value. + */ +struct Mem { + union MemValue { + double r; /* Real value used when MEM_Real is set in flags */ + 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. + * Used only when flags==MEM_Agg. + */ + struct func *func; + struct VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ + } u; + u32 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ + /** Subtype for this value. */ + enum sql_subtype subtype; + /** + * If value is fetched from tuple, then this property + * contains type of corresponding space's field. If it's + * value field_type_MAX then we can rely on on format + * (msgpack) type which is represented by 'flags'. + */ + enum field_type field_type; + int n; /* size (in bytes) of string value, excluding trailing '\0' */ + char *z; /* String or BLOB value */ + /* ShallowCopy only needs to copy the information above */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + int szMalloc; /* Size of the zMalloc allocation */ + u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ + sql *db; /* The associated database connection */ + void (*xDel) (void *); /* Destructor for Mem.z - only valid if MEM_Dyn */ +#ifdef SQL_DEBUG + Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ + void *pFiller; /* So that sizeof(Mem) is a multiple of 8 */ +#endif +}; + +/* + * Size of struct Mem not including the Mem.zMalloc member or anything that + * follows. + */ +#define MEMCELLSIZE offsetof(Mem,zMalloc) + +/* One or more of the following flags are set to indicate the validOK + * representations of the value stored in the Mem struct. + * + * If the MEM_Null flag is set, then the value is an SQL NULL value. + * No other flags may be set in this case. + * + * If the MEM_Str flag is set then Mem.z points at a string representation. + * Usually this is encoded in the same unicode encoding as the main + * database (see below for exceptions). If the MEM_Term flag is also + * set, then the string is nul terminated. The MEM_Int and MEM_Real + * flags may coexist with the MEM_Str flag. + */ +#define MEM_Null 0x0001 /* Value is NULL */ +#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_Blob 0x0010 /* Value is a BLOB */ +#define MEM_Bool 0x0020 /* Value is a bool */ +#define MEM_UInt 0x0040 /* Value is an unsigned integer */ +#define MEM_Frame 0x0080 /* Value is a VdbeFrame object */ +#define MEM_Undefined 0x0100 /* Value is undefined */ +#define MEM_Cleared 0x0200 /* NULL set by OP_Null, not from data */ +#define MEM_TypeMask 0x83ff /* Mask of type bits */ + +/* Whenever Mem contains a valid string or blob representation, one of + * the following flags must be set to determine the memory management + * policy for Mem.z. The MEM_Term flag tells us whether or not the + * string is \000 or \u0000 terminated + */ +#define MEM_Term 0x0400 /* String rep is nul terminated */ +#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_Agg 0x4000 /* Mem.z points to an agg function context */ +#define MEM_Zero 0x8000 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x10000 /* Mem.eSubtype is valid */ +#define MEM_Ptr 0x20000 /* Value is a generic pointer */ + +/** + * In contrast to Mem_TypeMask, this one allows to get + * pure type of memory cell, i.e. without _Dyn/_Zero and other + * auxiliary flags. + */ +enum { + MEM_PURE_TYPE_MASK = 0x7f +}; + +static_assert(MEM_PURE_TYPE_MASK == (MEM_Null | MEM_Str | MEM_Int | MEM_Real | + MEM_Blob | MEM_Bool | MEM_UInt), + "value of type mask must consist of corresponding to memory "\ + "type bits"); + +/** + * Simple type to str convertor. It is used to simplify + * error reporting. + */ +char * +mem_type_to_str(const struct Mem *p); + +/* + * Return the MP_type of the value of the MEM. + * Analogue of sql_value_type() but operates directly on + * transparent memory cell. + */ +enum mp_type +mem_mp_type(struct Mem *mem); + +enum mp_type +sql_value_type(struct Mem *); +u16 +numericType(Mem *pMem); + +int sqlValueBytes(struct Mem *); + +#ifdef SQL_DEBUG +int sqlVdbeCheckMemInvariants(struct Mem *); +void sqlVdbeMemPrettyPrint(Mem * pMem, char *zBuf); +void +registerTrace(int iReg, Mem *p); + +/* + * Return true if a memory cell is not marked as invalid. This macro + * is for use inside assert() statements only. + */ +#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 +#endif + +/** + * Try to convert a string value into a numeric representation + * if we can do so without loss of information. Firstly, value + * is attempted to be converted to integer, and in case of fail - + * to floating point number. Note that function is assumed to be + * called on memory cell containing string, i.e. mem->type == MEM_Str. + * + * @param record Memory cell containing value to be converted. + * @retval 0 If value can be converted to integer or number. + * @retval -1 Otherwise. + */ +int +mem_apply_numeric_type(struct Mem *record); +int sqlVdbeMemRealify(struct Mem *); + +/** + * Convert @a mem to NUMBER type, so that after conversion it has + * one of types MEM_Real, MEM_Int or MEM_UInt. If conversion is + * not possible, function returns -1. + * + * Beware - this function changes value and type of @a mem + * argument. + */ +int +vdbe_mem_numerify(struct Mem *mem); + +int sqlVdbeMemCast(struct Mem *, enum field_type type); +int mem_apply_integer_type(struct Mem *); +int sqlVdbeMemStringify(struct Mem *); +int sqlVdbeMemNulTerminate(struct Mem *); +int sqlVdbeMemExpandBlob(struct Mem *); +#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0) +void sql_value_apply_type(struct Mem *val, enum field_type type); + + +/** + * Processing is determined by the field type parameter: + * + * INTEGER: + * If memory holds floating point value and it can be + * converted without loss (2.0 - > 2), it's type is + * changed to INT. Otherwise, simply return success status. + * + * NUMBER: + * If memory holds INT or floating point value, + * no actions take place. + * + * STRING: + * Convert mem to a string representation. + * + * SCALAR: + * Mem is unchanged, but flag is set to BLOB in case of + * scalar-like type. Otherwise, (MAP, ARRAY) conversion + * is impossible. + * + * BOOLEAN: + * If memory holds BOOLEAN no actions take place. + * + * ANY: + * Mem is unchanged, no actions take place. + * + * MAP/ARRAY: + * These types can't be casted to scalar ones, or to each + * other. So the only valid conversion is to type itself. + * + * @param record The value to apply type to. + * @param type The type to be applied. + */ +int +mem_apply_type(struct Mem *record, enum field_type type); + +/** + * Convert the numeric value contained in MEM to another numeric + * type. + * + * @param mem The MEM that contains the numeric value. + * @param type The type to convert to. + * @retval 0 if the conversion was successful, -1 otherwise. + */ +int +mem_convert_to_numeric(struct Mem *mem, enum field_type type); + +/** Setters = Change MEM value. */ + +int sqlVdbeMemGrow(struct Mem * pMem, int n, int preserve); +int sqlVdbeMemClearAndResize(struct Mem * pMem, int n); + +void +mem_set_bool(struct Mem *mem, bool value); + +/** + * Set VDBE memory register with given pointer as a data. + * @param mem VDBE memory register to update. + * @param ptr Pointer to use. + */ +void +mem_set_ptr(struct Mem *mem, void *ptr); + +/** + * Set integer value. Depending on its sign MEM_Int (in case + * of negative value) or MEM_UInt flag is set. + */ +void +mem_set_i64(struct Mem *mem, int64_t value); + +/** Set unsigned value and MEM_UInt flag. */ +void +mem_set_u64(struct Mem *mem, uint64_t value); + +/** + * Set integer value. According to is_neg flag value is considered + * to be signed or unsigned. + */ +void +mem_set_int(struct Mem *mem, int64_t value, bool is_neg); + +/** Set double value and MEM_Real flag. */ +void +mem_set_double(struct Mem *mem, double value); + +int +sqlVdbeMemSetStr(struct Mem *, const char *, int, u8, void (*)(void *)); +void +sqlVdbeMemInit(struct Mem *, sql *, u32); +void +sqlVdbeMemSetNull(struct Mem *); +void +sqlVdbeMemSetZeroBlob(struct Mem *, int); +void sqlValueSetStr(struct Mem *, int, const void *, + void (*)(void *)); +void sqlValueSetNull(struct Mem *); +void sqlValueFree(struct Mem *); +struct Mem *sqlValueNew(struct sql *); + +/* + * Initialize an array of N Mem element. + */ +void +initMemArray(Mem * p, int N, sql * db, u32 flags); + +/* + * Release an array of N Mem elements + */ +void +releaseMemArray(Mem * p, int N); + +/* + * Clear any existing type flags from a Mem and replace them with f + */ +#define MemSetTypeFlag(p, f) \ + ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f) + +/** Getters. */ + +int +mem_value_bool(const struct Mem *mem, bool *b); +int sqlVdbeIntValue(struct Mem *, int64_t *, bool *is_neg); +int sqlVdbeRealValue(struct Mem *, double *); +const void * +sql_value_blob(struct Mem *); + +int +sql_value_bytes(struct Mem *); + +double +sql_value_double(struct Mem *); + +bool +sql_value_boolean(struct Mem *val); + +int +sql_value_int(struct Mem *); + +sql_int64 +sql_value_int64(struct Mem *); + +uint64_t +sql_value_uint64(struct Mem *val); + +const unsigned char * +sql_value_text(struct Mem *); + +const void *sqlValueText(struct Mem *); + +/** + * Return pointer to a string with the data type in the case of + * binary data stored in @a value. Otherwise, return the result + * of sql_value_text(). It is used due to the fact that not all + * binary strings can be displayed correctly (e.g. contain + * unprintable symbols). + */ +const char * +sql_value_to_diag_str(struct Mem *value); +#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) + +enum sql_subtype +sql_value_subtype(sql_value * pVal); + +const Mem * +columnNullValue(void); + +/** Checkers. */ + +static inline bool +sql_value_is_null(struct Mem *value) +{ + return sql_value_type(value) == MP_NIL; +} + +int sqlVdbeMemTooBig(Mem *); + +/* Return TRUE if Mem X contains dynamically allocated content - anything + * that needs to be deallocated to avoid a leak. + */ +#define VdbeMemDynamic(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_Frame))!=0) + + +int sqlMemCompare(const Mem *, const Mem *, const struct coll *); + +/** + * Check that MEM_type of the mem is compatible with given type. + * + * @param mem The MEM that contains the value to check. + * @param type The type to check. + * @retval TRUE if the MEM_type of the value and the given type + * are compatible, FALSE otherwise. + */ +bool +mem_is_type_compatible(struct Mem *mem, enum field_type type); + +/** MEM manipulate functions. */ + +int +vdbe_mem_alloc_blob_region(struct Mem *vdbe_mem, uint32_t size); +int sqlVdbeMemCopy(Mem *, const Mem *); +void sqlVdbeMemShallowCopy(Mem *, const Mem *, int); +void sqlVdbeMemMove(Mem *, Mem *); +int sqlVdbeMemMakeWriteable(Mem *); +void sqlVdbeMemRelease(Mem * p); + +/** + * 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); + +/** MEM and msgpack functions. */ + +/** + * Perform comparison of two keys: one is packed and one is not. + * + * @param key1 Pointer to pointer to first key. + * @param unpacked Pointer to unpacked tuple. + * @param key2_idx index of key in umpacked record to compare. + * + * @retval +1 if key1 > pUnpacked[iKey2], -1 ptherwise. + */ +int sqlVdbeCompareMsgpack(const char **key1, + struct UnpackedRecord *unpacked, int key2_idx); + +/** + * Perform comparison of two tuples: unpacked (key1) and packed (key2) + * + * @param key1 Packed key. + * @param unpacked Unpacked key. + * + * @retval +1 if key1 > unpacked, -1 otherwise. + */ +int sqlVdbeRecordCompareMsgpack(const void *key1, + struct UnpackedRecord *key2); + +/** + * Decode msgpack and save value into VDBE memory cell. + * + * @param buf Buffer to deserialize msgpack from. + * @param mem Memory cell to write value into. + * @param len[out] Length of decoded part. + * @retval Return code: < 0 in case of error. + * @retval 0 on success. + */ +int +vdbe_decode_msgpack_into_mem(const char *buf, struct Mem *mem, uint32_t *len); + +/** + * Perform encoding memory variable to stream. + * @param stream Initialized mpstream encoder object. + * @param var Vdbe memory variable to encode with stream. + */ +void +mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var); + +/** + * Perform encoding field_count Vdbe memory fields on region as + * msgpack array. + * @param fields The first Vdbe memory field to encode. + * @param field_count Count of fields to encode. + * @param[out] tuple_size Size of encoded tuple. + * @param region Region to use. + * @retval NULL on error, diag message is set. + * @retval Pointer to valid tuple on success. + */ +char * +sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count, + uint32_t *tuple_size, struct region *region); diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c index c15f2e0d1..b820b492e 100644 --- a/src/box/sql/pragma.c +++ b/src/box/sql/pragma.c @@ -39,6 +39,7 @@ #include "box/coll_id_cache.h" #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" #include "box/schema.h" #include "box/session.h" diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c index a3ff5bb09..cf32ba3f3 100644 --- a/src/box/sql/printf.c +++ b/src/box/sql/printf.c @@ -10,6 +10,7 @@ * sql. */ #include "sqlInt.h" +#include "mem.h" /* * Conversion types fall into various categories as defined by the diff --git a/src/box/sql/select.c b/src/box/sql/select.c index fb2795103..0fa388ae9 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -36,6 +36,7 @@ #include "coll/coll.h" #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" #include "box/box.h" #include "box/coll_id_cache.h" diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 95a6fedc9..c1a42fc2f 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -351,20 +351,6 @@ sql_stricmp(const char *, const char *); int sql_strnicmp(const char *, const char *, int); - const void * -sql_value_blob(sql_value *); - -int -sql_value_bytes(sql_value *); - -double -sql_value_double(sql_value *); - -bool -sql_value_boolean(sql_value *val); - -int -sql_value_int(sql_value *); /** * Get row column subtype. @@ -375,34 +361,6 @@ sql_value_int(sql_value *); enum sql_subtype sql_column_subtype(struct sql_stmt *stmt, int i); -sql_int64 -sql_value_int64(sql_value *); - -uint64_t -sql_value_uint64(sql_value *val); - -const unsigned char * -sql_value_text(sql_value *); - -/** - * Return pointer to a string with the data type in the case of - * binary data stored in @a value. Otherwise, return the result - * of sql_value_text(). It is used due to the fact that not all - * binary strings can be displayed correctly (e.g. contain - * unprintable symbols). - */ -const char * -sql_value_to_diag_str(sql_value *value); - -enum mp_type -sql_value_type(sql_value *); - -static inline bool -sql_value_is_null(sql_value *value) -{ - return sql_value_type(value) == MP_NIL; -} - sql * sql_context_db_handle(sql_context *); @@ -4000,16 +3958,8 @@ int sql_rem_int(int64_t lhs, bool is_lhs_neg, int64_t rhs, bool is_rhs_neg, int64_t *res, bool *is_res_neg); -const void *sqlValueText(sql_value *); -int sqlValueBytes(sql_value *); -void sqlValueSetStr(sql_value *, int, const void *, - void (*)(void *)); -void sqlValueSetNull(sql_value *); -void sqlValueFree(sql_value *); -sql_value *sqlValueNew(sql *); int sqlValueFromExpr(sql *, Expr *, enum field_type type, sql_value **); -void sql_value_apply_type(sql_value *val, enum field_type type); extern const unsigned char sqlOpcodeProperty[]; extern const unsigned char sqlUpperToLower[]; diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c index 0c387bc3b..7c983fea5 100644 --- a/src/box/sql/trigger.c +++ b/src/box/sql/trigger.c @@ -37,6 +37,7 @@ #include "box/schema.h" #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" /* See comment in sqlInt.h */ diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 18806b93f..775bce96f 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -46,6 +46,7 @@ #include "box/tuple.h" #include "box/port.h" #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "tarantoolInt.h" @@ -257,424 +258,6 @@ allocateCursor( return pCx; } -int -mem_apply_numeric_type(struct Mem *record) -{ - if ((record->flags & MEM_Str) == 0) - return -1; - int64_t integer_value; - bool is_neg; - if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) { - mem_set_int(record, integer_value, is_neg); - return 0; - } - double float_value; - if (sqlAtoF(record->z, &float_value, record->n) == 0) - return -1; - mem_set_double(record, float_value); - return 0; -} - -/** - * Processing is determined by the field type parameter: - * - * INTEGER: - * If memory holds floating point value and it can be - * converted without loss (2.0 - > 2), it's type is - * changed to INT. Otherwise, simply return success status. - * - * NUMBER: - * If memory holds INT or floating point value, - * no actions take place. - * - * STRING: - * Convert mem to a string representation. - * - * SCALAR: - * Mem is unchanged, but flag is set to BLOB in case of - * scalar-like type. Otherwise, (MAP, ARRAY) conversion - * is impossible. - * - * BOOLEAN: - * If memory holds BOOLEAN no actions take place. - * - * ANY: - * Mem is unchanged, no actions take place. - * - * MAP/ARRAY: - * These types can't be casted to scalar ones, or to each - * other. So the only valid conversion is to type itself. - * - * @param record The value to apply type to. - * @param type The type to be applied. - */ -static int -mem_apply_type(struct Mem *record, enum field_type type) -{ - if ((record->flags & MEM_Null) != 0) - return 0; - assert(type < field_type_MAX); - switch (type) { - case FIELD_TYPE_INTEGER: - case FIELD_TYPE_UNSIGNED: - if ((record->flags & (MEM_Bool | MEM_Blob)) != 0) - return -1; - if ((record->flags & MEM_UInt) == MEM_UInt) - return 0; - if ((record->flags & MEM_Real) == MEM_Real) { - double d = record->u.r; - if (d >= 0) { - if (double_compare_uint64(d, UINT64_MAX, - 1) > 0) - return 0; - if ((double)(uint64_t)d == d) - mem_set_u64(record, (uint64_t)d); - } else { - if (double_compare_nint64(d, INT64_MIN, 1) < 0) - return 0; - if ((double)(int64_t)d == d) - mem_set_int(record, (int64_t)d, true); - } - return 0; - } - if ((record->flags & MEM_Str) != 0) { - bool is_neg; - int64_t i; - if (sql_atoi64(record->z, &i, &is_neg, record->n) != 0) - return -1; - mem_set_int(record, i, is_neg); - } - if ((record->flags & MEM_Int) == MEM_Int) { - if (type == FIELD_TYPE_UNSIGNED) - return -1; - return 0; - } - return 0; - case FIELD_TYPE_BOOLEAN: - if ((record->flags & MEM_Bool) == MEM_Bool) - return 0; - return -1; - case FIELD_TYPE_NUMBER: - if ((record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0) - return 0; - return sqlVdbeMemRealify(record); - case FIELD_TYPE_DOUBLE: - if ((record->flags & MEM_Real) != 0) - return 0; - return sqlVdbeMemRealify(record); - case FIELD_TYPE_STRING: - /* - * Only attempt the conversion to TEXT if there is - * an integer or real representation (BLOB and - * NULL do not get converted). - */ - if ((record->flags & MEM_Str) == 0 && - (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0) - sqlVdbeMemStringify(record); - record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt); - return 0; - case FIELD_TYPE_VARBINARY: - if ((record->flags & MEM_Blob) == 0) - return -1; - return 0; - case FIELD_TYPE_SCALAR: - /* Can't cast MAP and ARRAY to scalar types. */ - if ((record->flags & MEM_Subtype) != 0 && - record->subtype == SQL_SUBTYPE_MSGPACK) { - assert(mp_typeof(*record->z) == MP_MAP || - mp_typeof(*record->z) == MP_ARRAY); - return -1; - } - return 0; - case FIELD_TYPE_MAP: - if ((record->flags & MEM_Subtype) != 0 && - record->subtype == SQL_SUBTYPE_MSGPACK && - mp_typeof(*record->z) == MP_MAP) - return 0; - return -1; - case FIELD_TYPE_ARRAY: - if ((record->flags & MEM_Subtype) != 0 && - record->subtype == SQL_SUBTYPE_MSGPACK && - mp_typeof(*record->z) == MP_ARRAY) - return 0; - return -1; - case FIELD_TYPE_ANY: - return 0; - default: - return -1; - } -} - -/* - * Exported version of mem_apply_type(). This one works on sql_value*, - * not the internal Mem* type. - */ -void -sql_value_apply_type( - sql_value *pVal, - enum field_type type) -{ - mem_apply_type((Mem *) pVal, type); -} - -/** - * Check that MEM_type of the mem is compatible with given type. - * - * @param mem The MEM that contains the value to check. - * @param type The type to check. - * @retval TRUE if the MEM_type of the value and the given type - * are compatible, FALSE otherwise. - */ -static bool -mem_is_type_compatible(struct Mem *mem, enum field_type type) -{ - enum mp_type mp_type = mem_mp_type(mem); - assert(mp_type < MP_EXT); - return field_mp_plain_type_is_compatible(type, mp_type, true); -} - -/** - * Convert the numeric value contained in MEM to double. - * - * @param mem The MEM that contains the numeric value. - * @retval 0 if the conversion was successful, -1 otherwise. - */ -static int -mem_convert_to_double(struct Mem *mem) -{ - if ((mem->flags & MEM_Real) != 0) - return 0; - if ((mem->flags & (MEM_Int | MEM_UInt)) == 0) - return -1; - double d; - if ((mem->flags & MEM_Int) != 0) - d = (double)mem->u.i; - else - d = (double)mem->u.u; - mem_set_double(mem, d); - return 0; -} - -/** - * Convert the numeric value contained in MEM to unsigned. - * - * @param mem The MEM that contains the numeric value. - * @retval 0 if the conversion was successful, -1 otherwise. - */ -static int -mem_convert_to_unsigned(struct Mem *mem) -{ - if ((mem->flags & MEM_UInt) != 0) - return 0; - if ((mem->flags & MEM_Int) != 0) - return -1; - if ((mem->flags & MEM_Real) == 0) - return -1; - double d = mem->u.r; - if (d < 0.0 || d >= (double)UINT64_MAX) - return -1; - mem_set_u64(mem, (uint64_t) d); - return 0; -} - -/** - * Convert the numeric value contained in MEM to integer. - * - * @param mem The MEM that contains the numeric value. - * @retval 0 if the conversion was successful, -1 otherwise. - */ -static int -mem_convert_to_integer(struct Mem *mem) -{ - if ((mem->flags & (MEM_UInt | MEM_Int)) != 0) - return 0; - if ((mem->flags & MEM_Real) == 0) - return -1; - double d = mem->u.r; - if (d >= (double)UINT64_MAX || d < (double)INT64_MIN) - return -1; - if (d < (double)INT64_MAX) - mem_set_int(mem, (int64_t) d, d < 0); - else - mem_set_int(mem, (uint64_t) d, false); - return 0; -} - -/** - * Convert the numeric value contained in MEM to another numeric - * type. - * - * @param mem The MEM that contains the numeric value. - * @param type The type to convert to. - * @retval 0 if the conversion was successful, -1 otherwise. - */ -static int -mem_convert_to_numeric(struct Mem *mem, enum field_type type) -{ - assert(mp_type_is_numeric(mem_mp_type(mem)) && - sql_type_is_numeric(type)); - assert(type != FIELD_TYPE_NUMBER); - if (type == FIELD_TYPE_DOUBLE) - return mem_convert_to_double(mem); - if (type == FIELD_TYPE_UNSIGNED) - return mem_convert_to_unsigned(mem); - assert(type == FIELD_TYPE_INTEGER); - return mem_convert_to_integer(mem); -} - -/* - * pMem currently only holds a string type (or maybe a BLOB that we can - * interpret as a string if we want to). Compute its corresponding - * numeric type, if has one. Set the pMem->u.r and pMem->u.i fields - * accordingly. - */ -static u16 SQL_NOINLINE computeNumericType(Mem *pMem) -{ - assert((pMem->flags & (MEM_Int | MEM_UInt | MEM_Real)) == 0); - assert((pMem->flags & (MEM_Str|MEM_Blob))!=0); - if (sqlAtoF(pMem->z, &pMem->u.r, pMem->n)==0) - return 0; - bool is_neg; - if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, pMem->n) == 0) - return is_neg ? MEM_Int : MEM_UInt; - return MEM_Real; -} - -/* - * Return the numeric type for pMem, either MEM_Int or MEM_Real or both or - * none. - * - * Unlike mem_apply_numeric_type(), this routine does not modify pMem->flags. - * But it does set pMem->u.r and pMem->u.i appropriately. - */ -static u16 numericType(Mem *pMem) -{ - if ((pMem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) - return pMem->flags & (MEM_Int | MEM_UInt | MEM_Real); - if (pMem->flags & (MEM_Str|MEM_Blob)) { - return computeNumericType(pMem); - } - return 0; -} - -#ifdef SQL_DEBUG -/* - * Write a nice string representation of the contents of cell pMem - * into buffer zBuf, length nBuf. - */ -void -sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf) -{ - char *zCsr = zBuf; - int f = pMem->flags; - - if (f&MEM_Blob) { - int i; - char c; - if (f & MEM_Dyn) { - c = 'z'; - assert((f & (MEM_Static|MEM_Ephem))==0); - } else if (f & MEM_Static) { - c = 't'; - assert((f & (MEM_Dyn|MEM_Ephem))==0); - } else if (f & MEM_Ephem) { - c = 'e'; - assert((f & (MEM_Static|MEM_Dyn))==0); - } else { - c = 's'; - } - - sql_snprintf(100, zCsr, "%c", c); - zCsr += sqlStrlen30(zCsr); - sql_snprintf(100, zCsr, "%d[", pMem->n); - zCsr += sqlStrlen30(zCsr); - for(i=0; i<16 && in; i++) { - sql_snprintf(100, zCsr, "%02X", ((int)pMem->z[i] & 0xFF)); - zCsr += sqlStrlen30(zCsr); - } - for(i=0; i<16 && in; i++) { - char z = pMem->z[i]; - if (z<32 || z>126) *zCsr++ = '.'; - else *zCsr++ = z; - } - 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 (f & MEM_Str) { - int j, k; - zBuf[0] = ' '; - if (f & MEM_Dyn) { - zBuf[1] = 'z'; - assert((f & (MEM_Static|MEM_Ephem))==0); - } else if (f & MEM_Static) { - zBuf[1] = 't'; - assert((f & (MEM_Dyn|MEM_Ephem))==0); - } else if (f & MEM_Ephem) { - zBuf[1] = 'e'; - assert((f & (MEM_Static|MEM_Dyn))==0); - } else { - zBuf[1] = 's'; - } - k = 2; - sql_snprintf(100, &zBuf[k], "%d", pMem->n); - k += sqlStrlen30(&zBuf[k]); - zBuf[k++] = '['; - for(j=0; j<15 && jn; j++) { - u8 c = pMem->z[j]; - if (c>=0x20 && c<0x7f) { - zBuf[k++] = c; - } else { - zBuf[k++] = '.'; - } - } - zBuf[k++] = ']'; - sql_snprintf(100,&zBuf[k],"(8)"); - k += sqlStrlen30(&zBuf[k]); - zBuf[k++] = 0; - } -} -#endif - -#ifdef SQL_DEBUG -/* - * Print the value of a register for tracing purposes: - */ -static void -memTracePrint(Mem *p) -{ - if (p->flags & MEM_Undefined) { - printf(" undefined"); - } else if (p->flags & MEM_Null) { - printf(" NULL"); - } else if ((p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str)) { - printf(" si:%lld", p->u.i); - } else if (p->flags & MEM_Int) { - printf(" i:%lld", p->u.i); - } else if (p->flags & MEM_UInt) { - printf(" u:%"PRIu64"", p->u.u); - } else if (p->flags & MEM_Real) { - printf(" r:%g", p->u.r); - } else if (p->flags & MEM_Bool) { - printf(" bool:%s", SQL_TOKEN_BOOLEAN(p->u.b)); - } else { - char zBuf[200]; - sqlVdbeMemPrettyPrint(p, zBuf); - printf(" %s", zBuf); - } - if (p->flags & MEM_Subtype) printf(" subtype=0x%02x", p->subtype); -} -static void -registerTrace(int iReg, Mem *p) { - printf("REG[%d] = ", iReg); - memTracePrint(p); - printf("\n"); -} -#endif - #ifdef SQL_DEBUG # define REGISTER_TRACE(P,R,M) \ if(P->sql_flags&SQL_VdbeTrace) registerTrace(R,M); @@ -727,43 +310,6 @@ vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id) return 0; } -char * -mem_type_to_str(const struct Mem *p) -{ - assert(p != NULL); - switch (p->flags & MEM_PURE_TYPE_MASK) { - case MEM_Null: - return "NULL"; - case MEM_Str: - return "text"; - case MEM_Int: - return "integer"; - case MEM_UInt: - return "unsigned"; - case MEM_Real: - return "real"; - case MEM_Blob: - return "varbinary"; - case MEM_Bool: - return "boolean"; - default: - unreachable(); - } -} - -/* Allocate memory for internal VDBE structure on region. */ -static int -vdbe_mem_alloc_blob_region(struct Mem *vdbe_mem, uint32_t size) -{ - vdbe_mem->n = size; - vdbe_mem->z = region_alloc(&fiber()->gc, size); - if (vdbe_mem->z == NULL) - return -1; - vdbe_mem->flags = MEM_Ephem | MEM_Blob; - assert(sqlVdbeCheckMemInvariants(vdbe_mem)); - return 0; -} - static inline const struct tuple_field * vdbe_field_ref_fetch_field(struct vdbe_field_ref *field_ref, uint32_t fieldno) { diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index 48815d4ec..118f1cd83 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -268,7 +268,6 @@ void sqlVdbeSwap(Vdbe *, Vdbe *); VdbeOp *sqlVdbeTakeOpArray(Vdbe *, int *, int *); sql_value *sqlVdbeGetBoundValue(Vdbe *, int, u8); char *sqlVdbeExpandSql(Vdbe *, const char *); -int sqlMemCompare(const Mem *, const Mem *, const struct coll *); /** * Perform unpacking of provided message pack. diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index 7205f1af3..b2d4f27c1 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -155,151 +155,6 @@ struct VdbeFrame { int nDbChange; /* Value of db->nChange */ }; -#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) - -/* - * Internally, the vdbe manipulates nearly all SQL values as Mem - * structures. Each Mem struct may cache multiple representations (string, - * integer etc.) of the same value. - */ -struct Mem { - union MemValue { - double r; /* Real value used when MEM_Real is set in flags */ - 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. - * Used only when flags==MEM_Agg. - */ - struct func *func; - VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ - } u; - u32 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ - /** Subtype for this value. */ - enum sql_subtype subtype; - /** - * If value is fetched from tuple, then this property - * contains type of corresponding space's field. If it's - * value field_type_MAX then we can rely on on format - * (msgpack) type which is represented by 'flags'. - */ - enum field_type field_type; - int n; /* size (in bytes) of string value, excluding trailing '\0' */ - char *z; /* String or BLOB value */ - /* ShallowCopy only needs to copy the information above */ - char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ - int szMalloc; /* Size of the zMalloc allocation */ - u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ - sql *db; /* The associated database connection */ - void (*xDel) (void *); /* Destructor for Mem.z - only valid if MEM_Dyn */ -#ifdef SQL_DEBUG - Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ - void *pFiller; /* So that sizeof(Mem) is a multiple of 8 */ -#endif -}; - -/* - * Size of struct Mem not including the Mem.zMalloc member or anything that - * follows. - */ -#define MEMCELLSIZE offsetof(Mem,zMalloc) - -/* One or more of the following flags are set to indicate the validOK - * representations of the value stored in the Mem struct. - * - * If the MEM_Null flag is set, then the value is an SQL NULL value. - * No other flags may be set in this case. - * - * If the MEM_Str flag is set then Mem.z points at a string representation. - * Usually this is encoded in the same unicode encoding as the main - * database (see below for exceptions). If the MEM_Term flag is also - * set, then the string is nul terminated. The MEM_Int and MEM_Real - * flags may coexist with the MEM_Str flag. - */ -#define MEM_Null 0x0001 /* Value is NULL */ -#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_Blob 0x0010 /* Value is a BLOB */ -#define MEM_Bool 0x0020 /* Value is a bool */ -#define MEM_UInt 0x0040 /* Value is an unsigned integer */ -#define MEM_Frame 0x0080 /* Value is a VdbeFrame object */ -#define MEM_Undefined 0x0100 /* Value is undefined */ -#define MEM_Cleared 0x0200 /* NULL set by OP_Null, not from data */ -#define MEM_TypeMask 0x83ff /* Mask of type bits */ - -/* Whenever Mem contains a valid string or blob representation, one of - * the following flags must be set to determine the memory management - * policy for Mem.z. The MEM_Term flag tells us whether or not the - * string is \000 or \u0000 terminated - */ -#define MEM_Term 0x0400 /* String rep is nul terminated */ -#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_Agg 0x4000 /* Mem.z points to an agg function context */ -#define MEM_Zero 0x8000 /* Mem.i contains count of 0s appended to blob */ -#define MEM_Subtype 0x10000 /* Mem.eSubtype is valid */ -#define MEM_Ptr 0x20000 /* Value is a generic pointer */ - -/** - * In contrast to Mem_TypeMask, this one allows to get - * pure type of memory cell, i.e. without _Dyn/_Zero and other - * auxiliary flags. - */ -enum { - MEM_PURE_TYPE_MASK = 0x7f -}; - -static_assert(MEM_PURE_TYPE_MASK == (MEM_Null | MEM_Str | MEM_Int | MEM_Real | - MEM_Blob | MEM_Bool | MEM_UInt), - "value of type mask must consist of corresponding to memory "\ - "type bits"); - -/** - * Simple type to str convertor. It is used to simplify - * error reporting. - */ -char * -mem_type_to_str(const struct Mem *p); - -/** - * Try to convert a string value into a numeric representation - * if we can do so without loss of information. Firstly, value - * is attempted to be converted to integer, and in case of fail - - * to floating point number. Note that function is assumed to be - * called on memory cell containing string, i.e. mem->type == MEM_Str. - * - * @param record Memory cell containing value to be converted. - * @retval 0 If value can be converted to integer or number. - * @retval -1 Otherwise. - */ -int -mem_apply_numeric_type(struct Mem *record); - -/* Return TRUE if Mem X contains dynamically allocated content - anything - * that needs to be deallocated to avoid a leak. - */ -#define VdbeMemDynamic(X) \ - (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_Frame))!=0) - -/* - * Clear any existing type flags from a Mem and replace them with f - */ -#define MemSetTypeFlag(p, f) \ - ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f) - -/* - * Return true if a memory cell is not marked as invalid. This macro - * is for use inside assert() statements only. - */ -#ifdef SQL_DEBUG -#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 -#endif - /* * The "context" argument for an installable function. A pointer to an * instance of this structure is the first argument to the routines used @@ -477,83 +332,8 @@ int sqlVdbeExec(Vdbe *); int sqlVdbeList(Vdbe *); int sqlVdbeHalt(Vdbe *); -int sqlVdbeMemTooBig(Mem *); -int sqlVdbeMemCopy(Mem *, const Mem *); -void sqlVdbeMemShallowCopy(Mem *, const Mem *, int); -void sqlVdbeMemMove(Mem *, Mem *); -int sqlVdbeMemNulTerminate(Mem *); -int sqlVdbeMemSetStr(Mem *, const char *, int, u8, void (*)(void *)); -void -mem_set_bool(struct Mem *mem, bool value); - -/** - * Set VDBE memory register with given pointer as a data. - * @param mem VDBE memory register to update. - * @param ptr Pointer to use. - */ -void -mem_set_ptr(struct Mem *mem, void *ptr); - -/** - * Set integer value. Depending on its sign MEM_Int (in case - * of negative value) or MEM_UInt flag is set. - */ -void -mem_set_i64(struct Mem *mem, int64_t value); - -/** Set unsigned value and MEM_UInt flag. */ -void -mem_set_u64(struct Mem *mem, uint64_t value); - -/** - * Set integer value. According to is_neg flag value is considered - * to be signed or unsigned. - */ -void -mem_set_int(struct Mem *mem, int64_t value, bool is_neg); - -/** Set double value and MEM_Real flag. */ -void -mem_set_double(struct Mem *mem, double value); - -void sqlVdbeMemInit(Mem *, sql *, u32); -void sqlVdbeMemSetNull(Mem *); -void sqlVdbeMemSetZeroBlob(Mem *, int); -int sqlVdbeMemMakeWriteable(Mem *); -int sqlVdbeMemStringify(Mem *); -int sqlVdbeIntValue(Mem *, int64_t *, bool *is_neg); - -int sqlVdbeRealValue(Mem *, double *); - -int -mem_value_bool(const struct Mem *mem, bool *b); - -int mem_apply_integer_type(Mem *); -int sqlVdbeMemRealify(Mem *); - -/** - * Convert @a mem to NUMBER type, so that after conversion it has - * one of types MEM_Real, MEM_Int or MEM_UInt. If conversion is - * not possible, function returns -1. - * - * Beware - this function changes value and type of @a mem - * argument. - */ -int -vdbe_mem_numerify(struct Mem *mem); - -int sqlVdbeMemCast(Mem *, enum field_type type); int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *); -void sqlVdbeMemRelease(Mem * p); - -/* - * Return the MP_type of the value of the MEM. - * Analogue of sql_value_type() but operates directly on - * transparent memory cell. - */ -enum mp_type -mem_mp_type(struct Mem *mem); /** * In terms of VDBE memory cell type, _BIN, _ARRAY and _MAP @@ -566,19 +346,7 @@ mem_mp_type(struct Mem *mem); #define mp_type_is_numeric(X) ((X) == MP_INT || (X) == MP_UINT ||\ (X) == MP_DOUBLE) -/** - * 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); - const char *sqlOpcodeName(int); -int sqlVdbeMemGrow(Mem * pMem, int n, int preserve); -int sqlVdbeMemClearAndResize(Mem * pMem, int n); int sqlVdbeCloseStatement(Vdbe *, int); void sqlVdbeFrameDelete(VdbeFrame *); int sqlVdbeFrameRestore(VdbeFrame *); @@ -598,7 +366,6 @@ int sqlVdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); #ifdef SQL_DEBUG void sqlVdbeMemAboutToChange(Vdbe *, Mem *); -int sqlVdbeCheckMemInvariants(Mem *); #endif int sqlVdbeCheckFk(Vdbe *, int); @@ -606,48 +373,9 @@ int sqlVdbeCheckFk(Vdbe *, int); int sqlVdbeMemTranslate(Mem *, u8); #ifdef SQL_DEBUG void sqlVdbePrintSql(Vdbe *); -void sqlVdbeMemPrettyPrint(Mem * pMem, char *zBuf); #endif int sqlVdbeMemHandleBom(Mem * pMem); -int sqlVdbeMemExpandBlob(Mem *); -#define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0) - -/** - * Perform comparison of two keys: one is packed and one is not. - * - * @param key1 Pointer to pointer to first key. - * @param unpacked Pointer to unpacked tuple. - * @param key2_idx index of key in umpacked record to compare. - * - * @retval +1 if key1 > pUnpacked[iKey2], -1 ptherwise. - */ -int sqlVdbeCompareMsgpack(const char **key1, - struct UnpackedRecord *unpacked, int key2_idx); - -/** - * Perform comparison of two tuples: unpacked (key1) and packed (key2) - * - * @param key1 Packed key. - * @param unpacked Unpacked key. - * - * @retval +1 if key1 > unpacked, -1 otherwise. - */ -int sqlVdbeRecordCompareMsgpack(const void *key1, - struct UnpackedRecord *key2); - -/** - * Decode msgpack and save value into VDBE memory cell. - * - * @param buf Buffer to deserialize msgpack from. - * @param mem Memory cell to write value into. - * @param len[out] Length of decoded part. - * @retval Return code: < 0 in case of error. - * @retval 0 on success. - */ -int -vdbe_decode_msgpack_into_mem(const char *buf, struct Mem *mem, uint32_t *len); - struct mpstream; struct region; @@ -658,26 +386,4 @@ set_encode_error(void *error_ctx) *(bool *)error_ctx = true; } -/** - * Perform encoding memory variable to stream. - * @param stream Initialized mpstream encoder object. - * @param var Vdbe memory variable to encode with stream. - */ -void -mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var); - -/** - * Perform encoding field_count Vdbe memory fields on region as - * msgpack array. - * @param fields The first Vdbe memory field to encode. - * @param field_count Count of fields to encode. - * @param[out] tuple_size Size of encoded tuple. - * @param region Region to use. - * @retval NULL on error, diag message is set. - * @retval Pointer to valid tuple on success. - */ -char * -sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count, - uint32_t *tuple_size, struct region *region); - #endif /* !defined(SQL_VDBEINT_H) */ diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c index 7c59ef83f..24fa99f17 100644 --- a/src/box/sql/vdbeapi.c +++ b/src/box/sql/vdbeapi.c @@ -35,6 +35,7 @@ * VDBE. */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "box/session.h" @@ -122,101 +123,6 @@ sql_metadata_is_full() return current_session()->sql_flags & SQL_FullMetadata; } -/**************************** sql_value_ ****************************** - * The following routines extract information from a Mem or sql_value - * structure. - */ -const void * -sql_value_blob(sql_value * pVal) -{ - Mem *p = (Mem *) pVal; - if (p->flags & (MEM_Blob | MEM_Str)) { - if (ExpandBlob(p) != 0) { - assert(p->flags == MEM_Null && p->z == 0); - return 0; - } - p->flags |= MEM_Blob; - return p->n ? p->z : 0; - } else { - return sql_value_text(pVal); - } -} - -int -sql_value_bytes(sql_value * pVal) -{ - return sqlValueBytes(pVal); -} - -double -sql_value_double(sql_value * pVal) -{ - double v = 0.0; - sqlVdbeRealValue((Mem *) pVal, &v); - return v; -} - -bool -sql_value_boolean(sql_value *val) -{ - bool b = false; - int rc = mem_value_bool((struct Mem *) val, &b); - assert(rc == 0); - (void) rc; - return b; -} - -int -sql_value_int(sql_value * pVal) -{ - int64_t i = 0; - bool is_neg; - sqlVdbeIntValue((Mem *) pVal, &i, &is_neg); - return (int)i; -} - -sql_int64 -sql_value_int64(sql_value * pVal) -{ - int64_t i = 0; - bool unused; - sqlVdbeIntValue((Mem *) pVal, &i, &unused); - return i; -} - -uint64_t -sql_value_uint64(sql_value *val) -{ - int64_t i = 0; - bool is_neg; - sqlVdbeIntValue((struct Mem *) val, &i, &is_neg); - assert(!is_neg); - return i; -} - -enum sql_subtype -sql_value_subtype(sql_value * pVal) -{ - return (pVal->flags & MEM_Subtype) != 0 ? pVal->subtype : SQL_SUBTYPE_NO; -} - -const unsigned char * -sql_value_text(sql_value * pVal) -{ - return (const unsigned char *)sqlValueText(pVal); -} - -/* EVIDENCE-OF: R-12793-43283 Every value in sql has one of five - * fundamental datatypes: 64-bit signed integer 64-bit IEEE floating - * point number string BLOB NULL - */ -enum mp_type -sql_value_type(sql_value *pVal) -{ - struct Mem *mem = (struct Mem *) pVal; - return mem_mp_type(mem); -} - /* Make a copy of an sql_value object */ sql_value * @@ -582,47 +488,6 @@ sql_data_count(sql_stmt * pStmt) return pVm->nResColumn; } -/* - * Return a pointer to static memory containing an SQL NULL value. - */ -static const Mem * -columnNullValue(void) -{ - /* Even though the Mem structure contains an element - * of type i64, on certain architectures (x86) with certain compiler - * switches (-Os), gcc may align this Mem object on a 4-byte boundary - * instead of an 8-byte one. This all works fine, except that when - * running with SQL_DEBUG defined the sql code sometimes assert()s - * that a Mem structure is located on an 8-byte boundary. To prevent - * these assert()s from failing, when building with SQL_DEBUG defined - * using gcc, we force nullMem to be 8-byte aligned using the magical - * __attribute__((aligned(8))) macro. - */ - static const Mem nullMem -#if defined(SQL_DEBUG) && defined(__GNUC__) - __attribute__ ((aligned(8))) -#endif - = { - /* .u = */ { - 0}, - /* .flags = */ (u16) MEM_Null, - /* .eSubtype = */ (u8) 0, - /* .field_type = */ field_type_MAX, - /* .n = */ (int)0, - /* .z = */ (char *)0, - /* .zMalloc = */ (char *)0, - /* .szMalloc = */ (int)0, - /* .uTemp = */ (u32) 0, - /* .db = */ (sql *) 0, - /* .xDel = */ (void (*)(void *))0, -#ifdef SQL_DEBUG - /* .pScopyFrom = */ (Mem *) 0, - /* .pFiller = */ (void *)0, -#endif - }; - return &nullMem; -} - /* * Check to see if column iCol of the given statement is valid. If * it is, return a pointer to the Mem for the value of that column. diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 772476377..79c3d60e0 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -41,6 +41,7 @@ #include "box/txn.h" #include "msgpuck/msgpuck.h" #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "tarantoolInt.h" #include "box/execute.h" @@ -1194,65 +1195,6 @@ sqlVdbePrintOp(FILE * pOut, int pc, Op * pOp) } #endif -/* - * Initialize an array of N Mem element. - */ -static void -initMemArray(Mem * p, int N, sql * db, u32 flags) -{ - while ((N--) > 0) { - p->db = db; - p->flags = flags; - p->szMalloc = 0; - p->field_type = field_type_MAX; -#ifdef SQL_DEBUG - p->pScopyFrom = 0; -#endif - p++; - } -} - -/* - * Release an array of N Mem elements - */ -static void -releaseMemArray(Mem * p, int N) -{ - if (p && N) { - Mem *pEnd = &p[N]; - sql *db = p->db; - do { - assert((&p[1]) == pEnd || p[0].db == p[1].db); - assert(sqlVdbeCheckMemInvariants(p)); - - /* This block is really an inlined version of sqlVdbeMemRelease() - * that takes advantage of the fact that the memory cell value is - * being set to NULL after releasing any dynamic resources. - * - * The justification for duplicating code is that according to - * callgrind, this causes a certain test case to hit the CPU 4.7 - * percent less (x86 linux, gcc version 4.1.2, -O6) than if - * sqlMemRelease() were called from here. With -O2, this jumps - * to 6.6 percent. The test case is inserting 1000 rows into a table - * with no indexes using a single prepared INSERT statement, bind() - * and reset(). Inserts are grouped into a transaction. - */ - testcase(p->flags & MEM_Agg); - testcase(p->flags & MEM_Dyn); - testcase(p->flags & MEM_Frame); - if (p-> - flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) { - sqlVdbeMemRelease(p); - } else if (p->szMalloc) { - sqlDbFree(db, p->zMalloc); - p->szMalloc = 0; - } - - p->flags = MEM_Undefined; - } while ((++p) < pEnd); - } -} - /* * Delete a VdbeFrame object and its contents. VdbeFrame objects are * allocated by the OP_Program opcode in sqlVdbeExec(). @@ -2334,204 +2276,6 @@ sqlVdbeAllocUnpackedRecord(struct sql *db, struct key_def *key_def) return p; } -/* - * Both *pMem1 and *pMem2 contain string values. Compare the two values - * using the collation sequence pColl. As usual, return a negative , zero - * or positive value if *pMem1 is less than, equal to or greater than - * *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". - * - * Strungs assume to be UTF-8 encoded - */ -static int -vdbeCompareMemString(const Mem * pMem1, const Mem * pMem2, - const struct coll * pColl) -{ - return pColl->cmp(pMem1->z, (size_t)pMem1->n, - pMem2->z, (size_t)pMem2->n, pColl); -} - -/* - * The input pBlob is guaranteed to be a Blob that is not marked - * with MEM_Zero. Return true if it could be a zero-blob. - */ -static int -isAllZero(const char *z, int n) -{ - int i; - for (i = 0; i < n; i++) { - if (z[i]) - return 0; - } - return 1; -} - -/* - * Compare two blobs. Return negative, zero, or positive if the first - * is less than, equal to, or greater than the second, respectively. - * If one blob is a prefix of the other, then the shorter is the lessor. - */ -static SQL_NOINLINE int -sqlBlobCompare(const Mem * pB1, const Mem * pB2) -{ - int c; - int n1 = pB1->n; - int n2 = pB2->n; - - /* 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 - * sqlMemCompare(). - */ - assert((pB1->flags & MEM_Zero) == 0 || n1 == 0); - assert((pB2->flags & MEM_Zero) == 0 || n2 == 0); - - if ((pB1->flags | pB2->flags) & MEM_Zero) { - if (pB1->flags & pB2->flags & MEM_Zero) { - return pB1->u.nZero - pB2->u.nZero; - } else if (pB1->flags & MEM_Zero) { - if (!isAllZero(pB2->z, pB2->n)) - return -1; - return pB1->u.nZero - n2; - } else { - if (!isAllZero(pB1->z, pB1->n)) - return +1; - return n1 - pB2->u.nZero; - } - } - c = memcmp(pB1->z, pB2->z, n1 > n2 ? n2 : n1); - if (c) - return c; - return n1 - n2; -} - -/* - * Compare the values contained by the two memory cells, returning - * negative, zero or positive if pMem1 is less than, equal to, or greater - * than pMem2. Sorting order is NULL's first, followed by numbers (integers - * and reals) sorted numerically, followed by text ordered by the collating - * sequence pColl and finally blob's ordered by memcmp(). - * - * Two NULL values are considered equal by this function. - */ -int -sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl) -{ - int f1, f2; - int combined_flags; - - f1 = pMem1->flags; - f2 = pMem2->flags; - combined_flags = f1 | f2; - - /* If one value is NULL, it is less than the other. If both values - * are NULL, return 0. - */ - if (combined_flags & MEM_Null) { - return (f2 & MEM_Null) - (f1 & MEM_Null); - } - - if ((combined_flags & MEM_Bool) != 0) { - if ((f1 & f2 & MEM_Bool) != 0) { - if (pMem1->u.b == pMem2->u.b) - return 0; - if (pMem1->u.b) - return 1; - return -1; - } - if ((f2 & MEM_Bool) != 0) - return +1; - return -1; - } - - /* At least one of the two values is a number - */ - if ((combined_flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) { - if ((f1 & f2 & MEM_Int) != 0) { - if (pMem1->u.i < pMem2->u.i) - return -1; - if (pMem1->u.i > pMem2->u.i) - return +1; - return 0; - } - if ((f1 & f2 & MEM_UInt) != 0) { - if (pMem1->u.u < pMem2->u.u) - return -1; - if (pMem1->u.u > pMem2->u.u) - return +1; - return 0; - } - if ((f1 & f2 & MEM_Real) != 0) { - if (pMem1->u.r < pMem2->u.r) - return -1; - if (pMem1->u.r > pMem2->u.r) - return +1; - return 0; - } - if ((f1 & MEM_Int) != 0) { - if ((f2 & MEM_Real) != 0) { - return double_compare_nint64(pMem2->u.r, - pMem1->u.i, -1); - } else { - return -1; - } - } - if ((f1 & MEM_UInt) != 0) { - if ((f2 & MEM_Real) != 0) { - return double_compare_uint64(pMem2->u.r, - pMem1->u.u, -1); - } else if ((f2 & MEM_Int) != 0) { - return +1; - } else { - return -1; - } - } - if ((f1 & MEM_Real) != 0) { - if ((f2 & MEM_Int) != 0) { - return double_compare_nint64(pMem1->u.r, - pMem2->u.i, 1); - } else if ((f2 & MEM_UInt) != 0) { - return double_compare_uint64(pMem1->u.r, - pMem2->u.u, 1); - } else { - return -1; - } - } - return +1; - } - - /* If one value is a string and the other is a blob, the string is less. - * If both are strings, compare using the collating functions. - */ - if (combined_flags & MEM_Str) { - if ((f1 & MEM_Str) == 0) { - return 1; - } - if ((f2 & MEM_Str) == 0) { - return -1; - } - /* The collation sequence must be defined at this point, even if - * the user deletes the collation sequence after the vdbe program is - * compiled (this was not always the case). - */ - if (pColl) { - return vdbeCompareMemString(pMem1, pMem2, pColl); - } else { - size_t n = pMem1->n < pMem2->n ? pMem1->n : pMem2->n; - int res; - res = memcmp(pMem1->z, pMem2->z, n); - if (res == 0) - res = (int)pMem1->n - (int)pMem2->n; - return res; - } - /* If a NULL pointer was passed as the collate function, fall through - * to the blob case and use memcmp(). - */ - } - - /* Both values must be blobs. Compare using memcmp(). */ - return sqlBlobCompare(pMem1, pMem2); -} - /* * This routine sets the value to be returned by subsequent calls to * sql_changes() on the database handle 'db'. @@ -2605,291 +2349,6 @@ sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff) return 0; } -int -sqlVdbeCompareMsgpack(const char **key1, - struct UnpackedRecord *unpacked, int key2_idx) -{ - const char *aKey1 = *key1; - Mem *pKey2 = unpacked->aMem + key2_idx; - Mem mem1; - int rc = 0; - switch (mp_typeof(*aKey1)) { - default:{ - /* FIXME */ - rc = -1; - break; - } - case MP_NIL:{ - rc = -((pKey2->flags & MEM_Null) == 0); - mp_decode_nil(&aKey1); - break; - } - case MP_BOOL:{ - mem1.u.b = mp_decode_bool(&aKey1); - if ((pKey2->flags & MEM_Bool) != 0) { - if (mem1.u.b != pKey2->u.b) - rc = mem1.u.b ? 1 : -1; - } else { - rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1; - } - break; - } - case MP_UINT:{ - mem1.u.u = mp_decode_uint(&aKey1); - if ((pKey2->flags & MEM_Int) != 0) { - rc = +1; - } else if ((pKey2->flags & MEM_UInt) != 0) { - if (mem1.u.u < pKey2->u.u) - rc = -1; - else if (mem1.u.u > pKey2->u.u) - rc = +1; - } else if ((pKey2->flags & MEM_Real) != 0) { - rc = double_compare_uint64(pKey2->u.r, - mem1.u.u, -1); - } else if ((pKey2->flags & MEM_Null) != 0) { - rc = 1; - } else if ((pKey2->flags & MEM_Bool) != 0) { - rc = 1; - } else { - rc = -1; - } - break; - } - case MP_INT:{ - mem1.u.i = mp_decode_int(&aKey1); - if ((pKey2->flags & MEM_UInt) != 0) { - rc = -1; - } else if ((pKey2->flags & MEM_Int) != 0) { - if (mem1.u.i < pKey2->u.i) { - rc = -1; - } else if (mem1.u.i > pKey2->u.i) { - rc = +1; - } - } else if (pKey2->flags & MEM_Real) { - rc = double_compare_nint64(pKey2->u.r, mem1.u.i, - -1); - } else if ((pKey2->flags & MEM_Null) != 0) { - rc = 1; - } else if ((pKey2->flags & MEM_Bool) != 0) { - rc = 1; - } else { - rc = -1; - } - break; - } - case MP_FLOAT:{ - mem1.u.r = mp_decode_float(&aKey1); - goto do_float; - } - case MP_DOUBLE:{ - mem1.u.r = mp_decode_double(&aKey1); - do_float: - if ((pKey2->flags & MEM_Int) != 0) { - rc = double_compare_nint64(mem1.u.r, pKey2->u.i, - 1); - } else if (pKey2->flags & MEM_UInt) { - rc = double_compare_uint64(mem1.u.r, - pKey2->u.u, 1); - } else if (pKey2->flags & MEM_Real) { - if (mem1.u.r < pKey2->u.r) { - rc = -1; - } else if (mem1.u.r > pKey2->u.r) { - rc = +1; - } - } else if ((pKey2->flags & MEM_Null) != 0) { - rc = 1; - } else if ((pKey2->flags & MEM_Bool) != 0) { - rc = 1; - } else { - rc = -1; - } - break; - } - case MP_STR:{ - if (pKey2->flags & MEM_Str) { - struct key_def *key_def = unpacked->key_def; - mem1.n = mp_decode_strl(&aKey1); - mem1.z = (char *)aKey1; - aKey1 += mem1.n; - struct coll *coll = - key_def->parts[key2_idx].coll; - if (coll != NULL) { - mem1.flags = MEM_Str; - rc = vdbeCompareMemString(&mem1, pKey2, - coll); - } else { - goto do_bin_cmp; - } - } else { - rc = (pKey2->flags & MEM_Blob) ? -1 : +1; - } - break; - } - case MP_BIN:{ - mem1.n = mp_decode_binl(&aKey1); - mem1.z = (char *)aKey1; - aKey1 += mem1.n; - do_blob: - if (pKey2->flags & MEM_Blob) { - if (pKey2->flags & MEM_Zero) { - if (!isAllZero - ((const char *)mem1.z, mem1.n)) { - rc = 1; - } else { - rc = mem1.n - pKey2->u.nZero; - } - } else { - int nCmp; - do_bin_cmp: - nCmp = MIN(mem1.n, pKey2->n); - rc = memcmp(mem1.z, pKey2->z, nCmp); - if (rc == 0) - rc = mem1.n - pKey2->n; - } - } else { - rc = 1; - } - break; - } - case MP_ARRAY: - case MP_MAP: - case MP_EXT:{ - mem1.z = (char *)aKey1; - mp_next(&aKey1); - mem1.n = aKey1 - (char *)mem1.z; - goto do_blob; - } - } - *key1 = aKey1; - return rc; -} - -int -sqlVdbeRecordCompareMsgpack(const void *key1, - struct UnpackedRecord *key2) -{ - int rc = 0; - u32 i, n = mp_decode_array((const char**)&key1); - - n = MIN(n, key2->nField); - - for (i = 0; i != n; i++) { - rc = sqlVdbeCompareMsgpack((const char**)&key1, key2, i); - if (rc != 0) { - if (key2->key_def->parts[i].sort_order != - SORT_ORDER_ASC) { - rc = -rc; - } - return rc; - } - } - - key2->eqSeen = 1; - return key2->default_rc; -} - -int -vdbe_decode_msgpack_into_mem(const char *buf, struct Mem *mem, uint32_t *len) -{ - const char *start_buf = buf; - switch (mp_typeof(*buf)) { - case MP_ARRAY: { - mem->z = (char *)buf; - mp_next(&buf); - mem->n = buf - mem->z; - mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype; - mem->subtype = SQL_SUBTYPE_MSGPACK; - mem->field_type = FIELD_TYPE_ARRAY; - break; - } - case MP_MAP: { - mem->z = (char *)buf; - mp_next(&buf); - mem->n = buf - mem->z; - mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype; - mem->subtype = SQL_SUBTYPE_MSGPACK; - mem->field_type = FIELD_TYPE_MAP; - break; - } - case MP_EXT: { - mem->z = (char *)buf; - mp_next(&buf); - mem->n = buf - mem->z; - mem->flags = MEM_Blob | MEM_Ephem; - mem->field_type = FIELD_TYPE_VARBINARY; - break; - } - case MP_NIL: { - mp_decode_nil(&buf); - mem->flags = MEM_Null; - mem->field_type = field_type_MAX; - break; - } - case MP_BOOL: { - mem->u.b = mp_decode_bool(&buf); - mem->flags = MEM_Bool; - mem->field_type = FIELD_TYPE_BOOLEAN; - break; - } - case MP_UINT: { - uint64_t v = mp_decode_uint(&buf); - mem->u.u = v; - mem->flags = MEM_UInt; - mem->field_type = FIELD_TYPE_INTEGER; - break; - } - case MP_INT: { - mem->u.i = mp_decode_int(&buf); - mem->flags = MEM_Int; - mem->field_type = FIELD_TYPE_INTEGER; - break; - } - case MP_STR: { - /* XXX u32->int */ - mem->n = (int) mp_decode_strl(&buf); - mem->flags = MEM_Str | MEM_Ephem; - mem->field_type = FIELD_TYPE_STRING; -install_blob: - mem->z = (char *)buf; - buf += mem->n; - break; - } - case MP_BIN: { - /* XXX u32->int */ - mem->n = (int) mp_decode_binl(&buf); - mem->flags = MEM_Blob | MEM_Ephem; - mem->field_type = FIELD_TYPE_VARBINARY; - goto install_blob; - } - case MP_FLOAT: { - mem->u.r = mp_decode_float(&buf); - if (sqlIsNaN(mem->u.r)) { - mem->flags = MEM_Null; - mem->field_type = FIELD_TYPE_DOUBLE; - } else { - mem->flags = MEM_Real; - mem->field_type = FIELD_TYPE_DOUBLE; - } - break; - } - case MP_DOUBLE: { - mem->u.r = mp_decode_double(&buf); - if (sqlIsNaN(mem->u.r)) { - mem->flags = MEM_Null; - mem->field_type = FIELD_TYPE_DOUBLE; - } else { - mem->flags = MEM_Real; - mem->field_type = FIELD_TYPE_DOUBLE; - } - break; - } - default: - unreachable(); - } - *len = (uint32_t)(buf - start_buf); - return 0; -} - void sqlVdbeRecordUnpackMsgpack(struct key_def *key_def, /* Information about the record format */ const void *pKey, /* The binary record */ diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c index 1101f7205..092350caa 100644 --- a/src/box/sql/vdbemem.c +++ b/src/box/sql/vdbemem.c @@ -37,867 +37,13 @@ * name sql_value */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" #include "tarantoolInt.h" #include "box/schema.h" #include "box/tuple.h" #include "mpstream/mpstream.h" -#ifdef SQL_DEBUG -/* - * Check invariants on a Mem object. - * - * This routine is intended for use inside of assert() statements, like - * this: assert( sqlVdbeCheckMemInvariants(pMem) ); - */ -int -sqlVdbeCheckMemInvariants(Mem * p) -{ - /* If MEM_Dyn is set then Mem.xDel!=0. - * Mem.xDel is might not be initialized if MEM_Dyn is clear. - */ - assert((p->flags & MEM_Dyn) == 0 || p->xDel != 0); - - /* MEM_Dyn may only be set if Mem.szMalloc==0. In this way we - * ensure that if Mem.szMalloc>0 then it is safe to do - * Mem.z = Mem.zMalloc without having to check Mem.flags&MEM_Dyn. - * That saves a few cycles in inner loops. - */ - assert((p->flags & MEM_Dyn) == 0 || p->szMalloc == 0); - - /* Cannot be both MEM_Int and MEM_Real at the same time */ - assert((p->flags & (MEM_Int | MEM_Real)) != (MEM_Int | MEM_Real)); - /* Can't be both UInt and Int at the same time. */ - assert((p->flags & (MEM_Int | MEM_UInt)) != (MEM_Int | MEM_UInt)); - - /* The szMalloc field holds the correct memory allocation size */ - assert(p->szMalloc == 0 || - p->szMalloc == sqlDbMallocSize(p->db, p->zMalloc)); - - /* If p holds a string or blob, the Mem.z must point to exactly - * one of the following: - * - * (1) Memory in Mem.zMalloc and managed by the Mem object - * (2) Memory to be freed using Mem.xDel - * (3) An ephemeral string or blob - * (4) A static string or blob - */ - if ((p->flags & (MEM_Str | MEM_Blob)) && p->n > 0) { - assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) + - ((p->flags & MEM_Dyn) != 0 ? 1 : 0) + - ((p->flags & MEM_Ephem) != 0 ? 1 : 0) + - ((p->flags & MEM_Static) != 0 ? 1 : 0) == 1); - } - return 1; -} -#endif - -/* - * Make sure pMem->z points to a writable allocation of at least - * min(n,32) bytes. - * - * If the bPreserve argument is true, then copy of the content of - * pMem->z into the new allocation. pMem must be either a string or - * blob if bPreserve is true. If bPreserve is false, any prior content - * in pMem->z is discarded. - */ -SQL_NOINLINE int -sqlVdbeMemGrow(Mem * pMem, int n, int bPreserve) -{ - assert(sqlVdbeCheckMemInvariants(pMem)); - testcase(pMem->db == 0); - - /* If the bPreserve flag is set to true, then the memory cell must already - * contain a valid string or blob value. - */ - assert(bPreserve == 0 || pMem->flags & (MEM_Blob | MEM_Str)); - testcase(bPreserve && pMem->z == 0); - - assert(pMem->szMalloc == 0 || - pMem->szMalloc == sqlDbMallocSize(pMem->db, pMem->zMalloc)); - if (pMem->szMalloc < n) { - if (n < 32) - n = 32; - if (bPreserve && pMem->szMalloc > 0 && pMem->z == pMem->zMalloc) { - pMem->z = pMem->zMalloc = - sqlDbReallocOrFree(pMem->db, pMem->z, n); - bPreserve = 0; - } else { - if (pMem->szMalloc > 0) - sqlDbFree(pMem->db, pMem->zMalloc); - pMem->zMalloc = sqlDbMallocRaw(pMem->db, n); - } - if (pMem->zMalloc == 0) { - sqlVdbeMemSetNull(pMem); - pMem->z = 0; - pMem->szMalloc = 0; - return -1; - } else { - pMem->szMalloc = sqlDbMallocSize(pMem->db, - pMem->zMalloc); - } - } - - if (bPreserve && pMem->z && pMem->z != pMem->zMalloc) { - memcpy(pMem->zMalloc, pMem->z, pMem->n); - } - if ((pMem->flags & MEM_Dyn) != 0) { - assert(pMem->xDel != 0 && pMem->xDel != SQL_DYNAMIC); - pMem->xDel((void *)(pMem->z)); - } - - pMem->z = pMem->zMalloc; - pMem->flags &= ~(MEM_Dyn | MEM_Ephem | MEM_Static); - return 0; -} - -/* - * Change the pMem->zMalloc allocation to be at least szNew bytes. - * If pMem->zMalloc already meets or exceeds the requested size, this - * routine is a no-op. - * - * Any prior string or blob content in the pMem object may be discarded. - * The pMem->xDel destructor is called, if it exists. Though MEM_Str - * and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null - * values are preserved. - * - * Return 0 on success or -1 if unable to complete the resizing. - */ -int -sqlVdbeMemClearAndResize(Mem * pMem, int szNew) -{ - assert(szNew > 0); - assert((pMem->flags & MEM_Dyn) == 0 || pMem->szMalloc == 0); - if (pMem->szMalloc < szNew) { - return sqlVdbeMemGrow(pMem, szNew, 0); - } - assert((pMem->flags & MEM_Dyn) == 0); - pMem->z = pMem->zMalloc; - pMem->flags &= (MEM_Null | MEM_Int | MEM_Real); - return 0; -} - -/* - * Change pMem so that its MEM_Str or MEM_Blob value is stored in - * MEM.zMalloc, where it can be safely written. - * - * Return 0 on success or -1 if malloc fails. - */ -int -sqlVdbeMemMakeWriteable(Mem * pMem) -{ - if ((pMem->flags & (MEM_Str | MEM_Blob)) != 0) { - if (ExpandBlob(pMem)) - return -1; - if (pMem->szMalloc == 0 || pMem->z != pMem->zMalloc) { - if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) { - return -1; - } - pMem->z[pMem->n] = 0; - pMem->z[pMem->n + 1] = 0; - pMem->flags |= MEM_Term; - } - } - pMem->flags &= ~MEM_Ephem; -#ifdef SQL_DEBUG - pMem->pScopyFrom = 0; -#endif - - return 0; -} - -/* - * 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->flags & MEM_Blob); - - /* 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; -} - -/* - * It is already known that pMem contains an unterminated string. - * Add the zero terminator. - */ -static SQL_NOINLINE int -vdbeMemAddTerminator(Mem * pMem) -{ - if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) { - return -1; - } - pMem->z[pMem->n] = 0; - pMem->z[pMem->n + 1] = 0; - pMem->flags |= MEM_Term; - return 0; -} - -/* - * Make sure the given Mem is \u0000 terminated. - */ -int -sqlVdbeMemNulTerminate(Mem * pMem) -{ - testcase((pMem->flags & (MEM_Term | MEM_Str)) == (MEM_Term | MEM_Str)); - testcase((pMem->flags & (MEM_Term | MEM_Str)) == 0); - if ((pMem->flags & (MEM_Term | MEM_Str)) != MEM_Str) { - return 0; /* Nothing to do */ - } else { - return vdbeMemAddTerminator(pMem); - } -} - -static inline bool -mem_has_msgpack_subtype(struct Mem *mem) -{ - return (mem->flags & MEM_Subtype) != 0 && - mem->subtype == SQL_SUBTYPE_MSGPACK; -} - -/* - * Add MEM_Str to the set of representations for the given Mem. Numbers - * are converted using sql_snprintf(). Converting a BLOB to a string - * is a no-op. - * - * Existing representations MEM_Int and MEM_Real are invalidated if - * bForce is true but are retained if bForce is false. - * - * A MEM_Null value will never be passed to this function. This function is - * used for converting values to text for returning to the user (i.e. via - * sql_value_text()), or for ensuring that values to be used as btree - * keys are strings. In the former case a NULL pointer is returned the - * user and the latter is an internal programming error. - */ -int -sqlVdbeMemStringify(Mem * pMem) -{ - int fg = pMem->flags; - int nByte = 32; - - if ((fg & (MEM_Null | MEM_Str | MEM_Blob)) != 0 && - !mem_has_msgpack_subtype(pMem)) - return 0; - - assert(!(fg & MEM_Zero)); - assert((fg & (MEM_Int | MEM_UInt | MEM_Real | MEM_Bool | - MEM_Blob)) != 0); - assert(EIGHT_BYTE_ALIGNMENT(pMem)); - - /* - * In case we have ARRAY/MAP we should save decoded value - * before clearing pMem->z. - */ - char *value = NULL; - if (mem_has_msgpack_subtype(pMem)) { - const char *value_str = mp_str(pMem->z); - nByte = strlen(value_str) + 1; - value = region_alloc(&fiber()->gc, nByte); - memcpy(value, value_str, nByte); - } - - if (sqlVdbeMemClearAndResize(pMem, nByte)) { - return -1; - } - if (fg & MEM_Int) { - sql_snprintf(nByte, pMem->z, "%lld", pMem->u.i); - pMem->flags &= ~MEM_Int; - } else if ((fg & MEM_UInt) != 0) { - sql_snprintf(nByte, pMem->z, "%llu", pMem->u.u); - pMem->flags &= ~MEM_UInt; - } else if ((fg & MEM_Bool) != 0) { - sql_snprintf(nByte, pMem->z, "%s", - SQL_TOKEN_BOOLEAN(pMem->u.b)); - pMem->flags &= ~MEM_Bool; - } else if (mem_has_msgpack_subtype(pMem)) { - sql_snprintf(nByte, pMem->z, "%s", value); - pMem->flags &= ~MEM_Subtype; - pMem->subtype = SQL_SUBTYPE_NO; - } else { - assert(fg & MEM_Real); - sql_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r); - pMem->flags &= ~MEM_Real; - } - pMem->n = sqlStrlen30(pMem->z); - pMem->flags |= MEM_Str | MEM_Term; - 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->flags & MEM_Null) != 0 || func == mem->u.func); - sql_context ctx; - memset(&ctx, 0, sizeof(ctx)); - Mem t; - memset(&t, 0, sizeof(t)); - t.flags = MEM_Null; - t.db = mem->db; - t.field_type = field_type_MAX; - 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; -} - -/* - * If the memory cell contains a value that must be freed by - * invoking the external callback in Mem.xDel, then this routine - * will free that value. It also sets Mem.flags to MEM_Null. - * - * This is a helper routine for sqlVdbeMemSetNull() and - * for sqlVdbeMemRelease(). Use those other routines as the - * entry point for releasing Mem resources. - */ -static SQL_NOINLINE void -vdbeMemClearExternAndSetNull(Mem * p) -{ - assert(VdbeMemDynamic(p)); - if (p->flags & MEM_Agg) { - sql_vdbemem_finalize(p, p->u.func); - assert((p->flags & MEM_Agg) == 0); - testcase(p->flags & MEM_Dyn); - } - if (p->flags & MEM_Dyn) { - assert(p->xDel != SQL_DYNAMIC && p->xDel != 0); - p->xDel((void *)p->z); - } else if (p->flags & MEM_Frame) { - VdbeFrame *pFrame = p->u.pFrame; - pFrame->pParent = pFrame->v->pDelFrame; - pFrame->v->pDelFrame = pFrame; - } - p->flags = MEM_Null; -} - -/* - * Release memory held by the Mem p, both external memory cleared - * by p->xDel and memory in p->zMalloc. - * - * This is a helper routine invoked by sqlVdbeMemRelease() in - * the unusual case where there really is memory in p that needs - * to be freed. - */ -static SQL_NOINLINE void -vdbeMemClear(Mem * p) -{ - if (VdbeMemDynamic(p)) { - vdbeMemClearExternAndSetNull(p); - } - if (p->szMalloc) { - sqlDbFree(p->db, p->zMalloc); - p->szMalloc = 0; - } - p->z = 0; -} - -/* - * Release any memory resources held by the Mem. Both the memory that is - * free by Mem.xDel and the Mem.zMalloc allocation are freed. - * - * Use this routine prior to clean up prior to abandoning a Mem, or to - * reset a Mem back to its minimum memory utilization. - * - * Use sqlVdbeMemSetNull() to release just the Mem.xDel space - * prior to inserting new content into the Mem. - */ -void -sqlVdbeMemRelease(Mem * p) -{ - assert(sqlVdbeCheckMemInvariants(p)); - if (VdbeMemDynamic(p) || p->szMalloc) { - vdbeMemClear(p); - } -} - -enum mp_type -mem_mp_type(struct Mem *mem) -{ - switch (mem->flags & MEM_PURE_TYPE_MASK) { - case MEM_Int: - return MP_INT; - case MEM_UInt: - return MP_UINT; - case MEM_Real: - return MP_DOUBLE; - case MEM_Str: - return MP_STR; - case MEM_Blob: - if ((mem->flags & MEM_Subtype) == 0 || - mem->subtype != SQL_SUBTYPE_MSGPACK) - return MP_BIN; - assert(mp_typeof(*mem->z) == MP_MAP || - mp_typeof(*mem->z) == MP_ARRAY); - return mp_typeof(*mem->z); - case MEM_Bool: - return MP_BOOL; - case MEM_Null: - return MP_NIL; - default: unreachable(); - } -} - -/* - * Convert a 64-bit IEEE double into a 64-bit signed integer. - * If the double is out of range of a 64-bit signed integer then - * return the closest available 64-bit signed integer. - */ -static int -doubleToInt64(double r, int64_t *i) -{ - /* - * Many compilers we encounter do not define constants for the - * minimum and maximum 64-bit integers, or they define them - * inconsistently. And many do not understand the "LL" notation. - * So we define our own static constants here using nothing - * larger than a 32-bit integer constant. - */ - static const int64_t maxInt = LARGEST_INT64; - static const int64_t minInt = SMALLEST_INT64; - if (r <= (double)minInt) { - *i = minInt; - return -1; - } else if (r >= (double)maxInt) { - *i = maxInt; - return -1; - } else { - *i = (int64_t) r; - return *i != r; - } -} - -/* - * Return some kind of integer value which is the best we can do - * at representing the value that *pMem describes as an integer. - * If pMem is an integer, then the value is exact. If pMem is - * a floating-point then the value returned is the integer part. - * If pMem is a string or blob, then we make an attempt to convert - * it into an integer and return that. If pMem represents an - * an SQL-NULL value, return 0. - * - * If pMem represents a string value, its encoding might be changed. - */ -int -sqlVdbeIntValue(Mem * pMem, int64_t *i, bool *is_neg) -{ - int flags; - assert(EIGHT_BYTE_ALIGNMENT(pMem)); - flags = pMem->flags; - if (flags & MEM_Int) { - *i = pMem->u.i; - *is_neg = true; - return 0; - } else if (flags & MEM_UInt) { - *i = pMem->u.u; - *is_neg = false; - return 0; - } else if (flags & MEM_Real) { - *is_neg = pMem->u.r < 0; - return doubleToInt64(pMem->u.r, i); - } else if (flags & (MEM_Str)) { - assert(pMem->z || pMem->n == 0); - if (sql_atoi64(pMem->z, i, is_neg, pMem->n) == 0) - return 0; - } - return -1; -} - -/* - * Return the best representation of pMem that we can get into a - * double. If pMem is already a double or an integer, return its - * value. If it is a string or blob, try to convert it to a double. - * If it is a NULL, return 0.0. - */ -int -sqlVdbeRealValue(Mem * pMem, double *v) -{ - assert(EIGHT_BYTE_ALIGNMENT(pMem)); - if (pMem->flags & MEM_Real) { - *v = pMem->u.r; - return 0; - } else if (pMem->flags & MEM_Int) { - *v = (double)pMem->u.i; - return 0; - } else if ((pMem->flags & MEM_UInt) != 0) { - *v = (double)pMem->u.u; - return 0; - } else if (pMem->flags & MEM_Str) { - if (sqlAtoF(pMem->z, v, pMem->n)) - return 0; - } - return -1; -} - -int -mem_value_bool(const struct Mem *mem, bool *b) -{ - if ((mem->flags & MEM_Bool) != 0) { - *b = mem->u.b; - return 0; - } - return -1; -} - -/* - * The MEM structure is already a MEM_Real. Try to also make it a - * MEM_Int if we can. - */ -int -mem_apply_integer_type(Mem *pMem) -{ - int rc; - i64 ix; - assert(pMem->flags & MEM_Real); - assert(EIGHT_BYTE_ALIGNMENT(pMem)); - - if ((rc = doubleToInt64(pMem->u.r, (int64_t *) &ix)) == 0) - mem_set_int(pMem, ix, pMem->u.r <= -1); - return rc; -} - -/* - * Convert pMem so that it is of type MEM_Real. - * Invalidate any prior representations. - */ -int -sqlVdbeMemRealify(Mem * pMem) -{ - assert(EIGHT_BYTE_ALIGNMENT(pMem)); - double v; - if (sqlVdbeRealValue(pMem, &v)) - return -1; - mem_set_double(pMem, v); - return 0; -} - -int -vdbe_mem_numerify(struct Mem *mem) -{ - if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real | MEM_Null)) != 0) - return 0; - if ((mem->flags & MEM_Bool) != 0) { - mem->u.u = mem->u.b; - MemSetTypeFlag(mem, MEM_UInt); - return 0; - } - assert((mem->flags & (MEM_Blob | MEM_Str)) != 0); - bool is_neg; - int64_t i; - if (sql_atoi64(mem->z, &i, &is_neg, mem->n) == 0) { - mem_set_int(mem, i, is_neg); - } else { - double d; - if (sqlAtoF(mem->z, &d, mem->n) == 0) - return -1; - mem_set_double(mem, d); - } - return 0; -} - -/** - * According to ANSI SQL string value can be converted to boolean - * type if string consists of literal "true" or "false" and - * number of leading and trailing spaces. - * - * For instance, " tRuE " can be successfully converted to - * boolean value true. - * - * @param str String to be converted to boolean. Assumed to be - * null terminated. - * @param[out] result Resulting value of cast. - * @retval 0 If string satisfies conditions above. - * @retval -1 Otherwise. - */ -static int -str_cast_to_boolean(const char *str, bool *result) -{ - assert(str != NULL); - for (; *str == ' '; str++); - if (strncasecmp(str, SQL_TOKEN_TRUE, strlen(SQL_TOKEN_TRUE)) == 0) { - *result = true; - str += 4; - } else if (strncasecmp(str, SQL_TOKEN_FALSE, - strlen(SQL_TOKEN_FALSE)) == 0) { - *result = false; - str += 5; - } else { - return -1; - } - for (; *str != '\0'; ++str) { - if (*str != ' ') - return -1; - } - return 0; -} - -/* - * Cast the datatype of the value in pMem according to the type - * @type. Casting is different from applying type in that a cast - * is forced. In other words, the value is converted into the desired - * type even if that results in loss of data. This routine is - * used (for example) to implement the SQL "cast()" operator. - */ -int -sqlVdbeMemCast(Mem * pMem, enum field_type type) -{ - assert(type < field_type_MAX); - if (pMem->flags & MEM_Null) - return 0; - switch (type) { - case FIELD_TYPE_SCALAR: - return 0; - case FIELD_TYPE_BOOLEAN: - if ((pMem->flags & MEM_Int) != 0) { - mem_set_bool(pMem, pMem->u.i); - return 0; - } - if ((pMem->flags & MEM_UInt) != 0) { - mem_set_bool(pMem, pMem->u.u); - return 0; - } - if ((pMem->flags & MEM_Real) != 0) { - mem_set_bool(pMem, pMem->u.r); - return 0; - } - if ((pMem->flags & MEM_Str) != 0) { - bool value; - if (str_cast_to_boolean(pMem->z, &value) != 0) - return -1; - mem_set_bool(pMem, value); - return 0; - } - if ((pMem->flags & MEM_Bool) != 0) - return 0; - return -1; - case FIELD_TYPE_INTEGER: - case FIELD_TYPE_UNSIGNED: - if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) { - bool is_neg; - int64_t val; - if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0) - return -1; - if (type == FIELD_TYPE_UNSIGNED && is_neg) - return -1; - mem_set_int(pMem, val, is_neg); - return 0; - } - if ((pMem->flags & MEM_Bool) != 0) { - pMem->u.u = pMem->u.b; - MemSetTypeFlag(pMem, MEM_UInt); - return 0; - } - if ((pMem->flags & MEM_Real) != 0) { - double d; - if (sqlVdbeRealValue(pMem, &d) != 0) - return -1; - if (d < (double)INT64_MAX && d >= (double)INT64_MIN) { - mem_set_int(pMem, d, d <= -1); - return 0; - } - if (d >= (double)INT64_MAX && d < (double)UINT64_MAX) { - mem_set_u64(pMem, d); - return 0; - } - return -1; - } - if (type == FIELD_TYPE_UNSIGNED && - (pMem->flags & MEM_UInt) == 0) - return -1; - return 0; - case FIELD_TYPE_DOUBLE: - return sqlVdbeMemRealify(pMem); - case FIELD_TYPE_NUMBER: - return vdbe_mem_numerify(pMem); - case FIELD_TYPE_VARBINARY: - if ((pMem->flags & MEM_Blob) != 0) - return 0; - if ((pMem->flags & MEM_Str) != 0) { - MemSetTypeFlag(pMem, MEM_Str); - return 0; - } - return -1; - default: - assert(type == FIELD_TYPE_STRING); - assert(MEM_Str == (MEM_Blob >> 3)); - if ((pMem->flags & MEM_Bool) != 0) { - const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b); - sqlVdbeMemSetStr(pMem, str_bool, strlen(str_bool), 1, - SQL_TRANSIENT); - return 0; - } - pMem->flags |= (pMem->flags & MEM_Blob) >> 3; - sql_value_apply_type(pMem, FIELD_TYPE_STRING); - assert(pMem->flags & MEM_Str || pMem->db->mallocFailed); - pMem->flags &= - ~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero); - return 0; - } -} - -/* - * Initialize bulk memory to be a consistent Mem object. - * - * The minimum amount of initialization feasible is performed. - */ -void -sqlVdbeMemInit(Mem * pMem, sql * db, u32 flags) -{ - assert((flags & ~MEM_TypeMask) == 0); - pMem->flags = flags; - pMem->db = db; - pMem->szMalloc = 0; - pMem->field_type = field_type_MAX; -} - -/* - * Delete any previous value and set the value stored in *pMem to NULL. - * - * This routine calls the Mem.xDel destructor to dispose of values that - * require the destructor. But it preserves the Mem.zMalloc memory allocation. - * To free all resources, use sqlVdbeMemRelease(), which both calls this - * routine to invoke the destructor and deallocates Mem.zMalloc. - * - * Use this routine to reset the Mem prior to insert a new value. - * - * Use sqlVdbeMemRelease() to complete erase the Mem prior to abandoning it. - */ -void -sqlVdbeMemSetNull(Mem * pMem) -{ - if (VdbeMemDynamic(pMem)) { - vdbeMemClearExternAndSetNull(pMem); - } else { - pMem->flags = MEM_Null; - } -} - -void -sqlValueSetNull(sql_value * p) -{ - sqlVdbeMemSetNull((Mem *) p); -} - -void -mem_set_ptr(struct Mem *mem, void *ptr) -{ - sqlVdbeMemRelease(mem); - mem->flags = MEM_Ptr; - mem->u.p = ptr; -} - -/* - * Delete any previous value and set the value to be a BLOB of length - * n containing all zeros. - */ -void -sqlVdbeMemSetZeroBlob(Mem * pMem, int n) -{ - sqlVdbeMemRelease(pMem); - pMem->flags = MEM_Blob | MEM_Zero; - pMem->n = 0; - if (n < 0) - n = 0; - pMem->u.nZero = n; - pMem->z = 0; -} - -void -mem_set_bool(struct Mem *mem, bool value) -{ - sqlVdbeMemSetNull(mem); - mem->u.b = value; - mem->flags = MEM_Bool; - mem->field_type = FIELD_TYPE_BOOLEAN; -} - -void -mem_set_i64(struct Mem *mem, int64_t value) -{ - if (VdbeMemDynamic(mem)) - sqlVdbeMemSetNull(mem); - mem->u.i = value; - int flag = value < 0 ? MEM_Int : MEM_UInt; - MemSetTypeFlag(mem, flag); - mem->field_type = FIELD_TYPE_INTEGER; -} - -void -mem_set_u64(struct Mem *mem, uint64_t value) -{ - if (VdbeMemDynamic(mem)) - sqlVdbeMemSetNull(mem); - mem->u.u = value; - MemSetTypeFlag(mem, MEM_UInt); - mem->field_type = FIELD_TYPE_UNSIGNED; -} - -void -mem_set_int(struct Mem *mem, int64_t value, bool is_neg) -{ - if (VdbeMemDynamic(mem)) - sqlVdbeMemSetNull(mem); - if (is_neg) { - assert(value < 0); - mem->u.i = value; - MemSetTypeFlag(mem, MEM_Int); - } else { - mem->u.u = value; - MemSetTypeFlag(mem, MEM_UInt); - } - mem->field_type = FIELD_TYPE_INTEGER; -} - -void -mem_set_double(struct Mem *mem, double value) -{ - sqlVdbeMemSetNull(mem); - if (sqlIsNaN(value)) - return; - mem->u.r = value; - MemSetTypeFlag(mem, MEM_Real); - mem->field_type = FIELD_TYPE_DOUBLE; -} - -/* - * Return true if the Mem object contains a TEXT or BLOB that is - * too large - whose size exceeds SQL_MAX_LENGTH. - */ -int -sqlVdbeMemTooBig(Mem * p) -{ - assert(p->db != 0); - if (p->flags & (MEM_Str | MEM_Blob)) { - int n = p->n; - if (p->flags & MEM_Zero) { - n += p->u.nZero; - } - return n > p->db->aLimit[SQL_LIMIT_LENGTH]; - } - return 0; -} - #ifdef SQL_DEBUG /* * This routine prepares a memory cell for modification by breaking @@ -921,173 +67,6 @@ sqlVdbeMemAboutToChange(Vdbe * pVdbe, Mem * pMem) pMem->pScopyFrom = 0; } #endif /* SQL_DEBUG */ - -/* - * Make an shallow copy of pFrom into pTo. Prior contents of - * pTo are freed. The pFrom->z field is not duplicated. If - * pFrom->z is used, then pTo->z points to the same thing as pFrom->z - * and flags gets srcType (either MEM_Ephem or MEM_Static). - */ -static SQL_NOINLINE void -vdbeClrCopy(Mem * pTo, const Mem * pFrom, int eType) -{ - vdbeMemClearExternAndSetNull(pTo); - assert(!VdbeMemDynamic(pTo)); - sqlVdbeMemShallowCopy(pTo, pFrom, eType); -} - -void -sqlVdbeMemShallowCopy(Mem * pTo, const Mem * pFrom, int srcType) -{ - assert(pTo->db == pFrom->db); - if (VdbeMemDynamic(pTo)) { - vdbeClrCopy(pTo, pFrom, srcType); - return; - } - memcpy(pTo, pFrom, MEMCELLSIZE); - if ((pFrom->flags & MEM_Static) == 0) { - pTo->flags &= ~(MEM_Dyn | MEM_Static | MEM_Ephem); - assert(srcType == MEM_Ephem || srcType == MEM_Static); - pTo->flags |= srcType; - } -} - -/* - * Make a full copy of pFrom into pTo. Prior contents of pTo are - * freed before the copy is made. - */ -int -sqlVdbeMemCopy(Mem * pTo, const Mem * pFrom) -{ - int rc = 0; - - if (VdbeMemDynamic(pTo)) - vdbeMemClearExternAndSetNull(pTo); - memcpy(pTo, pFrom, MEMCELLSIZE); - pTo->flags &= ~MEM_Dyn; - if (pTo->flags & (MEM_Str | MEM_Blob)) { - if (0 == (pFrom->flags & MEM_Static)) { - pTo->flags |= MEM_Ephem; - rc = sqlVdbeMemMakeWriteable(pTo); - } - } - - return rc; -} - -/* - * Transfer the contents of pFrom to pTo. Any existing value in pTo is - * freed. If pFrom contains ephemeral data, a copy is made. - * - * pFrom contains an SQL NULL when this routine returns. - */ -void -sqlVdbeMemMove(Mem * pTo, Mem * pFrom) -{ - assert(pFrom->db == 0 || pTo->db == 0 || pFrom->db == pTo->db); - - sqlVdbeMemRelease(pTo); - memcpy(pTo, pFrom, sizeof(Mem)); - pFrom->flags = MEM_Null; - pFrom->szMalloc = 0; -} - -/* - * Change the value of a Mem to be a string or a BLOB. - * - * The memory management strategy depends on the value of the xDel - * parameter. If the value passed is SQL_TRANSIENT, then the - * string is copied into a (possibly existing) buffer managed by the - * Mem structure. Otherwise, any existing buffer is freed and the - * pointer copied. - * - * If the string is too large (if it exceeds the SQL_LIMIT_LENGTH - * size limit) then no memory allocation occurs. If the string can be - * stored without allocating memory, then it is. If a memory allocation - * is required to store the string, then value of pMem is unchanged. In - * either case, error is returned. - */ -int -sqlVdbeMemSetStr(Mem * pMem, /* Memory cell to set to string value */ - const char *z, /* String pointer */ - int n, /* Bytes in string, or negative */ - u8 not_blob, /* Encoding of z. 0 for BLOBs */ - void (*xDel) (void *) /* Destructor function */ - ) -{ - int nByte = n; /* New value for pMem->n */ - int iLimit; /* Maximum allowed string or blob size */ - u16 flags = 0; /* New value for pMem->flags */ - - /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ - if (!z) { - sqlVdbeMemSetNull(pMem); - return 0; - } - - if (pMem->db) { - iLimit = pMem->db->aLimit[SQL_LIMIT_LENGTH]; - } else { - iLimit = SQL_MAX_LENGTH; - } - flags = (not_blob == 0 ? MEM_Blob : MEM_Str); - if (nByte < 0) { - assert(not_blob != 0); - nByte = sqlStrlen30(z); - if (nByte > iLimit) - nByte = iLimit + 1; - flags |= MEM_Term; - } - - /* The following block sets the new values of Mem.z and Mem.xDel. It - * also sets a flag in local variable "flags" to indicate the memory - * management (one of MEM_Dyn or MEM_Static). - */ - if (xDel == SQL_TRANSIENT) { - int nAlloc = nByte; - if (flags & MEM_Term) { - nAlloc += 1; //SQL_UTF8 - } - if (nByte > iLimit) { - diag_set(ClientError, ER_SQL_EXECUTE, "string or binary"\ - "string is too big"); - return -1; - } - testcase(nAlloc == 0); - testcase(nAlloc == 31); - testcase(nAlloc == 32); - if (sqlVdbeMemClearAndResize(pMem, MAX(nAlloc, 32))) { - return -1; - } - memcpy(pMem->z, z, nAlloc); - } else if (xDel == SQL_DYNAMIC) { - sqlVdbeMemRelease(pMem); - pMem->zMalloc = pMem->z = (char *)z; - pMem->szMalloc = sqlDbMallocSize(pMem->db, pMem->zMalloc); - } else { - sqlVdbeMemRelease(pMem); - pMem->z = (char *)z; - pMem->xDel = xDel; - flags |= ((xDel == SQL_STATIC) ? MEM_Static : MEM_Dyn); - } - - pMem->n = nByte; - pMem->flags = flags; - assert((pMem->flags & (MEM_Str | MEM_Blob)) != 0); - if ((pMem->flags & MEM_Str) != 0) - pMem->field_type = FIELD_TYPE_STRING; - else - pMem->field_type = FIELD_TYPE_VARBINARY; - - if (nByte > iLimit) { - diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\ - "is too big"); - return -1; - } - - return 0; -} - /* * Move data out of a btree key or data field and into a Mem structure. * The data is payload from the entry that pCur is currently pointing @@ -1153,79 +132,6 @@ sqlVdbeMemFromBtree(BtCursor * pCur, /* Cursor pointing at record to retrieve. * return rc; } -/* - * The pVal argument is known to be a value other than NULL. - * Convert it into a string with encoding enc and return a pointer - * to a zero-terminated version of that string. - */ -static SQL_NOINLINE const void * -valueToText(sql_value * pVal) -{ - assert(pVal != 0); - assert((pVal->flags & (MEM_Null)) == 0); - if ((pVal->flags & (MEM_Blob | MEM_Str)) && - !mem_has_msgpack_subtype(pVal)) { - if (ExpandBlob(pVal)) - return 0; - pVal->flags |= MEM_Str; - sqlVdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */ - } else { - sqlVdbeMemStringify(pVal); - assert(0 == (1 & SQL_PTR_TO_INT(pVal->z))); - } - return pVal->z; -} - -/* This function is only available internally, it is not part of the - * external API. It works in a similar way to sql_value_text(), - * except the data returned is in the encoding specified by the second - * parameter, which must be one of SQL_UTF16BE, SQL_UTF16LE or - * SQL_UTF8. - * - * (2006-02-16:) The enc value can be or-ed with SQL_UTF16_ALIGNED. - * If that is the case, then the result must be aligned on an even byte - * boundary. - */ -const void * -sqlValueText(sql_value * pVal) -{ - if (!pVal) - return 0; - if ((pVal->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term)) { - return pVal->z; - } - if (pVal->flags & MEM_Null) { - return 0; - } - return valueToText(pVal); -} - -const char * -sql_value_to_diag_str(sql_value *value) -{ - enum mp_type mp_type = sql_value_type(value); - if (mp_type_is_bloblike(mp_type)) { - if (mem_has_msgpack_subtype(value)) - return sqlValueText(value); - return "varbinary"; - } - return sqlValueText(value); -} - -/* - * Create a new sql_value object. - */ -sql_value * -sqlValueNew(sql * db) -{ - Mem *p = sqlDbMallocZero(db, sizeof(*p)); - if (p) { - p->flags = MEM_Null; - p->db = db; - } - return p; -} - /* * Context object passed by sqlStat4ProbeSetValue() through to * valueNew(). See comments above valueNew() for details. @@ -1747,125 +653,3 @@ sqlStat4ProbeFree(UnpackedRecord * pRec) sqlDbFree(aMem[0].db, pRec); } } - -/* - * Change the string value of an sql_value object - */ -void -sqlValueSetStr(sql_value * v, /* Value to be set */ - int n, /* Length of string z */ - const void *z, /* Text of the new string */ - void (*xDel) (void *) /* Destructor for the string */ - ) -{ - if (v) - sqlVdbeMemSetStr((Mem *) v, z, n, 1, xDel); -} - -/* - * Free an sql_value object - */ -void -sqlValueFree(sql_value * v) -{ - if (!v) - return; - sqlVdbeMemRelease((Mem *) v); - sqlDbFree(((Mem *) v)->db, v); -} - -/* - * The sqlValueBytes() routine returns the number of bytes in the - * sql_value object assuming that it uses the encoding "enc". - * The valueBytes() routine is a helper function. - */ -static SQL_NOINLINE int -valueBytes(sql_value * pVal) -{ - return valueToText(pVal) != 0 ? pVal->n : 0; -} - -int -sqlValueBytes(sql_value * pVal) -{ - Mem *p = (Mem *) pVal; - assert((p->flags & MEM_Null) == 0 - || (p->flags & (MEM_Str | MEM_Blob)) == 0); - if ((p->flags & MEM_Str) != 0) { - return p->n; - } - if ((p->flags & MEM_Blob) != 0) { - if (p->flags & MEM_Zero) { - return p->n + p->u.nZero; - } else { - return p->n; - } - } - if (p->flags & MEM_Null) - return 0; - return valueBytes(pVal); -} - -void -mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var) -{ - assert(memIsValid(var)); - int64_t i; - if (var->flags & MEM_Null) { - mpstream_encode_nil(stream); - } else if (var->flags & MEM_Real) { - mpstream_encode_double(stream, var->u.r); - } else if (var->flags & MEM_Int) { - i = var->u.i; - mpstream_encode_int(stream, i); - } else if (var->flags & MEM_UInt) { - i = var->u.u; - mpstream_encode_uint(stream, i); - } else if (var->flags & MEM_Str) { - mpstream_encode_strn(stream, var->z, var->n); - } else if (var->flags & MEM_Bool) { - mpstream_encode_bool(stream, var->u.b); - } else { - /* - * Emit BIN header iff the BLOB doesn't store - * MsgPack content. - */ - if (!mem_has_msgpack_subtype(var)) { - uint32_t binl = var->n + - ((var->flags & MEM_Zero) ? - var->u.nZero : 0); - mpstream_encode_binl(stream, binl); - } - mpstream_memcpy(stream, var->z, var->n); - if (var->flags & MEM_Zero) - mpstream_memset(stream, 0, var->u.nZero); - } -} - -char * -sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count, - uint32_t *tuple_size, struct region *region) -{ - size_t used = region_used(region); - bool is_error = false; - struct mpstream stream; - mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, - set_encode_error, &is_error); - mpstream_encode_array(&stream, field_count); - for (struct Mem *field = fields; field < fields + field_count; field++) - mpstream_encode_vdbe_mem(&stream, field); - mpstream_flush(&stream); - if (is_error) { - diag_set(OutOfMemory, stream.pos - stream.buf, - "mpstream_flush", "stream"); - return NULL; - } - *tuple_size = region_used(region) - used; - char *tuple = region_join(region, *tuple_size); - if (tuple == NULL) { - diag_set(OutOfMemory, *tuple_size, "region_join", "tuple"); - return NULL; - } - mp_tuple_assert(tuple, tuple + *tuple_size); - return tuple; -} diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c index a2d681255..927f85559 100644 --- a/src/box/sql/vdbesort.c +++ b/src/box/sql/vdbesort.c @@ -152,6 +152,7 @@ * the main thread to read from. */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" /* diff --git a/src/box/sql/vdbetrace.c b/src/box/sql/vdbetrace.c index 2ee9f668c..e84bb3192 100644 --- a/src/box/sql/vdbetrace.c +++ b/src/box/sql/vdbetrace.c @@ -37,6 +37,7 @@ * The Vdbe parse-tree explainer is also found here. */ #include "sqlInt.h" +#include "mem.h" #include "vdbeInt.h" /* diff --git a/src/box/sql/where.c b/src/box/sql/where.c index 9c4f8b917..e5f35fbf8 100644 --- a/src/box/sql/where.c +++ b/src/box/sql/where.c @@ -40,6 +40,7 @@ #include "coll/coll.h" #include "sqlInt.h" #include "tarantoolInt.h" +#include "mem.h" #include "vdbeInt.h" #include "whereInt.h" #include "box/coll_id_cache.h" diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c index 3811ef3cf..0c002dbee 100644 --- a/src/box/sql/whereexpr.c +++ b/src/box/sql/whereexpr.c @@ -40,6 +40,7 @@ #include "box/coll_id_cache.h" #include "coll/coll.h" #include "sqlInt.h" +#include "mem.h" #include "whereInt.h" /* Forward declarations */