[tarantool-patches] [PATCH v4 3/4] box: introduce column_mask for ck constraint
Kirill Shcherbatov
kshcherbatov at tarantool.org
Thu May 16 16:56:52 MSK 2019
This patch introduces a smart bitmask of fields are mentioned in
ck constraint. This allows to do not bind useless fields and
do not parse more fields that is required.
Needed for #3691
---
src/box/ck_constraint.c | 66 ++++++++++++++++++++++++++++++++++------
src/box/ck_constraint.h | 6 ++++
src/box/sql.h | 5 ++-
src/box/sql/build.c | 2 +-
src/box/sql/resolve.c | 19 +++++++++---
src/box/sql/sqlInt.h | 6 ++++
test/sql/checks.result | 59 +++++++++++++++++++++++++++++++++++
test/sql/checks.test.lua | 20 ++++++++++++
8 files changed, 167 insertions(+), 16 deletions(-)
diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c
index 43798be76..4756f7970 100644
--- a/src/box/ck_constraint.c
+++ b/src/box/ck_constraint.c
@@ -46,17 +46,21 @@ const char *ck_constraint_language_strs[] = {"SQL"};
* tree traversal.
* @param ck_constraint Check constraint object to update.
* @param space_def Space definition to use.
+ * @param column_mask[out] The "smart" column mask of fields are
+ * referenced by AST.
* @retval 0 on success.
* @retval -1 on error.
*/
static int
ck_constraint_resolve_field_names(struct Expr *expr,
- struct space_def *space_def)
+ struct space_def *space_def,
+ uint64_t *column_mask)
{
struct Parse parser;
sql_parser_create(&parser, sql_get(), default_flags);
parser.parse_only = true;
- sql_resolve_self_reference(&parser, space_def, NC_IsCheck, expr, NULL);
+ sql_resolve_self_reference(&parser, space_def, NC_IsCheck, expr, NULL,
+ column_mask);
int rc = parser.is_aborted ? -1 : 0;
sql_parser_destroy(&parser);
return rc;
@@ -75,6 +79,8 @@ ck_constraint_resolve_field_names(struct Expr *expr,
* given @ck_constraint_def, see for
* (sql_expr_compile +
* ck_constraint_resolve_space_def) implementation.
+ * @param column_mask The "smart" column mask of fields are
+ * referenced by @a expr.
* @param space_def The space definition of the space this check
* constraint is constructed for.
* @retval not NULL sql_stmt program pointer on success.
@@ -82,7 +88,8 @@ ck_constraint_resolve_field_names(struct Expr *expr,
*/
static struct sql_stmt *
ck_constraint_program_compile(struct ck_constraint_def *ck_constraint_def,
- struct Expr *expr, struct space_def *space_def)
+ struct Expr *expr, uint64_t column_mask,
+ struct space_def *space_def)
{
struct sql *db = sql_get();
struct Parse parser;
@@ -98,10 +105,24 @@ ck_constraint_program_compile(struct ck_constraint_def *ck_constraint_def,
* bind tuple fields there before execution.
*/
uint32_t field_count = space_def->field_count;
+ /*
+ * Use column mask to prepare binding only for
+ * referenced fields.
+ */
int bind_tuple_reg = sqlGetTempRange(&parser, field_count);
- for (uint32_t i = 0; i < field_count; i++) {
+ struct bit_iterator it;
+ bit_iterator_init(&it, &column_mask, sizeof(uint64_t), true);
+ size_t used_fieldno = bit_iterator_next(&it);
+ for (; used_fieldno < SIZE_MAX; used_fieldno = bit_iterator_next(&it)) {
sqlVdbeAddOp2(v, OP_Variable, ++parser.nVar,
- bind_tuple_reg + i);
+ bind_tuple_reg + used_fieldno);
+ }
+ if (column_mask_fieldno_is_set(column_mask, 63)) {
+ used_fieldno = 64;
+ for (; used_fieldno < (size_t)field_count; used_fieldno++) {
+ sqlVdbeAddOp2(v, OP_Variable, ++parser.nVar,
+ bind_tuple_reg + used_fieldno);
+ }
}
/* Generate ck constraint test code. */
vdbe_emit_ck_constraint(&parser, expr, ck_constraint_def->name,
@@ -142,6 +163,13 @@ ck_constraint_program_run(struct ck_constraint *ck_constraint,
*/
struct space *space = space_by_id(ck_constraint->space_id);
assert(space != NULL);
+ /* Use column mask to bind only referenced fields. */
+ struct bit_iterator it;
+ bit_iterator_init(&it, &ck_constraint->column_mask,
+ sizeof(uint64_t), true);
+ size_t used_fieldno = bit_iterator_next(&it);
+ if (used_fieldno == SIZE_MAX)
+ return 0;
/*
* When last format fields are nullable, they are
* 'optional' i.e. they may not be present in the tuple.
@@ -149,15 +177,33 @@ ck_constraint_program_run(struct ck_constraint *ck_constraint,
uint32_t tuple_field_count = mp_decode_array(&new_tuple);
uint32_t field_count =
MIN(tuple_field_count, space->def->field_count);
- for (uint32_t i = 0; i < field_count; i++) {
+ uint32_t bind_pos = 1;
+ for (uint32_t fieldno = 0; fieldno < field_count; fieldno++) {
+ if (used_fieldno == SIZE_MAX &&
+ !column_mask_fieldno_is_set(ck_constraint->column_mask,
+ 63)) {
+ /* No more required fields are left. */
+ break;
+ }
+ if (used_fieldno != SIZE_MAX &&
+ (size_t)fieldno < used_fieldno) {
+ /*
+ * Skip unused fields is mentioned in
+ * column mask.
+ */
+ mp_next(&new_tuple);
+ continue;
+ }
struct sql_bind bind;
- if (sql_bind_decode(&bind, i + 1, &new_tuple) != 0 ||
- sql_bind_column(ck_constraint->stmt, &bind, i + 1) != 0) {
+ if (sql_bind_decode(&bind, bind_pos, &new_tuple) != 0 ||
+ sql_bind_column(ck_constraint->stmt, &bind, bind_pos) != 0) {
diag_set(ClientError, ER_CK_CONSTRAINT_FAILED,
ck_constraint->def->name,
ck_constraint->def->expr_str);
return -1;
}
+ bind_pos++;
+ used_fieldno = bit_iterator_next(&it);
}
/* Checks VDBE can't expire, reset expired flag and go. */
struct Vdbe *v = (struct Vdbe *) ck_constraint->stmt;
@@ -220,7 +266,8 @@ ck_constraint_new(struct ck_constraint_def *ck_constraint_def,
sql_expr_compile(sql_get(), ck_constraint_def->expr_str,
strlen(ck_constraint_def->expr_str));
if (expr == NULL ||
- ck_constraint_resolve_field_names(expr, space_def) != 0) {
+ ck_constraint_resolve_field_names(expr, space_def,
+ &ck_constraint->column_mask) != 0) {
diag_set(ClientError, ER_CREATE_CK_CONSTRAINT,
ck_constraint_def->name,
box_error_message(box_error_last()));
@@ -228,6 +275,7 @@ ck_constraint_new(struct ck_constraint_def *ck_constraint_def,
}
ck_constraint->stmt =
ck_constraint_program_compile(ck_constraint_def, expr,
+ ck_constraint->column_mask,
space_def);
if (ck_constraint->stmt == NULL)
goto error;
diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h
index a948a074e..141bdc11d 100644
--- a/src/box/ck_constraint.h
+++ b/src/box/ck_constraint.h
@@ -88,6 +88,12 @@ struct ck_constraint {
* message when ck condition unsatisfied.
*/
struct sql_stmt *stmt;
+ /**
+ * The "smart" column mask of fields are referenced by
+ * AST. Then the last bit of the mask is set when field
+ * #63 and greater has referenced.
+ */
+ uint64_t column_mask;
/**
* Trigger object executing check constraint before
* insert and replace operations.
diff --git a/src/box/sql.h b/src/box/sql.h
index 9c8a96a75..13f5984f0 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -279,11 +279,14 @@ sql_expr_list_append(struct sql *db, struct ExprList *expr_list,
* @param type NC_IsCheck or NC_IdxExpr.
* @param expr Expression to resolve. May be NULL.
* @param expr_list Expression list to resolve. May be NUL.
+ * @param[out] column_mask The "smart" column mask of fields are
+ * referenced by AST.
*/
void
sql_resolve_self_reference(struct Parse *parser, struct space_def *def,
int type, struct Expr *expr,
- struct ExprList *expr_list);
+ struct ExprList *expr_list,
+ uint64_t *column_mask);
/**
* Initialize a new parser object.
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index f2bbbb9c4..5eac4ee40 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2198,7 +2198,7 @@ index_fill_def(struct Parse *parse, struct index *index,
for (int i = 0; i < expr_list->nExpr; i++) {
struct Expr *expr = expr_list->a[i].pExpr;
sql_resolve_self_reference(parse, space_def, NC_IdxExpr,
- expr, 0);
+ expr, NULL, NULL);
if (parse->is_aborted)
goto cleanup;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 7cf79692c..cbd5cd724 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -556,8 +556,11 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
case TK_ID:{
if ((pNC->ncFlags & NC_AllowAgg) != 0)
pNC->ncFlags |= NC_HasUnaggregatedId;
- return lookupName(pParse, 0, pExpr->u.zToken, pNC,
- pExpr);
+ int rc = lookupName(pParse, 0, pExpr->u.zToken, pNC,
+ pExpr);
+ column_mask_set_fieldno(&pNC->column_mask,
+ pExpr->iColumn);
+ return rc;
}
/* A table name and column name: ID.ID
@@ -583,8 +586,11 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
zTable = pRight->pLeft->u.zToken;
zColumn = pRight->pRight->u.zToken;
}
- return lookupName(pParse, zTable, zColumn, pNC,
- pExpr);
+ int rc = lookupName(pParse, zTable, zColumn, pNC,
+ pExpr);
+ column_mask_set_fieldno(&pNC->column_mask,
+ pExpr->iColumn);
+ return rc;
}
/* Resolve function names
@@ -1612,7 +1618,7 @@ sqlResolveSelectNames(Parse * pParse, /* The parser context */
void
sql_resolve_self_reference(struct Parse *parser, struct space_def *def,
int type, struct Expr *expr,
- struct ExprList *expr_list)
+ struct ExprList *expr_list, uint64_t *column_mask)
{
/* Fake SrcList for parser->create_table_def */
SrcList sSrc;
@@ -1632,8 +1638,11 @@ sql_resolve_self_reference(struct Parse *parser, struct space_def *def,
sNC.pParse = parser;
sNC.pSrcList = &sSrc;
sNC.ncFlags = type;
+ sNC.column_mask = 0;
if (sqlResolveExprNames(&sNC, expr) != 0)
return;
if (expr_list != NULL)
sqlResolveExprListNames(&sNC, expr_list);
+ if (column_mask != NULL)
+ *column_mask = sNC.column_mask;
}
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index d353d7906..0eca37653 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2349,6 +2349,12 @@ struct NameContext {
int nRef; /* Number of names resolved by this context */
int nErr; /* Number of errors encountered while resolving names */
u16 ncFlags; /* Zero or more NC_* flags defined below */
+ /**
+ * The "smart" column mask of fields are referenced
+ * by AST. Then the last bit of the mask is set when
+ * field #63 and greater has referenced.
+ */
+ uint64_t column_mask;
};
/*
diff --git a/test/sql/checks.result b/test/sql/checks.result
index b74572e3e..04f58c90d 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -454,6 +454,65 @@ s:insert({1, 666})
s:drop()
---
...
+--
+-- Test binding optimisation.
+--
+s = box.schema.create_space('test')
+---
+...
+_ = s:create_index('pk', {parts = {1, 'integer'}})
+---
+...
+format65 = {}
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+for i = 1,66 do
+ table.insert(format65, {name='X'..i, type='integer', is_nullable = true})
+end
+test_run:cmd("setopt delimiter ''");
+---
+...
+s:format(format65)
+---
+...
+_ = box.space._ck_constraint:insert({'X1is666andX65is666', s.id, false, 'X1 == 666 and X65 == 666 and X63 IS NOT NULL', 'SQL'})
+---
+...
+s:insert(s:frommap({X1 = 1, X65 = 1}))
+---
+- error: 'Check constraint failed ''X1is666andX65is666'': X1 == 666 and X65 == 666
+ and X63 IS NOT NULL'
+...
+s:insert(s:frommap({X1 = 666, X65 = 1}))
+---
+- error: 'Check constraint failed ''X1is666andX65is666'': X1 == 666 and X65 == 666
+ and X63 IS NOT NULL'
+...
+s:insert(s:frommap({X1 = 1, X65 = 666}))
+---
+- error: 'Check constraint failed ''X1is666andX65is666'': X1 == 666 and X65 == 666
+ and X63 IS NOT NULL'
+...
+s:insert(s:frommap({X1 = 666, X65 = 666}))
+---
+- error: 'Check constraint failed ''X1is666andX65is666'': X1 == 666 and X65 == 666
+ and X63 IS NOT NULL'
+...
+s:insert(s:frommap({X1 = 666, X65 = 666, X63 = 1}))
+---
+- [666, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, 1, null, 666]
+...
+s:drop()
+---
+...
test_run:cmd("clear filter")
---
- true
diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua
index a284edf52..a23dcce51 100644
--- a/test/sql/checks.test.lua
+++ b/test/sql/checks.test.lua
@@ -156,4 +156,24 @@ s:insert({1, 3.14})
s:insert({1, 666})
s:drop()
+--
+-- Test binding optimisation.
+--
+s = box.schema.create_space('test')
+_ = s:create_index('pk', {parts = {1, 'integer'}})
+format65 = {}
+test_run:cmd("setopt delimiter ';'")
+for i = 1,66 do
+ table.insert(format65, {name='X'..i, type='integer', is_nullable = true})
+end
+test_run:cmd("setopt delimiter ''");
+s:format(format65)
+_ = box.space._ck_constraint:insert({'X1is666andX65is666', s.id, false, 'X1 == 666 and X65 == 666 and X63 IS NOT NULL', 'SQL'})
+s:insert(s:frommap({X1 = 1, X65 = 1}))
+s:insert(s:frommap({X1 = 666, X65 = 1}))
+s:insert(s:frommap({X1 = 1, X65 = 666}))
+s:insert(s:frommap({X1 = 666, X65 = 666}))
+s:insert(s:frommap({X1 = 666, X65 = 666, X63 = 1}))
+s:drop()
+
test_run:cmd("clear filter")
--
2.21.0
More information about the Tarantool-patches
mailing list