I have:
static hasMany = [
services:String,
tags:String
]
I need to search the database for services.
This is the JSON for services
"services":["tid.2","tid.3"]
If services was a String (service) and not a hasMany String then tbis works
def inUse = ServiceTemplate.findAllByName(serviceTemplateInstance.service).size() > 1
How can I do this with services?
I've tried
def c = ServiceTemplate.createCriteria()
def results = c.list { eq('services', 'tid.2') }
but no luck...
You can use HQL instead. For example:
ServiceTemplate.findAll("from ServiceTemplate st where :service in elements(st.services)", [service:'a'])
Related
Using python to implement GraphQL across multiple microservices, some use Ariadne, and some use graphene (and graphene-Django). Because of the microservice architecture, it's chosen that Apollo Federation will merge the schemas from the different microservices.
With Ariadne, it's very simple (being schema first), and a small example:
from ariadne import QueryType, gql, make_executable_schema, MutationType, ObjectType
from ariadne.asgi import GraphQL
query = QueryType()
mutation = MutationType()
sdl = """
type _Service {
sdl: String
}
type Query {
_service: _Service!
hello: String
}
"""
#query.field("hello")
async def resolve_hello(_, info):
return "Hello"
#query.field("_service")
def resolve__service(_, info):
return {
"sdl": sdl
}
schema = make_executable_schema(gql(sdl), query)
app = GraphQL(schema, debug=True)
Now this is picked up with no problem with Apollo Federation:
const { ApolloServer } = require("apollo-server");
const { ApolloGateway } = require("#apollo/gateway");
const gateway = new ApolloGateway({
serviceList: [
// { name: 'msone', url: 'http://192.168.2.222:9091' },
{ name: 'mstwo', url: 'http://192.168.2.222:9092/graphql/' },
]
});
(async () => {
const { schema, executor } = await gateway.load();
const server = new ApolloServer({ schema, executor });
// server.listen();
server.listen(
3000, "0.0.0.0"
).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
})();
For which I can run graphql queries against the server on 3000.
But, with using graphene, trying to implement the same functionality as Ariadne:
import graphene
class _Service(graphene.ObjectType):
sdl = graphene.String()
class Query(graphene.ObjectType):
service = graphene.Field(_Service, name="_service")
hello = graphene.String()
def resolve_hello(self, info, **kwargs):
return "Hello world!"
def resolve_service(self, info, **kwargs):
from config.settings.shared import get_loaded_sdl
res = get_loaded_sdl() # gets the schema defined later in this file
return _Service(sdl=res)
schema = graphene.Schema(query=Query)
# urls.py
urlpatterns = [
url(r'^graphql/$', GraphQLView.as_view(graphiql=True)),
]
,... now results in an error from the Apollo Federation:
GraphQLSchemaValidationError: Type Query must define one or more fields.
As I checked into this matter, I found that apollo calls the microservice with a graphql query of:
query GetServiceDefinition { _service { sdl } }
Running it on the microservice via Insomnia/Postman/GraphiQL with Ariadne gives:
{
"data": {
"_service": {
"sdl": "\n\ntype _Service {\n sdl: String\n}\n\ntype Query {\n _service: _Service!\n hello: String\n}\n"
}
}
}
# Which expanding the `sdl` part:
type _Service {
sdl: String
}
type Query {
_service: _Service!
hello: String
}
and on the microservice with Graphene:
{
"data": {
"_service": {
"sdl": "schema {\n query: Query\n}\n\ntype Query {\n _service: _Service\n hello: String\n}\n\ntype _Service {\n sdl: String\n}\n"
}
}
}
# Which expanding the `sdl` part:
schema {
query: Query
}
type Query {
_service: _Service
hello: String
}
type _Service {
sdl: String
}
So, they both are the same thing for defining how to get sdl, I checked into the microservice response, and found that graphene response is sending the correct data too,
with the Json response "data" being equal to:
execution_Result: OrderedDict([('_service', OrderedDict([('sdl', 'schema {\n query: Query\n}\n\ntype Query {\n _service: _Service\n hello: String\n}\n\ntype _Service {\n sdl: String\n}\n')]))])
So what could the reason be for Apollo Federation not being able to successfully get this microservice schema?
This pip library can help https://pypi.org/project/graphene-federation/
Just use build_schema, and it'll add _service{sdl} for you:
import graphene
from graphene_federation import build_schema
class Query(graphene.ObjectType):
...
pass
schema = build_schema(Query) # add _service{sdl} field in Query
You are on the good path on the other answer, but it looks like you are going to need to strip out some stuff from the printed version.
here is the way I have used in a github issue
i sum up my code here:
schema = ""
class ServiceField(graphene.ObjectType):
sdl = String()
def resolve_sdl(parent, _):
string_schema = str(schema)
string_schema = string_schema.replace("\n", " ")
string_schema = string_schema.replace("type Query", "extend type Query")
string_schema = string_schema.replace("schema { query: Query mutation: MutationQuery }", "")
return string_schema
class Service:
_service = graphene.Field(ServiceField, name="_service", resolver=lambda x, _: {})
class Query(
# ...
Service,
graphene.ObjectType,
):
pass
schema = graphene.Schema(query=Query, types=CUSTOM_ATTRIBUTES_TYPES)
The solution is actually a slight hack the schema that is automatically generated via graphene. I thought I had tried this already and it still worked, but I just did it again now but it broke.
So if in Ariadne, I add
schema {
query: Query
}
into the sdl, Apollo Federation also raises Type Query must define one or more fields.. Without it, it works fine. So then I also went to graphene and in the resolve_service function I did:
def resolve_service(self, info, **kwargs):
from config.settings.shared import get_loaded_sdl
res = get_loaded_sdl()
res = res.replace("schema {\n query: Query\n}\n\n", "")
return _Service(sdl=res)
And now graphene works too, so I guess the problem was something I overlooked, it seems that Apollo Federation cannot handle schema grammar of:
schema {
query: Query
}
Update 1
A line I didn't notice on Apollo's website is that:
This SDL does not include the additions of the federation spec above. Given an input like this:
This is clear when combining the services together in Federation as it will raise the error:
GraphQLSchemaValidationError: Field "_Service.sdl" can only be defined once.
So, although in the full schema for the microservice with define _Service.sdl, we want that information gone for the string of the full-schema that is returned as the return String for _Service.sdl
Update 2
The Apollo Federation is now working fine, with making sure that the string returned by the sdl field does not contain federation specs.
In graphene, I think each implementation might differ, but in general you want to replace the following:
res = get_loaded_sdl()
res = res.replace("schema {\n query: Query\n}\n\n", "")
res = res.replace("type _Service {\n sdl: String\n}", "")
res = res.replace("\n _service: _Service!", "")
And in Ariadne, just need to define two sdl's, one containing the federation specs (for the schema returned by the service), and one without federation specs (the one returned by the sdl field)
In case anyone is wondering, this is because graphene v2 uses commas instead of ampersands in interfaces
interface x implements y, z {
...
}
and this syntax no longer works, a workaround is to monkey-patch get_sdl
import re
from myproject import Query, Mutation
from graphene_federation import service, build_schema
# monkey patch old get_sdl
old_get_sdl = service.get_sdl
def get_sdl(schema, custom_entities):
string_schema = old_get_sdl(schema, custom_entities)
string_schema = string_schema.replace('\n', ' ')
pattern_types_interfaces = r'type [A-Za-z]* implements ([A-Za-z]+\s*,?\s*)+'
pattern = re.compile(pattern_types_interfaces)
string_schema = pattern.sub(lambda matchObj: matchObj.group().replace(',', ' &'), string_schema)
return string_schema
service.get_sdl = get_sdl
schema = build_schema(Query, mutation=Mutation)
and it works.
I have a class Node extends V. I add instances to Node with some set of document type information provided. I want to query the OrientDB database and return some information from Node; to display this in a formatted way I want a list of all possible field names (in my application, there are currently 115 field names, only one of which is a property used as an index)
To do this in pyorient, the only solution I found so far is (client is the name of the database handle):
count = client.query("SELECT COUNT(*) FROM Node")[0].COUNT
node_records = client.query("SELECT FROM Node LIMIT {0}".format(count))
node_key_list = set([])
for node in node_records:
node_key_list |= node.oRecordData.keys()
I figured that much out pretty much through trial and error. It isn't very efficient or elegant. Surely there must be a way to have the database return a list of all possible fields for a class or any other document-type object. Is there a simple way to do this through either pyorient or the SQL commands?
I tried your case with this dataset:
And this is the structure of my class TestClass:
As you can see from my structure only name, surname and timeStamp have been created in schema-full mode, instead nameSchemaLess1 and nameSchemaLess1 have been inserted into the DB in schema-less mode.
After having done that, you could create a Javascript function in OrientDB Studio or Console (as explained here) and subsequently you can recall it from pyOrient by using a SQL command.
The following posted function retrieves all the fields names of the class TestClass without duplicates:
Javascript function:
var g = orient.getGraph();
var fieldsList = [];
var query = g.command("sql", "SELECT FROM TestClass");
for (var x = 0; x < query.length; x++){
var fields = query[x].getRecord().fieldNames();
for (var y = 0; y < fields.length; y++) {
if (fieldsList == false){
fieldsList.push(fields[y]);
} else {
var fieldFound = false;
for (var z = 0; z < fieldsList.length; z++){
if (fields[y] == fieldsList[z]){
fieldFound = true;
break;
}
}
if (fieldFound != true){
fieldsList.push(fields[y]);
}
}
}
}
return fieldsList;
pyOrient code:
import pyorient
db_name = 'TestDatabaseName'
print("Connecting to the server...")
client = pyorient.OrientDB("localhost", 2424)
session_id = client.connect("root", "root")
print("OK - sessionID: ", session_id, "\n")
if client.db_exists(db_name, pyorient.STORAGE_TYPE_PLOCAL):
client.db_open(db_name, "root", "root")
functionCall = client.command("SELECT myFunction() UNWIND myFunction")
for idx, val in enumerate(functionCall):
print("Field name: " + val.myFunction)
client.db_close()
Output:
Connecting to the server...
OK - sessionID: 54
Field name: name
Field name: surname
Field name: timeStamp
Field name: out_testClassEdge
Field name: nameSchemaLess1
Field name: in_testClassEdge
Field name: nameSchemaLess2
As you can see all of the fields names, both schema-full and schema-less, have been retrieved.
Hope it helps
Luca's answer worked. I modified it to fit my tastes/needs. Posting here to increase the amount of OrientDB documentation on Stack Exchange. I took Luca's answer and translated it to groovy. I also added a parameter to select the class to get fields for and removed the UNWIND in the results. Thank you to Luca for helping me learn.
Groovy code for function getFieldList with 1 parameter (class_name):
g = orient.getGraph()
fieldList = [] as Set
ret = g.command("sql", "SELECT FROM " + class_name)
for (record in ret) {
fieldList.addAll(record.getRecord().fieldNames())
}
return fieldList
For the pyorient part, removing the database connection it looks like this:
node_keys = {}
ret = client.command("SELECT getFieldList({0})".format("'Node'"))
node_keys = ret[0].oRecordData['getFieldList']
Special notice to the class name; in the string passed to client.command(), the parameter must be encased in quotes.
When creating rivers for composed objects, the resulting _mapping is set with the complete nested object definition rather than String field. This causes the data import to fail because the object references are not "dereferenced".
E.g.
collection1: {name: "test", items: [collection2/123, collection2/124] }
collection1: {somefield: "test"}
The resulting _mapping after creating the river for those collections within a single index is:
collection1: {name: String, items: { properties: { somefield: String } } }.
Importing data fails with the following error:
org.elasticsearch.index.mapper.MapperParsingException: object mapping [items] trying to serialize a value with no field associated with it, current value [collection1/123]
How can I either tell the arango db river to dereference the nested objects or set the mapping properly to work with references?
Rivers are now deprecated. I created a mixin for elasticsearch which updates the index when I save/update/delete objects (through my custom ODM).
Simply make yourself a wrapper around your data access layer with high level functions that also updates the ES index.
For example:
class Base(ArangoBase, es.Base):
def save(self):
ret = ArangoBase.save(self)
es.Base.save_es(self)
return ret
def update(self):
ret = ArangoBase.update(self)
es.Base.save_es(self)
return ret
def delete(self):
ret = ArangoBase.delete(self)
es.Base.delete_es(self)
return ret
from elasticsearch import Elasticsearch
class Base(object):
_es = None
_es_index = 'chopchop'
_es_type = None
def save_es(self):
self._es.index(index=self._es_index, doc_type=self._es_type, body=self._doc(), id=self.id)
def delete_es(self):
self._es.delete(index=self._es_index, doc_type=self._es_type, id=self.id)
I have a scenario where users are assigned to team.
Different ClientServices are allocated to different teams and
we need to assign user Of these teams to clientservice in RoundRobin fashion
I was trying to solve it as follows to get a map where team name and a list of ClientServiceInstance will be mapped so I can do further processing on it
def teamMap = [:]
clientServicesList.each {clientServiceInstance->
if(teamMap[clientServiceInstance.ownerTeam] == null){
teamMap.putAt(clientServiceInstance.ownerTeam, new ArrayList().push(clientServiceInstance))
}else{
def tmpList = teamMap[clientServiceInstance.ownerTeam]
tmpList.push(clientServiceInstance)
teamMap[clientServiceInstance.ownerTeam] = tmpList
}
}
but instead of pushing clientServiceInstance it pushes true.
Any idea?
I believe another version would be:
def teamMap = clientServicesList.inject( [:].withDefault { [] } ) { map, instance ->
map[ instance.ownerTeam ] << instance
map
}
new ArrayList().push(clientServiceInstance) returns true, which means you're putting that into your teamMap instead of what I assume should be a list? Instead you might want
teamMap.putAt(clientServiceInstance.ownerTeam, [clientServiceInstance])
By the way, your code is not very Groovy ;)
You could rewrite it as something like
def teamMap = [:]
clientServicesList.each { clientServiceInstance ->
if (teamMap[clientServiceInstance.ownerTeam]) {
teamMap[clientServiceInstance.ownerTeam] << clientServiceInstance
} else {
teamMap[clientServiceInstance.ownerTeam] = [clientServiceInstance]
}
}
Although I'm sure there are even better ways to write that.
Given the following object structure:
{
key1: "...",
key2: "...",
data: "..."
}
Is there any way to get this object from a CouchDB by quering both key1 and key2 without setting up two different views (one for each key) like:
select * from ... where key1=123 or key2=123
Kind regards,
Artjom
edit:
Here is a better description of the problem:
The object described above is a serialized game state. A game has exactly one creator user (key1) and his opponent (key2). For a given user I would like to get all games where he is involved (both as creator and opponent).
Emit both keys (or only one if equal):
function(doc) {
if (doc.hasOwnProperty('key1')) {
emit(doc.key1, 1);
}
if (doc.hasOwnProperty('key2') && doc.key1 !== doc.key2) {
emit(doc.key2, 1);
}
}
Query with (properly url-encoded):
?include_docs=true&key=123
or with multiple values:
?include_docs=true&keys=[123,567,...]
UPDATE: updated to query multiple values with a single query.
You could create a CouchDB view which produces output such as:
["key1", 111],
["key1", 123],
["key2", 111],
["key2", 123],
etc.
It is very simple to write a map view in javascript:
function(doc) {
emit(["key1", doc["key1"]], null);
emit(["key2", doc["key2"]], null);
}
When querying, you can query using multiple keys:
{"keys": [["key1", 123], ["key2", 123]]}
You can send that JSON as the data in a POST to the view. Or preferably use an API for your programming language. The results of this query will be each row in the view that matches either key. So, every document which matches on both key1 and key2 will return two rows in the view results.
I also was struggling with simular question, how to use
"select * from ... where key1=123 or key2=123".
The following view would allow you to lookup customer documents by the LastName or FirstName fields:
function(doc) {
if (doc.Type == "customer") {
emit(doc.LastName, {FirstName: doc.FirstName, Address: doc.Address});
emit(doc.FirstName, {LastName: doc.LastName, Address: doc.Address});
}
}
I am using this for a web service that queries all my docs and returns every doc that matches both the existence of a node and the query. In this example I am using the node 'detail' for the search. If you would like to search a different node, you need to specify.
This is my first Stack Overflow post, so I hope I can help someone out :)
***Python Code
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import httplib, json
from tornado.options import define,options
define("port", default=8000, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
def get(self):
db_host = 'YOUR_COUCHDB_SERVER'
db_port = 5984
db_name = 'YOUR_COUCHDB_DATABASE'
node = self.get_argument('node',None)
query = self.get_argument('query',None)
cleared = None
cleared = 1 if node else self.write('You have not supplied an object node.<br>')
cleared = 2 if query else self.write('You have not supplied a query string.<br>')
if cleared is 2:
uri = ''.join(['/', db_name, '/', '_design/keysearch/_view/' + node + '/?startkey="' + query + '"&endkey="' + query + '\u9999"'])
connection = httplib.HTTPConnection(db_host, db_port)
headers = {"Accept": "application/json"}
connection.request("GET", uri, None, headers)
response = connection.getresponse()
self.write(json.dumps(json.loads(response.read()), sort_keys=True, indent=4))
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler)
]
settings = dict(
debug = True
)
tornado.web.Application.__init__(self, handlers, **settings)
def main():
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
***CouchDB Design View
{
"_id": "_design/keysearch",
"language": "javascript",
"views": {
"detail": {
"map": "function(doc) { var docs = doc['detail'].match(/[A-Za-z0-9]+/g); if(docs) { for(var each in docs) { emit(docs[each],doc); } } }"
}
}
}