diff --git a/src/ast/gql_core/converter.rs b/src/ast/gql_core/converter.rs index 194ba76..744301a 100644 --- a/src/ast/gql_core/converter.rs +++ b/src/ast/gql_core/converter.rs @@ -1,8 +1,12 @@ +use std::ops::Deref; + use apollo_compiler::{ExecutableDocument, Node}; +use apollo_compiler::ast::{Argument, Value}; use apollo_compiler::executable::{Field, OperationType, Selection, SelectionSet}; use pyo3::{PyAny, Python}; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyList, PyString}; +use pyo3::types::{PyDict, PyTuple, PyString}; +use crate::ast::gql_core::reduced_core_mirror::ArgumentNode; struct CoreOperationType { Query: Py, @@ -41,6 +45,19 @@ pub struct CoreConversionContext { field_node: Py, document_node: Py, name_node: Py, + argument_node: Py, + + value_node: Py, + variable_node: Py, + int_value_node: Py, + float_value_node: Py, + string_value_node: Py, + boolean_value_node: Py, + null_value_node: Py, + enum_value_node: Py, + list_value_node: Py, + object_value_node: Py, + object_field_node: Py, } @@ -53,6 +70,20 @@ impl CoreConversionContext { let PySelectionSetNode = graphql_core_ast.getattr("SelectionSetNode").unwrap(); let PyFieldNode = graphql_core_ast.getattr("FieldNode").unwrap(); let PyNameNode = graphql_core_ast.getattr("NameNode").unwrap(); + let PyArgumentNode = graphql_core_ast.getattr("ArgumentNode").unwrap(); + + let PyValueNode = graphql_core_ast.getattr("ValueNode").unwrap(); + let PyVariableNode = graphql_core_ast.getattr("VariableNode").unwrap(); + let PyIntValueNode = graphql_core_ast.getattr("IntValueNode").unwrap(); + let PyFloatValueNode = graphql_core_ast.getattr("FloatValueNode").unwrap(); + let PyStringValueNode = graphql_core_ast.getattr("StringValueNode").unwrap(); + let PyBooleanValueNode = graphql_core_ast.getattr("BooleanValueNode").unwrap(); + let PyNullValueNode = graphql_core_ast.getattr("NullValueNode").unwrap(); + let PyEnumValueNode = graphql_core_ast.getattr("EnumValueNode").unwrap(); + let PyTupleValueNode = graphql_core_ast.getattr("ListValueNode").unwrap(); + let PyObjectValueNode = graphql_core_ast.getattr("ObjectValueNode").unwrap(); + let PyObjectFieldNode = graphql_core_ast.getattr("ObjectFieldNode").unwrap(); + Self { operation_type: CoreOperationType::new(PyOperationType), @@ -61,6 +92,19 @@ impl CoreConversionContext { field_node: PyFieldNode.into(), document_node: PyDocumentNode.into(), name_node: PyNameNode.into(), + argument_node: PyArgumentNode.into(), + + value_node: PyValueNode.into(), + variable_node: PyVariableNode.into(), + int_value_node: PyIntValueNode.into(), + float_value_node: PyFloatValueNode.into(), + string_value_node: PyStringValueNode.into(), + boolean_value_node: PyBooleanValueNode.into(), + null_value_node: PyNullValueNode.into(), + enum_value_node: PyEnumValueNode.into(), + list_value_node: PyTupleValueNode.into(), + object_value_node: PyObjectValueNode.into(), + object_field_node: PyObjectFieldNode.into(), } } @@ -72,6 +116,70 @@ impl CoreConversionContext { self.name_node.call_bound(py, (), Some(&name_node_kwargs)) } + fn convert_value_to_core_value(&self, py: Python, value: &Node) -> PyResult { + let value_node_kwargs = PyDict::new_bound(py); + + match value.deref() { + Value::Null => { + self.null_value_node.call_bound(py, (), None) + } + Value::Enum(name) => { + value_node_kwargs.set_item("value", name.as_str())?; + self.enum_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::Variable(name) => { + value_node_kwargs.set_item("name", self.get_name_node(py, name.as_str())?)?; + self.variable_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::String(string) => { + value_node_kwargs.set_item("value", string.as_str())?; + self.string_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::Float(float) => { + value_node_kwargs.set_item("value", float.to_string())?; + self.float_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::Int(int) => { + value_node_kwargs.set_item("value", int.to_string())?; + self.int_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::Boolean(boolean) => { + value_node_kwargs.set_item("value", boolean)?; + self.boolean_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::List(values) => { + let core_values: Vec = values.iter().map(|value| { + self.convert_value_to_core_value(py, value) + }).collect::>()?; + value_node_kwargs.set_item("values", PyTuple::new_bound(py, core_values.iter()))?; + self.list_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + Value::Object(fields) => { + let core_fields: Vec = fields.iter().map(|(name, value)| { + let object_field_node_kwargs = PyDict::new_bound(py); + object_field_node_kwargs.set_item("name", self.get_name_node(py, name.as_str())?)?; + object_field_node_kwargs.set_item("value", self.convert_value_to_core_value(py, value)?)?; + + self.object_field_node.call_bound(py, (), Some(&object_field_node_kwargs)) + }).collect::>()?; + + value_node_kwargs.set_item("fields", PyTuple::new_bound(py, core_fields.iter()))?; + self.object_value_node.call_bound(py, (), Some(&value_node_kwargs)) + } + } + } + + + fn convert_argument_to_core_argument(&self, py: Python, argument: &Node) -> PyResult { + let name = self.get_name_node(py, argument.name.as_str()); + let value = self.convert_value_to_core_value(py, &argument.value); + + + let argument_node_kwargs = PyDict::new_bound(py); + argument_node_kwargs.set_item("name", name?)?; + argument_node_kwargs.set_item("value", value?)?; + self.argument_node.call_bound(py, (), Some(&argument_node_kwargs)) + } fn convert_field_to_core_field(&self, py: Python, field: &Node) -> PyResult { //println!("Converting field to core field..."); @@ -85,7 +193,7 @@ impl CoreConversionContext { //println!("Alias"); if let Some(alias) = &field.alias { - field_node_kwargs.set_item("alias", PyString::new_bound(py, alias.as_str()))?; + field_node_kwargs.set_item("alias", self.get_name_node(py, alias.as_str())?)?; } //println!("Name"); @@ -95,8 +203,12 @@ impl CoreConversionContext { //println!("Initing lists"); // init an empty list of pyobjects - let arguments = PyList::empty_bound(py).to_object(py); - let directives = PyList::empty_bound(py).to_object(py); + let arguments : Vec = field.arguments.iter().map(|argument| { + self.convert_argument_to_core_argument(py, argument) + }).collect::>()?; + + let arguments = PyTuple::new_bound(py, arguments.iter()); + let directives = PyTuple::empty_bound(py).to_object(py); field_node_kwargs.set_item("arguments", arguments)?; @@ -112,19 +224,26 @@ impl CoreConversionContext { //println!("Converting selection set..."); let selection_set_kwargs = PyDict::new_bound(py); // FIXME do we NEED to use PyTuple here? - let selections = PyList::empty_bound(py); - - for selection in &selection_set.selections { - let core_selection = match selection { - Selection::Field(field) => Some(self.convert_field_to_core_field(py, field)), - Selection::FragmentSpread(fragment_spread) => None,//self.convert_fragment_spread_to_core_fragment_spread(py, fragment_spread), - Selection::InlineFragment(inline_fragment) => None,// self.convert_inline_fragment_to_core_inline_fragment(py, inline_fragment), - }; - if let Some(core_selection) = core_selection { - //println!("Appending new Selection to the set..."); - selections.append(core_selection?)?; + + + let selection_vec : Vec = selection_set.selections.iter().map(|selection| { + match selection { + Selection::Field(field) => self.convert_field_to_core_field(py, field), + Selection::FragmentSpread(_) => { + return Err(PyErr::new::( + "FragmentSpread is not yet implemented", + )) + } + Selection::InlineFragment(_) => { + return Err(PyErr::new::( + "InlineFragment is not yet implemented", + )) + } } - } + }).collect::>()?; + + let selections = PyTuple::new_bound(py, selection_vec.iter()); + //println!("Done converting selections!"); selection_set_kwargs.set_item("selections", selections)?; //println!("Appended selections to kwargs!"); @@ -135,20 +254,15 @@ impl CoreConversionContext { let operations = document.all_operations(); let fragments = &document.fragments; - let core_operations = PyList::empty_bound(py); - for operation in operations { + let core_operations : Vec = operations.map(|operation| { let operation_kwargs = PyDict::new_bound(py); - if let Some(operation_name) = &operation.name{ + if let Some(operation_name) = &operation.name { let operation_name = self.get_name_node(py, operation_name)?; - // FIXME is this necessary? - //println!("Trying to set name!"); operation_kwargs.set_item("name", operation_name)?; - //println!("Name set!"); } - let operation_type = self.operation_type.get_operation_type(operation.operation_type); //println!("Operation type resolved!"); @@ -167,15 +281,12 @@ impl CoreConversionContext { //println!("Operation type set kwarg!"); operation_kwargs.set_item("selection_set", self.convert_selection_set_to_core_selection_set(py, selection_set)?)?; - //println!("Selection Set converted!"); - - //println!("Creating Operation def node..."); - core_operations.append(self.operation_definition.call_bound(py, (), Some(&operation_kwargs))?)?; + self.operation_definition.call_bound(py, (), Some(&operation_kwargs)) + }).collect::>>()?; - //println!("Created Operation def node!"); - } let document_node_kwargs = PyDict::new_bound(py); - document_node_kwargs.set_item("definitions", core_operations)?; + let py_operations = PyTuple::new_bound(py, core_operations.iter()); + document_node_kwargs.set_item("definitions", py_operations)?; //println!("Creating document node!"); self.document_node.call_bound(py, (), Some(&document_node_kwargs)) diff --git a/test_parser/benchmarks/test_core_full_schema_bench.py b/test_parser/benchmarks/test_core_full_schema_bench.py index a197d88..924dbe8 100644 --- a/test_parser/benchmarks/test_core_full_schema_bench.py +++ b/test_parser/benchmarks/test_core_full_schema_bench.py @@ -1,6 +1,7 @@ import pytest from graphql import execute, GraphQLObjectType, GraphQLArgument, GraphQLSchema, GraphQLField, GraphQLString, \ GraphQLList, validate, ExecutionResult +from graphql.language.printer import print_ast from rustberry import QueryCompiler @@ -273,6 +274,17 @@ def test_pycon_query_execution_rustberry(): e = execute( schema, query, ) +@pytest.mark.benchmark +def test_pycon_query_execution_rustberry_no_mirror(): + document = compiler.parse(operation) + validation_success = compiler.validate(document) + query = compiler.gql_core_ast_mirror(document) + if not validation_success: + return ExecutionResult(data=None, errors=validation_errors) + + e = execute( + schema, query, + ) @pytest.mark.benchmark def test_pure_execution_core(benchmark): @@ -284,6 +296,11 @@ def test_pure_execution_rustberry(benchmark): document = compiler.parse(operation) query = compiler.gql_core_ast_mirror(document) benchmark(execute, schema, query) +@pytest.mark.benchmark +def test_pure_execution_rustberry_no_mirror(benchmark): + document = compiler.parse(operation) + query = compiler.gql_core_ast(document) + benchmark(execute, schema, query) @@ -297,3 +314,10 @@ def test_pure_execution_rustberry_large(benchmark): document = compiler.parse(operation_large) query = compiler.gql_core_ast_mirror(document) benchmark(execute, schema, query) + +@pytest.mark.benchmark +def test_pure_execution_rustberry_large_no_mirrpr(benchmark): + document = compiler.parse(operation_large) + query = compiler.gql_core_ast(document) + #print(print_ast(query)) + benchmark(execute, schema, query)