def testSearchWithManyQueries(self): """ L{ObjectAPI.search} can be used to resolve many L{Query}s at once. """ TagAPI(self.user).create([(u'user/tag', u'description')]) objectID1 = uuid4() objectID2 = uuid4() index = ObjectIndex(self.client) yield index.update({ objectID1: { u'user/tag': 42 }, objectID2: { u'user/tag': 65 } }) yield index.commit() query1 = parseQuery(u'user/tag = 42') query2 = parseQuery(u'user/tag = 65') result = self.objects.search([query1, query2]) result = yield result.get() self.assertEqual({ query1: set([objectID1]), query2: set([objectID2]) }, result)
def testIsEqualsQuery(self): """ L{isEqualsQuery} returns C{True} if the given query uses the C{equals} operator and the given path. """ query = parseQuery(u'fluiddb/about = "test"') self.assertTrue(isEqualsQuery(query, u'fluiddb/about'))
def testIsNotHasQuery(self): """ L{isHasQuery} returns C{False} if the given query doesn't use the C{has} operator. """ query = parseQuery(u'has username/tag1 or username/tag2 = 42') self.assertFalse(isHasQuery(query))
def testIsEqualsQueryWithOtherOperator(self): """ L{isEqualsQuery} returns C{False} if the given query doesn't use the C{equals} operator operator and the given path. """ query = parseQuery(u'fluiddb/about > "test"') self.assertFalse(isEqualsQuery(query, u'fluiddb/about'))
def resolveQuery(self, session, query): """Get the object IDs that match a query. @param session: The L{FluidinfoSession} for the request. @param query: The query to resolve. @raise TBadRequest: If the given query is not encoded properly. @raise TParseError: If the query is not well formed. @raise TNonexistentTag: If the user doesn't have read permissions on the tags in the query. @return: A C{Deferred} that will fire with a C{list} of object ID C{str}s that match the query. """ try: query = query.decode('utf-8') except UnicodeDecodeError as error: session.log.exception(error) error = TBadRequest('Query string %r was not valid UTF-8.' % query) return fail(error) try: parsedQuery = parseQuery(query) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) def run(): objects = SecureObjectAPI(session.auth.user) objectIDs = self._resolveQuery(session, objects, parsedQuery) return [str(objectID) for objectID in objectIDs] return session.transact.run(run)
def deleteValuesForQuery(self, session, query, tags=None): """Delete L{TagValue}s that match a query. @param session: The L{FluidinfoSession} for the request. @param query: The query to resolve. @param tags: Optionally, the sequence of L{Tag.path}s to delete values for. @raise TNonexistentTag: Raised if any of the L{Tag}s in the L{Query} or to delete does not exist. @raise TPathPermissionDenied: Raised if the L{User} does not have L{Operation.READ_TAG_VALUE} permission on any of the L{Tag}s in the L{Query} or does not have L{Operation.DELETE_TAG_VALUE} permission on any of the L{Tag}s to set. @raise TParseError: Raised if the L{Query} can't be parsed. """ try: parsedQuery = parseQuery(query.decode('utf-8')) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) if tags is not None: tags = [tag.decode('utf-8') for tag in tags] def run(): tagValues = SecureTagValueAPI(session.auth.user) objects = SecureObjectAPI(session.auth.user) objectIDs = self._resolveQuery(session, objects, parsedQuery) values = [] if tags is None: # delete all tags user has permissions for result = objects.getTagsByObjects(objectIDs, Operation.DELETE_TAG_VALUE) for objectID, paths in result.iteritems(): for path in paths: values.append((objectID, path)) else: # delete only tags requested by user result = objects.getTagsByObjects(objectIDs) for objectID, paths in result.iteritems(): for path in paths: if tags is None or path in tags: values.append((objectID, path)) if values: try: tagValues.delete(values) except UnknownPathError as error: session.log.exception(error) path = error.paths[0] raise TNonexistentTag(path.encode('utf-8')) except PermissionDeniedError as error: session.log.exception(error) path_, operation = error.pathsAndOperations[0] category, action = getCategoryAndAction(operation) raise TPathPermissionDenied(category, action, path_) return session.transact.run(run)
def testIsEqualsQueryWithDifferentPath(self): """ L{isEqualsQuery} returns C{True} if the given query uses the C{equals} operator but not the given path. """ query = parseQuery(u'other/path = "test"') self.assertFalse(isEqualsQuery(query, u'different/path'))
def testIsHasQuery(self): """ L{isHasQuery} returns C{True} if the given query uses the C{has} operator. """ query = parseQuery(u'has test/path') self.assertTrue(isHasQuery(query))
def testSearchWithMatchesAndManyTermsIsCaseInsensitive(self): """ L{ObjectIndex.search} can match terms with spaces when the C{matches} query is used. """ objectID1 = uuid4() objectID2 = uuid4() objectID3 = uuid4() yield self.index.update({ objectID1: { u'test/tag': u'APPLE ORANGE CHERRY' }, objectID2: { u'test/tag': u'apple orange cherry' }, objectID3: { u'test/tag': u'apple orange cherry' }, uuid4(): { u'test/tag': u'devalue' } }) yield self.index.commit() query = parseQuery(u'test/tag matches "aPpLe OrAnGe"') result = yield self.index.search(query) self.assertEqual(set([objectID1, objectID2, objectID3]), result)
def testSearchWithMatchesIsCaseInsensitive(self): """ L{ObjectIndex.search} performs C{matches} queries case-insensitively. """ objectID1 = uuid4() objectID2 = uuid4() objectID3 = uuid4() yield self.index.update({ objectID1: { u'test/tag': u'VALUE' }, objectID2: { u'test/tag': u'value' }, objectID3: { u'test/tag': u'VaLuE' }, uuid4(): { u'test/tag': u'devalue' } }) yield self.index.commit() query = parseQuery(u'test/tag matches "vAlUe"') result = yield self.index.search(query) self.assertEqual(set([objectID1, objectID2, objectID3]), result)
def testSearchWithEqualsAndFluidDBSlashID(self): """ A L{SearchError} is raised if an C{equals} query is used with the special C{fluiddb/id} virtual tag. """ objectID = uuid4() query = parseQuery(u'fluiddb/id = "%s"' % objectID) return self.assertFailure(self.index.search(query), SearchError)
def testSearchWithoutData(self): """ L{ObjectIndex.search} returns an empty result if there are no documents in the index. """ query = parseQuery(u'test/tag = 5') result = yield self.index.search(query) self.assertEqual(set(), result)
def testSearchWithInvalidFluiddbIDValue(self): """ L{ObjectAPI.search} raises L{SearchError} if a wrong value is used in a C{fluiddb/id = "..."} query. """ query = parseQuery(u'fluiddb/id = "INVALID"') deferred = self.objects.search([query]).get() return self.assertFailure(deferred, SearchError)
def testSearchWithGreaterThanOrEqualFluidDBSlashIDComparison(self): """ A L{SearchError} is raised if a C{>=} comparison is used with the special C{fluiddb/id} virtual tag. """ objectID = uuid4() query = parseQuery(u'fluiddb/id > "%s"' % objectID) return self.assertFailure(self.index.search(query), SearchError)
def testSearchWithHasFluiddbIDQuery(self): """ L{ObjectAPI.search} raises L{SearchError} if a C{has fluiddb/id} query is found. """ query = parseQuery(u'has fluiddb/id') deferred = self.objects.search([query]).get() return self.assertFailure(deferred, SearchError)
def testSearchWithContainsAndFluidDBSlashID(self): """ A L{SearchError} is raised if a C{contains} query is used with the special C{fluiddb/id} virtual tag. """ objectID = uuid4() query = parseQuery(u'fluiddb/id contains "%s"' % objectID) return self.assertFailure(self.index.search(query), SearchError)
def testSearchWithManyQueries(self): """ L{ObjectAPI.search} can be used to resolve many L{Query}s at once. """ TagAPI(self.user).create([(u'user/tag', u'description')]) objectID1 = uuid4() objectID2 = uuid4() index = ObjectIndex(self.client) yield index.update({objectID1: {u'user/tag': 42}, objectID2: {u'user/tag': 65}}) yield index.commit() query1 = parseQuery(u'user/tag = 42') query2 = parseQuery(u'user/tag = 65') result = self.objects.search([query1, query2]) result = yield result.get() self.assertEqual({query1: set([objectID1]), query2: set([objectID2])}, result)
def testSearchWithInvalidAboutValue(self): """ L{ObjectAPI.search} raises L{SearchError} if a non-string value is used in a C{fluiddb/about = "..."} query. """ query = parseQuery(u'fluiddb/about = 5') deferred = self.objects.search([query]).get() return self.assertFailure(deferred, SearchError)
def testSearchWithoutImplicitObjectCreation(self): """ L{ObjectAPI.search} doesn't automatically create objects for nonexistent C{fluiddb/about} values if the C{implicitCreate} flag is C{False}. """ query = parseQuery(u'fluiddb/about = "TestObject"') result = yield self.objects.search([query], False).get() self.assertEqual(0, len(result[query]))
def testSearchWithImplicitObjectCreation(self): """ L{ObjectAPI.search} automatically creates objects for nonexistent C{fluiddb/about} values. """ query = parseQuery(u'fluiddb/about = "TestObject"') result = yield self.objects.search([query], True).get() self.assertEqual(1, len(result[query])) self.assertEqual(1, len(self.objects.get([u"TestObject"])))
def testSearchWithMatches(self): """L{ObjectIndex.search} can perform C{matches} queries.""" objectID = uuid4() yield self.index.update({objectID: {u'test/tag': u'value'}, uuid4(): {u'test/tag': u'devalue'}}) yield self.index.commit() query = parseQuery(u'test/tag matches "value"') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithUnknownPaths(self): """ L{ObjectAPI.set} raises an L{UnknownPathError} if a path for unknown L{Tag}s is in the Query. """ query = parseQuery(u'unknown/path = "hello world"') error = self.assertRaises(UnknownPathError, self.objects.search, [query]) self.assertEqual([u'unknown/path'], error.paths)
def testSearchWithContains(self): """L{ObjectIndex.search} can perform C{contains} queries.""" objectID = uuid4() yield self.index.update({objectID: {u'test/tag': [u'foo', u'bar']}, uuid4(): {u'test/tag': [u'baz']}}) yield self.index.commit() query = parseQuery(u'test/tag contains "foo"') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def assertQuery(self, objectIDs, query): """Asserts if a query to fluidinfo returns the expected results. @param objectIDs: A sequence with the expected object IDs for the query. @param query: The fluidinfo query to check. """ query = parseQuery(query) results = yield self.index.search(query) self.assertEqual(set(objectIDs), results)
def testSearchWithoutMatch(self): """ L{ObjectIndex.search} returns an empty result if no documents in the index match the specified L{Query}. """ yield self.index.update({uuid4(): {u'test/tag': 42}}) yield self.index.commit() query = parseQuery(u'unknown/tag = 5') result = yield self.index.search(query) self.assertEqual(set(), result)
def testSearchWithAnd(self): """L{ObjectIndex.search} can perform C{and} queries.""" objectID = uuid4() yield self.index.update({objectID: {u'test/int': 42, u'test/unicode': u'value'}, uuid4(): {u'test/int': 67}}) yield self.index.commit() query = parseQuery(u'test/int = 42 and test/unicode = "value"') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithImplicitObjectCreation(self): """ L{SecureObjectAPI.search} doesn't raise a L{PermissionDeniedError} if the anonymous user tries to create new objects using C{fluiddb/about} queries, instead an empty result is returned. """ query = parseQuery(u'fluiddb/about = "TestObject"') result = self.objects.search([query], True) result = yield result.get() self.assertEqual({query: set()}, result)
def testSearchWithHasQueryDoesNotHitSolr(self): """ L{ObjectAPI.search} doesn't hit Solr to resolve C{has <path>} queries. """ # Use an invalid Solr URL to test that we're not hitting Solr. self.config.set('index', 'url', 'http://none') objectID = self.objects.create(u'TestObject') TagValueAPI(self.user).set({objectID: {u'username/test': 'value'}}) query = parseQuery(u'has username/test') result = yield self.objects.search([query]).get() self.assertEqual({query: set([objectID])}, result)
def testSearchWithOr(self): """L{ObjectIndex.search} can perform C{or} queries.""" objectID1 = uuid4() objectID2 = uuid4() yield self.index.update({objectID1: {u'test/int': 42}, objectID2: {u'test/int': 67}, uuid4(): {u'test/int': 93}}) yield self.index.commit() query = parseQuery(u'test/int = 42 or test/int = 67') result = yield self.index.search(query) self.assertEqual(set([objectID1, objectID2]), result)
def testSearchWithOrUnmatched(self): """ L{ObjectIndex.search} only returns objects that match one side of an C{or} query. """ yield self.index.update({uuid4(): {u'test/int': 42}, uuid4(): {u'test/int': 67}}) yield self.index.commit() query = parseQuery(u'test/int = 41 or test/int = 66') result = yield self.index.search(query) self.assertEqual(set([]), result)
def testSearchWithHasSetValue(self): """ L{ObjectIndex.search} can perform C{has} queries with C{list} values. """ objectID = uuid4() yield self.index.update({objectID: {u'test/tag1': [u'foo', u'bar']}, uuid4(): {u'test/tag2': [u'foo', u'bar']}}) yield self.index.commit() query = parseQuery(u'has test/tag1') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithFluiddbIDValueDoesNotHitSolr(self): """ L{ObjectAPI.search} doesn't hit Solr to resolve C{fluiddb/id = "..."} queries. """ # Use an invalid Solr URL to test that we're not hitting Solr. self.config.set('index', 'url', 'http://none') objectID = uuid4() query = parseQuery(u'fluiddb/id = "%s"' % objectID) result = yield self.objects.search([query]).get() self.assertEqual({query: set([objectID])}, result)
def testSearchWithGreaterThanOrEqualIntComparison(self): """ L{ObjectIndex.search} can perform C{>=} comparisons with C{int} values. """ objectID = uuid4() yield self.index.update({objectID: {u'test/int': 43}, uuid4(): {u'test/int': 42}}) yield self.index.commit() query = parseQuery(u'test/int >= 43') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithEqualsWithEmptyValue(self): """ L{ObjectIndex.search} can perform C{=} comparisons with empty strings. """ objectID = uuid4() yield self.index.update({objectID: {u'test/tag': u''}, uuid4(): {u'test/tag': u'devalue'}}) yield self.index.commit() query = parseQuery(u'test/tag = ""') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithExcept(self): """L{ObjectIndex.search} can perform C{except} queries.""" objectID1 = uuid4() objectID2 = uuid4() yield self.index.update({objectID1: {u'test/int': 42, u'test/unicode': u'value'}, objectID2: {u'test/int': 42, u'test/unicode': u'hello'}}) yield self.index.commit() query = parseQuery(u'test/int = 42 except test/unicode = "value"') result = yield self.index.search(query) self.assertEqual(set([objectID2]), result)
def getRecentUserActivityForQuery(self, session, query): """ Get information about recent tag values by the users whose objects are returned by the given query. @param session: The L{FluidinfoSession} for the request. @param query: A UTF-8 C{str} with the query to resolve. @return: A C{list} of C{dict}s matching the following format:: [{'tag': <path>, 'id': <object-id>, 'about': <about-value>, 'value': <value>, 'username': <username>, 'updated-at': <timestamp>}, ...] """ try: # Extend the query to get only objects for users. query = '(%s) AND HAS fluiddb/users/username' % query parsedQuery = parseQuery(query.decode('utf-8')) except QueryParseError as error: session.log.exception(error) return fail(TParseError(query, error.message)) except IllegalQueryError as error: return fail(TBadRequest(str(error))) def run(): objects = SecureObjectAPI(session.auth.user) # _resolveQuery is implemented in FacadeTagValueMixin objectIDs = self._resolveQuery(session, objects, parsedQuery) if not objectIDs: return [] # FIXME: This sucks, but right now if the query returns too many # objects, RecentActivityAPI will blow up. While we fix this, it's # better to return a 400 than a 500. if len(objectIDs) > 2000: raise TBadRequest('The given query returns to many objects.') values = SecureTagValueAPI(session.auth.user) result = values.get(objectIDs, [u'fluiddb/users/username']) usernames = [result[objectID][u'fluiddb/users/username'].value for objectID in result] recentActivity = SecureRecentActivityAPI(session.auth.user) result = recentActivity.getForUsers(usernames) return self._formatResult(result) return session.transact.run(run)
def testSearch(self): """ L{SecureObjectAPI.search} resolves the specified L{Query}s if the anonymous user has C{Operation.READ_TAG_VALUE} permissions on the requested L{Tag.path}s. """ objectID = uuid4() index = ObjectIndex(self.client) yield index.update({objectID: {u'username/tag': 42}}) yield self.client.commit() query = parseQuery(u'username/tag = 42') result = self.objects.search([query]) result = yield result.get() self.assertEqual({query: set([objectID])}, result)
def testSearchAboutValueUsesTheCache(self): """ L{ObjectAPI.search} uses the cache to get the results of C{fluiddb/about = "..."} queries if they're available. """ objectID = self.objects.create(u'about') query = parseQuery('fluiddb/about = "about"') # Get the value once to store it in the cache. self.objects.search([query]) # Remove the value from the store to check that we're using the cache. getAboutTagValues(values=[u'about']).remove() result = self.objects.search([query]) result = yield result.get() self.assertEqual({query: set([objectID])}, result)
def testSearchWithMatches(self): """L{ObjectIndex.search} can perform C{matches} queries.""" objectID = uuid4() yield self.index.update({ objectID: { u'test/tag': u'value' }, uuid4(): { u'test/tag': u'devalue' } }) yield self.index.commit() query = parseQuery(u'test/tag matches "value"') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithContains(self): """L{ObjectIndex.search} can perform C{contains} queries.""" objectID = uuid4() yield self.index.update({ objectID: { u'test/tag': [u'foo', u'bar'] }, uuid4(): { u'test/tag': [u'baz'] } }) yield self.index.commit() query = parseQuery(u'test/tag contains "foo"') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)
def testSearchWithComplexQuery(self): """L{ObjectIndex.search} can handle complex queries.""" objectID = uuid4() yield self.index.update({ objectID: { u'test/unicode': u'value', u'test/int': 42, u'test/float': 42.1 } }) yield self.index.commit() query = parseQuery(u'test/unicode = "value" and ' u'(test/int = 42 or test/float = 42.1) ' u'except test/unknown = 10') result = yield self.index.search(query) self.assertEqual(set([objectID]), result)