<HTML><BODY><div>QA LGTM</div><div> </div><div> </div><div data-signature-widget="container"><div data-signature-widget="content"><div>--<br>Vitaliia Ioffe</div></div></div><div> </div><div> </div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;">Вторник, 17 августа 2021, 14:56 +03:00 от Mergen Imeev via Tarantool-patches <tarantool-patches@dev.tarantool.org>:<br> <div id=""><div class="js-helper js-readmsg-msg"><div><div id="style_16292013880392580671_BODY">Prior to this patch, most ephemeral space fields were defined using the<br>SCALAR type. After this patch, almost all fields will be properly<br>defined. However, there are still cases where SCALAR will be set by<br>default. For example, in IN, where rules is still not defined (#4692).<br>Another example is when a field is not resolved because of a too complex<br>query.<br><br>Part of #6213<br>---<br> src/box/sql.c | 213 ++++++++++++--------<br> src/box/sql/delete.c | 35 ++--<br> src/box/sql/expr.c | 19 +-<br> src/box/sql/insert.c | 57 ++++--<br> src/box/sql/select.c | 400 ++++++++++++++++++++++++++++---------<br> src/box/sql/sqlInt.h | 66 +++++-<br> src/box/sql/tarantoolInt.h | 15 --<br> src/box/sql/update.c | 15 +-<br> src/box/sql/vdbe.c | 17 +-<br> src/box/sql/vdbe.h | 13 +-<br> src/box/sql/vdbeaux.c | 12 --<br> src/box/sql/where.c | 10 +-<br> src/box/sql/wherecode.c | 14 +-<br> 13 files changed, 595 insertions(+), 291 deletions(-)<br><br>diff --git a/src/box/sql.c b/src/box/sql.c<br>index 0e93aa7bb..d15159d6e 100644<br>--- a/src/box/sql.c<br>+++ b/src/box/sql.c<br>@@ -243,114 +243,161 @@ tarantoolsqlCount(struct BtCursor *pCur)<br>  return index_count(pCur->index, pCur->iter_type, NULL, 0);<br> }<br> <br>-struct space *<br>-sql_ephemeral_space_create(uint32_t field_count, struct sql_key_info *key_info)<br>+struct sql_space_info *<br>+sql_space_info_new(uint32_t field_count, uint32_t part_count)<br> {<br>- struct key_def *def = NULL;<br>- uint32_t part_count = field_count;<br>- if (key_info != NULL) {<br>- def = sql_key_info_to_key_def(key_info);<br>- if (def == NULL)<br>- return NULL;<br>- /*<br>- * In case is_pk_rowid is true we can use rowid<br>- * as the only part of the key.<br>- */<br>- if (key_info->is_pk_rowid)<br>- part_count = 1;<br>+ assert(field_count > 0);<br>+ uint32_t info_size = sizeof(struct sql_space_info);<br>+ uint32_t field_size = field_count * sizeof(enum field_type);<br>+ uint32_t colls_size = field_count * sizeof(uint32_t);<br>+ uint32_t parts_size = part_count * sizeof(uint32_t);<br>+ uint32_t sort_orders_size = part_count * sizeof(enum sort_order);<br>+ uint32_t size = info_size + field_size + colls_size + parts_size +<br>+ sort_orders_size;<br>+<br>+ struct sql_space_info *info = sqlDbMallocRawNN(sql_get(), size);<br>+ if (info == NULL) {<br>+ diag_set(OutOfMemory, size, "sqlDbMallocRawNN", "info");<br>+ return NULL;<br>  }<br>+ info->types = (enum field_type *)((char *)info + info_size);<br>+ info->coll_ids = (uint32_t *)((char *)info->types + field_size);<br>+ info->parts = part_count == 0 ? NULL :<br>+ (uint32_t *)((char *)info->coll_ids + colls_size);<br>+ info->sort_orders = part_count == 0 ? NULL :<br>+ (uint32_t *)((char *)info->parts + parts_size);<br>+ info->field_count = field_count;<br>+ info->part_count = part_count;<br> <br>- struct region *region = &fiber()->gc;<br>+ for (uint32_t i = 0; i < field_count; ++i) {<br>+ info->types[i] = FIELD_TYPE_SCALAR;<br>+ info->coll_ids[i] = COLL_NONE;<br>+ }<br>+ for (uint32_t i = 0; i < part_count; ++i) {<br>+ info->parts[i] = i;<br>+ info->sort_orders[i] = SORT_ORDER_ASC;<br>+<br>+ }<br>+ return info;<br>+}<br>+<br>+struct sql_space_info *<br>+sql_space_info_new_from_space_def(const struct space_def *def)<br>+{<br>+ uint32_t field_count = def->field_count + 1;<br>+ struct sql_space_info *info = sql_space_info_new(field_count, 0);<br>+ if (info == NULL)<br>+ return NULL;<br>+ for (uint32_t i = 0; i < def->field_count; ++i) {<br>+ info->types[i] = def->fields[i].type;<br>+ info->coll_ids[i] = def->fields[i].coll_id;<br>+ }<br>+ /* Add one more field for rowid. */<br>+ info->types[def->field_count] = FIELD_TYPE_INTEGER;<br>+ return info;<br>+}<br>+<br>+struct sql_space_info *<br>+sql_space_info_new_from_index_def(const struct index_def *def, bool has_rowid)<br>+{<br>+ uint32_t field_count = def->key_def->part_count;<br>+ if (has_rowid)<br>+ ++field_count;<br>+ struct sql_space_info *info = sql_space_info_new(field_count, 0);<br>+ if (info == NULL)<br>+ return NULL;<br>+ for (uint32_t i = 0; i < def->key_def->part_count; ++i) {<br>+ info->types[i] = def->key_def->parts[i].type;<br>+ info->coll_ids[i] = def->key_def->parts[i].coll_id;<br>+ }<br>+ if (has_rowid)<br>+ info->types[def->key_def->part_count] = FIELD_TYPE_INTEGER;<br>+ return info;<br>+}<br>+<br>+struct space *<br>+sql_ephemeral_space_new(const struct sql_space_info *info)<br>+{<br>+ uint32_t field_count = info->field_count;<br>+ uint32_t part_count = info->parts == NULL ? field_count :<br>+ info->part_count;<br>+ uint32_t parts_indent = field_count * sizeof(struct field_def);<br>+ uint32_t names_indent = part_count * sizeof(struct key_part_def) +<br>+ parts_indent;<br>  /*<br>- * Name of the fields will be "_COLUMN_1", "_COLUMN_2"<br>- * and so on. Due to this, length of each name is no more<br>- * than strlen("_COLUMN_") plus length of UINT32_MAX<br>- * turned to string, which is 10 and plus 1 for \0.<br>+ * Name of the fields will be "_COLUMN_1", "_COLUMN_2" and so on. Due to<br>+ * this, length of each name is no more than 19 == strlen("_COLUMN_")<br>+ * plus length of UINT32_MAX turned to string, which is 10 and plus 1<br>+ * for '\0'.<br>  */<br>- uint32_t name_len = strlen("_COLUMN_") + 11;<br>- uint32_t size = field_count * (sizeof(struct field_def) + name_len) +<br>- part_count * sizeof(struct key_part_def);<br>+ uint32_t size = names_indent + field_count * 19;<br>+<br>+ struct region *region = &fiber()->gc;<br>+ size_t svp = region_used(region);<br>  struct field_def *fields = region_aligned_alloc(region, size,<br>  alignof(fields[0]));<br>  if (fields == NULL) {<br>  diag_set(OutOfMemory, size, "region_aligned_alloc", "fields");<br>  return NULL;<br>  }<br>- struct key_part_def *ephemer_key_parts =<br>- (void *)fields + field_count * sizeof(struct field_def);<br>- static_assert(alignof(*fields) == alignof(*ephemer_key_parts),<br>- "allocated in one block, and should have the same "<br>- "alignment");<br>- char *names = (char *)ephemer_key_parts +<br>- part_count * sizeof(struct key_part_def);<br>- for (uint32_t i = 0; i < field_count; ++i) {<br>- struct field_def *field = &fields[i];<br>- field->name = names;<br>- names += name_len;<br>- sprintf(field->name, "_COLUMN_%d", i);<br>- field->is_nullable = true;<br>- field->nullable_action = ON_CONFLICT_ACTION_NONE;<br>- field->default_value = NULL;<br>- field->default_value_expr = NULL;<br>- if (def != NULL && i < def->part_count) {<br>- assert(def->parts[i].type < field_type_MAX);<br>- field->type = def->parts[i].type;<br>- field->coll_id = def->parts[i].coll_id;<br>- } else {<br>- field->coll_id = COLL_NONE;<br>- field->type = FIELD_TYPE_SCALAR;<br>- }<br>+ struct key_part_def *parts = (struct key_part_def *)((char *)fields +<br>+ parts_indent);<br>+ static_assert(alignof(*fields) == alignof(*parts), "allocated in one "<br>+ "block, and should have the same alignment");<br>+ char *names = (char *)fields + names_indent;<br>+<br>+ for (uint32_t i = 0; i < info->field_count; ++i) {<br>+ fields[i].name = names;<br>+ sprintf(names, "_COLUMN_%d", i);<br>+ names += strlen(fields[i].name) + 1;<br>+ fields[i].is_nullable = true;<br>+ fields[i].nullable_action = ON_CONFLICT_ACTION_NONE;<br>+ fields[i].default_value = NULL;<br>+ fields[i].default_value_expr = NULL;<br>+ fields[i].type = info->types[i];<br>+ fields[i].coll_id = info->coll_ids[i];<br>  }<br>-<br>  for (uint32_t i = 0; i < part_count; ++i) {<br>- struct key_part_def *part = &ephemer_key_parts[i];<br>- /*<br>- * In case we need to save initial order of<br>- * inserted into ephemeral space rows we use rowid<br>- * as the only part of PK. If ephemeral space has<br>- * a rowid, it is always the last column.<br>- */<br>- uint32_t j = i;<br>- if (key_info != NULL && key_info->is_pk_rowid)<br>- j = field_count - 1;<br>- part->fieldno = j;<br>- part->nullable_action = ON_CONFLICT_ACTION_NONE;<br>- part->is_nullable = true;<br>- part->exclude_null = false;<br>- part->sort_order = SORT_ORDER_ASC;<br>- part->path = NULL;<br>- part->type = fields[j].type;<br>- part->coll_id = fields[j].coll_id;<br>+ uint32_t j = info->parts == NULL ? i : info->parts[i];<br>+ parts[i].fieldno = j;<br>+ parts[i].nullable_action = ON_CONFLICT_ACTION_NONE;<br>+ parts[i].is_nullable = true;<br>+ parts[i].exclude_null = false;<br>+ parts[i].sort_order = SORT_ORDER_ASC;<br>+ parts[i].path = NULL;<br>+ parts[i].type = info->types[j];<br>+ parts[i].coll_id = info->coll_ids[j];<br>  }<br>- struct key_def *ephemer_key_def = key_def_new(ephemer_key_parts,<br>- part_count, false);<br>- if (ephemer_key_def == NULL)<br>+<br>+ struct key_def *key_def = key_def_new(parts, part_count, false);<br>+ if (key_def == NULL)<br>  return NULL;<br> <br>- struct index_def *ephemer_index_def =<br>- index_def_new(0, 0, "ephemer_idx", strlen("ephemer_idx"), TREE,<br>- &index_opts_default, ephemer_key_def, NULL);<br>- key_def_delete(ephemer_key_def);<br>- if (ephemer_index_def == NULL)<br>+ const char *name = "ephemer_idx";<br>+ struct index_def *index_def = index_def_new(0, 0, name, strlen(name),<br>+ TREE, &index_opts_default,<br>+ key_def, NULL);<br>+ key_def_delete(key_def);<br>+ if (index_def == NULL)<br>  return NULL;<br> <br>  struct rlist key_list;<br>  rlist_create(&key_list);<br>- rlist_add_entry(&key_list, ephemer_index_def, link);<br>+ rlist_add_entry(&key_list, index_def, link);<br> <br>- struct space_def *ephemer_space_def =<br>- space_def_new_ephemeral(field_count, fields);<br>- if (ephemer_space_def == NULL) {<br>- index_def_delete(ephemer_index_def);<br>+ struct space_def *space_def = space_def_new_ephemeral(field_count,<br>+ fields);<br>+ if (space_def == NULL) {<br>+ index_def_delete(index_def);<br>  return NULL;<br>  }<br> <br>- struct space *ephemer_new_space = space_new_ephemeral(ephemer_space_def,<br>- &key_list);<br>- index_def_delete(ephemer_index_def);<br>- space_def_delete(ephemer_space_def);<br>+ struct space *space = space_new_ephemeral(space_def, &key_list);<br>+ index_def_delete(index_def);<br>+ space_def_delete(space_def);<br>+ region_truncate(region, svp);<br> <br>- return ephemer_new_space;<br>+ return space;<br> }<br> <br> int tarantoolsqlEphemeralInsert(struct space *space, const char *tuple,<br>diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c<br>index 5226dd6ea..b82a6874e 100644<br>--- a/src/box/sql/delete.c<br>+++ b/src/box/sql/delete.c<br>@@ -224,12 +224,13 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,<br>  * is held in ephemeral table, there is no PK for<br>  * it, so columns should be loaded manually.<br>  */<br>- struct sql_key_info *pk_info = NULL;<br>  int reg_eph = ++parse->nMem;<br>  int reg_pk = parse->nMem + 1;<br>- int pk_len;<br>+ int pk_len = is_view ? space->def->field_count + 1 :<br>+ space->index[0]->def->key_def->part_count;<br>  int eph_cursor = parse->nTab++;<br>  int addr_eph_open = sqlVdbeCurrentAddr(v);<br>+ struct sql_space_info *info;<br>  if (is_view) {<br>  /*<br>  * At this stage SELECT is already materialized<br>@@ -249,22 +250,20 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,<br>  * account that id field as well. That's why pk_len<br>  * has one field more than view format.<br>  */<br>- pk_len = space->def->field_count + 1;<br>- parse->nMem += pk_len;<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,<br>- pk_len);<br>+ info = sql_space_info_new_from_space_def(space->def);<br>  } else {<br>                         assert(space->index_count > 0);<br>- pk_info = sql_key_info_new_from_key_def(db,<br>- space->index[0]->def->key_def);<br>- if (pk_info == NULL)<br>- goto delete_from_cleanup;<br>- pk_len = pk_info->part_count;<br>- parse->nMem += pk_len;<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph,<br>- pk_len, 0,<br>- (char *)pk_info, P4_KEYINFO);<br>+ struct index_def *index_def = space->index[0]->def;<br>+ info = sql_space_info_new_from_index_def(index_def,<br>+ false);<br>  }<br>+ if (info == NULL) {<br>+ parse->is_aborted = true;<br>+ goto delete_from_cleanup;<br>+ }<br>+ parse->nMem += pk_len;<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, 0, 0, (char *)info,<br>+ P4_DYNAMIC);<br> <br>  /* Construct a query to find the primary key for<br>  * every row to be deleted, based on the WHERE<br>@@ -295,8 +294,9 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,<br> <br>  /* Extract the primary key for the current row */<br>  if (!is_view) {<br>- struct key_part_def *part = pk_info->parts;<br>- for (int i = 0; i < pk_len; i++, part++) {<br>+ struct key_def *def = space->index[0]->def->key_def;<br>+ for (int i = 0; i < pk_len; i++) {<br>+ struct key_part *part = &def->parts[i];<br>  sqlVdbeAddOp3(v, OP_Column, tab_cursor,<br>  part->fieldno, reg_pk + i);<br>  }<br>@@ -373,7 +373,6 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,<br>  if (one_pass != ONEPASS_OFF) {<br>  /* OP_Found will use an unpacked key. */<br>  assert(key_len == pk_len);<br>- assert(pk_info != NULL || space->def->opts.is_view);<br>  sqlVdbeAddOp4Int(v, OP_NotFound, tab_cursor,<br>  addr_bypass, reg_key, key_len);<br> <br>diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c<br>index 6f40183ac..8902c648f 100644<br>--- a/src/box/sql/expr.c<br>+++ b/src/box/sql/expr.c<br>@@ -2740,7 +2740,6 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br> <br>  switch (pExpr->op) {<br>  case TK_IN:{<br>- int addr; /* Address of OP_OpenEphemeral instruction */<br>  Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */<br>  int nVal; /* Size of vector pLeft */<br> <br>@@ -2761,13 +2760,14 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br>  */<br>  pExpr->iTable = pParse->nTab++;<br>  int reg_eph = ++pParse->nMem;<br>- addr = sqlVdbeAddOp2(v, OP_OpenTEphemeral,<br>- reg_eph, nVal);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new(nVal, 0);<br>+ if (info == NULL)<br>+ return 0;<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, pExpr->iTable, 0,<br>  reg_eph);<br>- struct sql_key_info *key_info = sql_key_info_new(pParse->db, nVal);<br>- if (key_info == NULL)<br>- return 0;<br> <br>  if (ExprHasProperty(pExpr, EP_xIsSelect)) {<br>  /* Case 1: expr IN (SELECT ...)<br>@@ -2797,7 +2797,6 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br>  (pParse, pSelect, &dest)) {<br>  sqlDbFree(pParse->db,<br>  dest.dest_type);<br>- sql_key_info_unref(key_info);<br>  return 0;<br>  }<br>  sqlDbFree(pParse->db,<br>@@ -2809,7 +2808,7 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br>  sqlVectorFieldSubexpr<br>  (pLeft, i);<br>  if (sql_binary_compare_coll_seq(pParse, p, pEList->a[i].pExpr,<br>- &key_info->parts[i].coll_id) != 0)<br>+ &info->coll_ids[i]) != 0)<br>  return 0;<br>  }<br>  }<br>@@ -2829,7 +2828,7 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br>  bool unused;<br>  struct coll *unused_coll;<br>  if (sql_expr_coll(pParse, pExpr->pLeft, &unused,<br>- &key_info->parts[0].coll_id,<br>+ &info->coll_ids[0],<br>  &unused_coll) != 0)<br>  return 0;<br> <br>@@ -2861,8 +2860,6 @@ sqlCodeSubselect(Parse * pParse, /* Parsing context */<br>  sqlReleaseTempReg(pParse, r1);<br>  sqlReleaseTempReg(pParse, r2);<br>  }<br>- sqlVdbeChangeP4(v, addr, (void *)key_info,<br>- P4_KEYINFO);<br>  break;<br>  }<br> <br>diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c<br>index 21b4f2407..341c202a2 100644<br>--- a/src/box/sql/insert.c<br>+++ b/src/box/sql/insert.c<br>@@ -60,6 +60,27 @@ sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)<br>  (char *)colls_type, P4_DYNAMIC);<br> }<br> <br>+static struct sql_space_info *<br>+sql_space_info_new_from_id_list(const struct space_def *def, int len,<br>+ const struct IdList *list)<br>+{<br>+ struct sql_space_info *info = sql_space_info_new(len + 1, 1);<br>+ if (info == NULL)<br>+ return NULL;<br>+ assert(len > 0 && len <= (int)def->field_count);<br>+ struct field_def *fields = def->fields;<br>+ for (int i = 0; i < len; ++i) {<br>+ int j = list == NULL ? i : list->a[i].idx;<br>+ info->types[i] = fields[j].type;<br>+ info->coll_ids[i] = fields[j].coll_id;<br>+ }<br>+ /* Add one more field for rowid. */<br>+ info->types[len] = FIELD_TYPE_INTEGER;<br>+ /* Set rowid as the only part of primary index. */<br>+ info->parts[0] = len;<br>+ return info;<br>+}<br>+<br> /**<br>  * In SQL table can be created with AUTOINCREMENT.<br>  * In Tarantool it can be detected as primary key which consists<br>@@ -442,34 +463,30 @@ sqlInsert(Parse * pParse, /* Parser context */<br>  reg_eph = ++pParse->nMem;<br>  regRec = sqlGetTempReg(pParse);<br>  regCopy = sqlGetTempRange(pParse, nColumn + 1);<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,<br>- nColumn + 1);<br>  /*<br>- * This key_info is used to show that<br>- * rowid should be the first part of PK in<br>- * case we used AUTOINCREMENT feature.<br>- * This way we will save initial order of<br>- * the inserted values. The order is<br>- * important if we use the AUTOINCREMENT<br>- * feature, since changing the order can<br>- * change the number inserted instead of<br>- * NULL.<br>+ * Order of inserted values is important since it is<br>+ * possible, that NULL will be inserted in field with<br>+ * AUTOINCREMENT. So, the first part of key should be<br>+ * rowid. Since each rowid is unique, we do not need any<br>+ * other parts.<br>  */<br>- if (space->sequence != NULL) {<br>- struct sql_key_info *key_info =<br>- sql_key_info_new(pParse->db,<br>- nColumn + 1);<br>- key_info->parts[nColumn].type =<br>- FIELD_TYPE_UNSIGNED;<br>- key_info->is_pk_rowid = true;<br>- sqlVdbeChangeP4(v, -1, (void *)key_info,<br>- P4_KEYINFO);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_id_list(space_def,<br>+ nColumn,<br>+ pColumn);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto insert_cleanup;<br>  }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>  addrL = sqlVdbeAddOp1(v, OP_Yield, dest.iSDParm);<br>  VdbeCoverage(v);<br>  sqlVdbeAddOp2(v, OP_NextIdEphemeral, reg_eph,<br>  regCopy + nColumn);<br>  sqlVdbeAddOp3(v, OP_Copy, regFromSelect, regCopy, nColumn-1);<br>+ sqlVdbeAddOp4(v, OP_ApplyType, regCopy, nColumn + 1, 0,<br>+ (char *)info->types, P4_STATIC);<br>  sqlVdbeAddOp3(v, OP_MakeRecord, regCopy,<br>  nColumn + 1, regRec);<br>  /* Set flag to save memory allocating one by malloc. */<br>diff --git a/src/box/sql/select.c b/src/box/sql/select.c<br>index 7cfe60db6..021e0ebd5 100644<br>--- a/src/box/sql/select.c<br>+++ b/src/box/sql/select.c<br>@@ -99,6 +99,177 @@ struct SortCtx {<br> #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */<br> #define SORTFLAG_DESC 0xF0<br> <br>+static inline uint32_t<br>+multi_select_coll_seq(struct Parse *parser, struct Select *p, int n);<br>+<br>+static struct sql_space_info *<br>+sql_space_info_new_from_expr_list(struct Parse *parser, struct ExprList *list,<br>+ bool has_rowid)<br>+{<br>+ int n = list->nExpr;<br>+ if (has_rowid)<br>+ ++n;<br>+ struct sql_space_info *info = sql_space_info_new(n, 0);<br>+ if (info == NULL)<br>+ return NULL;<br>+ for (int i = 0; i < list->nExpr; ++i) {<br>+ bool b;<br>+ struct coll *coll;<br>+ struct Expr *expr = list->a[i].pExpr;<br>+ enum field_type type = sql_expr_type(expr);<br>+ /*<br>+ * Type ANY could mean that field was unresolved. We have no way<br>+ * but to set it as SCALAR, however this could lead to<br>+ * unexpected change of type.<br>+ */<br>+ if (type == FIELD_TYPE_ANY)<br>+ type = FIELD_TYPE_SCALAR;<br>+ uint32_t coll_id;<br>+ if (sql_expr_coll(parser, expr, &b, &coll_id, &coll) != 0)<br>+ return NULL;<br>+ info->types[i] = type;<br>+ info->coll_ids[i] = coll_id;<br>+ }<br>+ if (has_rowid)<br>+ info->types[list->nExpr] = FIELD_TYPE_INTEGER;<br>+ return info;<br>+}<br>+<br>+static struct sql_space_info *<br>+sql_space_info_new_from_order_by(struct Parse *parser, struct Select *select,<br>+ struct ExprList *order_by)<br>+{<br>+ int n = order_by->nExpr + 2;<br>+ struct sql_space_info *info = sql_space_info_new(n, 0);<br>+ if (info == NULL)<br>+ return NULL;<br>+ for (int i = 0; i < order_by->nExpr; ++i) {<br>+ struct Expr *expr = order_by->a[i].pExpr;<br>+ enum field_type type = sql_expr_type(expr);<br>+ /*<br>+ * Type ANY could mean that field was unresolved. We have no way<br>+ * but to set it as SCALAR, however this could lead to<br>+ * unexpected change of type.<br>+ */<br>+ if (type == FIELD_TYPE_ANY)<br>+ type = FIELD_TYPE_SCALAR;<br>+ info->types[i] = type;<br>+ uint32_t *id = &info->coll_ids[i];<br>+ if ((expr->flags & EP_Collate) != 0) {<br>+ bool b;<br>+ struct coll *coll;<br>+ if (sql_expr_coll(parser, expr, &b, id, &coll) != 0)<br>+ return NULL;<br>+ continue;<br>+ }<br>+ uint32_t fieldno = order_by->a[i].u.x.iOrderByCol - 1;<br>+ info->coll_ids[i] = multi_select_coll_seq(parser, select,<br>+ fieldno);<br>+ if (info->coll_ids[i] != COLL_NONE) {<br>+ const char *name = coll_by_id(info->coll_ids[i])->name;<br>+ order_by->a[i].pExpr =<br>+ sqlExprAddCollateString(parser, expr, name);<br>+ }<br>+ }<br>+ info->types[order_by->nExpr] = FIELD_TYPE_INTEGER;<br>+ info->types[order_by->nExpr + 1] = FIELD_TYPE_VARBINARY;<br>+ return info;<br>+}<br>+<br>+static struct sql_space_info *<br>+sql_space_info_new_for_sorting(struct Parse *parser, struct ExprList *order_by,<br>+ struct ExprList *list, int start, bool has_rowid)<br>+{<br>+ /*<br>+ * Index consist of fields that were not included into index plus rowid,<br>+ * is has_rowid is TRUE.<br>+ */<br>+ uint32_t part_count = order_by->nExpr - start;<br>+ if (has_rowid)<br>+ ++part_count;<br>+ /*<br>+ * Number of fields is number of parts in index plus number of fields<br>+ * that is not appear in ORDER BY, but were in SELECT.<br>+ */<br>+ uint32_t field_count = part_count;<br>+ /* If iOrderByCol != 0 than fields appear in ORDER BY. */<br>+ for (int i = 0; i < list->nExpr; ++i)<br>+ field_count += list->a[i].u.x.iOrderByCol == 0 ? 1 : 0;<br>+<br>+ struct sql_space_info *info =<br>+ sql_space_info_new(field_count, part_count);<br>+ if (info == NULL)<br>+ return NULL;<br>+ int k;<br>+ for (k = 0; k < order_by->nExpr - start; ++k) {<br>+ bool b;<br>+ struct coll *coll;<br>+ struct Expr *expr = order_by->a[k + start].pExpr;<br>+ enum field_type type = sql_expr_type(expr);<br>+ /*<br>+ * Type ANY could mean that field was unresolved. We have no way<br>+ * but to set it as SCALAR, however this could lead to<br>+ * unexpected change of type.<br>+ */<br>+ if (type == FIELD_TYPE_ANY)<br>+ type = FIELD_TYPE_SCALAR;<br>+ uint32_t coll_id;<br>+ if (sql_expr_coll(parser, expr, &b, &coll_id, &coll) != 0)<br>+ return NULL;<br>+ info->types[k] = type;<br>+ info->coll_ids[k] = coll_id;<br>+ info->sort_orders[k] = order_by->a[k + start].sort_order;<br>+ }<br>+ if (has_rowid)<br>+ info->types[k++] = FIELD_TYPE_INTEGER;<br>+ assert(k == (int)part_count);<br>+ for (int i = 0; i < list->nExpr; ++i) {<br>+ if (list->a[i].u.x.iOrderByCol != 0)<br>+ continue;<br>+ bool b;<br>+ struct Expr *expr = list->a[i].pExpr;<br>+ enum field_type type = sql_expr_type(expr);<br>+ if (type == FIELD_TYPE_ANY)<br>+ type = FIELD_TYPE_SCALAR;<br>+ uint32_t id;<br>+ struct coll *coll;<br>+ if (sql_expr_coll(parser, expr, &b, &id, &coll) != 0)<br>+ return NULL;<br>+ info->types[k] = type;<br>+ info->coll_ids[k] = id;<br>+ ++k;<br>+ }<br>+ assert(k == (int)field_count);<br>+ return info;<br>+}<br>+<br>+static struct sql_key_info *<br>+sql_key_info_new_from_space_info(const struct sql_space_info *info)<br>+{<br>+ uint32_t part_count = info->part_count - 1;<br>+ assert(part_count > 0);<br>+ uint32_t size = sizeof(struct sql_key_info) +<br>+ part_count * sizeof(struct key_part_def);<br>+ struct sql_key_info *key_info = sqlDbMallocRawNN(sql_get(), size);<br>+ if (key_info == NULL)<br>+ return NULL;<br>+ key_info->db = sql_get();<br>+ key_info->key_def = NULL;<br>+ key_info->refs = 1;<br>+ key_info->part_count = part_count;<br>+ for (uint32_t i = 0; i < part_count; ++i) {<br>+ key_info->parts[i].fieldno = i;<br>+ key_info->parts[i].nullable_action = ON_CONFLICT_ACTION_ABORT;<br>+ key_info->parts[i].is_nullable = false;<br>+ key_info->parts[i].exclude_null = false;<br>+ key_info->parts[i].sort_order = info->sort_orders[i];<br>+ key_info->parts[i].path = NULL;<br>+ key_info->parts[i].type = info->types[i];<br>+ key_info->parts[i].coll_id = info->coll_ids[i];<br>+ }<br>+ return key_info;<br>+}<br>+<br> /*<br>  * Delete all the content of a Select structure. Deallocate the structure<br>  * itself only if bFree is true.<br>@@ -819,13 +990,32 @@ pushOntoSorter(Parse * pParse, /* Parser context */<br>  if (pParse->db->mallocFailed)<br>  return;<br>  pOp->p2 = nKey + nData;<br>- struct sql_key_info *key_info = pOp->p4.key_info;<br>+ assert(pOp->opcode == OP_OpenTEphemeral ||<br>+ pOp->opcode == OP_SorterOpen);<br>+ struct sql_key_info *key_info =<br>+ pOp->opcode == OP_SorterOpen ? pOp->p4.key_info :<br>+ sql_key_info_new_from_space_info(pOp->p4.space_info);<br>  for (uint32_t i = 0; i < key_info->part_count; i++)<br>  key_info->parts[i].sort_order = SORT_ORDER_ASC;<br>  sqlVdbeChangeP4(v, -1, (char *)key_info, P4_KEYINFO);<br>- pOp->p4.key_info = sql_expr_list_to_key_info(pParse,<br>- pSort->pOrderBy,<br>- nOBSat);<br>+ if (pOp->opcode == OP_SorterOpen) {<br>+ pOp->p4.key_info =<br>+ sql_expr_list_to_key_info(pParse,<br>+ pSort->pOrderBy,<br>+ nOBSat);<br>+ } else {<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_for_sorting(pParse,<br>+ pSort->pOrderBy,<br>+ pSelect->pEList,<br>+ nOBSat, bSeq);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ return;<br>+ }<br>+ sqlVdbeChangeP4(v, pSort->addrSortIndex, (char *)info,<br>+ P4_DYNAMIC);<br>+ }<br>  addrJmp = sqlVdbeCurrentAddr(v);<br>  sqlVdbeAddOp3(v, OP_Jump, addrJmp + 1, 0, addrJmp + 1);<br>  VdbeCoverage(v);<br>@@ -1044,21 +1234,33 @@ selectInnerLoop(Parse * pParse, /* The parser context */<br>  * space format.<br>  */<br>  uint32_t excess_field_count = 0;<br>+ struct VdbeOp *op = sqlVdbeGetOp(v,<br>+ pSort->addrSortIndex);<br>  for (i = pSort->nOBSat; i < pSort->pOrderBy->nExpr;<br>  i++) {<br>  int j = pSort->pOrderBy->a[i].u.x.iOrderByCol;<br>- if (j > 0) {<br>- excess_field_count++;<br>- pEList->a[j - 1].u.x.iOrderByCol =<br>- (u16) (i + 1 - pSort->nOBSat);<br>+ if (j == 0)<br>+ continue;<br>+ assert(j > 0);<br>+ excess_field_count++;<br>+ pEList->a[j - 1].u.x.iOrderByCol =<br>+ (uint16_t)(i + 1 - pSort->nOBSat);<br>+ if (op->opcode != OP_OpenTEphemeral)<br>+ continue;<br>+ struct sql_space_info *info = op->p4.space_info;<br>+ --info->field_count;<br>+ for (int k = j; k < pEList->nExpr; ++k) {<br>+ int n = k + pSort->pOrderBy->nExpr + 1;<br>+ info->types[n - 1] = info->types[n];<br>+ info->coll_ids[n - 1] =<br>+ info->coll_ids[n];<br>  }<br>  }<br>- struct VdbeOp *open_eph_op =<br>- sqlVdbeGetOp(v, pSort->addrSortIndex);<br>- assert(open_eph_op->p2 - excess_field_count > 0);<br>- sqlVdbeChangeP2(v, pSort->addrSortIndex,<br>- open_eph_op->p2 -<br>- excess_field_count);<br>+ if (op->opcode != OP_OpenTEphemeral) {<br>+ assert(op->p2 - excess_field_count > 0);<br>+ sqlVdbeChangeP2(v, pSort->addrSortIndex,<br>+ op->p2 - excess_field_count);<br>+ }<br>  regOrig = 0;<br>  assert(eDest == SRT_Set || eDest == SRT_Mem<br>  || eDest == SRT_Coroutine<br>@@ -1419,7 +1621,6 @@ sql_key_info_new(sql *db, uint32_t part_count)<br>  key_info->key_def = NULL;<br>  key_info->refs = 1;<br>  key_info->part_count = part_count;<br>- key_info->is_pk_rowid = false;<br>  for (uint32_t i = 0; i < part_count; i++) {<br>  struct key_part_def *part = &key_info->parts[i];<br>  part->fieldno = i;<br>@@ -1434,24 +1635,6 @@ sql_key_info_new(sql *db, uint32_t part_count)<br>  return key_info;<br> }<br> <br>-struct sql_key_info *<br>-sql_key_info_new_from_key_def(sql *db, const struct key_def *key_def)<br>-{<br>- struct sql_key_info *key_info = sqlDbMallocRawNN(db,<br>- sql_key_info_sizeof(key_def->part_count));<br>- if (key_info == NULL) {<br>- sqlOomFault(db);<br>- return NULL;<br>- }<br>- key_info->db = db;<br>- key_info->key_def = NULL;<br>- key_info->refs = 1;<br>- key_info->part_count = key_def->part_count;<br>- key_info->is_pk_rowid = false;<br>- key_def_dump_parts(key_def, key_info->parts, NULL);<br>- return key_info;<br>-}<br>-<br> struct sql_key_info *<br> sql_key_info_ref(struct sql_key_info *key_info)<br> {<br>@@ -2453,22 +2636,26 @@ generateWithRecursiveQuery(Parse * pParse, /* Parsing context */<br>  /* Allocate cursors for Current, Queue, and Distinct. */<br>  regCurrent = ++pParse->nMem;<br>  sqlVdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);<br>+ struct sql_space_info *info;<br>  if (pOrderBy) {<br>- struct sql_key_info *key_info =<br>- sql_multiselect_orderby_to_key_info(pParse, p, 1);<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_queue,<br>- pOrderBy->nExpr + 2, 0, (char *)key_info,<br>- P4_KEYINFO);<br>  VdbeComment((v, "Orderby table"));<br>+ info = sql_space_info_new_from_order_by(pParse, p, pOrderBy);<br>  destQueue.pOrderBy = pOrderBy;<br>  } else {<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_queue, nCol + 1);<br>  VdbeComment((v, "Queue table"));<br>+ info = sql_space_info_new_from_expr_list(pParse, p->pEList,<br>+ true);<br>+ }<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto end_of_recursive_query;<br>  }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_queue, 0, 0, (char *)info,<br>+ P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, iQueue, 0, reg_queue);<br>  if (iDistinct) {<br>  p->addrOpenEphm[0] =<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_dist, 1);<br>+ sqlVdbeAddOp1(v, OP_OpenTEphemeral, reg_dist);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, iDistinct, 0, reg_dist);<br>  p->selFlags |= SF_UsesEphemeral;<br>  VdbeComment((v, "Distinct table"));<br>@@ -2672,8 +2859,16 @@ multiSelect(Parse * pParse, /* Parsing context */<br>  */<br>  if (dest.eDest == SRT_EphemTab) {<br>  assert(p->pEList);<br>- int nCols = p->pEList->nExpr;<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, dest.reg_eph, nCols + 1);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_expr_list(pParse, p->pEList,<br>+ true);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ rc = 1;<br>+ goto multi_select_end;<br>+ }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, dest.reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, dest.iSDParm, 0, dest.reg_eph);<br>  VdbeComment((v, "Destination temp"));<br>  dest.eDest = SRT_Table;<br>@@ -2789,10 +2984,9 @@ multiSelect(Parse * pParse, /* Parsing context */<br>  unionTab = pParse->nTab++;<br>  reg_union = ++pParse->nMem;<br>  assert(p->pOrderBy == 0);<br>- addr =<br>- sqlVdbeAddOp2(v,<br>- OP_OpenTEphemeral,<br>- reg_union, 0);<br>+ addr = sqlVdbeAddOp1(v,<br>+ OP_OpenTEphemeral,<br>+ reg_union);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, unionTab, 0, reg_union);<br>  assert(p->addrOpenEphm[0] == -1);<br>  p->addrOpenEphm[0] = addr;<br>@@ -2905,9 +3099,8 @@ multiSelect(Parse * pParse, /* Parsing context */<br>  reg_eph2 = ++pParse->nMem;<br>  assert(p->pOrderBy == 0);<br> <br>- addr =<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_eph1,<br>- 0);<br>+ addr = sqlVdbeAddOp1(v, OP_OpenTEphemeral,<br>+ reg_eph1);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, tab1, 0, reg_eph1);<br>  assert(p->addrOpenEphm[0] == -1);<br>  p->addrOpenEphm[0] = addr;<br>@@ -2927,9 +3120,8 @@ multiSelect(Parse * pParse, /* Parsing context */<br> <br>  /* Code the current SELECT into temporary table "tab2"<br>  */<br>- addr =<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_eph2,<br>- 0);<br>+ addr = sqlVdbeAddOp1(v, OP_OpenTEphemeral,<br>+ reg_eph2);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, tab2, 0, reg_eph2);<br>  assert(p->addrOpenEphm[1] == -1);<br>  p->addrOpenEphm[1] = addr;<br>@@ -3001,14 +3193,14 @@ multiSelect(Parse * pParse, /* Parsing context */<br>  if (p->selFlags & SF_UsesEphemeral) {<br>  assert(p->pNext == NULL);<br>  int nCol = p->pEList->nExpr;<br>- struct sql_key_info *key_info = sql_key_info_new(db, nCol);<br>- if (key_info == NULL)<br>+ struct sql_space_info *info = sql_space_info_new(nCol, 0);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>  goto multi_select_end;<br>- for (int i = 0; i < nCol; i++) {<br>- key_info->parts[i].coll_id =<br>- multi_select_coll_seq(pParse, p, i);<br>  }<br>-<br>+ for (int i = 0; i < nCol; ++i)<br>+ info->coll_ids[i] = multi_select_coll_seq(pParse, p, i);<br>+ bool is_info_used = false;<br>  for (struct Select *pLoop = p; pLoop; pLoop = pLoop->pPrior) {<br>  for (int i = 0; i < 2; i++) {<br>  int addr = pLoop->addrOpenEphm[i];<br>@@ -3020,13 +3212,15 @@ multiSelect(Parse * pParse, /* Parsing context */<br>  break;<br>  }<br>  sqlVdbeChangeP2(v, addr, nCol);<br>- sqlVdbeChangeP4(v, addr,<br>- (char *)sql_key_info_ref(key_info),<br>- P4_KEYINFO);<br>+ sqlVdbeChangeP4(v, addr, (char *)info,<br>+ is_info_used ?<br>+ P4_STATIC : P4_DYNAMIC);<br>+ is_info_used = true;<br>  pLoop->addrOpenEphm[i] = -1;<br>  }<br>  }<br>- sql_key_info_unref(key_info);<br>+ if (!is_info_used)<br>+ sqlDbFree(pParse->db, info);<br>  }<br> <br>  multi_select_end:<br>@@ -5347,17 +5541,22 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)<br>  "exactly one argument");<br>  pParse->is_aborted = true;<br>  pFunc->iDistinct = -1;<br>- } else {<br>- struct sql_key_info *key_info =<br>- sql_expr_list_to_key_info(pParse,<br>+ return;<br>+ }<br>+ assert(pE->x.pList->nExpr == 1);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_expr_list(pParse,<br>  pE->x.pList,<br>- 0);<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral,<br>- pFunc->reg_eph, 1, 0,<br>- (char *)key_info, P4_KEYINFO);<br>- sqlVdbeAddOp3(v, OP_IteratorOpen,<br>- pFunc->iDistinct, 0, pFunc->reg_eph);<br>+ false);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ pFunc->iDistinct = -1;<br>+ return;<br>  }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, pFunc->reg_eph, 0,<br>+ 0, (char *)info, P4_DYNAMIC);<br>+ sqlVdbeAddOp3(v, OP_IteratorOpen, pFunc->iDistinct, 0,<br>+ pFunc->reg_eph);<br>  }<br>  }<br> }<br>@@ -5862,23 +6061,21 @@ sqlSelect(Parse * pParse, /* The parser context */<br>  * that change.<br>  */<br>  if (sSort.pOrderBy) {<br>- struct sql_key_info *key_info =<br>- sql_expr_list_to_key_info(pParse, sSort.pOrderBy, 0);<br>  sSort.reg_eph = ++pParse->nMem;<br>  sSort.iECursor = pParse->nTab++;<br>- /* Number of columns in transient table equals to number of columns in<br>- * SELECT statement plus number of columns in ORDER BY statement<br>- * and plus one column for ID.<br>- */<br>- int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;<br>- if (key_info->parts[0].sort_order == SORT_ORDER_DESC) {<br>- sSort.sortFlags |= SORTFLAG_DESC;<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_for_sorting(pParse, sSort.pOrderBy,<br>+ pEList, 0, true);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto select_end;<br>  }<br>+ if (info->sort_orders[0] == SORT_ORDER_DESC)<br>+ sSort.sortFlags |= SORTFLAG_DESC;<br>  sSort.addrSortIndex =<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral,<br>- sSort.reg_eph,<br>- nCols,<br>- 0, (char *)key_info, P4_KEYINFO);<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, sSort.reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>+<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, sSort.iECursor, 0, sSort.reg_eph);<br>  VdbeComment((v, "Sort table"));<br>  } else {<br>@@ -5888,11 +6085,14 @@ sqlSelect(Parse * pParse, /* The parser context */<br>  /* If the output is destined for a temporary table, open that table.<br>  */<br>  if (pDest->eDest == SRT_EphemTab) {<br>- struct sql_key_info *key_info =<br>- sql_expr_list_to_key_info(pParse, pEList, 0);<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral, pDest->reg_eph,<br>- pEList->nExpr + 1, 0, (char *)key_info,<br>- P4_KEYINFO);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_expr_list(pParse, pEList, true);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto select_end;<br>+ }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, pDest->reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, pDest->iSDParm, 0,<br>  pDest->reg_eph);<br> <br>@@ -5907,8 +6107,15 @@ sqlSelect(Parse * pParse, /* The parser context */<br>  }<br>  computeLimitRegisters(pParse, p, iEnd);<br>  if (p->iLimit == 0 && sSort.addrSortIndex >= 0) {<br>+ struct VdbeOp *op = sqlVdbeGetOp(v, sSort.addrSortIndex);<br>+ struct sql_key_info *key_info =<br>+ sql_key_info_new_from_space_info(op->p4.space_info);<br>  sqlVdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen);<br>  sqlVdbeChangeP1(v, sSort.addrSortIndex, sSort.iECursor);<br>+ sqlVdbeChangeP2(v, sSort.addrSortIndex,<br>+ op->p4.space_info->field_count);<br>+ sqlVdbeChangeP4(v, sSort.addrSortIndex, (char *)key_info,<br>+ P4_KEYINFO);<br>  sqlVdbeChangeToNoop(v, sSort.addrSortIndex + 1);<br>  sSort.sortFlags |= SORTFLAG_UseSorter;<br>  }<br>@@ -5918,13 +6125,16 @@ sqlSelect(Parse * pParse, /* The parser context */<br>  if (p->selFlags & SF_Distinct) {<br>  sDistinct.cur_eph = pParse->nTab++;<br>  sDistinct.reg_eph = ++pParse->nMem;<br>- struct sql_key_info *key_info =<br>- sql_expr_list_to_key_info(pParse, p->pEList, 0);<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_expr_list(pParse, pEList,<br>+ false);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto select_end;<br>+ }<br>  sDistinct.addrTnct = sqlVdbeAddOp4(v, OP_OpenTEphemeral,<br>- sDistinct.reg_eph,<br>- key_info->part_count,<br>- 0, (char *)key_info,<br>- P4_KEYINFO);<br>+ sDistinct.reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, sDistinct.cur_eph, 0,<br>  sDistinct.reg_eph);<br>  VdbeComment((v, "Distinct table"));<br>diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h<br>index 1f87e6823..540c3a2ff 100644<br>--- a/src/box/sql/sqlInt.h<br>+++ b/src/box/sql/sqlInt.h<br>@@ -4013,8 +4013,6 @@ struct sql_key_info {<br>  struct key_def *key_def;<br>  /** Reference counter. */<br>  uint32_t refs;<br>- /** Rowid should be the only part of PK, if true. */<br>- bool is_pk_rowid;<br>  /** Number of parts in the key. */<br>  uint32_t part_count;<br>  /** Definition of the key parts. */<br>@@ -4028,12 +4026,6 @@ struct sql_key_info {<br> struct sql_key_info *<br> sql_key_info_new(sql *db, uint32_t part_count);<br> <br>-/**<br>- * Allocate a key_info object from the given key definition.<br>- */<br>-struct sql_key_info *<br>-sql_key_info_new_from_key_def(sql *db, const struct key_def *key_def);<br>-<br> /**<br>  * Increment the reference counter of a key_info object.<br>  */<br>@@ -4055,6 +4047,64 @@ sql_key_info_unref(struct sql_key_info *key_info);<br> struct key_def *<br> sql_key_info_to_key_def(struct sql_key_info *key_info);<br> <br>+/**<br>+ * Structure that is used to store information about ephemeral space field types<br>+ * and fieldno of key parts.<br>+ */<br>+struct sql_space_info {<br>+ /** Field types of all fields of ephemeral space. */<br>+ enum field_type *types;<br>+ /** Collation ids of all fields of ephemeral space. */<br>+ uint32_t *coll_ids;<br>+ /**<br>+ * Fieldno key parts of the ephemeral space. If NULL, then the index<br>+ * consists of all fields in sequential order.<br>+ */<br>+ uint32_t *parts;<br>+ /** Sort order of index. */<br>+ enum sort_order *sort_orders;<br>+ /** Number of fields of ephemetal space. */<br>+ uint32_t field_count;<br>+ /**<br>+ * Number of parts in primary index of ephemetal space. If 0 then parts<br>+ * is also NULL.<br>+ */<br>+ uint32_t part_count;<br>+};<br>+<br>+/**<br>+ * Allocate and initialize with default values a structure that will be used to<br>+ * store information about ephemeral space field types and key parts.<br>+ */<br>+struct sql_space_info *<br>+sql_space_info_new(uint32_t field_count, uint32_t part_count);<br>+<br>+/**<br>+ * Initialize the field types and key parts of space_info with space_def.<br>+ * Additionally added one more field type and key part for rowid. Rowid is<br>+ * always INTEGER. Key parts will be initialized with the same values as the<br>+ * field types. The number of initialized field types and key parts will be the<br>+ * same as the field_count in space_def plus one.<br>+ */<br>+struct sql_space_info *<br>+sql_space_info_new_from_space_def(const struct space_def *def);<br>+<br>+/**<br>+ * Initialize the field types and key parts of space_info with index_def.<br>+ * Key parts will be initialized with the same values as the field types. The<br>+ * number of initialized field types and key parts will be the same as the<br>+ * part_count in index_def.<br>+ */<br>+struct sql_space_info *<br>+sql_space_info_new_from_index_def(const struct index_def *def, bool has_rowid);<br>+<br>+/**<br>+ * Allocate and initialize an ephemeral space. Information about field types and<br>+ * key parts is taken from the space_info structure.<br>+ */<br>+struct space *<br>+sql_ephemeral_space_new(const struct sql_space_info *info);<br>+<br> /**<br>  * Check if the function implements LIKE-style comparison & if it<br>  * is appropriate to apply a LIKE query optimization.<br>diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h<br>index 8fdc50432..adb7b5f9f 100644<br>--- a/src/box/sql/tarantoolInt.h<br>+++ b/src/box/sql/tarantoolInt.h<br>@@ -61,21 +61,6 @@ sql_rename_table(uint32_t space_id, const char *new_name);<br> int tarantoolsqlRenameTrigger(const char *zTriggerName,<br>  const char *zOldName, const char *zNewName);<br> <br>-/**<br>- * Create ephemeral space. Features of ephemeral spaces: id == 0,<br>- * name == "ephemeral", memtx engine (in future it can be changed,<br>- * but now only memtx engine is supported), primary index which<br>- * covers all fields and no secondary indexes, given field number<br>- * and collation sequence. All fields are scalar and nullable.<br>- *<br>- * @param field_count Number of fields in ephemeral space.<br>- * @param key_info Keys description for new ephemeral space.<br>- *<br>- * @retval Pointer to created space, NULL if error.<br>- */<br>-struct space *<br>-sql_ephemeral_space_create(uint32_t filed_count, struct sql_key_info *key_info);<br>-<br> /**<br>  * Insert tuple into ephemeral space.<br>  * In contrast to ordinary spaces, there is no need to create and<br>diff --git a/src/box/sql/update.c b/src/box/sql/update.c<br>index 22f82390c..b4827f242 100644<br>--- a/src/box/sql/update.c<br>+++ b/src/box/sql/update.c<br>@@ -226,9 +226,20 @@ sqlUpdate(Parse * pParse, /* The parser context */<br>  iEph = pParse->nTab++;<br>  sqlVdbeAddOp2(v, OP_Null, 0, iPk);<br> <br>+ struct sql_space_info *info;<br>+ assert(space->index_count > 0 || is_view);<br>+ if (is_view)<br>+ info = sql_space_info_new_from_space_def(def);<br>+ else<br>+ info = sql_space_info_new_from_index_def(pPk->def, false);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ goto update_cleanup;<br>+ }<br>  /* Address of the OpenEphemeral instruction. */<br>- int addrOpen = sqlVdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,<br>- pk_part_count);<br>+ int addrOpen = sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, 0, 0,<br>+ (char *)info, P4_DYNAMIC);<br>+<br>  pWInfo = sqlWhereBegin(pParse, pTabList, pWhere, 0, 0,<br>  WHERE_ONEPASS_DESIRED, pk_cursor);<br>  if (pWInfo == 0)<br>diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c<br>index fcea9eefe..ce30afc5a 100644<br>--- a/src/box/sql/vdbe.c<br>+++ b/src/box/sql/vdbe.c<br>@@ -2033,10 +2033,9 @@ case OP_Fetch: {<br> case OP_ApplyType: {<br>  enum field_type *types = pOp->p4.types;<br>  assert(types != NULL);<br>- assert(types[pOp->p2] == field_type_MAX);<br>  pIn1 = &aMem[pOp->p1];<br>- enum field_type type;<br>- while((type = *(types++)) != field_type_MAX) {<br>+ for (int i = 0; i < pOp->p2; ++i, ++pIn1) {<br>+ enum field_type type = types[i];<br>  assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);<br>  assert(memIsValid(pIn1));<br>  if (mem_cast_implicit(pIn1, type) != 0) {<br>@@ -2044,7 +2043,6 @@ case OP_ApplyType: {<br>  mem_str(pIn1), field_type_strs[type]);<br>  goto abort_due_to_error;<br>  }<br>- pIn1++;<br>  }<br>  break;<br> }<br>@@ -2380,10 +2378,9 @@ open_cursor_set_hints:<br> }<br> <br> /**<br>- * Opcode: OpenTEphemeral P1 P2 * P4 *<br>+ * Opcode: OpenTEphemeral P1 * * P4 *<br>  * Synopsis:<br>  * @param P1 register, where pointer to new space is stored.<br>- * @param P2 number of columns in a new table.<br>  * @param P4 key def for new table, NULL is allowed.<br>  *<br>  * This opcode creates Tarantool's ephemeral table and stores pointer<br>@@ -2391,11 +2388,11 @@ open_cursor_set_hints:<br>  */<br> case OP_OpenTEphemeral: {<br>  assert(pOp->p1 >= 0);<br>- assert(pOp->p2 > 0);<br>- assert(pOp->p4type != P4_KEYINFO || pOp->p4.key_info != NULL);<br> <br>- struct space *space = sql_ephemeral_space_create(pOp->p2,<br>- pOp->p4.key_info);<br>+ assert(pOp->p4type == P4_DYNAMIC || pOp->p4type == P4_STATIC);<br>+ struct sql_space_info *info = pOp->p4.space_info;<br>+ assert(info != NULL);<br>+ struct space *space = sql_ephemeral_space_new(info);<br> <br>  if (space == NULL)<br>  goto abort_due_to_error;<br>diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h<br>index be112c72d..e40a1a0b3 100644<br>--- a/src/box/sql/vdbe.h<br>+++ b/src/box/sql/vdbe.h<br>@@ -93,6 +93,10 @@ struct VdbeOp {<br>  * doing a cast.<br>  */<br>  enum field_type *types;<br>+ /**<br>+ * Information about ephemeral space field types and key parts.<br>+ */<br>+ struct sql_space_info *space_info;<br>  } p4;<br> #ifdef SQL_ENABLE_EXPLAIN_COMMENTS<br>  char *zComment; /* Comment to improve readability */<br>@@ -210,15 +214,6 @@ int sqlVdbeDeletePriorOpcode(Vdbe *, u8 op);<br> void sqlVdbeChangeP4(Vdbe *, int addr, const char *zP4, int N);<br> void sqlVdbeAppendP4(Vdbe *, void *pP4, int p4type);<br> <br>-/**<br>- * Set the P4 on the most recently added opcode to the key_def for the<br>- * index given.<br>- * @param Parse context, for error reporting.<br>- * @param key_def Definition of a key to set.<br>- */<br>-void<br>-sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);<br>-<br> VdbeOp *sqlVdbeGetOp(Vdbe *, int);<br> int sqlVdbeMakeLabel(Vdbe *);<br> void sqlVdbeRunOnlyOnce(Vdbe *);<br>diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c<br>index 61be7b489..2d7800b17 100644<br>--- a/src/box/sql/vdbeaux.c<br>+++ b/src/box/sql/vdbeaux.c<br>@@ -787,18 +787,6 @@ sqlVdbeAppendP4(Vdbe * p, void *pP4, int n)<br>  }<br> }<br> <br>-void<br>-sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def)<br>-{<br>- struct Vdbe *v = parse->pVdbe;<br>- assert(v != NULL);<br>- assert(key_def != NULL);<br>- struct sql_key_info *key_info =<br>- sql_key_info_new_from_key_def(parse->db, key_def);<br>- if (key_info != NULL)<br>- sqlVdbeAppendP4(v, key_info, P4_KEYINFO);<br>-}<br>-<br> #ifdef SQL_ENABLE_EXPLAIN_COMMENTS<br> /*<br>  * Change the comment on the most recently coded instruction. Or<br>diff --git a/src/box/sql/where.c b/src/box/sql/where.c<br>index 16766f2f8..d8d23161b 100644<br>--- a/src/box/sql/where.c<br>+++ b/src/box/sql/where.c<br>@@ -919,15 +919,15 @@ constructAutomaticIndex(Parse * pParse, /* The parsing context */<br>  /* Create the automatic index */<br>  assert(pLevel->iIdxCur >= 0);<br>  pLevel->iIdxCur = pParse->nTab++;<br>- struct sql_key_info *pk_info =<br>- sql_key_info_new_from_key_def(pParse->db, idx_def->key_def);<br>- if (pk_info == NULL) {<br>+ struct sql_space_info *info = sql_space_info_new_from_index_def(idx_def,<br>+ true);<br>+ if (info == NULL) {<br>  pParse->is_aborted = true;<br>  return;<br>  }<br>  int reg_eph = sqlGetTempReg(pParse);<br>- sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, nKeyCol + 1, 0,<br>- (char *)pk_info, P4_KEYINFO);<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_eph, 0, 0, (char *)info,<br>+ P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, pLevel->iIdxCur, 0, reg_eph);<br>  VdbeComment((v, "for %s", space->def->name));<br> <br>diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c<br>index df6cc92e1..0d0cb054d 100644<br>--- a/src/box/sql/wherecode.c<br>+++ b/src/box/sql/wherecode.c<br>@@ -1134,11 +1134,19 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about the W<br>  if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {<br>  cur_row_set = pParse->nTab++;<br>  reg_row_set = ++pParse->nMem;<br>- sqlVdbeAddOp2(v, OP_OpenTEphemeral,<br>- reg_row_set, pk_part_count);<br>+ struct index_def *index_def = space->index[0]->def;<br>+ struct sql_space_info *info =<br>+ sql_space_info_new_from_index_def(index_def,<br>+ false);<br>+ if (info == NULL) {<br>+ pParse->is_aborted = true;<br>+ return notReady;<br>+ }<br>+ sqlVdbeAddOp4(v, OP_OpenTEphemeral, reg_row_set,<br>+ pk_part_count, 0, (char *)info,<br>+ P4_DYNAMIC);<br>  sqlVdbeAddOp3(v, OP_IteratorOpen, cur_row_set, 0,<br>  reg_row_set);<br>- sql_vdbe_set_p4_key_def(pParse, pk_key_def);<br>  regPk = ++pParse->nMem;<br>  }<br>  iRetInit = sqlVdbeAddOp2(v, OP_Integer, 0, regReturn);<br>--<br>2.25.1</div></div></div></div></blockquote><div> </div></BODY></HTML>