⬆️ ⬇️

SQL Access to NoSQL Data: Implementing SQL Procedure in Caché with Dynamic Detection of Returned Metadata

As you know, Caché can be used as a relational DBMS, including through JDBC / ODBC drivers , with the ability to execute arbitrary SQL queries and call SQL procedures.

It is also known that all data in Caché is stored in multidimensional sparse arrays - globals . This allows, in case of insufficient performance of a single SQL procedure, not to use the standard CachéSQL engine, but to rewrite its execution code in the Caché ObjectScript (COS) server business logic language, in which you can implement an optimal algorithm for executing the SQL procedure, often using more optimal NoSQL data structures (globals).

However, there is one limitation in the standard Caché class library: for SQL procedures in which the selection is performed by self-written COS code, it is necessary to define a set of returned fields at the compilation stage — that is, There is no possibility to dynamically set metadata for a SQL procedure working with NoSQL structures.



How to remove this restriction, it is told under cat.



Work with SQL procedures in Caché


Requests via JDBC / ODBC to non-relational Caché structures are implemented using stored procedures as follows:



image

')

Such a stored procedure can return one or several record sets (ResultSets), or a scalar value.



As an example, let's call the stored procedure sample.SP_Sample_By_Name from the Samples area using one of the tools for working with ODBC:



image



By the signature of the SQL procedure, it is not known what it will return, it becomes known only during the execution of the procedure.



Caché allows you to make class methods stored SQL procedures both returning a value and returning a ResultSet. For example, this is how a stored procedure is declared that returns a ResultSet:



ClassMethod SomeSqlProc ( p1 As% Integer = 0 ) [ ReturnResultsets , SqlProc ]



With this construct, you can write Caché ObjectScript code that can be called via ODBC as a stored procedure, which will return a ResultSet (or several).



In Caché, there are two standard methods for generating NoSQL data, returned as a ResultSet:



The first way. Using class queries


Using class queries
ClassMethod SomeSqlProc ( p1 As% Integer = 0 ) [ ReturnResultsets , SqlProc ]

{

if ' $ isobject ( $ Get ( % sqlcontext )) { set % sqlcontext = ## class ( % ProcedureContext ). % New () }

Set query = ## class ( % ResultSet ). % New ( "User.SomeClass: Query" )

Do query . Execute ( p1 )

do % sqlcontext . AddResultSet ( query )

}



Look here for details



This method allows you to write arbitrary code to form data in Caché ObjectScript, but the metadata of the returned ResultSet is generated by the compiler based on the% Query parameter. # ROWSPEC, i.e. at compile time.



The second way. Using% SQL.CustomResultSet


Using% SQL.CustomResultSet
ClassMethod SomeSqlProc ( p1 As% Integer = 0 ) [ ReturnResultsets , SqlProc ]

{

if ' $ isobject ( $ Get ( % sqlcontext )) { set % sqlcontext = ## class ( % ProcedureContext ). % New () }

Set query = ## class ( User.MyResultSet ). % New (, p1 )

do % sqlcontext . AddResultSet ( query )

}



Learn more about % SQL.CustomResultSet , an example implementation.



The method is similar to the previous one, but the metadata is generated based on the definition of the class-descendant% SQL.CustomResultSet - just as in the previous case, at compile time.



Note: Similarly, you can get and SQL-data:



SQL data retrieval
ClassMethod SomeSqlProc ( p1 As% Integer = 0 ) [ ReturnResultsets , SqlProc ]

{

s sqltext = "SELECT * FROM dbo.Classname" ##; Prepare request text

if ' $ isobject ( $ Get ( % sqlcontext )) { set % sqlcontext = ## class ( % ProcedureContext ). % New () }

Set query = ## class ( % ResultSet ). % New ( "% DynamicQuery: SQL" )

Do query . Prepare ( sqltext )

Do query . Execute ()

do % sqlcontext . AddResultSet ( query )

}



In this case, metadata is generated at runtime, but data can only be obtained from SQL.



Thus, if we want to generate result metadata in runtime and use arbitrary Caché ObjectScript to generate data, then as can be seen - there are not enough funds in the Caché distribution.



The solution of the problem


There are 4 solutions to the problem:





I chose the last method - it seemed to me the most elegant (looking ahead, I could not manage without crutches).



To begin with, let's create a User.Query class and inherit it from% Query - so as not to overwrite the implementation of all% Query. When% Query is used by the consumer (% ResultSet), metadata is requested via two class methods: GetInfo and GetODBCInfo. In the heir class, you must write alternative implementations of these methods. Through several experiments (it's easier than to understand the generators), I found out about the parameters GetInfo (.colinfo, .parminfo, .idinfo, .qHandle, extoption, .extinfo):







With GetODBCInfo, everything is the same, there are slightly more fields, and the result should be added to one-level lists, but in general it’s still the same.



In order to return the correct metadata from GetInfo and GetODBCInfo, I need to find several not quite obvious techniques, which are mainly given below:

Source: https://habr.com/ru/post/160147/



All Articles