From: Kirill Shcherbatov <kshcherbatov@tarantool.org> To: tarantool-patches@freelists.org Cc: v.shpilevoy@tarantool.org, Kirill Shcherbatov <kshcherbatov@tarantool.org> Subject: [tarantool-patches] [PATCH v6 3/4] sql: space_def* instead of Table* in Expr Date: Tue, 15 May 2018 20:03:20 +0300 [thread overview] Message-ID: <6702e8ecd47238730a4ba27e87fe7bf082a874c6.1526403792.git.kshcherbatov@tarantool.org> (raw) In-Reply-To: <cover.1526403792.git.kshcherbatov@tarantool.org> In-Reply-To: <cover.1526403792.git.kshcherbatov@tarantool.org> This patch allows to remove Checks from SQL to server as sqlite3ResolveSelfReference requires Expr structure pointer. Part of #3272. --- src/box/field_def.c | 1 + src/box/field_def.h | 14 +++++ src/box/sql.c | 20 +++--- src/box/sql/alter.c | 2 +- src/box/sql/analyze.c | 2 +- src/box/sql/build.c | 100 +++++++++++------------------- src/box/sql/delete.c | 4 +- src/box/sql/expr.c | 158 ++++++++++++++++++++++++++---------------------- src/box/sql/fkey.c | 18 +++--- src/box/sql/insert.c | 47 ++++++++------ src/box/sql/pragma.c | 10 +-- src/box/sql/resolve.c | 12 ++-- src/box/sql/select.c | 26 +++++--- src/box/sql/sqliteInt.h | 44 +++----------- src/box/sql/update.c | 35 +++++------ src/box/sql/vdbe.c | 26 ++++---- src/box/sql/vdbeaux.c | 37 ------------ src/box/sql/vdbemem.c | 18 +++--- src/box/sql/where.c | 15 +++-- src/box/sql/wherecode.c | 35 ++++++----- src/box/sql/whereexpr.c | 6 +- 21 files changed, 293 insertions(+), 337 deletions(-) diff --git a/src/box/field_def.c b/src/box/field_def.c index 010b3b7..4d39d03 100644 --- a/src/box/field_def.c +++ b/src/box/field_def.c @@ -100,6 +100,7 @@ const struct opt_def field_def_reg[] = { const struct field_def field_def_default = { .type = FIELD_TYPE_ANY, + .affinity = AFFINITY_UNDEFINED, .name = NULL, .is_nullable = false, .nullable_action = ON_CONFLICT_ACTION_DEFAULT, diff --git a/src/box/field_def.h b/src/box/field_def.h index cfb0d13..7da06c0 100644 --- a/src/box/field_def.h +++ b/src/box/field_def.h @@ -70,6 +70,15 @@ enum on_conflict_action { on_conflict_action_MAX }; +enum affinity_type { + AFFINITY_UNDEFINED = 0, + AFFINITY_BLOB = 'A', + AFFINITY_TEXT = 'B', + AFFINITY_NUMERIC = 'C', + AFFINITY_INTEGER = 'D', + AFFINITY_REAL = 'E', +}; + /** \endcond public */ extern const char *field_type_strs[]; @@ -102,6 +111,11 @@ struct field_def { * then UNKNOWN is stored for it. */ enum field_type type; + /** + * Affinity type for comparations in SQL. + * FIXME: Remove affinity after types redesign in SQL. + */ + enum affinity_type affinity; /** 0-terminated field name. */ char *name; /** True, if a field can store NULL. */ diff --git a/src/box/sql.c b/src/box/sql.c index 8c7a45e..357cbf9 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1402,17 +1402,17 @@ static const char *convertSqliteAffinity(int affinity, bool allow_nulls) switch (affinity) { default: assert(false); - case SQLITE_AFF_BLOB: + case AFFINITY_BLOB: return "scalar"; - case SQLITE_AFF_TEXT: + case AFFINITY_TEXT: return "string"; - case SQLITE_AFF_NUMERIC: - case SQLITE_AFF_REAL: + case AFFINITY_NUMERIC: + case AFFINITY_REAL: /* Tarantool workaround: to make comparators able to compare, e.g. double and int use generic type. This might be a performance issue. */ /* return "number"; */ return "scalar"; - case SQLITE_AFF_INTEGER: + case AFFINITY_INTEGER: /* See comment above. */ /* return "integer"; */ return "scalar"; @@ -1428,7 +1428,6 @@ static const char *convertSqliteAffinity(int affinity, bool allow_nulls) */ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf) { - struct Column *aCol = pTable->aCol; const struct Enc *enc = get_enc(buf); const struct space_def *def = pTable->def; assert(def != NULL); @@ -1471,8 +1470,9 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf) if (i == pk_forced_int) { t = "integer"; } else { - t = aCol[i].affinity == SQLITE_AFF_BLOB ? "scalar" : - convertSqliteAffinity(aCol[i].affinity, + char affinity = def->fields[i].affinity; + t = affinity == AFFINITY_BLOB ? "scalar" : + convertSqliteAffinity(affinity, def->fields[i].is_nullable); } p = enc->encode_str(p, t, strlen(t)); @@ -1529,7 +1529,6 @@ int tarantoolSqlite3MakeTableOpts(Table *pTable, const char *zSql, void *buf) */ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf) { - struct Column *aCol = pIndex->pTable->aCol; struct space_def *def = pIndex->pTable->def; assert(def != NULL); @@ -1566,7 +1565,8 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf) if (pk_forced_int == col) { t = "integer"; } else { - t = convertSqliteAffinity(aCol[col].affinity, + char affinity = def->fields[col].affinity; + t = convertSqliteAffinity(affinity, def->fields[col].is_nullable); } /* do not decode default collation */ diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c index 11d4dc7..bd8009c 100644 --- a/src/box/sql/alter.c +++ b/src/box/sql/alter.c @@ -218,7 +218,7 @@ sqlite3AlterFinishAddColumn(Parse * pParse, Token * pColDef) sqlite3_value *pVal = 0; int rc; rc = sqlite3ValueFromExpr(db, pDflt, - SQLITE_AFF_BLOB, &pVal); + AFFINITY_BLOB, &pVal); assert(rc == SQLITE_OK || rc == SQLITE_NOMEM); if (rc != SQLITE_OK) { assert(db->mallocFailed == 1); diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c index 2722c08..5dee1e8 100644 --- a/src/box/sql/analyze.c +++ b/src/box/sql/analyze.c @@ -1042,7 +1042,7 @@ analyzeOneTable(Parse * pParse, /* Parser context */ /* Add the entry to the stat1 table. */ callStatGet(v, regStat4, STAT_GET_STAT1, regStat1); - assert("BBB"[0] == SQLITE_AFF_TEXT); + assert("BBB"[0] == AFFINITY_TEXT); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_IdxInsert, iStatCur, regTemp); diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 6139e65..718809d 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -696,7 +696,7 @@ sqlite3AddColumn(Parse * pParse, Token * pName, Token * pType) * TODO: since SQL standard prohibits column creation without * specified type, the code below should emit an error. */ - pCol->affinity = SQLITE_AFF_BLOB; + column_def->affinity = AFFINITY_BLOB; pCol->szEst = 1; column_def->type = FIELD_TYPE_SCALAR; } else { @@ -707,14 +707,14 @@ sqlite3AddColumn(Parse * pParse, Token * pName, Token * pType) pType->n == 7) || (sqlite3StrNICmp(pType->z, "INT", 3) == 0 && pType->n == 3)) { - pCol->affinity = SQLITE_AFF_INTEGER; + column_def->affinity = AFFINITY_INTEGER; column_def->type = FIELD_TYPE_INTEGER; } else { zType = sqlite3_malloc(pType->n + 1); memcpy(zType, pType->z, pType->n); zType[pType->n] = 0; sqlite3Dequote(zType); - pCol->affinity = sqlite3AffinityType(zType, 0); + column_def->affinity = sqlite3AffinityType(zType, 0); sqlite3_free(zType); column_def->type = FIELD_TYPE_SCALAR; } @@ -770,7 +770,7 @@ char sqlite3AffinityType(const char *zIn, u8 * pszEst) { u32 h = 0; - char aff = SQLITE_AFF_NUMERIC; + char aff = AFFINITY_NUMERIC; const char *zChar = 0; assert(zIn != 0); @@ -778,31 +778,31 @@ sqlite3AffinityType(const char *zIn, u8 * pszEst) h = (h << 8) + sqlite3UpperToLower[(*zIn) & 0xff]; zIn++; if (h == (('c' << 24) + ('h' << 16) + ('a' << 8) + 'r')) { /* CHAR */ - aff = SQLITE_AFF_TEXT; + aff = AFFINITY_TEXT; zChar = zIn; } else if (h == (('c' << 24) + ('l' << 16) + ('o' << 8) + 'b')) { /* CLOB */ - aff = SQLITE_AFF_TEXT; + aff = AFFINITY_TEXT; } else if (h == (('t' << 24) + ('e' << 16) + ('x' << 8) + 't')) { /* TEXT */ - aff = SQLITE_AFF_TEXT; + aff = AFFINITY_TEXT; } else if (h == (('b' << 24) + ('l' << 16) + ('o' << 8) + 'b') /* BLOB */ - &&(aff == SQLITE_AFF_NUMERIC - || aff == SQLITE_AFF_REAL)) { - aff = SQLITE_AFF_BLOB; + &&(aff == AFFINITY_NUMERIC + || aff == AFFINITY_REAL)) { + aff = AFFINITY_BLOB; if (zIn[0] == '(') zChar = zIn; #ifndef SQLITE_OMIT_FLOATING_POINT } else if (h == (('r' << 24) + ('e' << 16) + ('a' << 8) + 'l') /* REAL */ - &&aff == SQLITE_AFF_NUMERIC) { - aff = SQLITE_AFF_REAL; + &&aff == AFFINITY_NUMERIC) { + aff = AFFINITY_REAL; } else if (h == (('f' << 24) + ('l' << 16) + ('o' << 8) + 'a') /* FLOA */ - &&aff == SQLITE_AFF_NUMERIC) { - aff = SQLITE_AFF_REAL; + &&aff == AFFINITY_NUMERIC) { + aff = AFFINITY_REAL; } else if (h == (('d' << 24) + ('o' << 16) + ('u' << 8) + 'b') /* DOUB */ - &&aff == SQLITE_AFF_NUMERIC) { - aff = SQLITE_AFF_REAL; + &&aff == AFFINITY_NUMERIC) { + aff = AFFINITY_REAL; #endif } else if ((h & 0x00FFFFFF) == (('i' << 16) + ('n' << 8) + 't')) { /* INT */ - aff = SQLITE_AFF_INTEGER; + aff = AFFINITY_INTEGER; break; } } @@ -813,7 +813,7 @@ sqlite3AffinityType(const char *zIn, u8 * pszEst) if (pszEst) { *pszEst = 1; /* default size is approx 4 bytes */ - if (aff < SQLITE_AFF_NUMERIC) { + if (aff < AFFINITY_NUMERIC) { if (zChar) { while (zChar[0]) { if (sqlite3Isdigit(zChar[0])) { @@ -1030,47 +1030,16 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken) */ for (pIdx = p->pIndex; pIdx; pIdx = pIdx->pNext) { assert(pIdx->nColumn == 1); - if (pIdx->aiColumn[0] == i) - pIdx->coll_array[0] = sql_column_collation(p, i); + if (pIdx->aiColumn[0] == i) { + pIdx->coll_array[0] = + coll_by_id(p->def->fields[i].coll_id); + } } } sqlite3DbFree(db, zColl); } /** - * Return collation of given column from table. - * - * @param table Table which is used to fetch column. - * @param column Number of column. - * @retval Pointer to collation. - */ -struct coll * -sql_column_collation(Table *table, uint32_t column) -{ - assert(table != NULL); - uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum); - struct space *space = space_by_id(space_id); - /* - * It is not always possible to fetch collation directly - * from struct space. To be more precise when: - * 1. space is ephemeral. Thus, its id is zero and - * it can't be found in space cache. - * 2. space is a view. Hence, it lacks any functional - * parts such as indexes or fields. - * 3. space is under construction. So, the same as p.1 - * it can't be found in space cache. - * In cases mentioned above collation is fetched from - * SQL specific structures. - */ - if (space == NULL || space_index(space, 0) == NULL) { - assert(column < (uint32_t)table->def->field_count); - return coll_by_id(table->def->fields[column].coll_id); - } - - return space->format->fields[column].coll; -} - -/** * Return name of given column collation from index. * * @param idx Index which is used to fetch column. @@ -1319,18 +1288,19 @@ createTableStmt(sqlite3 * db, Table * p) k += sqlite3Strlen30(&zStmt[k]); zSep = zSep2; identPut(zStmt, &k, p->def->fields[i].name); - assert(pCol->affinity - SQLITE_AFF_BLOB >= 0); - assert(pCol->affinity - SQLITE_AFF_BLOB < ArraySize(azType)); - testcase(pCol->affinity == SQLITE_AFF_BLOB); - testcase(pCol->affinity == SQLITE_AFF_TEXT); - testcase(pCol->affinity == SQLITE_AFF_NUMERIC); - testcase(pCol->affinity == SQLITE_AFF_INTEGER); - testcase(pCol->affinity == SQLITE_AFF_REAL); - - zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; + char affinity = p->def->fields[i].affinity; + assert(affinity - AFFINITY_BLOB >= 0); + assert(affinity - AFFINITY_BLOB < ArraySize(azType)); + testcase(affinity == AFFINITY_BLOB); + testcase(affinity == AFFINITY_TEXT); + testcase(affinity == AFFINITY_NUMERIC); + testcase(affinity == AFFINITY_INTEGER); + testcase(affinity == AFFINITY_REAL); + + zType = azType[affinity - AFFINITY_BLOB]; len = sqlite3Strlen30(zType); - assert(pCol->affinity == SQLITE_AFF_BLOB - || pCol->affinity == sqlite3AffinityType(zType, 0)); + assert(affinity == AFFINITY_BLOB + || affinity == sqlite3AffinityType(zType, 0)); memcpy(&zStmt[k], zType, len); k += len; assert(k <= n); @@ -3100,7 +3070,7 @@ sqlite3CreateIndex(Parse * pParse, /* All information about this parse */ goto exit_create_index; } } else if (j >= 0) { - coll = sql_column_collation(pTab, j); + coll = coll_by_id(pTab->def->fields[j].coll_id); } else { coll = NULL; } diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c index 37baca2..5056005 100644 --- a/src/box/sql/delete.c +++ b/src/box/sql/delete.c @@ -431,7 +431,7 @@ sqlite3DeleteFrom(Parse * pParse, /* The parser context */ if (!isView) { for (i = 0; i < nPk; i++) { assert(pPk->aiColumn[i] >= 0); - sqlite3ExprCodeGetColumnOfTable(v, pTab, + sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iTabCur, pPk-> aiColumn[i], @@ -747,7 +747,7 @@ sqlite3GenerateRowDelete(Parse * pParse, /* Parsing context */ testcase(mask != 0xffffffff && iCol == 32); if (mask == 0xffffffff || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) { - sqlite3ExprCodeGetColumnOfTable(v, pTab, + sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur, iCol, iOld + iCol + 1); diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index 119940c..9a8f045 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -34,6 +34,7 @@ * for generating VDBE code that evaluates expressions in SQLite. */ #include <box/coll.h> +#include "box/coll_cache.h" #include "sqliteInt.h" #include "box/session.h" @@ -46,10 +47,11 @@ static int exprCodeVector(Parse * pParse, Expr * p, int *piToFree); * Return the affinity character for a single column of a table. */ char -sqlite3TableColumnAffinity(Table * pTab, int iCol) +sqlite3TableColumnAffinity(struct space_def *def, int iCol) { - assert(iCol < (int)pTab->def->field_count); - return iCol >= 0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; + assert(iCol < (int)def->field_count); + return iCol >= 0 ? def->fields[iCol].affinity : + AFFINITY_INTEGER; } /* @@ -90,7 +92,8 @@ sqlite3ExprAffinity(Expr * pExpr) } #endif if (op == TK_AGG_COLUMN || op == TK_COLUMN) { - return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn); + return sqlite3TableColumnAffinity(pExpr->space_def, + pExpr->iColumn); } if (op == TK_SELECT_COLUMN) { assert(pExpr->pLeft->flags & EP_xIsSelect); @@ -179,13 +182,14 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_found) } if ((op == TK_AGG_COLUMN || op == TK_COLUMN || op == TK_REGISTER || op == TK_TRIGGER) && - p->pTab != 0) { + p->space_def != NULL) { /* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally * a TK_COLUMN but was previously evaluated and cached in a register */ int j = p->iColumn; if (j >= 0) { - coll = sql_column_collation(p->pTab, j); + coll = coll_by_id( + p->space_def->fields[j].coll_id); *is_found = true; } break; @@ -244,15 +248,15 @@ sqlite3CompareAffinity(Expr * pExpr, char aff2) */ if (sqlite3IsNumericAffinity(aff1) || sqlite3IsNumericAffinity(aff2)) { - return SQLITE_AFF_NUMERIC; + return AFFINITY_NUMERIC; } else { - return SQLITE_AFF_BLOB; + return AFFINITY_BLOB; } } else if (!aff1 && !aff2) { /* Neither side of the comparison is a column. Compare the * results directly. */ - return SQLITE_AFF_BLOB; + return AFFINITY_BLOB; } else { /* One side is a column, the other is not. Use the columns affinity. */ assert(aff1 == 0 || aff2 == 0); @@ -281,7 +285,7 @@ comparisonAffinity(Expr * pExpr) sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); } else if (NEVER(aff == 0)) { - aff = SQLITE_AFF_BLOB; + aff = AFFINITY_BLOB; } return aff; } @@ -297,10 +301,10 @@ sqlite3IndexAffinityOk(Expr * pExpr, char idx_affinity) { char aff = comparisonAffinity(pExpr); switch (aff) { - case SQLITE_AFF_BLOB: + case AFFINITY_BLOB: return 1; - case SQLITE_AFF_TEXT: - return idx_affinity == SQLITE_AFF_TEXT; + case AFFINITY_TEXT: + return idx_affinity == AFFINITY_TEXT; default: return sqlite3IsNumericAffinity(idx_affinity); } @@ -2132,10 +2136,10 @@ sqlite3ExprCanBeNull(const Expr * p) case TK_BLOB: return 0; case TK_COLUMN: - assert(p->pTab != 0); + assert(p->space_def != NULL); return ExprHasProperty(p, EP_CanBeNull) || (p->iColumn >= 0 - && table_column_is_nullable(p->pTab, p->iColumn)); + && p->space_def->fields[p->iColumn].is_nullable); default: return 1; } @@ -2155,7 +2159,7 @@ int sqlite3ExprNeedsNoAffinityChange(const Expr * p, char aff) { u8 op; - if (aff == SQLITE_AFF_BLOB) + if (aff == AFFINITY_BLOB) return 1; while (p->op == TK_UPLUS || p->op == TK_UMINUS) { p = p->pLeft; @@ -2165,15 +2169,15 @@ sqlite3ExprNeedsNoAffinityChange(const Expr * p, char aff) op = p->op2; switch (op) { case TK_INTEGER:{ - return aff == SQLITE_AFF_INTEGER - || aff == SQLITE_AFF_NUMERIC; + return aff == AFFINITY_INTEGER + || aff == AFFINITY_NUMERIC; } case TK_FLOAT:{ - return aff == SQLITE_AFF_REAL - || aff == SQLITE_AFF_NUMERIC; + return aff == AFFINITY_REAL + || aff == AFFINITY_NUMERIC; } case TK_STRING:{ - return aff == SQLITE_AFF_TEXT; + return aff == AFFINITY_TEXT; } case TK_BLOB:{ return 1; @@ -2181,8 +2185,8 @@ sqlite3ExprNeedsNoAffinityChange(const Expr * p, char aff) case TK_COLUMN:{ assert(p->iTable >= 0); /* p cannot be part of a CHECK constraint */ return p->iColumn < 0 - && (aff == SQLITE_AFF_INTEGER - || aff == SQLITE_AFF_NUMERIC); + && (aff == AFFINITY_INTEGER + || aff == AFFINITY_NUMERIC); } default:{ return 0; @@ -2435,20 +2439,22 @@ sqlite3FindInIndex(Parse * pParse, /* Parsing context */ for (i = 0; i < nExpr && affinity_ok; i++) { Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i); int iCol = pEList->a[i].pExpr->iColumn; - char idxaff = sqlite3TableColumnAffinity(pTab, iCol); /* RHS table */ + /* RHS table */ + char idxaff = + sqlite3TableColumnAffinity(pTab->def, iCol); char cmpaff = sqlite3CompareAffinity(pLhs, idxaff); - testcase(cmpaff == SQLITE_AFF_BLOB); - testcase(cmpaff == SQLITE_AFF_TEXT); + testcase(cmpaff == AFFINITY_BLOB); + testcase(cmpaff == AFFINITY_TEXT); switch (cmpaff) { - case SQLITE_AFF_BLOB: + case AFFINITY_BLOB: break; - case SQLITE_AFF_TEXT: + case AFFINITY_TEXT: /* sqlite3CompareAffinity() only returns TEXT if one side or the * other has no affinity and the other side is TEXT. Hence, * the only way for cmpaff to be TEXT is for idxaff to be TEXT * and for the term on the LHS of the IN to have no affinity. */ - assert(idxaff == SQLITE_AFF_TEXT); + assert(idxaff == AFFINITY_TEXT); break; default: affinity_ok = sqlite3IsNumericAffinity(idxaff); @@ -2830,7 +2836,7 @@ sqlite3CodeSubselect(Parse * pParse, /* Parsing context */ affinity = sqlite3ExprAffinity(pLeft); if (!affinity) { - affinity = SQLITE_AFF_BLOB; + affinity = AFFINITY_BLOB; } if (pKeyInfo) { bool unused; @@ -3175,8 +3181,9 @@ sqlite3ExprCodeIN(Parse * pParse, /* Parsing and code generating context */ struct Index *pk = sqlite3PrimaryKeyIndex(tab); assert(pk); + char affinity = tab->def->fields[pk->aiColumn[0]].affinity; if (pk->nColumn == 1 - && tab->aCol[pk->aiColumn[0]].affinity == 'D' + && affinity == AFFINITY_INTEGER && pk->aiColumn[0] < nVector) { int reg_pk = rLhs + pk->aiColumn[0]; sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse); @@ -3519,45 +3526,48 @@ sqlite3ExprCodeLoadIndexColumn(Parse * pParse, /* The parsing context */ sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); } else { - sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, + sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def, iTabCur, iTabCol, regOut); } } -/* +/** * Generate code to extract the value of the iCol-th column of a table. + * @param v The VDBE under construction. + * @param space_def Space definition. + * @param iTabCur The PK cursor. + * @param iCol Index of the column to extract. + * @param regOut Extract the value into this register. */ void -sqlite3ExprCodeGetColumnOfTable(Vdbe * v, /* The VDBE under construction */ - Table * pTab, /* The table containing the value */ - int iTabCur, /* The PK cursor */ - int iCol, /* Index of the column to extract */ - int regOut /* Extract the value into this register */ - ) +sqlite3ExprCodeGetColumnOfTable(Vdbe *v, struct space_def *space_def, + int iTabCur, int iCol, int regOut) { sqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut); if (iCol >= 0) { - sqlite3ColumnDefault(v, pTab, iCol, regOut); + sqlite3ColumnDefault(v, space_def, iCol, regOut); } } -/* +/** * Generate code that will extract the iColumn-th column from * table pTab and store the column value in a register. * - * An effort is made to store the column value in register iReg. This - * is not garanteeed for GetColumn() - the result can be stored in - * any register. But the result is guaranteed to land in register iReg - * for GetColumnToReg(). + * An effort is made to store the column value in register iReg. + * This is not garanteeed for GetColumn() - the result can be + * stored in any register. But the result is guaranteed to land + * in register iReg for GetColumnToReg(). + * @param pParse Parsing and code generating context. + * @param space_def Space definition. + * @param iColumn Index of the table column. + * @param iTable The cursor pointing to the table. + * @param iReg Store results here. + * @param p5 P5 value for OP_Column + FLAGS. + * @return iReg value. */ int -sqlite3ExprCodeGetColumn(Parse * pParse, /* Parsing and code generating context */ - Table * pTab, /* Description of the table we are reading from */ - int iColumn, /* Index of the table column */ - int iTable, /* The cursor pointing to the table */ - int iReg, /* Store results here */ - u8 p5 /* P5 value for OP_Column + FLAGS */ - ) +sqlite3ExprCodeGetColumn(Parse *pParse, struct space_def *space_def, + int iColumn, int iTable, int iReg, u8 p5) { Vdbe *v = pParse->pVdbe; int i; @@ -3572,7 +3582,7 @@ sqlite3ExprCodeGetColumn(Parse * pParse, /* Parsing and code generating context } } assert(v != 0); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iTable, iColumn, iReg); + sqlite3ExprCodeGetColumnOfTable(v, space_def, iTable, iColumn, iReg); if (p5) { sqlite3VdbeChangeP5(v, p5); } else { @@ -3581,16 +3591,22 @@ sqlite3ExprCodeGetColumn(Parse * pParse, /* Parsing and code generating context return iReg; } +/** + * Generate code that will extract the iColumn-th column from + * table pTab and store the column value in a register, copy the + * result. + * @param pParse Parsing and code generating context. + * @param space_def Space definition. + * @param iColumn Index of the table column. + * @param iTable The cursor pointing to the table. + * @param iReg Store results here. + */ void -sqlite3ExprCodeGetColumnToReg(Parse * pParse, /* Parsing and code generating context */ - Table * pTab, /* Description of the table we are reading from */ - int iColumn, /* Index of the table column */ - int iTable, /* The cursor pointing to the table */ - int iReg /* Store results here */ - ) +sqlite3ExprCodeGetColumnToReg(Parse * pParse, struct space_def * space_def, + int iColumn, int iTable, int iReg) { int r1 = - sqlite3ExprCodeGetColumn(pParse, pTab, iColumn, iTable, iReg, 0); + sqlite3ExprCodeGetColumn(pParse, space_def, iColumn, iTable, iReg, 0); if (r1 != iReg) sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, r1, iReg); } @@ -3777,7 +3793,7 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target) iTab = pParse->iSelfTab; } } - return sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, + return sqlite3ExprCodeGetColumn(pParse, pExpr->space_def, pExpr->iColumn, iTab, target, pExpr->op2); } @@ -4241,23 +4257,21 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target) * p1==1 -> old.a p1==4 -> new.a * p1==2 -> old.b p1==5 -> new.b */ - Table *pTab = pExpr->pTab; + struct space_def *def = pExpr->space_def; int p1 = - pExpr->iTable * (pTab->def->field_count + 1) + 1 + + pExpr->iTable * (def->field_count + 1) + 1 + pExpr->iColumn; assert(pExpr->iTable == 0 || pExpr->iTable == 1); assert(pExpr->iColumn >= 0 - && pExpr->iColumn < (int)pTab->def->field_count); - assert(pTab->iPKey < 0 - || pExpr->iColumn != pTab->iPKey); + && pExpr->iColumn < (int)def->field_count); assert(p1 >= 0 && p1 < - ((int)pTab->def->field_count * 2 + 2)); + ((int)def->field_count * 2 + 2)); sqlite3VdbeAddOp2(v, OP_Param, p1, target); VdbeComment((v, "%s.%s -> $%d", (pExpr->iTable ? "new" : "old"), - pExpr->pTab->def->fields[pExpr->iColumn].name, + def->fields[pExpr->iColumn].name, target)); #ifndef SQLITE_OMIT_FLOATING_POINT @@ -4267,9 +4281,9 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target) * EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to * floating point when extracting it from the record. */ + char affinity = def->fields[pExpr->iColumn].affinity; if (pExpr->iColumn >= 0 - && pTab->aCol[pExpr->iColumn].affinity == - SQLITE_AFF_REAL) { + && affinity == AFFINITY_REAL) { sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } #endif @@ -5440,8 +5454,8 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr) pAggInfo)) >= 0) { pCol = &pAggInfo->aCol[k]; - pCol->pTab = - pExpr->pTab; + pCol->space_def = + pExpr->space_def; pCol->iTable = pExpr->iTable; pCol->iColumn = diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c index 916b346..c7b1cda 100644 --- a/src/box/sql/fkey.c +++ b/src/box/sql/fkey.c @@ -34,6 +34,7 @@ * support to compiled SQL statements. */ #include <box/coll.h> +#include "box/coll_cache.h" #include "sqliteInt.h" #include "box/session.h" #include "tarantoolInt.h" @@ -298,8 +299,8 @@ sqlite3FkLocateIndex(Parse * pParse, /* Parse context to store any error in */ * unusable. Bail out early in this case. */ struct coll *def_coll; - def_coll = sql_column_collation(pParent, - iCol); + def_coll = coll_by_id( + pParent->def->fields[iCol].coll_id); struct coll *coll; coll = sql_index_collation(pIdx, i); if (def_coll != coll) @@ -526,20 +527,19 @@ exprTableRegister(Parse * pParse, /* Parsing and code generating context */ ) { Expr *pExpr; - Column *pCol; sqlite3 *db = pParse->db; pExpr = sqlite3Expr(db, TK_REGISTER, 0); if (pExpr) { if (iCol >= 0 && iCol != pTab->iPKey) { - pCol = &pTab->aCol[iCol]; pExpr->iTable = regBase + iCol + 1; - pExpr->affinity = pCol->affinity; + char affinity = pTab->def->fields[iCol].affinity; + pExpr->affinity = affinity; pExpr = sqlite3ExprAddCollateString(pParse, pExpr, "binary"); } else { pExpr->iTable = regBase; - pExpr->affinity = SQLITE_AFF_INTEGER; + pExpr->affinity = AFFINITY_INTEGER; } } return pExpr; @@ -551,14 +551,14 @@ exprTableRegister(Parse * pParse, /* Parsing and code generating context */ */ static Expr * exprTableColumn(sqlite3 * db, /* The database connection */ - Table * pTab, /* The table whose column is desired */ + struct space_def *def, int iCursor, /* The open cursor on the table */ i16 iCol /* The column that is wanted */ ) { Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); if (pExpr) { - pExpr->pTab = pTab; + pExpr->space_def = def; pExpr->iTable = iCursor; pExpr->iColumn = iCol; } @@ -671,7 +671,7 @@ fkScanChildren(Parse * pParse, /* Parse context */ i16 iCol = pIdx->aiColumn[i]; assert(iCol >= 0); pLeft = exprTableRegister(pParse, pTab, regData, iCol); - pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, + pRight = exprTableColumn(db, pTab->def, pSrc->a[0].iCursor, iCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pAll = sqlite3ExprAnd(db, pAll, pEq); diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 24b0ce6..1a883e2 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -37,6 +37,7 @@ #include "tarantoolInt.h" #include "box/session.h" #include "box/schema.h" +#include "box/coll_cache.h" #include "bit/bit.h" /* @@ -90,7 +91,6 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx) */ int n; int nColumn = index_column_count(pIdx); - Table *pTab = pIdx->pTable; pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, nColumn + 1); if (!pIdx->zColAff) { @@ -100,7 +100,9 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx) for (n = 0; n < nColumn; n++) { i16 x = pIdx->aiColumn[n]; if (x >= 0) { - pIdx->zColAff[n] = pTab->aCol[x].affinity; + char affinity = pIdx->pTable-> + def->fields[x].affinity; + pIdx->zColAff[n] = affinity; } else { char aff; assert(x == XN_EXPR); @@ -109,7 +111,7 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx) sqlite3ExprAffinity(pIdx->aColExpr->a[n]. pExpr); if (aff == 0) - aff = SQLITE_AFF_BLOB; + aff = AFFINITY_BLOB; pIdx->zColAff[n] = aff; } } @@ -155,11 +157,12 @@ sqlite3TableAffinity(Vdbe * v, Table * pTab, int iReg) } for (i = 0; i < (int)pTab->def->field_count; i++) { - zColAff[i] = pTab->aCol[i].affinity; + char affinity = pTab->def->fields[i].affinity; + zColAff[i] = affinity; } do { zColAff[i--] = 0; - } while (i >= 0 && zColAff[i] == SQLITE_AFF_BLOB); + } while (i >= 0 && zColAff[i] == AFFINITY_BLOB); pTab->zColAff = zColAff; } i = sqlite3Strlen30(zColAff); @@ -1117,7 +1120,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse, /* The parser context */ /* Don't bother checking for NOT NULL on columns that do not change */ continue; } - if (table_column_is_nullable(pTab, i) + if (pTab->def->fields[i].is_nullable || (pTab->tabFlags & TF_Autoincrement && pTab->iAutoIncPKey == i)) continue; /* This column is allowed to be NULL */ @@ -1300,7 +1303,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse, /* The parser context */ * not as affinity. Emit code for type checking */ if (nIdxCol == 1) { reg_pk = regNewData + 1 + pIdx->aiColumn[0]; - if (pTab->zColAff[pIdx->aiColumn[0]] == 'D') { + if (pTab->zColAff[pIdx->aiColumn[0]] == + AFFINITY_INTEGER) { int skip_if_null = sqlite3VdbeMakeLabel(v); if ((pTab->tabFlags & TF_Autoincrement) != 0) { sqlite3VdbeAddOp2(v, OP_IsNull, @@ -1798,19 +1802,22 @@ xferOptimization(Parse * pParse, /* Parser context */ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ } for (i = 0; i < (int)pDest->def->field_count; i++) { - Column *pDestCol = &pDest->aCol[i]; - Column *pSrcCol = &pSrc->aCol[i]; - if (pDestCol->affinity != pSrcCol->affinity) { - return 0; /* Affinity must be the same on all columns */ - } - if (sql_column_collation(pDest, i) != - sql_column_collation(pSrc, i)) { - return 0; /* Collating sequence must be the same on all columns */ - } - if (!table_column_is_nullable(pDest, i) - && table_column_is_nullable(pSrc, i)) { - return 0; /* tab2 must be NOT NULL if tab1 is */ - } + char pdest_affinity = pDest->def->fields[i].affinity; + char psrc_affinity = pSrc->def->fields[i].affinity; + /* Affinity must be the same on all columns. */ + if (pdest_affinity != psrc_affinity) + return 0; + /* + * Collating sequence must be the same on all + * columns. + */ + if (coll_by_id(pDest->def->fields[i].coll_id) != + coll_by_id(pSrc->def->fields[i].coll_id)) + return 0; + /* The tab2 must be NOT NULL if tab1 is */ + if (!pDest->def->fields[i].is_nullable + && pSrc->def->fields[i].is_nullable) + return 0; /* Default values for second and subsequent columns need to match. */ if (i > 0) { uint32_t src_space_id = diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c index 6293599..c5ae5cb 100644 --- a/src/box/sql/pragma.c +++ b/src/box/sql/pragma.c @@ -373,7 +373,9 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ i; k++) { } } - bool nullable = table_column_is_nullable(pTab, i); + bool is_nullable = + pTab->def->fields[i]. + is_nullable; uint32_t space_id = SQLITE_PAGENO_TO_SPACEID( pTab->tnum); @@ -388,7 +390,7 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ sqlite3VdbeMultiLoad(v, 1, "issisi", i, name, field_type_strs[type], - nullable == 0, + is_nullable == false, expr_str, k); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6); @@ -689,7 +691,7 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ iKey, regRow); sqlite3ColumnDefault(v, - pTab, + pTab->def, iKey, regRow); sqlite3VdbeAddOp2(v, @@ -706,7 +708,7 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ } else { for (j = 0; j < pFK->nCol; j++) { sqlite3ExprCodeGetColumnOfTable - (v, pTab, 0, + (v, pTab->def, 0, aiCols ? aiCols[j] : pFK->aCol[j]. iFrom, regRow + j); diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index f95ef27..bf729b7 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -227,7 +227,7 @@ lookupName(Parse * pParse, /* The parsing context */ /* Initialize the node to no-match */ pExpr->iTable = -1; - pExpr->pTab = 0; + pExpr->space_def = NULL; ExprSetVVAProperty(pExpr, EP_NoReduce); /* Start at the inner-most context and move outward until a match is found */ @@ -300,7 +300,7 @@ lookupName(Parse * pParse, /* The parsing context */ } if (pMatch) { pExpr->iTable = pMatch->iCursor; - pExpr->pTab = pMatch->pTab; + pExpr->space_def = pMatch->pTab->def; /* RIGHT JOIN not (yet) supported */ assert((pMatch->fg.jointype & JT_RIGHT) == 0); if ((pMatch->fg.jointype & JT_LEFT) != 0) { @@ -347,7 +347,7 @@ lookupName(Parse * pParse, /* The parsing context */ cnt++; if (iCol < 0) { pExpr->affinity = - SQLITE_AFF_INTEGER; + AFFINITY_INTEGER; } else if (pExpr->iTable == 0) { testcase(iCol == 31); testcase(iCol == 32); @@ -364,7 +364,7 @@ lookupName(Parse * pParse, /* The parsing context */ : (((u32) 1) << iCol)); } pExpr->iColumn = (i16) iCol; - pExpr->pTab = pTab; + pExpr->space_def = pTab->def; isTrigger = 1; } } @@ -498,9 +498,9 @@ sqlite3CreateColumnExpr(sqlite3 * db, SrcList * pSrc, int iSrc, int iCol) Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); if (p) { struct SrcList_item *pItem = &pSrc->a[iSrc]; - p->pTab = pItem->pTab; + p->space_def = pItem->pTab->def; p->iTable = pItem->iCursor; - if (p->pTab->iPKey == iCol) { + if (pItem->pTab->iPKey == iCol) { p->iColumn = -1; } else { p->iColumn = (ynVar) iCol; diff --git a/src/box/sql/select.c b/src/box/sql/select.c index e08d709..48aaffc 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -1636,7 +1636,7 @@ columnTypeImpl(NameContext * pNC, Expr * pExpr, break; } - assert(pTab && pExpr->pTab == pTab); + assert(pTab != NULL && pExpr->space_def == pTab->def); if (pS) { /* The "table" is actually a sub-select or a view in the FROM clause * of the SELECT statement. Return the declaration type and origin @@ -1850,19 +1850,23 @@ sqlite3ColumnsFromExprList(Parse * pParse, /* Parsing context */ /* If the column contains an "AS <name>" phrase, use <name> as the name */ } else { Expr *pColExpr = p; /* The expression that is the result column name */ - Table *pTab; /* Table associated with this expression */ + struct space_def *space_def; while (pColExpr->op == TK_DOT) { pColExpr = pColExpr->pRight; assert(pColExpr != 0); } if (pColExpr->op == TK_COLUMN - && ALWAYS(pColExpr->pTab != 0)) { + && ALWAYS(pColExpr->space_def != NULL)) { /* For columns use the column name name */ int iCol = pColExpr->iColumn; - pTab = pColExpr->pTab; + space_def = pColExpr->space_def; + Table *pTable = + sqlite3LocateTable(pParse, 0, + space_def->name); + assert(pTable != NULL); if (iCol < 0) - iCol = pTab->iPKey; - zName = pTab->def->fields[iCol].name; + iCol = pTable->iPKey; + zName = space_def->fields[iCol].name; } else if (pColExpr->op == TK_ID) { assert(!ExprHasProperty(pColExpr, EP_IntValue)); zName = pColExpr->u.zToken; @@ -1960,11 +1964,13 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse, /* Parsing contexts */ p = a[i].pExpr; type = columnType(&sNC, p, 0, 0, 0, &pCol->szEst); szAll += pCol->szEst; - pCol->affinity = sqlite3ExprAffinity(p); pTab->def->fields[i].type = type; - if (pCol->affinity == 0) - pCol->affinity = SQLITE_AFF_BLOB; + char affinity = sqlite3ExprAffinity(p); + if (affinity == 0) + affinity = AFFINITY_BLOB; + pTab->def->fields[i].affinity = affinity; + bool unused; struct coll *coll = sql_expr_coll(pParse, p, &unused); if (coll != NULL && pTab->def->fields[i].coll_id == COLL_NONE) @@ -5948,7 +5954,7 @@ sqlite3Select(Parse * pParse, /* The parser context */ if (pCol->iSorterColumn >= j) { int r1 = j + regBase; sqlite3ExprCodeGetColumnToReg - (pParse, pCol->pTab, + (pParse, pCol->space_def, pCol->iColumn, pCol->iTable, r1); j++; diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index 772fa26..75699ae 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -1867,7 +1867,6 @@ struct Savepoint { * of this structure. */ struct Column { - char affinity; /* One of the SQLITE_AFF_... values */ u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */ u8 is_primkey; /* Boolean propertie for being PK */ }; @@ -1879,27 +1878,7 @@ struct Column { #define SQLITE_SO_DESC 1 /* Sort in ascending order */ #define SQLITE_SO_UNDEFINED -1 /* No sort order specified */ -/* - * Column affinity types. - * - * These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and - * 't' for SQLITE_AFF_TEXT. But we can save a little space and improve - * the speed a little by numbering the values consecutively. - * - * But rather than start with 0 or 1, we begin with 'A'. That way, - * when multiple affinity types are concatenated into a string and - * used as the P4 operand, they will be more readable. - * - * Note also that the numeric types are grouped together so that testing - * for a numeric type is a single comparison. And the BLOB type is first. - */ -#define SQLITE_AFF_BLOB 'A' -#define SQLITE_AFF_TEXT 'B' -#define SQLITE_AFF_NUMERIC 'C' -#define SQLITE_AFF_INTEGER 'D' -#define SQLITE_AFF_REAL 'E' - -#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) +#define sqlite3IsNumericAffinity(X) ((X)>=AFFINITY_NUMERIC) /* * The SQLITE_AFF_MASK values masks off the significant bits of an @@ -2226,7 +2205,8 @@ struct AggInfo { int mnReg, mxReg; /* Range of registers allocated for aCol and aFunc */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ - Table *pTab; /* Source table */ + /** Pointer to space definition. */ + struct space_def *space_def; int iTable; /* Cursor number of the source table */ int iColumn; /* Column number within the source table */ int iSorterColumn; /* Column number in the sorting index */ @@ -2361,7 +2341,8 @@ struct Expr { * TK_AGG_FUNCTION: nesting depth */ AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ - Table *pTab; /* Table for TK_COLUMN expressions. */ + /** Pointer for table relative definition. */ + struct space_def *space_def; }; /* @@ -3518,8 +3499,6 @@ void sqlite3AddCollateType(Parse *, Token *); const char * column_collation_name(Table *, uint32_t); -struct coll * -sql_column_collation(Table *, uint32_t); const char * index_collation_name(Index *, uint32_t); struct coll * @@ -3606,9 +3585,9 @@ int sqlite3WhereOkOnePass(WhereInfo *, int *); #define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */ #define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */ void sqlite3ExprCodeLoadIndexColumn(Parse *, Index *, int, int, int); -int sqlite3ExprCodeGetColumn(Parse *, Table *, int, int, int, u8); -void sqlite3ExprCodeGetColumnToReg(Parse *, Table *, int, int, int); -void sqlite3ExprCodeGetColumnOfTable(Vdbe *, Table *, int, int, int); +int sqlite3ExprCodeGetColumn(Parse *, struct space_def *, int, int, int, u8); +void sqlite3ExprCodeGetColumnToReg(Parse *, struct space_def *, int, int, int); +void sqlite3ExprCodeGetColumnOfTable(Vdbe *, struct space_def *, int, int, int); void sqlite3ExprCodeMove(Parse *, int, int, int); void sqlite3ExprCacheStore(Parse *, int, int, int); void sqlite3ExprCachePush(Parse *); @@ -3801,7 +3780,7 @@ const char *sqlite3IndexAffinityStr(sqlite3 *, Index *); void sqlite3TableAffinity(Vdbe *, Table *, int); char sqlite3CompareAffinity(Expr * pExpr, char aff2); int sqlite3IndexAffinityOk(Expr * pExpr, char idx_affinity); -char sqlite3TableColumnAffinity(Table *, int); +char sqlite3TableColumnAffinity(struct space_def *, int); char sqlite3ExprAffinity(Expr * pExpr); int sqlite3Atoi64(const char *, i64 *, int); int sqlite3DecOrHexToI64(const char *, i64 *); @@ -3889,7 +3868,7 @@ int sqlite3ResolveExprListNames(NameContext *, ExprList *); void sqlite3ResolveSelectNames(Parse *, Select *, NameContext *); void sqlite3ResolveSelfReference(Parse *, Table *, int, Expr *, ExprList *); int sqlite3ResolveOrderGroupBy(Parse *, Select *, ExprList *, const char *); -void sqlite3ColumnDefault(Vdbe *, Table *, int, int); +void sqlite3ColumnDefault(Vdbe *, struct space_def *, int, int); void sqlite3AlterFinishAddColumn(Parse *, Token *); void sqlite3AlterBeginAddColumn(Parse *, SrcList *); char* rename_table(sqlite3 *, const char *, const char *, bool *); @@ -4138,9 +4117,6 @@ extern int sqlite3InitDatabase(sqlite3 * db); enum on_conflict_action table_column_nullable_action(struct Table *tab, uint32_t column); -bool -table_column_is_nullable(struct Table *tab, uint32_t column); - /** * Initialize a new parser object. * @param parser object to initialize. diff --git a/src/box/sql/update.c b/src/box/sql/update.c index 5f5807c..635b2d6 100644 --- a/src/box/sql/update.c +++ b/src/box/sql/update.c @@ -69,30 +69,27 @@ * space. */ void -sqlite3ColumnDefault(Vdbe * v, Table * pTab, int i, int iReg) +sqlite3ColumnDefault(Vdbe *v, struct space_def *def, int i, int iReg) { - assert(pTab != 0); - assert(pTab->def->opts.is_view == (pTab->pSelect != NULL)); - if (!pTab->def->opts.is_view) { + assert(def != NULL); + + if (!def->opts.is_view) { sqlite3_value *pValue = 0; - Column *pCol = &pTab->aCol[i]; - VdbeComment((v, "%s.%s", pTab->def->name, - pTab->def->fields[i].name)); - assert(i < (int)pTab->def->field_count); + char affinity = def->fields[i].affinity; + VdbeComment((v, "%s.%s", def->name, def->fields[i].name)); + assert(i < (int)def->field_count); Expr *expr = NULL; - struct space *space = - space_cache_find(SQLITE_PAGENO_TO_SPACEID(pTab->tnum)); - if (space != NULL && space->def->fields != NULL) - expr = space->def->fields[i].default_value_expr; + assert(def->fields != NULL && i < (int)def->field_count); + if (def->fields != NULL) + expr = def->fields[i].default_value_expr; sqlite3ValueFromExpr(sqlite3VdbeDb(v), - expr, - pCol->affinity, &pValue); + expr, affinity, &pValue); if (pValue) { sqlite3VdbeAppendP4(v, pValue, P4_MEM); } #ifndef SQLITE_OMIT_FLOATING_POINT - if (pTab->aCol[i].affinity == SQLITE_AFF_REAL) { + if (affinity == AFFINITY_REAL) { sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); } #endif @@ -381,7 +378,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ } else { for (i = 0; i < nPk; i++) { assert(pPk->aiColumn[i] >= 0); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, + sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur, pPk->aiColumn[i], iPk + i); } @@ -486,7 +483,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ || (i < 32 && (oldmask & MASKBIT32(i)) != 0) || table_column_is_in_pk(pTab, i)) { testcase(oldmask != 0xffffffff && i == 31); - sqlite3ExprCodeGetColumnOfTable(v, pTab, + sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur, i, regOld + i); } else { @@ -529,7 +526,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ */ testcase(i == 31); testcase(i == 32); - sqlite3ExprCodeGetColumnToReg(pParse, pTab, i, + sqlite3ExprCodeGetColumnToReg(pParse, pTab->def, i, iDataCur, regNew + i); } else { @@ -570,7 +567,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ */ for (i = 0; i < (int)pTab->def->field_count; i++) { if (aXRef[i] < 0 && i != pTab->iPKey) { - sqlite3ExprCodeGetColumnOfTable(v, pTab, + sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur, i, regNew + i); } diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index dc779b4..e4951c2 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -325,9 +325,9 @@ applyAffinity( char affinity /* The affinity to be applied */ ) { - if (affinity>=SQLITE_AFF_NUMERIC) { - assert(affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL - || affinity==SQLITE_AFF_NUMERIC); + if (affinity>=AFFINITY_NUMERIC) { + assert(affinity==AFFINITY_INTEGER || affinity==AFFINITY_REAL + || affinity==AFFINITY_NUMERIC); if ((pRec->flags & MEM_Int)==0) { /*OPTIMIZATION-IF-FALSE*/ if ((pRec->flags & MEM_Real)==0) { if (pRec->flags & MEM_Str) applyNumericAffinity(pRec,1); @@ -335,7 +335,7 @@ applyAffinity( sqlite3VdbeIntegerAffinity(pRec); } } - } else if (affinity==SQLITE_AFF_TEXT) { + } else if (affinity==AFFINITY_TEXT) { /* Only attempt the conversion to TEXT if there is an integer or real * representation (blob and NULL do not get converted) but no string * representation. It would be harmless to repeat the conversion if @@ -1887,7 +1887,7 @@ case OP_AddImm: { /* in1 */ case OP_MustBeInt: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; if ((pIn1->flags & MEM_Int)==0) { - applyAffinity(pIn1, SQLITE_AFF_NUMERIC); + applyAffinity(pIn1, AFFINITY_NUMERIC); VdbeBranchTaken((pIn1->flags&MEM_Int)==0, 2); if ((pIn1->flags & MEM_Int)==0) { if (pOp->p2==0) { @@ -1938,12 +1938,12 @@ case OP_RealAffinity: { /* in1 */ * A NULL value is not changed by this routine. It remains NULL. */ case OP_Cast: { /* in1 */ - assert(pOp->p2>=SQLITE_AFF_BLOB && pOp->p2<=SQLITE_AFF_REAL); - testcase( pOp->p2==SQLITE_AFF_TEXT); - testcase( pOp->p2==SQLITE_AFF_BLOB); - testcase( pOp->p2==SQLITE_AFF_NUMERIC); - testcase( pOp->p2==SQLITE_AFF_INTEGER); - testcase( pOp->p2==SQLITE_AFF_REAL); + assert(pOp->p2>=AFFINITY_BLOB && pOp->p2<=AFFINITY_REAL); + testcase( pOp->p2==AFFINITY_TEXT); + testcase( pOp->p2==AFFINITY_BLOB); + testcase( pOp->p2==AFFINITY_NUMERIC); + testcase( pOp->p2==AFFINITY_INTEGER); + testcase( pOp->p2==AFFINITY_REAL); pIn1 = &aMem[pOp->p1]; memAboutToChange(p, pIn1); rc = ExpandBlob(pIn1); @@ -2104,7 +2104,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } else { /* Neither operand is NULL. Do a comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; - if (affinity>=SQLITE_AFF_NUMERIC) { + if (affinity>=AFFINITY_NUMERIC) { if ((flags1 | flags3)&MEM_Str) { if ((flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str) { applyNumericAffinity(pIn1,0); @@ -2124,7 +2124,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ res = 0; goto compare_op; } - } else if (affinity==SQLITE_AFF_TEXT) { + } else if (affinity==AFFINITY_TEXT) { if ((flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0) { testcase( pIn1->flags & MEM_Int); testcase( pIn1->flags & MEM_Real); diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index aad1b37..4e3b236 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -4712,40 +4712,3 @@ table_column_nullable_action(struct Table *tab, uint32_t column) return field.nullable_action; } - -/** - * Return nullable flag value of given column in given table. - * FIXME: This is implemented in expensive way. For each invocation table lookup - * is performed. In future, first param will be replaced with pointer to struct - * space. - * - * @param tab pointer to the table - * @param column column number for which action to be returned - * @return return nullability flag value - */ -bool -table_column_is_nullable(struct Table *tab, uint32_t column) -{ - /* Temporary hack: until Tarantoool's ephemeral spaces are on-boarded, - * views are not handled properly in Tarantool as well. */ - if (!(tab->tabFlags | TF_Ephemeral || space_is_view(tab))) { - uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(tab->tnum); - struct space *space = space_cache_find(space_id); - - assert(space); - - struct tuple_format *format = space->format; - - assert(format); - assert(format->field_count > column); - - return nullable_action_is_nullable( - format->fields[column].nullable_action); - } else { - /* tab is ephemeral (in SQLite sense). */ - assert(tab->def->fields[column].is_nullable == - nullable_action_is_nullable( - tab->def->fields[column].nullable_action)); - return tab->def->fields[column].is_nullable; - } -} diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c index 9dd254f..099068e 100644 --- a/src/box/sql/vdbemem.c +++ b/src/box/sql/vdbemem.c @@ -588,9 +588,9 @@ sqlite3VdbeMemCast(Mem * pMem, u8 aff) if (pMem->flags & MEM_Null) return; switch (aff) { - case SQLITE_AFF_BLOB:{ /* Really a cast to BLOB */ + case AFFINITY_BLOB:{ /* Really a cast to BLOB */ if ((pMem->flags & MEM_Blob) == 0) { - sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT); + sqlite3ValueApplyAffinity(pMem, AFFINITY_TEXT); assert(pMem->flags & MEM_Str || pMem->db->mallocFailed); if (pMem->flags & MEM_Str) @@ -600,23 +600,23 @@ sqlite3VdbeMemCast(Mem * pMem, u8 aff) } break; } - case SQLITE_AFF_NUMERIC:{ + case AFFINITY_NUMERIC:{ sqlite3VdbeMemNumerify(pMem); break; } - case SQLITE_AFF_INTEGER:{ + case AFFINITY_INTEGER:{ sqlite3VdbeMemIntegerify(pMem); break; } - case SQLITE_AFF_REAL:{ + case AFFINITY_REAL:{ sqlite3VdbeMemRealify(pMem); break; } default:{ - assert(aff == SQLITE_AFF_TEXT); + assert(aff == AFFINITY_TEXT); assert(MEM_Str == (MEM_Blob >> 3)); pMem->flags |= (pMem->flags & MEM_Blob) >> 3; - sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT); + sqlite3ValueApplyAffinity(pMem, AFFINITY_TEXT); assert(pMem->flags & MEM_Str || pMem->db->mallocFailed); pMem->flags &= ~(MEM_Int | MEM_Real | MEM_Blob | MEM_Zero); @@ -1300,8 +1300,8 @@ valueFromExpr(sqlite3 * db, /* The database connection */ sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_DYNAMIC); } if ((op == TK_INTEGER || op == TK_FLOAT) - && affinity == SQLITE_AFF_BLOB) { - sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC); + && affinity == AFFINITY_BLOB) { + sqlite3ValueApplyAffinity(pVal, AFFINITY_NUMERIC); } else { sqlite3ValueApplyAffinity(pVal, affinity); } diff --git a/src/box/sql/where.c b/src/box/sql/where.c index 9ab6295..f3a10c0 100644 --- a/src/box/sql/where.c +++ b/src/box/sql/where.c @@ -378,7 +378,9 @@ whereScanInit(WhereScan * pScan, /* The WhereScan object being initialized */ if (iColumn == XN_EXPR) { pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; } else if (iColumn >= 0) { - pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; + char affinity = + pIdx->pTable->def->fields[iColumn].affinity; + pScan->idxaff = affinity; pScan->coll = sql_index_collation(pIdx, j); pScan->is_column_seen = true; } @@ -491,7 +493,7 @@ indexColumnNotNull(Index * pIdx, int iCol) assert(iCol >= 0 && iCol < (int)index_column_count(pIdx)); j = pIdx->aiColumn[iCol]; if (j >= 0) { - return !table_column_is_nullable(pIdx->pTable, j); + return !pIdx->pTable->def->fields[j].is_nullable; } else if (j == (-1)) { return 1; } else { @@ -1111,7 +1113,7 @@ sqlite3IndexColumnAffinity(sqlite3 * db, Index * pIdx, int iCol) assert(iCol >= 0 && iCol < (int)index_column_count(pIdx)); if (!pIdx->zColAff) { if (sqlite3IndexAffinityStr(db, pIdx) == 0) - return SQLITE_AFF_BLOB; + return AFFINITY_BLOB; } return pIdx->zColAff[iCol]; } @@ -2236,7 +2238,8 @@ whereRangeVectorLen(Parse * pParse, /* Parsing context */ aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs)); idxaff = - sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); + sqlite3TableColumnAffinity(pIdx->pTable->def, + pLhs->iColumn); if (aff != idxaff) break; @@ -3330,8 +3333,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo, /* The WHERE clause */ if (isOrderDistinct && iColumn >= 0 && j >= pLoop->nEq - && table_column_is_nullable(pIndex->pTable, - iColumn)) { + && pIndex->pTable->def->fields[iColumn]. + is_nullable) { isOrderDistinct = 0; } diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c index 9d4055a..a3db23b 100644 --- a/src/box/sql/wherecode.c +++ b/src/box/sql/wherecode.c @@ -377,12 +377,12 @@ codeApplyAffinity(Parse * pParse, int base, int n, char *zAff) /* Adjust base and n to skip over SQLITE_AFF_BLOB entries at the beginning * and end of the affinity string. */ - while (n > 0 && zAff[0] == SQLITE_AFF_BLOB) { + while (n > 0 && zAff[0] == AFFINITY_BLOB) { n--; base++; zAff++; } - while (n > 1 && zAff[n - 1] == SQLITE_AFF_BLOB) { + while (n > 1 && zAff[n - 1] == AFFINITY_BLOB) { n--; } @@ -411,9 +411,9 @@ updateRangeAffinityStr(Expr * pRight, /* RHS of comparison */ int i; for (i = 0; i < n; i++) { Expr *p = sqlite3VectorFieldSubexpr(pRight, i); - if (sqlite3CompareAffinity(p, zAff[i]) == SQLITE_AFF_BLOB + if (sqlite3CompareAffinity(p, zAff[i]) == AFFINITY_BLOB || sqlite3ExprNeedsNoAffinityChange(p, zAff[i])) { - zAff[i] = SQLITE_AFF_BLOB; + zAff[i] = AFFINITY_BLOB; } } } @@ -753,7 +753,7 @@ codeAllEqualityTerms(Parse * pParse, /* Parsing context */ * affinity of the comparison has been applied to the value. */ if (zAff) - zAff[j] = SQLITE_AFF_BLOB; + zAff[j] = AFFINITY_BLOB; } } else if ((pTerm->eOperator & WO_ISNULL) == 0) { Expr *pRight = pTerm->pExpr->pRight; @@ -765,12 +765,12 @@ codeAllEqualityTerms(Parse * pParse, /* Parsing context */ } if (zAff) { if (sqlite3CompareAffinity(pRight, zAff[j]) == - SQLITE_AFF_BLOB) { - zAff[j] = SQLITE_AFF_BLOB; + AFFINITY_BLOB) { + zAff[j] = AFFINITY_BLOB; } if (sqlite3ExprNeedsNoAffinityChange (pRight, zAff[j])) { - zAff[j] = SQLITE_AFF_BLOB; + zAff[j] = AFFINITY_BLOB; } } } @@ -909,7 +909,7 @@ codeCursorHintFixExpr(Walker * pWalker, Expr * pExpr) if (pExpr->iTable != pHint->iTabCur) { Vdbe *v = pWalker->pParse->pVdbe; int reg = ++pWalker->pParse->nMem; /* Register for column value */ - sqlite3ExprCodeGetColumnOfTable(v, pExpr->pTab, + sqlite3ExprCodeGetColumnOfTable(v, pExpr->pTab->def, pExpr->iTable, pExpr->iColumn, reg); pExpr->op = TK_REGISTER; @@ -1260,8 +1260,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about t * FYI: entries in an index are ordered as follows: * NULL, ... NULL, min_value, ... */ - if ((j >= 0 && table_column_is_nullable(pIdx->pTable, j)) - || j == XN_EXPR) { + if ((j >= 0 && + pIdx->pTable->def->fields[j].is_nullable) || + j == XN_EXPR) { assert(pLoop->nSkip == 0); bSeekPastNull = 1; nExtraReg = 1; @@ -1306,8 +1307,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about t #endif if (pRangeStart == 0) { j = pIdx->aiColumn[nEq]; - if ((j >= 0 - && table_column_is_nullable(pIdx->pTable, j)) || j == XN_EXPR) { + if ((j >= 0 && + pIdx->pTable->def->fields[j].is_nullable) || + j == XN_EXPR) { bSeekPastNull = 1; } } @@ -1386,8 +1388,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about t struct Index *pk = sqlite3PrimaryKeyIndex(pIdx->pTable); assert(pk); int nPkCol = index_column_count(pk); - if (nPkCol == 1 - && pIdx->pTable->aCol[pk->aiColumn[0]].affinity == 'D') { + char affinity = + pIdx->pTable->def->fields[pk->aiColumn[0]].affinity; + if (nPkCol == 1 && affinity == AFFINITY_INTEGER) { /* Right now INTEGER PRIMARY KEY is the only option to * get Tarantool's INTEGER column type. Need special handling * here: try to loosely convert FLOAT to INT. If RHS type @@ -1724,7 +1727,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about t for (iPk = 0; iPk < nPk; iPk++) { int iCol = pPk->aiColumn[iPk]; sqlite3ExprCodeGetColumnToReg - (pParse, pTab, + (pParse, pTab->def, iCol, iCur, r + iPk); } diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c index e602111..2636cd5 100644 --- a/src/box/sql/whereexpr.c +++ b/src/box/sql/whereexpr.c @@ -261,7 +261,7 @@ isLikeOrGlob(Parse * pParse, /* Parsing and code generating context */ #endif pList = pExpr->x.pList; pLeft = pList->a[1].pExpr; - if (pLeft->op != TK_COLUMN || sqlite3ExprAffinity(pLeft) != SQLITE_AFF_TEXT /* Value might be numeric */ + if (pLeft->op != TK_COLUMN || sqlite3ExprAffinity(pLeft) != AFFINITY_TEXT /* Value might be numeric */ ) { /* IMP: R-02065-49465 The left-hand side of the LIKE or GLOB operator must * be the name of an indexed column with TEXT affinity. @@ -276,7 +276,7 @@ isLikeOrGlob(Parse * pParse, /* Parsing and code generating context */ Vdbe *pReprepare = pParse->pReprepare; int iCol = pRight->iColumn; pVal = - sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB); + sqlite3VdbeGetBoundValue(pReprepare, iCol, AFFINITY_BLOB); if (pVal && sqlite3_value_type(pVal) == SQLITE_TEXT) { z = (char *)sqlite3_value_text(pVal); } @@ -1516,7 +1516,7 @@ sqlite3WhereTabFuncArgs(Parse * pParse, /* Parsing context */ return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; - pColRef->pTab = pTab; + pColRef->space_def = pTab->def; pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0)); -- 2.7.4
next prev parent reply other threads:[~2018-05-15 17:03 UTC|newest] Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-05-15 17:03 [tarantool-patches] [PATCH v6 0/4] sql: moved Checks to server Kirill Shcherbatov 2018-05-15 17:03 ` [tarantool-patches] [PATCH v6 1/4] sql: fix code style in sqlite3Pragma Kirill Shcherbatov 2018-05-15 17:03 ` [tarantool-patches] [PATCH v6 2/4] sql: remove SQL fields from Table and Column Kirill Shcherbatov 2018-05-17 17:25 ` [tarantool-patches] " n.pettik 2018-05-18 15:35 ` Kirill Shcherbatov 2018-05-18 17:24 ` n.pettik 2018-05-18 19:45 ` Kirill Shcherbatov 2018-05-18 20:13 ` n.pettik 2018-05-15 17:03 ` Kirill Shcherbatov [this message] 2018-05-16 12:33 ` [tarantool-patches] Re: [PATCH v6 3/4] sql: space_def* instead of Table* in Expr Vladislav Shpilevoy 2018-05-16 13:10 ` Kirill Shcherbatov 2018-05-16 13:11 ` Vladislav Shpilevoy [not found] ` <26E4269B-2BCB-42C3-8216-D51E290E4723@corp.mail.ru> 2018-05-18 15:26 ` Kirill Shcherbatov 2018-05-18 17:04 ` n.pettik 2018-05-21 12:48 ` [tarantool-patches] " Nikita Pettik 2018-05-15 17:03 ` [tarantool-patches] [PATCH v6 4/4] sql: remove Checks to server Kirill Shcherbatov 2018-05-16 17:59 ` [tarantool-patches] " Vladislav Shpilevoy 2018-05-16 11:52 ` [tarantool-patches] Re: [PATCH v6 0/4] sql: moved " Vladislav Shpilevoy 2018-05-16 13:13 ` Kirill Shcherbatov 2018-05-23 5:19 ` Kirill Yukhin
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=6702e8ecd47238730a4ba27e87fe7bf082a874c6.1526403792.git.kshcherbatov@tarantool.org \ --to=kshcherbatov@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=v.shpilevoy@tarantool.org \ --subject='Re: [tarantool-patches] [PATCH v6 3/4] sql: space_def* instead of Table* in Expr' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox