SPL: User-defined Functions
SPL allows programmers to write their own functions to achieve specific functionalities. These user-defined functions, as the built-in ones, can be directly used in a SPL script after they are registered.
1. Definition, deployment and registration
Every user-defined function is defined with a Java class. The class must satisfy the following conditions:
1. It should be a public class.
2. It inherits from the base class com.scudata.expression.Function.
3. We implement the abstract method public Object calculate(Context ctx) in the base class. In this method, function parameters and options are parsed, functionalities are achieved, and the computing result is returned. The context in which the function is executed is ctx, which includes configurations in the system configuration file and the SPL script file parameters.
A ready-for-use user-defined function should be deployed in the SPL class path. It is usually put into a jar and place the jar in the lib directory under the installation directory. Or you can put this type of class file directly in the classes directory under the installation directory. When you are using a web application, put the file in its class path.
A user-defined function can be used only after it is registered in SPL. First we need to write a user-defined function configuration file in the following format:
In the configuration file, each line is a function definition. The part before the equals sign is function name, which should not be a namesake with a built-in function or any other user-defined function and thus is unique. There are two parts separated by a comma after the equals sign. The first part is value 0 or 1. 0 represents an ordinary function while 1 means a member function of an object, such as s.len(), which is the function of a string object. The second part is the user-defined function’s complete Java class name.
At the startup of the application, we need to call the following method to complete the registration:
com.scudata.expression.FunctionLib.loadCustomFunctions("d:/myapplication/customFunction.properties");
The parameter is the full path of the finished user-defined function configuration file.
If you need to use a user-defined function in esProc IDE, just deploy it in the file customFunctions.properties under config directory in installation directory. Then it will be automatically registered as IDE is started.
2. Parsing of parameters and options
2.1 Base class functions
The Java class of a user-defined function inherits from the base class com.scudata.expression.Function, so we can use attributes of the Function class directly. Common attributes are as follows:
protected String functionName Function name
protected String option Function option, which is a string of options added to a function expression, such as f@tcq(), where "tcq" is the option value
protected com.scudata.expression.IParam param Function parameter object
2.2 Parameter class IParam
A function parameter is parsed and stored under param of base class Function. It is the familiar tree storage structure, where param is the parameter tree’s root node. We can get and analyze a function parameter using com.scudata.expression.IParam interface methods. The methods are as follows:
char getType() Return the type of parameter node, that is the type of separator. There are three types of separators - IParam.Semicolon (semicolon), IParam.Comma (comma) and IParam.Colon (colon). For f(p1,p2;a1), the parameter node type is semicolon. For f(p1,p2), it is comma, and for f(p1:p2), it is colon.
boolean isLeaf() Return whether a node is a leaf (which does not have any child nodes). For f(p1,p2), the root node has two sub-nodes and it is a non-leaf node. p1 node does not have any sub-nodes and it is a leaf node.
int getSubSize() Return the number of sub-nodes. The root node of f(p1,p2;a1;k:n) has three sub-nodes where the third one k:n has two sub-nodes – k and n.
IParam getSub(int index) Return the indexth sub-node (numbers begin from 0).
Expression getLeafExpression() Return expression of the current leaf node.
2.3 Example of simple parameter parsing
First, let’s illustrate the parsing of parameters beginning from functions with simple parameters. Take the SPL built-in function abs() as an example, its complete function syntax is as follows:
abs(number)
The function is used to calculate the absolute value of the numeric value number. The corresponding Java class is com.scudata.expression.fn.math.Abs. Below is the function’s source code:
In line 12, the system checks whether the current parameter is null or is a non-leaf node and report error if it is. Line 17 gets expression of the parameter sing ugetLeafExpression()method and calls calculate(ctx) to calculate value of the expression, that is, the parameter value. If the parameter value is numeric type, then call Variant.abs(result) to calculate the parameter’s absolute value and return it. If the parameter is null, then return null. If the parameter is a non-null of any other data type, the system will report parameter data type error.
2.4 Example of complicated parameter parsing
Take SPL built-in function httpfile() as an example, let’s look at how to parse complicated parameters. Here is the function’s complete syntax:
httpfile(url:cs, post:cs, contenttype; header:value,....)
It is used to access an HTTP web page and get the content. url is the address of to-be-accessed HTTP web page; cs is the character set used by the returned content; post is the parameter string passed in the POST format for accessing the url, and the cs after it is the character set used by this parameter string; content type is the format of passed-in application parameter. The part after the semicolon is (are) attribute(s) of RequestHeader(s).
The function’s corresponding Java class is com.scudata.expression.fn.CreateHttpFile. Below is the first part of the source code:
this.param represents the function parameter tree’s root node. The httpfile() function does not allow null parameters, and requires that at least url be present. So, line 35 checks whether the parameters are null and report error if they are. The RequestHeader parameter after the semicolon can be absent. And line 38 checks whether the root node type is semicolon. If it is, line 39 checks whether the number of its sub-nodes is 2; and if it isn’t, report function parameter error. If the result of line 39 is 2, set the request header parameter headerParam as the second sub-node and param as the first one. If param is null, report function parameter error.
Let’s move on to the next part of the source code:
In this part, we define url node variable urlParam and post parameter sub-node variable postParam. Line 55 checks whether the param type is comma, counts the number of its sub-nodes if it is, and report function parameter error if the number is greater than 3. Get param’s first sub-node as urlParam and report parameter error if none is got. Get param’s second sub-node postParam. If param has more than two sub-nodes, get its third sub-node and make it post parameter format typeParam. If typeParam is non-null, get its expression using getLeafExpression()method and calculate the expression by calling calculate(ctx). If the expression value is a string object, assign it to variable type, otherwise report parameter type error.
If param type is not comma, there is only a url parameter under param node without post and contenttype parameters. Now we can set param as urlParam and postParam as null.
Line 86 checks whether urlParam is a leaf node. If it is, the url parameter does not have the character set attribute. Then urlParam.getLeafExpression().calculate(ctx) is used to get url expression and calculate the value, and error is reported if the value is not a string.
If urlParam is a non-leaf node, it has the character set attribute parameter. Then we check whether it has two sub-nodes. Get expression of the first sub-node to calculate url path and check whether the result is non-null and string type. Get expression of the second sub-node to calculate character set parameter value and check whether the value is string type.
The basic methods of parsing the other parameters in the source code are similar. You can read the code to learn about them.
3. Parameters and data type of return value
Both the value obtained from calculating a function parameter expression and the returned function value are an Object object. Object is the super class of all Java objects. During programming we should check whether a parameter value instance is the required data type; and convert it if it is, or parameter data type error will be reported if it isn’t.
Below is a list of Java objects corresponding to common data types of the SPL function parameters and returned values:
bool |
java.lang.Boolean |
int |
java.lang.Integer |
long |
java.lang.Long |
float |
java.lang.Double |
decimal |
java.math.BigDecimal |
number |
java.lang.Number |
date |
java.sql.Date |
time |
java.sql.Time |
datetime |
java.sql.Timerstamp |
string |
java.lang.String |
blob |
byte [] |
Sequence |
com.scudata.dm.Sequence |
Table sequence |
com.scudata.dm.Table |
Cursor |
com.scudata.dw.Cursor |
All data types have simple uses except for sequence, table sequence and cursor (Read SPL: Invoking Java Functions to learn about uses of the three data types). Make a note that, different from invoke function, you cannot convert a sequence into Object[] when writing user-defined functions. Instead, you must use the com.scudata.dm.Sequence object.
SPL Official Website 👉 https://www.scudata.com
SPL Feedback and Help 👉 https://www.reddit.com/r/esProc_SPL
SPL Learning Material 👉 https://c.scudata.com
SPL Source Code and Package 👉 https://github.com/SPLWare/esProc
Discord 👉 https://discord.gg/cFTcUNs7
Youtube 👉 https://www.youtube.com/@esProc_SPL