mirror of
https://github.com/samba-team/samba.git
synced 2025-08-04 08:22:08 +03:00
VLV: handle empty results correctly
The VLV was wrongly returning an operations error when the list of results was empty. Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Pair-programmed-with: Garming Sam <garming@catalyst.net.nz> Reviewed-by: Garming Sam <garming@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
committed by
Garming Sam
parent
34d2bfe5de
commit
b59b22a117
@ -355,48 +355,57 @@ static int vlv_results(struct vlv_context *ac)
|
|||||||
vlv_details = ac->store->vlv_details;
|
vlv_details = ac->store->vlv_details;
|
||||||
sort_details = ac->store->sort_details;
|
sort_details = ac->store->sort_details;
|
||||||
|
|
||||||
if (vlv_details->type == 1) {
|
if (ac->store->num_entries != 0) {
|
||||||
target = vlv_gt_eq_to_index(ac, ac->store->results, vlv_details,
|
if (vlv_details->type == 1) {
|
||||||
sort_details, &ret);
|
target = vlv_gt_eq_to_index(ac, ac->store->results,
|
||||||
if (ret != LDB_SUCCESS) {
|
vlv_details,
|
||||||
return ret;
|
sort_details, &ret);
|
||||||
|
if (ret != LDB_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = vlv_calc_real_offset(vlv_details->match.byOffset.offset,
|
||||||
|
vlv_details->match.byOffset.contentCount,
|
||||||
|
ac->store->num_entries);
|
||||||
|
if (target == -1) {
|
||||||
|
return LDB_ERR_OPERATIONS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send the results */
|
||||||
|
first_i = MAX(target - vlv_details->beforeCount, 0);
|
||||||
|
last_i = MIN(target + vlv_details->afterCount,
|
||||||
|
ac->store->num_entries - 1);
|
||||||
|
|
||||||
|
for (i = first_i; i <= last_i; i++) {
|
||||||
|
struct ldb_result *result;
|
||||||
|
struct GUID *guid = &ac->store->results[i];
|
||||||
|
ret = dsdb_search_by_dn_guid(ldb, ac,
|
||||||
|
&result,
|
||||||
|
guid,
|
||||||
|
ac->req->op.search.attrs,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (ret == LDAP_NO_SUCH_OBJECT) {
|
||||||
|
/* The thing isn't there, which we quietly
|
||||||
|
ignore and go on to send an extra one
|
||||||
|
instead. */
|
||||||
|
if (last_i < ac->store->num_entries - 1) {
|
||||||
|
last_i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (ret != LDB_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ldb_module_send_entry(ac->req, result->msgs[0],
|
||||||
|
NULL);
|
||||||
|
if (ret != LDB_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = vlv_calc_real_offset(vlv_details->match.byOffset.offset,
|
target = -1;
|
||||||
vlv_details->match.byOffset.contentCount,
|
|
||||||
ac->store->num_entries);
|
|
||||||
if (target == -1) {
|
|
||||||
return LDB_ERR_OPERATIONS_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* send the results */
|
|
||||||
first_i = MAX(target - vlv_details->beforeCount, 0);
|
|
||||||
last_i = MIN(target + vlv_details->afterCount, ac->store->num_entries - 1);
|
|
||||||
|
|
||||||
for (i = first_i; i <= last_i; i++) {
|
|
||||||
struct ldb_result *result;
|
|
||||||
struct GUID *guid = &ac->store->results[i];
|
|
||||||
ret = dsdb_search_by_dn_guid(ldb, ac,
|
|
||||||
&result,
|
|
||||||
guid,
|
|
||||||
ac->req->op.search.attrs,
|
|
||||||
0);
|
|
||||||
|
|
||||||
if (ret == LDAP_NO_SUCH_OBJECT) {
|
|
||||||
/* The thing isn't there, which we quietly ignore and
|
|
||||||
go on to send an extra one instead. */
|
|
||||||
if (last_i < ac->store->num_entries - 1) {
|
|
||||||
last_i++;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else if (ret != LDB_SUCCESS) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = ldb_module_send_entry(ac->req, result->msgs[0], NULL);
|
|
||||||
if (ret != LDB_SUCCESS) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return result done */
|
/* return result done */
|
||||||
@ -512,12 +521,14 @@ static int vlv_search_callback(struct ldb_request *req, struct ldb_reply *ares)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case LDB_REPLY_DONE:
|
case LDB_REPLY_DONE:
|
||||||
store->results = talloc_realloc(store, store->results,
|
if (store->num_entries != 0) {
|
||||||
struct GUID,
|
store->results = talloc_realloc(store, store->results,
|
||||||
store->num_entries);
|
struct GUID,
|
||||||
if (store->results == NULL) {
|
store->num_entries);
|
||||||
return ldb_module_done(ac->req, NULL, NULL,
|
if (store->results == NULL) {
|
||||||
LDB_ERR_OPERATIONS_ERROR);
|
return ldb_module_done(ac->req, NULL, NULL,
|
||||||
|
LDB_ERR_OPERATIONS_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
store->result_array_size = store->num_entries;
|
store->result_array_size = store->num_entries;
|
||||||
|
|
||||||
|
@ -212,11 +212,12 @@ class VLVTests(samba.tests.TestCase):
|
|||||||
controls = res.controls
|
controls = res.controls
|
||||||
return full_results, controls, sort_control
|
return full_results, controls, sort_control
|
||||||
|
|
||||||
def get_expected_order(self, attr):
|
def get_expected_order(self, attr, expression=None):
|
||||||
"""Fetch the whole list sorted on the attribute, using sort only."""
|
"""Fetch the whole list sorted on the attribute, using sort only."""
|
||||||
sort_control = "server_sort:1:0:%s" % attr
|
sort_control = "server_sort:1:0:%s" % attr
|
||||||
res = self.ldb.search(self.ou,
|
res = self.ldb.search(self.ou,
|
||||||
scope=ldb.SCOPE_ONELEVEL,
|
scope=ldb.SCOPE_ONELEVEL,
|
||||||
|
expression=expression,
|
||||||
attrs=[attr],
|
attrs=[attr],
|
||||||
controls=[sort_control])
|
controls=[sort_control])
|
||||||
results = [x[attr][0] for x in res]
|
results = [x[attr][0] for x in res]
|
||||||
@ -226,8 +227,8 @@ class VLVTests(samba.tests.TestCase):
|
|||||||
self.ldb.delete(user['dn'])
|
self.ldb.delete(user['dn'])
|
||||||
del self.users[self.users.index(user)]
|
del self.users[self.users.index(user)]
|
||||||
|
|
||||||
def get_gte_tests_and_order(self, attr):
|
def get_gte_tests_and_order(self, attr, expression=None):
|
||||||
expected_order = self.get_expected_order(attr)
|
expected_order = self.get_expected_order(attr, expression=expression)
|
||||||
gte_users = []
|
gte_users = []
|
||||||
if attr in self.delicate_keys:
|
if attr in self.delicate_keys:
|
||||||
gte_keys = [
|
gte_keys = [
|
||||||
@ -260,8 +261,10 @@ class VLVTests(samba.tests.TestCase):
|
|||||||
'\n\t\t',
|
'\n\t\t',
|
||||||
'桑巴',
|
'桑巴',
|
||||||
'zzzz',
|
'zzzz',
|
||||||
expected_order[len(expected_order) // 2] + ' tail',
|
|
||||||
]
|
]
|
||||||
|
if expected_order:
|
||||||
|
gte_keys.append(expected_order[len(expected_order) // 2] + ' tail')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# "numeric" means positive integers
|
# "numeric" means positive integers
|
||||||
# doesn't work with -1, 3.14, ' 3', '9' * 20
|
# doesn't work with -1, 3.14, ' 3', '9' * 20
|
||||||
@ -285,7 +288,7 @@ class VLVTests(samba.tests.TestCase):
|
|||||||
self.delete_user(user)
|
self.delete_user(user)
|
||||||
|
|
||||||
# for sanity's sake
|
# for sanity's sake
|
||||||
expected_order_2 = self.get_expected_order(attr)
|
expected_order_2 = self.get_expected_order(attr, expression=expression)
|
||||||
self.assertEqual(expected_order, expected_order_2)
|
self.assertEqual(expected_order, expected_order_2)
|
||||||
|
|
||||||
# Map gte tests to indexes in expected order. This will break
|
# Map gte tests to indexes in expected order. This will break
|
||||||
@ -377,6 +380,105 @@ class VLVTests(samba.tests.TestCase):
|
|||||||
self.assertCorrectResults(results, expected_order,
|
self.assertCorrectResults(results, expected_order,
|
||||||
offset, before, after)
|
offset, before, after)
|
||||||
|
|
||||||
|
def run_index_tests_with_expressions(self, expressions):
|
||||||
|
# Here we don't test every before/after combination.
|
||||||
|
attrs = [x for x in self.users[0].keys() if x not in
|
||||||
|
('dn', 'objectclass')]
|
||||||
|
for attr in attrs:
|
||||||
|
for expression in expressions:
|
||||||
|
expected_order = self.get_expected_order(attr, expression)
|
||||||
|
sort_control = "server_sort:1:0:%s" % attr
|
||||||
|
res = None
|
||||||
|
n = len(expected_order)
|
||||||
|
for before in range(0, 11):
|
||||||
|
after = before
|
||||||
|
for offset in range(max(1, before - 2),
|
||||||
|
min(n - after + 2, n)):
|
||||||
|
if res is None:
|
||||||
|
vlv_search = "vlv:1:%d:%d:%d:0" % (before, after,
|
||||||
|
offset)
|
||||||
|
else:
|
||||||
|
cookie = get_cookie(res.controls)
|
||||||
|
vlv_search = ("vlv:1:%d:%d:%d:%s:%s" %
|
||||||
|
(before, after, offset, n,
|
||||||
|
cookie))
|
||||||
|
|
||||||
|
res = self.ldb.search(self.ou,
|
||||||
|
expression=expression,
|
||||||
|
scope=ldb.SCOPE_ONELEVEL,
|
||||||
|
attrs=[attr],
|
||||||
|
controls=[sort_control,
|
||||||
|
vlv_search])
|
||||||
|
|
||||||
|
results = [x[attr][0] for x in res]
|
||||||
|
|
||||||
|
self.assertCorrectResults(results, expected_order,
|
||||||
|
offset, before, after)
|
||||||
|
|
||||||
|
def test_server_vlv_with_failing_expression(self):
|
||||||
|
"""What happens when we run the VLV on an expression that matches
|
||||||
|
nothing?"""
|
||||||
|
expressions = ["(samaccountname=testferf)",
|
||||||
|
"(cn=hefalump)",
|
||||||
|
]
|
||||||
|
self.run_index_tests_with_expressions(expressions)
|
||||||
|
|
||||||
|
def run_gte_tests_with_expressions(self, expressions):
|
||||||
|
# Here we don't test every before/after combination.
|
||||||
|
attrs = [x for x in self.users[0].keys() if x not in
|
||||||
|
('dn', 'objectclass')]
|
||||||
|
for expression in expressions:
|
||||||
|
for attr in attrs:
|
||||||
|
gte_order, expected_order, gte_map = \
|
||||||
|
self.get_gte_tests_and_order(attr, expression)
|
||||||
|
# In case there is some order dependency, disorder tests
|
||||||
|
gte_tests = gte_order[:]
|
||||||
|
random.seed(2)
|
||||||
|
random.shuffle(gte_tests)
|
||||||
|
res = None
|
||||||
|
sort_control = "server_sort:1:0:%s" % attr
|
||||||
|
|
||||||
|
expected_order = self.get_expected_order(attr, expression)
|
||||||
|
sort_control = "server_sort:1:0:%s" % attr
|
||||||
|
res = None
|
||||||
|
for before in range(0, 11):
|
||||||
|
after = before
|
||||||
|
for gte in gte_tests:
|
||||||
|
if res is not None:
|
||||||
|
cookie = get_cookie(res.controls)
|
||||||
|
else:
|
||||||
|
cookie = None
|
||||||
|
vlv_search = encode_vlv_control(before=before,
|
||||||
|
after=after,
|
||||||
|
gte=gte,
|
||||||
|
cookie=cookie)
|
||||||
|
|
||||||
|
res = self.ldb.search(self.ou,
|
||||||
|
scope=ldb.SCOPE_ONELEVEL,
|
||||||
|
expression=expression,
|
||||||
|
attrs=[attr],
|
||||||
|
controls=[sort_control,
|
||||||
|
vlv_search])
|
||||||
|
|
||||||
|
results = [x[attr][0] for x in res]
|
||||||
|
offset = gte_map.get(gte, len(expected_order))
|
||||||
|
|
||||||
|
# here offset is 0-based
|
||||||
|
start = max(offset - before, 0)
|
||||||
|
end = offset + 1 + after
|
||||||
|
|
||||||
|
expected_results = expected_order[start: end]
|
||||||
|
|
||||||
|
self.assertEquals(expected_results, results)
|
||||||
|
|
||||||
|
def test_vlv_gte_with_failing_expression(self):
|
||||||
|
"""What happens when we run the VLV on an expression that matches
|
||||||
|
nothing?"""
|
||||||
|
expressions = ["(samaccountname=testferf)",
|
||||||
|
"(cn=hefalump)",
|
||||||
|
]
|
||||||
|
self.run_gte_tests_with_expressions(expressions)
|
||||||
|
|
||||||
def test_server_vlv_with_cookie_while_adding_and_deleting(self):
|
def test_server_vlv_with_cookie_while_adding_and_deleting(self):
|
||||||
"""What happens if we add or remove items in the middle of the VLV?
|
"""What happens if we add or remove items in the middle of the VLV?
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user