- Created by Mark Juras , last modified on Aug 17, 2018
You are viewing an old version of this page. View the current version.
Compare with Current View Version History
« Previous Version 2 Next »
Sample Applications of gmSL
This section contains samples of gmSL applications.Migration of VB6 RDO code to ADO.NET
The original request from the user was "Our VB6 code is in the following pretty standard pattern:"
Dim SQL As String Dim SP As rdoQuery Dim Results As rdoResultset SQL = "select * from departments where category_id = ?" Set SP = MainForm.EnablerRDOCN.CreateQuery("QueryDept", SQL) SP.rdoParameters(0).value = CategoryID; Set Results = SP.OpenResultset(rdOpenForwardOnly) While Not Results.EOF DeptID = Results.rdoColumns("department_id").value Results.MoveNext Wend SP.Close
string SQL = "select * from departments order by department_id"; DbCommand SP = DatabaseConnection().CreateCommand(); SP.CommandText = SQL; SP.Parameters[0].Value = CategoryID; using (DbDataReader Results = SP.ExecuteReader()) { while(Results.Read()) { DeptID = Convert.ToInt32(Results["department_id"]); } }
An example code was created
An example VB6 code was created that uses the external library "MSRDO20.DLL#Microsoft Remote Data Object 2.0". Some time was spent to make it a real working example using the .NET class "System.Data.SqlClient", that could passed on to show how gmBasic is able to perform these types of code transformations. The two specific VB6 methods to be transformed are as follows.
Public Sub connectDB() Set conn = New rdoConnection Dim dbs As String _ dbs = _ "UID=stocks_login;PWD=password;Database=stocks;" _ & "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" _ & "DSN='';" conn.Connect = dbs conn.EstablishConnection End Sub Public Sub execQuery() Dim SQL As String Dim SP As rdoQuery Dim Results As rdoResultset Dim f As String SQL = "select * from accounts where accountID < ? and FirstName like ?" Set SP = conn.CreateQuery("QueryAcct", SQL) SP.rdoParameters(0).Value = "5005" SP.rdoParameters(1).Value = "Test%" Set Results = SP.OpenResultset(rdOpenForwardOnly) While Not Results.EOF f = Results.rdoColumns("FirstName").Value writeLog f f = Results.rdoColumns("eMail").Value writeLog f Results.MoveNext Wend SP.Close End Sub
public static void connectDB() { conn = new System.Data.SqlClient.SqlConnection(); string dbs = ""; dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;"; conn.ConnectionString = dbs; conn.Open(); } public static void execQuery() { string SQL = ""; System.Data.SqlClient.SqlCommand SP = null; string f = ""; SQL = "select * from accounts where accountID < @0 and FirstName like @1"; (SP = conn.CreateCommand()).CommandText = SQL; SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005"; SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@1", null)).Value = "Test%"; using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader()) { while (Results.Read()) { f = Results["FirstName"].ToStr(); writeLog(f); f = Results["eMail"].ToStr(); writeLog(f); } } }
The unmigrated translation
Using the reference script for MSRDO20.Dll exactly as produced by the gmBasic IDL translator and the following very simple gmSL based translation script
gmBasic> gmSL> Execute.Storage Action:="Create", Identifier:="\gmManual\Samples\RDOtoNet\csh\RDOToNet_std" Select.DevEnv = "VS2013" Select.Dialect = Dialects.csh Select.BuildFile = BuildFileStatus.On Select.System = "\gmManual\Samples\FromIdl" Select.Target = "\gmManual\Samples\FromIdl" Select.DeployLocation = "\Temp" Execute.Compile Project:="\gmManual\Samples\RDOtoNet\src\RDOToNET.vbp" Execute.Analyse Execute.Output Status:="New", Filename:="\gmManual\Samples\RDOtoNet\csh\RDOToNet_std.bnd" Execute.Author Execute.Storage Action:="Close" </gmSL> </gmBasic>
public static void connectDB() { conn = new RDO.rdoConnection(); string dbs = ""; dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';"; conn.Connect = dbs; conn.EstablishConnection(null,null,null); } public static void execQuery() { string SQL = ""; RDO.rdoQuery SP = null; RDO.rdoResultset Results = null; string f = ""; SQL = "select * from accounts where accountID < ? and FirstName like ?"; SP = conn.CreateQuery("QueryAcct",SQL); SP.rdoParameters[0].Value = "5005"; SP.rdoParameters[1].Value = "Test%"; Results = SP.OpenResultset(RDO.ResultsetTypeConstants.rdOpenForwardOnly,null,null); while (!Results.EOF) { f = Results.rdoColumns["FirstName"].Value.ToStr(); writeLog(f); f = Results.rdoColumns["eMail"].Value.ToStr(); writeLog(f); Results.MoveNext(); } SP.Close(); }
Adding a few migration names
The first step in any migration is to add migration names, using gmPL, to reflect any simple name changes that need to be done. The reader is assumed to be familiar with the gmPL component refactoring attributes -- in this case migName and migPattern. Doing this takes a lot of noise out of the differences between the migrated and unmigrated translations. In this case, they are as summarized here.
<library id="MSRDO20.DLL" name="RDO" migName="System.Data.SqlClient" .. > <class id="_rdoConnection" ..> <property id="Connect" type="String" status="InOut" migName="ConnectionString" /> <method id="EstablishConnection" type="Void" migPattern="%1d.Open()\c"> <method id="CreateQuery" type="rdoQuery" migName="CreateCommand"> ... <class id="rdoPreparedStatement" <property id="rdoParameters" type="rdoParameters" status="Out" migName="Parameters" /> <method id="OpenResultset" type="rdoResultset" migPattern="%1d.ExecuteReader()" > ... <coclass id="rdoConnection" migName="SqlConnection"> <coclass id="rdoResultset" creatable="off" migName="SqlDataReader"> <coclass id="rdoQuery" migName="SqlCommand">
public static void connectDB() { conn = new System.Data.SqlClient.SqlConnection(); string dbs = ""; dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';"; conn.ConnectionString = dbs; conn.Open(); } public static void execQuery() { string SQL = ""; System.Data.SqlClient.SqlCommand SP = null; System.Data.SqlClient.SqlDataReader Results = null; string f = ""; SQL = "select * from accounts where accountID < ? and FirstName like ?"; SP = conn.CreateCommand("QueryAcct",SQL); SP.Parameters[0].Value = "5005"; SP.Parameters[1].Value = "Test%"; Results = SP.ExecuteReader(); while (!Results.EOF) { f = Results.rdoColumns["FirstName"].Value.ToStr(); writeLog(f); f = Results.rdoColumns["eMail"].Value.ToStr(); writeLog(f); Results.MoveNext(); } SP.Close(); }
Starting the transformation process
The first difference between the current translation and the target translation is in the method connectDB.
Current: dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';"; Target : dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;";
The first question that has to be asked is "How do we determine which character strings need to be changed?". In an editing context, perhaps all strings could be searched for these pairs and they could be universally removed. This would almost certainly work in this case, but in general changes need to be contingent on their use. The string above needs to be changed because in this later statement
conn.ConnectionString = dbs;
Transformation is not a process that works with source code or target code. It works with the intermediate code produced by the compiler. After the compiler has completed and after the analyser has finished the standard migrations built into gmBasic, references in the intermediate code to components in external libraries are scanned looking for those that have user supplied code registered for them. The purpose of that user code is to change that intermediate code so that it performs the target operation as opposed to its original source operation.
Once a statement that requires transformation has been identified, the first step is to examine the intermediate code currently produced for it. This can be seen in an audit report of the vbi file produced by the translation script or can be produced on-the-fly using the Opcode.DumpCode method. The intermediate code for the conectionString assignment statement is as follows.
72 | | | | NEW | 43 conn.Connect = dbs 75 | | | | LEV | Nest0 77 | 1.77 | 1.77 | String | LDA | Variable:dbs:88066 82 | 1.77 | | | ARG | String 84 | 2.84 | 1.84 | RDO.rdoConnection | LDA | Variable:conn:87690 89 | 3.89 | 1.84 | String | LLP | Component:Connect:50673 94 | 2.84 | 1.84 | String | MEM | Child 96 | | | | STR | AssignValue
| 50673 | 39973 | Lib_Property | RDO._rdoConnection.Connect
Introducing a component transform method
The gmSL transform methods are introduced within library description file Refactor statement sections using the gmSL statement. In this case this is as follows.
<Refactor id="RDO" event="rdoHandlers" > <gmSL NameSpace="rdoHandlers" Class="Transform" Source="msrdo20Transform.gmsl" /> </Refactor>
For now the file msrdo20Transform.gmsl is simply as follows.
int __rdoConnection_Connect(int subRoot,int iStart,int iRefer) { System.LogMessage("RDO#01: sub<" + Symbol.FullName(subRoot,-1) + "> iStart=" + iStart + " iRefer=" + iRefer); return 0; }
The names of transpose methods are an "underline converted" form of the host relative identifier of the component whose reference code is to be transformed. Underline conversion changes all periods in the identifier to an underscore and changes all underscores in the identifier to double underscores. In this case the component identifier is RDO._rdoConnection.Connect. Making it host relative removes the leading RDO. and doing the underline conversion makes it __rdoConnection_Connect. This conversion is necessary to create unique but well-formed method identifiers. After the gmSL file is compiled, gmBasic scans the refactor host for components that match the underscore converted methods, sets their hasCodeHandler property True, and sets their migTransform member equal to the root of the transform method.
All transform methods have three parameters. The parameter subRoot is the root of the source code component that references the component being transformed. The parameter iStart is a starting code location that marks where the referencing code began. Its exact value will vary by the type of reference. The parameter iRefer is the code location of the actual reference to the transform component. To bring this into focus, the initial version of the transform method merely logs the content of these parameters. Running the translation script with Progress="1" produces the following log.
Loading reference:[Language.std] \gmIDF\Language.std.xml Reprocessing file: Transform.cls Reprocessing file: Transform.cls Processing file: \gmManual\Samples\RDOtoNet\src\RDOToNET.vbp Loading reference:[stdole2.tlb] \gmIDF\stdole2.tlb.xml Loading reference:[MSRDO20.DLL] \gmManual\Samples\FromIdl\MSRDO20.DLL.xml Reprocessing file: \gmManual\Samples\RDOtoNet\src\modRDOToNET.bas Reprocessing file: \gmManual\Samples\RDOtoNet\src\modRDOToNET.bas RDO#01: sub<RDOToNET.connectDB> iStart=77 iRefer=89
The log more importantly brings out the integration between the gmSL transpose capability and the overall processing of the translation script. Translation, when it becomes migration, is a very complex, iterative process. Things go wrong. Things do not work as expected. The translation produces a log file that describes what happens, and it also produces a vbi file that contains all of the detailed information about how the source code was migrated and what it was migrated into. When things go wrong it is important to have available an exact representation of the logic used to do the migration in the vbi file that actually produced it. Note in lines 05 and 06 above that the transpose class is being compiled in the same manner as the source code. All code associated with it is in the vbi file where it can be audited and examined in precisely the same manner as any other code. The translation produced is identical to the one before the refactor section was added, but the vbi is different. First the transform method itself has been added into the symbol table.
2 | 85026 | 81920 | ClassFile | Transform.cls 3 | 85249 | 85026 | Subprogram | rdoHandlers.Transform.__rdoConnection_Connect 4 | 85337 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.subRoot 4 | 85418 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.iStart 4 | 85463 | 85249 | Variable | rdoHandlers.Transform.__rdoConnection_Connect.iRefer 2 | 85128 | 81920 | Vb_Name.85026 | rdoHandlers.Transform
Text Associated with Scanned Code: RecNo | Rai | nRec | Content ----- | --- | ---- | ------- 1 | 0 | 65 | int __rdoConnection_Connect(int subRoot,int iStart,int iRefer){ 2 | 0 | 110 | System.LogMessage("RDO#01: sub<" + Symbol.FullName(subRoot,-1) + "> iStart=" + iStart + " iRefer=" + iRefer) 3 | 0 | 10 | return 0 4 | 0 | 3 | }
Actual C# Codeblock Associated with __rdoConnection_Connect: Offset | Sl.Start | Ql.Start | Quantity type | Opcode | Operation support information ------ | -------- | -------- | ------------- | ------ | ----------------------------- 0 | | | | LEV | Nest0 2 | 1.2 | 1.2 | String | LSC | 12:RDO#01: sub< 7 | 1.2 | 1.2 | String | LEV | Nest1 9 | 2.9 | 2.9 | Integer | LDA | Variable:subRoot:85337 ... 59 | 2.9 | | | SCM | System_LogMessage 61 | 2.9 | | | LEV | Nest0 63 | 3.63 | 1.63 | Integer | LIC | 0 66 | 3.63 | | | ARG | Integer 68 | | | | EXI | Function
Detailed Description of Lib_Property RDO._rdoConnection.ConnectionString with root address 50673: Property | Content -------- | ------- Migrate Status | Referenced Migrate Flags | HasCodeHandler Transformation | 85249 BinaryType | String:8
The RDO._rdoConnection.Connect transform Code
The actual code used to ultimately change the content of the dbs connection string works through references to the RDO._rdoConnection.Connect component. It is as follows. It begins with the same underlined converted method identifier, with the same standard parameters, and then the declarations of the local variables. Reading about, writing about, and understanding code whose purpose is to manage other code can get confusing quickly, so keep in mind that there are two coding levels.
int __rdoConnection_Connect(int subRoot,int iStart,int iRefer) { tCodeBlock codptr; int nCode; int opcd; int subcd; int icode; int iEnd; int localVar; int iAssign; string connect; int iPos; int semi; int nDelete; int addr;
codptr = Opcode.GetCode(); nCode = Opcode.GetLength(); opcd = Opcode.GetOperation(codptr,iStart,localVar); if(opcd != OPC.LDA) return 0; icode = iRefer + sizeof(OPC.LLP); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.MEM) return 0; icode = icode + sizeof(OPC.MEM); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.STR) return 0;
89 | 3.89 | 1.84 | String | LLP | Component:Connect:50673 94 | 2.84 | 1.84 | String | MEM | Child 96 | | | | STR | AssignValue
iAssign = RefactorCode_FindAssign(localVar,iStart); if(iAssign == 0) return 0;
iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode); connect = Opcode.GetString(iAssign,iEnd); if(!connect) return 0;
37 | | | | LEV | Nest0 39 | 1.39 | 1.39 | String | LSC | 46:UID=stocks_login;PWD=password;Database=stocks; 44 | 2.44 | 2.44 | String | LSC | 47:Server=GMI-CS-01.gmi.local;Driver={SQL Server}; 49 | 1.39 | 1.39 | String | CAT | String 51 | 2.51 | 2.51 | String | LSC | 7:DSN=''; 56 | 1.39 | 1.39 | String | CAT | String 58 | 1.39 | | | ARG | String
iPos = Character.FindFirst(connect,0,"Driver="); if(iPos) { iPos = iPos - 1; semi = Character.FindFirst(connect,iPos,";"); if(semi) { connect = Character.Remove(connect,iPos,semi); } } iPos = Character.FindFirst(connect,0,"DSN="); if(iPos) { iPos = iPos - 1; semi = Character.FindFirst(connect,iPos,";"); if(semi) { connect = Character.Remove(connect,iPos,semi); } }
nDelete = RefactorCode_ReplaceAssign(iAssign,connect); iRefer = iRefer - nDelete; return iRefer; }
int RefactorCode_FindAssign(int varRoot,int iEnd) { tCodeBlock codptr; int lastLev0; int icode; int addr; int opcd; int subcd; codptr = Opcode.GetCode(); lastLev0 = 0; for(icode = 0; icode >= 0; icode = Opcode.GetNext(codptr,icode,iEnd)) { opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd == OPC.LEV && subcd == 0) lastLev0 = icode; else if(opcd == OPC.LDA && subcd == varRoot) { opcd = Opcode.GetOperation(codptr,icode+sizeof(OPC.LDA),subcd); if(opcd == OPC.STR) return lastLev0; } } return 0; }
The method RefactorCode_ReplaceAssign deletes the original code in the expression and then inserts a reference to the replacement string.
int RefactorCode_ReplaceAssign(int iAssign, string replacement) { tCodeBlock codptr; int nCode; int iEnd; int nDelete; int addr; codptr = Opcode.GetCode(); nCode = Opcode.GetLength(); iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode); iAssign = iAssign + sizeof(OPC.LEV); nDelete = iEnd - iAssign - sizeof(OPC.LSC) - sizeof(OPC.ARG); if(nDelete > 0) { nCode = Opcode.DeleteCode(iAssign,nCode,nDelete); Opcode.SetLength(nCode); } addr = Store.String(replacement); Opcode.SetOperation(codptr,iAssign,OPC.LSC,addr); return nDelete; }
Running this new code does now produce the desired change as the following file comparison shows.
Comparing files rdotran.bnd and RDOTRAN.SAV ***** rdotran.bnd dbs = "UID=stocks_login;PWD=password;Database=stocks;Server=GMI-CS-01.gmi.local;"; ***** RDOTRAN.SAV dbs = "UID=stocks_login;PWD=password;Database=stocks;" + "Server=GMI-CS-01.gmi.local;Driver={SQL Server};" + "DSN='';"; *****
The RDO._rdoConnection.CreateQuery transform Code
The next difference between the unmigrated and migrated code is in execQuery in the string assigned to the variable SQL.
Current: SQL = "select * from accounts where accountID < ? and FirstName like ?"; Target : SQL = "select * from accounts where accountID < @0 and FirstName like @1";
Set SP = conn.CreateQuery("QueryAcct", SQL)
int __rdoConnection_CreateQuery(int subRoot,int iStart,int iRefer) { tCodeBlock codptr; int nCode; int opcd; int subcd; int icode; int sqlString; int sqlVar; string query; int iAssign; int index; int iPos; int lPos; int nDelete; int createCommand; int iEnd;
codptr = Opcode.GetCode(); nCode = Opcode.GetLength(); icode = iRefer; opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.LLP) return 0; icode = icode + sizeof(OPC.LLP); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.MEM) return 0; icode = icode + sizeof(OPC.MEM); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.LEV) return 0; sqlString = Opcode.FindArgumentEnd(codptr,icode,nCode); opcd = Opcode.GetOperation(codptr,sqlString,subcd); if(opcd != OPC.LEV) return 0; sqlString = sqlString + sizeof(OPC.LEV); opcd = Opcode.GetOperation(codptr,sqlString,sqlVar); if(opcd != OPC.LDA) return 0; sqlString = sqlString+sizeof(OPC.LDA); opcd = Opcode.GetOperation(codptr,sqlString,subcd); if(opcd != OPC.ARG) return 0; sqlString = sqlString+sizeof(OPC.ARG);
iAssign = RefactorCode_FindAssign(sqlVar,iRefer); if(iAssign == 0) return 0;
iEnd = Opcode.FindArgumentEnd(codptr,iAssign,nCode); query = Opcode.GetString(iAssign,iEnd); if(!query) return 0;
if(iAssign) { iPos = 0; for(index = 0; index < 10; index = index + 1) { lPos = Character.FindFirst(query,iPos,"?"); if(!lPos) break; iPos = iPos + lPos - 1; query = Character.Remove(query,iPos,1); query = Character.Insert(query,iPos,"@" + index); } if(iPos != 0) { nDelete = RefactorCode_ReplaceAssign(iAssign,query); iRefer = iRefer - nDelete; sqlString = sqlString - nDelete; } } return iRefer; }
Comparing files rdotran.bnd and RDOTRAN.SAV ***** rdotran.bnd SQL = "select * from accounts where accountID < @0 and FirstName like @1"; ***** RDOTRAN.SAV SQL = "select * from accounts where accountID < ? and FirstName like ?"; *****
Current: SP = conn.CreateCommand("QueryAcct",SQL); Target : (SP = conn.CreateCommand()).CommandText = SQL;
<migclass id="DotNet"> <method id="CreateCommand" type="void" migPattern="(%1d = %2d()).CommandText = %4d\c"> <argument id="conn" type="Object" status="ByVal" /> <argument id="Name" type="String" status="ByVal"/> <argument id="SqlString" type="Variant" status="ByVal"/> </method> </migClass>
iEnd = sqlString; opcd = Opcode.GetOperation(codptr,iEnd,subcd); if(opcd != OPC.CUF) return 0; iEnd = iEnd + sizeof(OPC.CUF); opcd = Opcode.GetOperation(codptr,iEnd,subcd); if(opcd != OPC.REF) return 0; iEnd = iEnd + sizeof(OPC.REF); opcd = Opcode.GetOperation(codptr,iEnd,subcd); if(opcd != OPC.ARG) return 0; iEnd = iEnd + sizeof(OPC.ARG); opcd = Opcode.GetOperation(codptr,iEnd,subcd); if(opcd != OPC.CMD) return 0; iEnd = iEnd + sizeof(OPC.CMD); nDelete = iEnd - sqlString - sizeof(OPC.PAT); createCommand = Symbol.FindIdentifier("RDO.DotNet.CreateCommand"); nCode = Opcode.DeleteCode(sqlString,nCode,nDelete); Opcode.SetLength(nCode); Opcode.SetOperation(codptr,sqlString,OPC.PAT,createCommand);
***** rdotran.bnd (SP = conn.CreateCommand()).CommandText = SQL; ***** RDOTRAN.SAV SP = conn.CreateCommand("QueryAcct",SQL); *****
The RDO.rdoPreparedStatement.rdoParameters transform Code
The next difference between the unmigrated and migrated code is as follows. This particular difference occurs twice.
Current: SP.Parameters[0].Value = "5005"; Target : SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005";
<Method id="Parameters" type="object" migPattern="%1d.Add(new System.Data.SqlClient.SqlParameter(\S@%2d\S, null))" > <argument id="index" type="Integer" status="ByVal" /> </Method>
int rdoPreparedStatement_rdoParameters(int subRoot,int icode,int iRefer) { int nCode; tCodeBlock codptr; int iEnd; int opcd; int subc; int parameters; nCode = Opcode.GetLength(); codptr = Opcode.GetCode(); iEnd = Opcode.FindArgumentEnd(codptr,iRefer+7,nCode); opcd = Opcode.GetOperation(codptr,iEnd,subc); if(opcd != OPC.COL || subc != OPC.COL.Item) return 0; parameters = Symbol.FindIdentifier("RDO.DotNet.Parameters"); nCode = Opcode.ExpandCode(iEnd,nCode,sizeof(OPC.PAT) - sizeof(OPC.COL)); Opcode.SetLength(nCode); Opcode.SetOperation(codptr,iEnd,OPC.PAT,parameters); return iRefer; }
Comparing files rdotran.bnd and RDOTRAN.SAV ***** rdotran.bnd SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@0", null)).Value = "5005"; SP.Parameters.Add(new System.Data.SqlClient.SqlParameter("@1", null)).Value = "Test%"; ***** RDOTRAN.SAV SP.Parameters[0].Value = "5005"; SP.Parameters[1].Value = "Test%"; *****
The RDO.rdoPreparedStatement.OpenResultset transform code
An important difference in the migrated code has to do with the SqlDataReader variable Results. In the unmigrated version, it is declared as a method level local variable and is then opened in the code.
System.Data.SqlClient.SqlDataReader Results = null; ... Results = SP.ExecuteReader();
using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader()) {
The transform method itself begins in the standard way with the required declaractions.
int rdoPreparedStatement_OpenResultset(int subRoot,int iStart,int iRefer) { tCodeBlock codptr; int nCode; int opcd; int subcd; int icode; int varRoot; tVariable varInfo;
codptr = Opcode.GetCode(); nCode = Opcode.GetLength(); icode = iRefer; opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.REF) return 0; icode = icode + sizeof(OPC.REF); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.ARG) return 0; icode = icode + sizeof(OPC.ARG); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.CMD) return 0; opcd = Opcode.GetOperation(codptr,iStart,varRoot); if(opcd != OPC.LDA) return 0;
varInfo = Store.DeltaVector(varRoot); varInfo.DeadCode = True;
Opcode.SetOperation(codptr,icode,OPC.IFS,OPC.IFS.Using); nCode = Opcode.ExpandCode(icode,nCode,sizeof(OPC.TYV)); Opcode.SetLength(nCode); Opcode.SetOperation(codptr,icode,OPC.TYV,varRoot); return iRefer; }
Comparing files rdotran.bnd and RDOTRAN.SAV ***** rdotran.bnd using (System.Data.SqlClient.SqlDataReader Results = SP.ExecuteReader()) { while (!Results.EOF) .... ***** RDOTRAN.SAV Results = SP.ExecuteReader(); while (!Results.EOF) .. *****
AUTHOR WARNING#02: Indentation level not balanced old = 3 current = 4 in <RDOToNET.execQuery>
The RDO.rdoPreparedStatement.Close transform code
Within the unmigrated code the Openresultset method has a corresponding Close method. In the migrated approach this method call is replaced by an IFS.EndUsing operation. The transform code is as follows.
int rdoPreparedStatement_Close(int subRoot,int icode,int iRefer) { int nCode; tCodeBlock codptr; iRefer = Opcode.CommentOut(iRefer,OPC.CMT.Delete); iRefer = iRefer + sizeof(OPC.CMT); nCode = Opcode.GetLength(); codptr = Opcode.GetCode(); nCode = Opcode.ExpandCode(iRefer,nCode,sizeof(OPC.IFS)); Opcode.SetLength(nCode); Opcode.SetOperation(codptr,iRefer,OPC.IFS,OPC.IFS.EndUsing); return iRefer; }
***** rdotran.bnd } ***** RDOTRAN.SAV SP.Close(); *****
The RDO._rdoResultset.EOF transform code
The next difference is in the while loop that reads the records from the result set.
Current: while (!Results.EOF) Target : while (Results.Read())
<property id="EOF" type="Boolean" status="Out" migName="Read()"/>
266 | 2.266 | 1.261 | Boolean | LLP | Component:EOF:59440 271 | 1.261 | 1.261 | Boolean | MEM | Child 273 | 1.261 | 1.261 | Boolean | NOT | Arithmetic
int __rdoResultset_EOF(int subRoot,int iStart,int iRefer) { int nCode; tCodeBlock codptr; int opcd; int subcd; int icode; nCode = Opcode.GetLength(); codptr = Opcode.GetCode(); icode = iRefer + sizeof(OPC.LLP); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.MEM) return 0; icode = icode + sizeof(OPC.MEM); opcd = Opcode.GetOperation(codptr,icode,subcd); if(opcd != OPC.NOT) return 0; nCode = Opcode.DeleteCode(icode,nCode,sizeof(OPC.NOT)); Opcode.SetLength(nCode); return iRefer; }
***** rdotran.bnd while (Results.Read()) ***** RDOTRAN.SAV while (!Results.EOF) *****
In places such as this, shallow transforms are fine, but beware of them in larger scale migrations where they can introduce problems. A more complex approach would introduce a DotNet method Read and then do a contingent replacement.
The RDO._rdoColumn.Value and RDO._rdoResultset.rdoColumns transform code
The next difference is in the while loop that reads the records from the result set.
Current: f = Results.rdoColumns["FirstName"].ToStr(); Target : f = Results["FirstName"].ToStr();
280 | | | | LEV | Nest0 282 | 1.282 | 1.282 | RDO.rdoResultset | LDA | Variable:Results:106065 287 | 2.287 | 1.282 | RDO.rdoColumns | LLP | Component:rdoColumns:57254 292 | 1.282 | 1.282 | RDO.rdoColumns | MEM | Child 294 | 1.282 | 1.282 | RDO.rdoColumns | LEV | Nest1 296 | 2.296 | 2.296 | String | LSC | 9:FirstName 301 | 2.296 | 1.282 | RDO.rdoColumns | ARG | String 303 | 1.282 | 1.282 | RDO.rdoColumn | COL | Item 305 | 2.305 | 1.282 | Variant | LLP | Component:Value:54311 310 | 1.282 | 1.282 | Variant | MEM | Child
Except for the names of the methods they are identical.
int __rdoColumn_Value(int subRoot,int icode,int iRefer) { int nCode; tCodeBlock codptr; nCode = Opcode.GetLength(); codptr = Opcode.GetCode(); nCode = Opcode.DeleteCode(iRefer,nCode,sizeof(OPC.LLP) + sizeof(OPC.MEM)); Opcode.SetLength(nCode); return iRefer; } int __rdoResultset_rdoColumns(int subRoot,int icode,int iRefer) { int nCode; tCodeBlock codptr; nCode = Opcode.GetLength(); codptr = Opcode.GetCode(); nCode = Opcode.DeleteCode(iRefer,nCode,sizeof(OPC.LLP) + sizeof(OPC.MEM)); Opcode.SetLength(nCode); return iRefer; }
Comparing files rdotran.bnd and RDOTRAN.SAV ***** rdotran.bnd f = Results["FirstName"].ToStr(); writeLog(f); f = Results["eMail"].ToStr(); ***** RDOTRAN.SAV f = Results.rdoColumns["FirstName"].Value.ToStr(); writeLog(f); f = Results.rdoColumns["eMail"].Value.ToStr(); *****
The RDO._rdoResultset_MoveNext code
The final difference between the two translations is that the MoveNext call is no longer needed. The migrated code already does the read in the while loop. The transform method can simply comment out the unwanted statement.
int __rdoResultset_MoveNext(int subRoot,int icode,int iRefer) { iRefer = Opcode.CommentOut(iRefer,OPC.CMT.Delete); return iRefer; }
Table of Contents
- No labels