JayData under the curtains – how JavaScript functions turn into network operations

06 June 2012

expressions, JSLQ, JavaScript, featured post

We are in the process of creating the JayData provider developer walkthroughs, until they are available this is the first of two posts that give you a little insider info – so that you can better understand the workings of the library.

 

The lifecycle of a predicate function

 

Let me jump into the middle of things and just focus on the filter method  of the $data.Queryable. It has three overloads, the first accepts a Boolean predicate function much similar to the ES5 array.filter’s predicate. (The other two let’s you specify your predicate with a string or with a self crafted expression tree) Let’s take the simplest example:

 

 

northwind.Products
.filter( function(product) { return product.Price < this.p }, {p: 42 })

 

The Abstract Syntax Tree

First of all the predicate function is parsed and an AST graph is built. Below is the JSON representation, at least the most interesting parts of it. At this point the only requirement against the predicate is that it is a syntactically correct JavaScript function.

{
"value": "(begin)",
"first": [
{
"value": "function",
"arity": "statement",
"first": [
{
"value": "product"
}
],
"block": [
{
"value": "return",
"arity": "statement",
"first": {
"value": "===",
"arity": "infix",
"first": {
"value": ".",
"arity": "infix",
"first": {
"value": "product"
},
"second": {
"value": "Category_ID"
}
},
"second": {
"value": ".",
"arity": "infix",
"first": {
"value": "this"
},
"second": {
"value": "Id"
}
}
}
}
]
}
]

It is worth noting that some primitive constant expressions are evaluated at this point. So these two predicates

 

function(product) { return product.Price < 100 + 200 } 
function(product) { return product.Price < 300 }

 

produce the same AST.

AST instances are a little bit hard to work with, as they are far from semantic, representing only the raw code and not exactly the meaning of it. In the next steps we transform this content into more palatable forms.

 

The JavaScript Code Expression

JavaScript Code Expressions are language level expressions represented as instances of types from the $data.Expressions namespace. Some of the trivial express types are FunctionExpression that represents a call to a function, PropertyExpression which represents accessing a member of an object, SimpleBinaryExpression that has a left and right side plus and operand like equal, lessThen or multiply, and the ConstantExpression that stands for any constant values that appear in our predicate. Check them all at http://jaydata.org/api



The JavaScript code expression tree is produced with an AST visitor and below is what it looks like for the example predicate.



{
"expressionType": "$data.Expressions.FunctionExpression",
"parameters": [
{
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameter",
"name": "product",
"type": "unknown"
}
],
"body": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.PropertyExpression",
"expression": {
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameterReference",
"name": "product",
"type": "unknown",
"paramIndex": 0
},
"member": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": "Price",
"type": "string"
}
},
"right": {
"expressionType": "$data.Expressions.PropertyExpression",
"expression": {
"expressionType": "$data.Expressions.ThisExpression"
},
"member": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": "p",
"type": "string"
}
},
"nodeType": "lessThan",
"operator": "<",
"type": "boolean"
}
}"

 

Not all JavaScript language features are supported (but most), only the subset that let’s you express a predicate or a projector. RC1 of the JayData library does not support multi statement code blocks and the variable assignment operator.

 

While creating the JavaScript Code Expression tree, expressions that are local only get evaluated and only their value get into the tree as ConstantExpressions. A local only expression is an expression that is only composed of references that are available on the client.

 

For example the following two predicates will generate the same computed JavaScript code expression tree.

 

function(product) {    
   return product.Name.startsWith( window.location.href.substr(7) + 'foo' ) 
}

function(product) {    
   return product.Name.startsWith('http://foo')
}

 

Note that in RC1 version of JayData you are not able to delegate a local only expression to the storage side. In the released version you can use the asRemote() global function to wrap blocks that you don’t want to be evaluated on the client.

 

JavaScript Code Expressions are unaware of the underlying domain and are generally untyped (beyond basic type information inferred from variables and expression type results), and the requirement against it that client variable references must exists.

 

So you can write product.Nam   and within the realm of the JavaScript Code Expression this is quite valid as we don’t know anything about what a product is. You are however get an “Unknown global reference” or “Unknown property/method” exception if you make a typo: product.Name == windo.location.href.

 

You can use the Code Expression layer independent of any other components of JayData to provide custom predicate or projection based functionality. Here is how you can convert a function to it’s expression tree representations:

var e = Container.createCodeParser().createExpression(
function(a,b) { return "a" > "b" || (a + b % 2) == 0});
console.log(e.getJSON());

 

The JS Code Expression tree will be as follows.

{
"expressionType": "$data.Expressions.FunctionExpression",
"parameters": [
{
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameter",
"name": "a",
"type": "unknown"
},
{
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameter",
"name": "b",
"type": "unknown"
}
],
"body": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": "a",
"type": "string"
},
"right": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": "b",
"type": "string"
},
"nodeType": "greaterThan",
"operator": ">",
"type": "boolean"
},
"right": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameterReference",
"name": "a",
"type": "unknown",
"paramIndex": 0
},
"right": {
"expressionType": "$data.Expressions.SimpleBinaryExpression",
"left": {
"expressionType": "$data.Expressions.ParameterExpression",
"nodeType": "lambdaParameterReference",
"name": "b",
"type": "unknown",
"paramIndex": 1
},
"right": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": 2,
"type": "number"
},
"nodeType": "modulo",
"operator": "%",
"type": "number"
},
"nodeType": "add",
"operator": "+",
"type": "number"
},
"right": {
"expressionType": "$data.Expressions.ConstantExpression",
"value": 0,
"type": "number"
},
"nodeType": "equal",
"operator": "==",
"type": "boolean"
},
"nodeType": "or",
"operator": "||",
"type": "boolean"
}
}

 

 

The Domain Expressions

... coming ...

Share this

Leave a comment

comments powered by Disqus