Etiqueta

24 septiembre 2014

Uso de variables vinculadas (Bind) en sentencias SQL

Al ejecutarse (por defecto) cualquiera de los métodos disponibles para una instancia de tipo "Table", como por ejemplo $select() o $insert(), Omnis genera dinámicamente la sentencia SQL correspondiente, pero antes de su ejecución, compone (proceso conocido como “tokenize”) todos y cada uno de los elementos (variables, valores, etc) que conformaran la sentencia SQL que enviará al servidor.

Particularmente, esto significa que Omnis deberá localizar las referencias a las variables "bind" que hayamos utilizado, es decir las usadas mediante la notación @[variable]. Cuando Omnis "tokenize" la sentencia, tendrá en cuenta el ámbito en el que espera encontrar las variables referenciadas. En el caso de una instancia de tipo "Table", el ámbito de aplicación será, el de la propia instancia.

Llamamos "ámbito de aplicación" al medio en que podremos encontrar las variables declaradas, según sean "Task" (Nivel de Tarea), "Class" (Nivel de clase), "Local" (Nivel local), "Parameter" (Equivalente al nivel local) o "Hash" (Globales).

En el caso de que se trate de una lista creada en la instancia "Table" y definida mediante una clase "schema" o "query", se permitirá el uso de los siguientes ámbitos:

  • @[#...] - Una variable "hash", como por ejemplo @[#S1], o @[#10]. Con la excepción de #D, #T, #CT que son técnicamente "macro-hash" (evaluadas en el momento de ser referenciadas) y no pueden ser evaluadas durante el proceso "tokenize". Si necesitamos hacer uso de la fecha actual, podríamos optar por lo siguiente: Calculate lvCurrentDate as #D, para después, usar @[lCurrentDate].
  • @[taskvar] - una variable de tarea, siempre y cuando la tarea que ejecuta y define la lista sean la misma.
  • @[$cinst.column] - cuando se trata de una "row" definida con una clase SQL, "column", sería una de las columnas de la "row".
  • @[$cinst.N.column] - cuando se trata de una lista definida con una clase SQL, "column", sería una de las columnas de la lista, "N" sería un número de línea literal. Por ejemplo, @[$cinst.5.column] haría referencia al valor de "column" en la línea 5. No está permitido el uso de otras notaciones entre corchetes, dentro de éstas, es decir, la referencia @[$cinst.[lvRowNo].column] no es admitida como variable "bind". Una solución a esto, podría ser, hacer uso del comando Calculate $cinst.$line as lvRowNo, para después, usar @[$cinst.column].

A continuación mostramos un ejemplo sobre como hacer uso de una variable "bind" al obtener datos, en correspondencia con los almacenados en la columna "Col3" de una lista:

Begin Statement
   Sta: Select * from Table
   Sta: Where Col = @[iList.Col3]
End Statement

Do lStatementObj.$prepare() Returns lStatus
For iList.$line from 1 to iList.$linecount
   Do lStatementObj.$execute() return lStatus
   Do lStatementObj.$fetch(iRow)
   .....
End for

En el caso de que se trate de una lista creada en la instancia "Table" y definida mediante una clase "schema" o "query", también podría utilizar:

  • @[$cinst.iVar] donde "iVar" sería una variable de instancia en la clase "Table" y que podría haber sido configurada mediante la ejecución previa de un método, como por ejemplo MyList.$setbind('valor') antes de invocar al MyList.$select(), MyList.$insert(), etc

En el ejemplo siguiente, se invoca al método $setbind que contendría a su vez el parámetro "pVar" y un sola línea "Calculate iVar as pVar". Luego, desde fuera de la clase "Table" tendríamos lo siguiente:

Calculate lFruit as 'Oranges'
Do lList.$definefromsqlclass('MyTable')
Do lList.$setbind(lFruit)
Do lList.$sessionobject.$assign(iSessionObj)
Do lList.$select('WHERE Fruit=@[$cinst.iVar]')
Do lList.$fetch(kFetchAll)

La formula descrita en éste artículo nos permite el uso de variables que de otra manera no estarían a nuestro alcance desde la clase "Table".

17 septiembre 2014

Uso de "Item reference"

En éste artículo hablaremos un poco sobre las variables de tipo "Item reference", que no deben ser (en ningún caso) confundidas con las de tipo "Object reference" de las que ya hemos hablado en un anterior artículo, las variables "Item reference" puede referenciar a un punto concreto dentro del árbol de objetos y han sido creadas para contener cadenas de notación largas, tales como:

Set reference myRef to $root.$iwindows.wTest.$objs.Tabpane.$objs.Button
De éste modo, puedo simplificar la notación con la que hago referencia a las propiedades de un objeto, como (por ejemplo) para el caso expuesto mediante el comando de asignación anterior:

Do myRef.$text.$assign('Mi botón')

En el capítulo 3 del manual de Programación Omnis en castellano, podrá encontrar una descripción de cómo utilizar este tipo de variables, así como del comando utilizado para la asignación de su posibles valores, el comando "Set Reference".

 

Eliminación de un "Item reference"


Por supuesto las variables del tipo "Item reference" pueden ser re-asignadas, es decir modificar o alterar su contenido, mediante la instrucción:

Set reference myRef to $root.$iwindows.wTest.$objs.Tabpane.$objs.Button2

"myRef" ahora estaría apuntando a un botón diferente.

 

Pero, ¿cómo se elimina un "Item reference"?


El comando...

Calculate myRef as #NULL

...produciría un error en la notación resultante y...

Set reference myRef to #NULL

...generaría una referencia válida, pero, hacia la constante #NULL.

Así que la respuesta es:

Set reference myRef to

(Sin indicar notación alguna) lo cual creará una referencia vacía, pero sin errores.

 

Verificación de "Item reference"


Con frecuencia será necesario comprobar, si la notación contenida en una variable "Item reference" sigue siendo válida. En la mayoría de los casos bastará con utilizar el siguiente código:

If myRef
     OK message Comprobación de referencias {La referencia es correcta}
Else
     OK message Comprobación de referencias {La referencia no es correcta}
End If

La instrucción "If" no precisa la inclusión de cálculo alguno (excepto para el caso expuesto más abajo); "If isnull(myRef)" o "If not(myRef)" no siempre produce los resultados esperados. Si la cuestión es tratar el caso de que la referencia no es correcta, es mejor usar la condición "Else" y programar en ese punto lo que proceda.

En ocasiones, puede ser que lo que deseemos sea crear una referencia a una variable. En este caso, deberemos agregar la notación ".$ref' en el final del nombre de la variable y en el momento de su construcción. De modo que, el código para crear una referencia a una variable sería:

Set reference myRef to ivString.$ref

Pero en estos casos, se revela un pequeño problema:

En el caso de la consulta mostrada anteriormente "If myRef" no funcionará cuando se trata de referencias a variables, es decir, siempre deriva hacia la rama del "Else", aún cuando "myRef" esté apuntando hacia una variable totalmente correcta. De modo que para comprobar la validez de éste tipo de referencias, será necesario usar lo siguiente:

If myRef.$ident

Esto devolverá kTrue si la variable "ivString" existe realmente, incluso aunque contenga el valor #NULL. Por cierto, la consulta "If myRef.$ident" también funciona cuando se está haciendo referencia a una instancia.

Lamentablemente no es posible utilizar éste método de comprobación para ver si la referencia a un grupo de objetos se ha establecido correctamente. Por ejemplo, si se ha definido previamente myRef con:

Set reference myRef to $clib.$classes

En el caso de "If myRef.$ident" siempre derivará hacia la rama "Else". No obstante y para estos casos, la consulta "If myRef" funcionará sin ningún problema. Por lo tanto, si deseamos aunar ambas posibilidades (es decir referencias a variables y a otras notaciones) deberíamos realizar la consulta del siguiente modo:

If myRef | myRef.$ident

Sin embargo, normalmente conoceremos de antemano si la referencia en cuestión apunta hacia una instancia, hacia una variable o hacia un grupo.

10 septiembre 2014

Uso de las variables "Object Reference"

Un problema


A medida que nos hemos ido familiarizando con el uso de objetos y especialmente cuando los hacemos disponibles por medio de variables de instancia, empezamos a tropezar con situaciones en las que nos gustaría acceder a un objeto (variable) desde fuera de su ámbito normal. Por ejemplo, podríamos querer pasar un objeto que contiene datos importantes desde una instancia-ventana a una instancia-informe o incluso a otra instancia-ventana diferente. Pero si pasamos la variable de objeto como parámetro, obtendremos una copia nueva e independiente de ese mismo objeto y puesto que se trata de un parámetro, solo alcanzable desde un ambito local. Si posteriormente copiamos el objeto sobre una nueva variable de instancia, con el fin de hacerlo disponible en ese ambito, estaremos duplicando de nuevo el objeto original, al final podría resultar que terminásemos con un sin fin de copias del mismo objeto.

Naturalmente, cualquier cambio que posteriormente realicemos sobre estas copias no será reflejado en el objeto original. Podemos decir que las copias pasan a tener vida propia tan pronto como son creadas. Por supuesto podemos compartir un objeto común mediante (por ejemplo) el uso de una variable de clase, ya que estaría disponible para todas las instancias generadas a partir de esa misma clase, por ejemplo, entre las varias instancias de la misma clase-ventana. Pero en ese caso ya no tendríamos el objeto a nivel de instancia, sino que afectaría a todas las diferentes instancias de la clase.

Una solución


Las variables "Object Reference" nos permiten crear un puntero hacia un objeto, eliminando la necesidad de realizar una nueva copia del mismo. Esto resulta en un uso más eficiente de los recursos de memoria RAM en nuestra aplicación y al mismo tiempo dotamos de una mayor coherencia a las acciones requeridas para con ese Objeto. Como veremos, también disponemos de algunas "utilidades" con las que realizar un seguimiento de las referencias o punteros en uso.

Pero no podemos simplemente crear referencias para las variables-objeto que tengamos creadas. Debemos tener en cuenta, que las variables-objeto son en realidad contenedores de los objetos instanciados y las "Object Reference" generan punteros hacia dichas instancias por lo que sólo podemos usarlos con instancias de objetos que han sido generadas de un modo algo diferente al habitual. Puesto que no se ha acuñado término alguno para éste tipo de instancias, las llamaremos "referenciables" o también "instancias refrenciadas".

 

Creación de un "Object Reference"


Ya sabemos que podemos generar dinámicamente instancias de un objeto mediante su método $new(), el valor devuelto por éste, es la instancia propiamente dicha. Podemos hacer esto mediante el comando "Do" o mediante "Calculate", tal como se observa en el siguiente ejemplo:

Do $objects.demoObject.$new(5) Returns demoObj
Calculate demoObj as $objects.demoObject.$new(5)

El "5" en estos ejemplos es un valor enviado como parámetro al método $construct de la instancia y que (como sabemos) es ejecutado automáticamente en el momento de su creación.

Por supuesto ésta no es la única forma de generar una instancia, (por ejemplo) podríamos hacerlo mediante definir una variable objeto (en el panel de definición de variables) y asignarle un valor como sub-tipo desde una clase en nuestra librería u otras opciones similares, pero en éste artículo optamos por la creación dinámica de instancias, a fin de que pueda observarse con mayor facilidad los elementos que son objeto de discusión.

Esencialmente, las variables "Object Reference" son asignadas bajo el mismo criterio o modo que las variables "Object". De hecho, desde la versión 4.0, existe en Omnis Studio un método creado específicamente con este propósito. El método $newref(), su uso:

Do $objects.demoObject.$newref(5) Returns objref
Calculate objref as $objects.demoObject.$new(5)

Al ejecutarse, este método se crean ambas cosas, es decir, un objeto y una referencia, se crea una nueva instancia de ese objeto y un valor de referencia que apunta hacia esa misma instancia. La instancia es creada directamente a partir de la clase, Omnis añadirá un carácter de subrayado y un número entero al componer el nombre de la instancia con el fin de que sea único y al mismo tiempo guarda un puntero hacia esa misma instancia, en la variable de tipo "Object Reference" especificada.

Ambos tipos de variables (Object y Object Reference) son iguales desde el punto de vista operacional, pero con una sutil y gran diferencia...

Cuando hacemos uso de "Object reference" para crear instancias, el Objeto "referenciable" (término con el que hemos bautizado a las instancias referenciadas), es decir la instancia continuará existiendo aún después de que su puntero o variable "Object reference" haya sido destruida. De hecho, perdemos contacto con dicha instancia (excepto si es a través del grupo $inst) a menos que nos aseguremos de tenerlo a buen recaudo.

Paso de "Object References"


Si queremos pasar un valor "Object reference" como parámetro, tendremos simplemente que especificar el nombre de la variable de tipo "Object reference" (que contiene el valor) como parámetro durante la llamada al método, igual que lo haríamos con cualquier otro. La variable-parámetro en el método de recepción, también deberá ser de tipo "Object reference". En su ejecución, dicha variable-parámetro constituirá otro punto de acceso a la misma instancia.

Si lo deseamos, también podemos duplicar la referencia sobre otra variable mediante el comando "Calculate", el método "$assign()" o bien mediante asignar su valor de retorno, a continuación mostramos los tres posibles modos:

Calculate dataObjRef2 as dataObjRef1
Do dataObjRef1 Returns dataObjRef2
Do dataObjRef2.$assign(dataObjRef1)

Note que (en éste caso) no podemos hacer uso del comando "Set reference", ya que sólo funciona con variables de tipo "Item reference" y evidentemente las "Object reference" no son "Item reference". La transmisión de valores deberá hacerse entre variables del mismo tipo.

Dado que diferentes celdillas de una variable de tipo lista, también se comportan como variables en si mismas, podemos hacer uso de las mismas técnicas para la asignación de valores, así un "Object reference", puede ser guardado en una celdilla. Por ejemplo, si se definió "listVar" para incluir una columna de tipo "Object reference" con el nombre "dataObjRef2" y queremos copiar la referencia de "dataObjRef1" sobre la celda de la línea 3, usaríamos:

Calculate listVar.3.dataObjRef2 as dataObjRef1

Tanto "dataObjRef1", como la celda en la lista, apuntan ahora a la misma instancia.

Un mayor poder, conlleva una mayor responsabilidad


Si usamos esta herramienta, deberemos también asumir la responsabilidad de mantener aseados nuestros propios líos (La versión 6.1 de Omnis permitirá otorgar cierto automatismo a ésta tarea). En su uso habitual con una variable de tipo "Object", la instancia correspondiente es destruida automáticamente tras eliminarse su variable, lo cual ocurre cuando el elemento (método, instancia o tarea) que lo la contiene se cierra, en estos casos, Omnis Studio gestiona esta tarea de limpieza por nosotros.

Pero cuando usamos una variable "Object reference" y luego la destruimos, la instancia del objeto referenciado al que señalaba continúa existiendo. Sólo se destruye su referencia. La instancia del objeto sigue viva en algún lugar de la memoria RAM, ya que podrían existir otras variables de tipo "Object reference" que aún la necesiten. Nos corresponde a nosotros escribir el código necesario para destruir la instancia y mantener limpio el espacio RAM. El único momento en que la instancia de un objeto referenciado es destruido automáticamente, es cuando a su vez la tarea que lo contiene es destruida o bien al salir de Omnis Studio.

Entonces, ¿cómo eliminar las instancias "huérfanas"? Podremos hacerlo mediante el método $deleteref(). Este método nos permitirá eliminar la instancia del objeto al que apunta la variable "Object reference" indicada. Su ejecución causará la llamada del método $destruct de la instancia, como parte del proceso. Exactamente del mismo modo que sucede con cualquier otra instancia.

No podemos aplicar el método $deleteref() directamente sobre la instancia, pero si disponemos de un modo para localizar las instancias-referenciadas, se trata del método $listrefs(). (Se ha de tener en cuenta que mediante este método obtendremos todas instancias-referenciadas, sea que estén "huérfanas" o no) Podremos usarlo en cualquier lugar de nuestra aplicación. Se nos devolverá una lista de todas las instancias-referenciadas, según el nivel del árbol de notación en que lo apliquemos, si es $root, será a nivel global, es decir todas las existentes en nuestra aplicación.

La lista constará de una sola columna con los valores "Object reference", sobre las que se podrá aplicar el método $deleteref(), para hacerlo sobre todo el conjunto, usaríamos el método $sendall(). El Manual de programación Omnis en castellano, proporciona algunos ejemplos útiles sobre su uso.

Si lo que quiséramos fuera hacer una copia, disponemos del método $copyref(). En éste caso, el valor de retorno del método será un nuevo "Object reference", al igual que ocurría al aplicar el método $newref() y al igual que éste, $copyref() generará una nueva instancia del objeto referenciado, se trata (por tanto) de un duplicado exacto (a excepción de su nombre, por supuesto) de la instancia a la que apunta las variable "Object reference" original.

En el ejemplo siguiente usamos "dataObjRef1" para crear un duplicado de la instancia a la que hace referencia, guardando en "dataObjRef2" un puntero hacia la recién creada:

Calculate dataObjRef2 as dataObjRef1.$copyref()

También podemos utilizar el comando "Do", con o sin el método "$assign()", para realizar la misma tarea. Se ha de tener en cuenta que las instancias creadas son completamente independientes entre sí, pero estará bien, si eso es lo que queremos. El posterior uso de "$listrefs()" sobre la clase de objeto original, mostraría (al menos) dos instancias referenciadas en la lista.

Pero ¿qué pasa si por error destruimos una instancia, cuando resulta que aún existía una variable "Object reference" apuntando hacia ella? ¿Cómo podemos saber si el "Object reference" que deseamos utilizar sigue siendo válido? No nos alarmemos, el método "$validref()", acude en nuestra ayuda, nos permite probar si la referencia es aún utilizable. Este método devuelve un valor booleano, si es kTrue significará que la referencia es válida.

Aunque bien podría considerarse una exageración utilizar este método antes de realizar cualquier acción que implique el uso de una variable "Object reference", habrá ocasiones en las que sea razonable realizar una comprobación de este tipo. Considere (por ejemplo) el caso en que haya recibido la referencia desde una fuente externa, si es así, es muy posible que deseemos comprobar si aún sigue siendo válida, supongamos que hemos programado en una ventana la ejecución del método "$deleteref()" en su "$destruct", cuando es el caso que existen otras ventanas aún abiertas que requieren de su uso, ocurriría que la referencia pasada a esta segunda ventana no sería válida.

Llegados a éste punto, les propongo desde "Aula Omnis" un ejercicio sencillo para demostrar que una instancia referenciable puede continuar vinculada. Estos son los pasos:

  • En primer lugar, ejecutamos el método "$newref()" dentro de una tarea para generar una instancia referenciable y capturar su referencia en una variable de tipo "Object reference".
  • A continuación, pasamos el "Object reference" a otra tarea y la testamos ($validref) para asegurarnos de que nos estamos comunicando con la instancia original.
  • Luego destruimos la primera tarea, que a su vez destruirá la instancia referenciable que hemos generado desde dentro de ella.
  • Desde la tarea que nos queda, testemos de nuevo la variable "Object reference" con el método "$validref()". No será válida, lo que significa que la instancia original fue destruida.

Si lo desea también podría construir una lista de todas las instancias mediante "$listrefs()" antes y después de la destrucción de la tarea.

(basado de un articulo de David Swain)