Ver Mensaje Individual
  #34  
Antiguo 15-12-2008
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Reputación: 29
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Smile

¡Hola de nuevo!

Quisiera hacer algunas puntualizaciones.


Cita:
Empezado por Lepe Ver Mensaje
Ya sé que no es excusa, pero los creadores de MDO son portugueses, al menos el creador original.
Vamos, lo que dije al respecto fue:
Cita:
Empezado por Al González Ver Mensaje
El texto no parece tener una buena redacción, pero lo entiendo como "movimos esto a otro lugar, porque en este punto el componente no tiene nombre y por tanto el nombre del cursor sería solamente un número aleatorio que podría llegar a repetirse".
Dicho de otra forma: si bien no fue escrito por un editor de Borland creo que es suficientemente entendible como para darse cuenta de cuál fue el motivo del cambio. No pretendería deslustrar la redacción técnica, y menos tratándose de un idioma que no domino, de alguien que ha realizado y puesto a disposición del público un trabajo tan importante. En ese sentido comparto varios de los comentarios que hiciste anteriormente.


Cita:
Empezado por Lepe Ver Mensaje
Si los de borland no lo han solucionado... no voy a ser yo quien lo haga .
Entiendo el punto, y creo que aludes (corrígeme si no le atino) a esto mismo que Coso comentó después:
Cita:
Empezado por coso Ver Mensaje
El generador de numeros random de delphi es de pseudoaleatorios. Si se tiene algun randomize o randseed fijo en la aplicación, justo antes de crear un MDOQuery dinamicamente, se le repetira el numero en la misma iteración.
Hace muchos años que conozco este aspecto de la función Random. Es bastante factible generar la misma secuencia de números aleatorios si se conoce y reestablece el valor de la semilla. Pero eso no quiere decir que la función sea inservible para generar números aleatorios. que los genera si la usamos en combinación con Randomize, la cual inicializa la semilla en base a un contador interno del sistema.

Claro está, no debemos usarla en casos donde necesitemos generar números únicos, porque, como ya hemos dicho, aleatorio no es lo mismo que único. De ahí mi sugerencia de usar otro método, pero aclarando que, por lo mismo, se nota que un hubo un intento (que pudo ser exitoso en su época) de añadir el nombre del objeto en la biblioteca de la cual derivaron los IBX y MDO.

Cita:
Empezado por coso Ver Mensaje
No. El nombre se asigna desde el inspector de propiedades después del create. En el caso de asignarlo tu en el create, si se muestra.
Vamos, esto lo tengo bien claro. Sé que así es en todas o casi todas las versiones de Delphi. Mi duda surgió al ver el extraño uso de la propiedad Name dentro de un constructor en los IBX (uso que, como vimos, también tuvieron los MDO en algún momento).

Como dije anteriormente, no tiene sentido usar el valor de la propiedad Name si está vacía. Las interfaces IDesigner e IDesignerHook son "relativamente" recientes en Delphi, estoy seguro que no existían en las primeras versiones, y que ese método UniqueName de la segunda se implementaba de una forma más visible. Tengo la duda de si en aquel entonces, y sólo en tiempo de diseño, se daba el nombre predeterminado de los componentes desde el interior del constructor de TComponent, y no después de la construcción (algo en mi memoria trata de resurgir, pero lamentablemente no tengo Delphi 3, 4 o 5 conmigo para comprobarlo). Quizá ese método InsertComponent que vemos ahora ser llamado por el constructor, dentro de la unidad Classes.pas, era algo distinto. No puedo precisarlo sin una copia antigua de la VCL...

Pero si así fuera, eso le daría justificación histórica al uso de Name dentro del constructor, pero, repito, sólo cuando tales objetos IBSQL o MDOSQL fuesen agregados a un contenedor en tiempo de diseño. Y, de lo contrario, nunca hubiese tenido sentido y se trataría de un error desde el comienzo de esos componentes.

Cita:
Empezado por coso Ver Mensaje
No Al, es muy cutre dejar un error de este tipo en componentes que van a ser internacionales y lo peor, a sabiendas. Si ya saben que se puede repetir el nombre, aun en casos concretos, no es dificil resolverlo antes que salte la excepción (por ejemplo con el try ... except que le puse de ejemplo, y es bastante mejorable). No esta de mas, por ejemplo, revisar todos los cursores existentes antes de intentar asignar ese nombre. Incluso, generar un numero a partir de todas las cifras de la fecha y hora, pues este siempre sera diferente.
Cita:
Empezado por ChangeLog.txt de MDO
14-nov-2005 - Alter Prepare method from TMDOSQL to set FCursor name; Removed the code FCursor set name from the TMDOSQL.Create. This is needed because if you create the MDOSQL dynamically, the name is not assigned in the create and the cursor is only based on a 'random' number. And, in some special situation, duplicate cursornames will appear. (By Marco de Groot)
Estoy de acuerdo en que mover el código de lugar no soluciona del todo el problema. Según se mira en la documentación, lo hicieron en noviembre de 2005, pero no me queda claro con qué versión lo probaron, aunque en otro documento se sugiere su uso con Delphi 5, 6, 7 y 2005.

No sé qué tan culpables sean los autores por dejar un componente de software libre como está ahora y sugerirlo para versiones recientes, pero al menos yo veo un intento de mejorar el componente de parte de Marco De Groot. Y si bien este cambio ayuda poco en Delphi 7 (por las razones que comenté antes de los objetos MDOSQL internos), hubiera estado peor dejarlo como se encuentra ahora en los IBX.

Vamos, creo que llevas algo de razón en lo que dices, pero me pareció oportuno señalar la precipitación que hubo en uno de tus comentarios, porque parecía que dabas por hecho la mala calidad de un software sin bases sólidas, sólo por lo desprendido hasta ese momento en el hilo, que, dicho sea de paso, derivó en una calificación un tanto injusta del comentario puesto por De Groot.

Está claro que él u otra persona pudo implementar una mejor solución, como usar GUIDs o simplemente una variable global como contador, pero mover la sentencia al método Prepare fue mejor que dejarla como estaba, al menos para los componentes MDOSQL agregados en tiempo de diseño, no así con los que usan internamente los queries (a no ser que hubiera más cambios relacionados en los componentes durante estos últimos años, cosa que desconozco).

Cita:
Empezado por coso Ver Mensaje
Sobre el hecho de que solo ocurra en una maquina, y esta sea la del cliente, es lo que mas me intriga...
Por la misma inquietud, mi comentario anterior:
Cita:
Empezado por Al González Ver Mensaje
¿Será que él es el único que hace un uso extensivo de la aplicación?...¿cuántas consultas realiza el programa en una sola sesión de ese cliente? [de cualquier tabla]...¿no estará alguna parte del programa jugando con la variable RandSeed?

Cita:
Empezado por Sick boy Ver Mensaje
...me dices que name deberia tener un valor en componentes creados en tiempo de diseño, pero estoy casi seguro de que no es asi...veo que TMDOSQL no tienen por ninguna parte la propiedad Fname...
Creo que te ha faltado hacer dos cosas: Decirnos qué clases específicas de componentes MDO estás utilizando (intuyo que derivados de TMDOCustomDataSet), y observar la jerarquía (herencia) de los componentes.

TMDOSQL es un derivado directo de TComponent, por lo que hereda de éste su propiedad Name.
Código Delphi [-]
// En MDOSQL.pas
TMDOSQL = class (TComponent)
...
Los métodos de los que hemos estado hablando principalmente son de esa misma clase (TMDOSQL.Create, TMDOSQL.Prepare y TMDOSQL.ExecQuery). Es cuando agregas un componente TMDOSQL de la paleta de componentes cuando el diseñador de Delphi le dará un nombre predeterminado (y seguramente tú le darías alguno más apropiado con el inspector de objetos). Y, como expliqué anteriormente, parece ser que este es el único caso donde el cambio hecho por De Groot tiene un beneficio automático, debido a que este componente suele utilizarse más de manera interna, implícitamente, por parte de los componentes derivados de TMDOCustomDataSet: TMDODataSet, TMDOTable, TMDOQuery. Alguno de los cuales es el que seguramente estás utilizando.

No es la propiedad Name del conjunto de datos (data set) de la que se ha hablado, sino de la propiedad Name de uno de esos componentes internos TMDOSQL que hay dentro del conjunto de datos. Mira esto:

Código Delphi [-]
// En MDOCustomDataSet.pas
  TMDOCustomDataSet = class (TDataset)
  private
    ...
    FQSelect: TMDOSQL;
    ...
  protected
    ...
    property QSelect: TMDOSQL read FQSelect;
...

constructor TMDOCustomDataSet.Create(AOwner: TComponent);
begin
  ...
  FQSelect := TMDOSQL.Create(Self);
  FQSelect.OnSQLChanging := SQLChanging;
  FQSelect.GoToFirstRecordOnExecute := False;
...

procedure TMDOCustomDataSet.InternalExecQuery;
var
  DidActivate: Boolean;
  SetCursor: Boolean;
begin
  ...
    if not FInternalPrepared then
      InternalPrepare;
  ...
      FQSelect.ExecQuery;

procedure TMDOCustomDataSet.InternalPrepare;
var
  SetCursor: Boolean;
  DidActivate: Boolean;
begin
  ...
      if not FQSelect.Prepared then
      begin
        FQSelect.ParamCheck := ParamCheck;
        FQSelect.Prepare;

Cita:
Empezado por Sick boy Ver Mensaje
...no veo la forma de darle valor a Name, creo que esto mejoraria la situacion.
Si tu componente es un TMDODataSet, sería ejecutar algo como:
Código Delphi [-]
MDODataSet1.QSelect.Name := CadenaUnica;
Pero si es un TMDOQuery o TMDOTable, tendrás que usar el clásico truco de molde de tipo (type cast) para acceder a la propiedad QSelect, ya que en estas dos clases la propiedad permanece en ámbito protegido (no está redeclarada en la sección Public como en TMDODataSet):
Código Delphi [-]
Type
  TMDOQueryAccess = Class (TMDOQuery);
...
Begin
  TMDOQueryAccess (MDOQuery1).QSelect.Name := CadenaUnica;
Obviamente, esta acción debe ocurrir antes de que el método Prepare del objeto QSelect (TMDOSQL.Prepare) haga referencia a esa propiedad Name para formar el nombre del cursor.


Cita:
Empezado por Sick boy Ver Mensaje
Acabo de pasar el randomString(1), es decir, los cursores son un numero de un digito, name sigue siendo nulo. Como era de esperar la aplicacion arranca con problemas, pero ya puedo reproducir el error facilmente.
¡Estupendo! ¿Los síntomas son similares a los que ocurren con tu cliente?


Cita:
Empezado por Sick boy Ver Mensaje
...¿cuantos select/insert/... se realizan??...quizas 500 sentencias por hora. Algunas de las operaciones habituales requieren unos 6 cursores...
Esa es una tasa bastante significativa, le da peso a la hipótesis de los números aleatorios repetidos.


Cita:
Empezado por Sick boy Ver Mensaje
La sorpresa es que los select internos tipo "Select F.RDB$COMPUTED_BLR, F.RDB$DEFAULT_VALUE ....."...

Cuando comienzan a abrirse estos cursores, en pocas sentencias se produce el error...
Creo que estas consultas requerirán otra solución. Mira esto:
Código Delphi [-]
// En MDOCustomDataSet.pas
procedure TMDOCustomDataSet.InternalInitFieldDefs;
  
  const
    DefaultSQL = 'Select F.RDB$COMPUTED_BLR, '...

  var
    ...
    Query : TMDOSQL;
...
begin
  ...
  Query := TMDOSQL.Create(self);
  try
    Query.Database := DataBase;
    Query.Transaction := Database.InternalTransaction;
    ...
    Query.SQL.Text := DefaultSQL;
    Query.Prepare;
Como podrás notar, esos cursores están creándose sin prefijo (su nombre será solamente la cadena de ocho dígitos aleatorios), debido a que el objeto de la variable Query recién creado no tiene valor en su propiedad Name.


Cita:
Empezado por Sick boy Ver Mensaje
La gente de MDO no creo los componentes desde cero, asi que no se de quien es el merito...
Es bueno dejar claro eso. Existen varias ramificaciones del proyecto original, que me parece es Free IB Components, y al parecer mucha gente ha intervenido en esas diferentes ramificaciones. Siendo honestos, he notado tanto en IBX como en MDO cierta falta de actualización, pero no como para descartar del todo su uso en algún proyecto específico. Creo que es cuestión de reconocer las oportunidades y limitantes que ofrecen.


Cita:
Empezado por Sick boy Ver Mensaje
Como dice Al, mejoraron la solucion de los IBX, un punto a su favor...
Cierto, aunque recalco que la mejora no es muy grande que digamos, por las razones que ya expliqué. Bien recibida, eso sí.


Cita:
Empezado por Sick boy Ver Mensaje
A sugerencia de Coso, le añadi un timetostr(now) al nombre del cursor. Le añadi tambien un numero aleatorio de 1 digito y ya no aparece el error en mi equipo, el lunes lo probaré en el cliente, pero con la hora y 8 digitos aleatorios.

Esto deberia ser suficiente para que no se repitan.
Vale, ya lo se, cada 24 horas.... el tiempo comienza a repetirse, pero los 8 digitos aleatorios deberian ser suficientes.

He pensado en añadirele tambien el dia del mes, ¿que pensais vosotros??
Después de lo visto en el método TMDOCustomDataSet.InternalInitFieldDefs que señalé más arriba, creo que, finalmente, lo que te recomendaría es modificar la función RandomString para usar una variable global Integer a manera de contador, y que la función devuelva el siguiente número convertido a cadena en cada ocasión (un "Gen_ID" a nivel Delphi).

No veo solución práctica por el lado de derivar clases de componentes, ya que la obtención del famoso número aleatorio no está dentro de algún bloque de código que sea fácilmente redefinible.


Cita:
Empezado por Al González Ver Mensaje
¿no estará alguna parte del programa jugando con la variable RandSeed?
Cita:
Empezado por Sick boy Ver Mensaje
Si, tengo un randseed, y es lo que me anda provocando el problema, lo tengo bastante claro.

Tengo un timer que encripta y envia una información cada cierto tiempo.
Es algo opcional, parece que solo este cliente lo tiene activado.
Al encriptar utilizo una rutina que inicializa el randseed.
Cuando hicimos las pruebas, no esperamos el tiempo suficiente para que los cursores empezaran a repetirse, asi que todo parecia estar correctamente.
¡Estupendo, parece que sí era por ahí entonces!

Cita:
Empezado por Sick boy Ver Mensaje
Ya sabemos las "determinadas circunstancias" en las que los MDO daran problemas. Es mas, creo que los IBX tambien tendran el mismo problema.
Dalo por hecho, así lo muestra el código fuente.


Cita:
Empezado por Sick boy Ver Mensaje
La solucion pasa por eliminar los cursores aleatorios y convertirlos en unicos.
Lo mismo que vengo diciendo hace rato.

Cita:
Empezado por Sick boy Ver Mensaje
Lo ideal seria conseguir numeros unicos, quizas utilice GUI, si es que no se ve afectado por el randseed.
La función CreateGUID no usa la variable RandSeed, pertenece a una API de Windows. Y es, en términos prácticos, infalible. Aunque en este caso sería más sencillo emplear un simple contador global, como comenté anteriormente en este mismo hilo.


Cita:
Empezado por Lepe Ver Mensaje
Yo no gastaría más tiempo en el randseed, está claro que al ejecutarlo reinicializa la semilla y es cuando empiezan los problemas.
Bueno, Lepe, creo que los problemas ocurrirán incluso sin reinicializar la semilla. Es más seguro que Random arroje un número repetido, a que termine con todos los números no usados antes de repetir alguno, por lo mismo que ya se ha dicho: Random no es un generador de números únicos. Su propósito es otro. Entiendo que tú ya lo sabes, lo comento para dejarlo asentado en la discusión solamente.


Cita:
Empezado por Lepe Ver Mensaje
¿puedes crear un simple número Int64 e ir incrementándolo cada vez que se crea un nuevo cursor? Obviamente lo pasas a string y ya tienes números bastante grandes. Incluso puedes guardarlo en un archivo .ini y continuar con él hasta cierto número predeterminado...
Creo que bastaría con un entero de 32 bits Integer, ya que éste puede ir desde -2147483648 hasta 2147483647, lo que nos da 4294967296 valores (al igual que un Cardinal que empiece en 0).

Suponiendo que alguno de los clientes de Sick Boy (¿cuál es tu nombre?) mantuviera la conexión indefinidamente, realizando cinco mil consultas por hora (10 veces más del volúmen bajo que mencionó), se necesitarían más de 98 años sin interrupción alguna del sistema para agotar el contador. Claro está, si bajo esas mismas circunstancias alguna otra computadora ejecutara, digamos, cincuenta mil consultas por hora, habrían de requerirse solamente 9.8 años, y como es muy probable que el programador siga vivo para entonces, lo mejor sería preverlo.


Cita:
Empezado por Lepe Ver Mensaje
El problema según creo entender son esos cursores que acceden a las tablas de sistema ¿no?
Así es Lepe. Principalmente por operaciones como la que hace el método InternalInitFieldDefs, donde no hay oportunidad de nombrar uno mismo al cursor (a menos que modifiquemos el código fuente).


Cita:
Empezado por Lepe Ver Mensaje
Creo que no estoy entendiendo algo.... no puede ser tan fácil, a ver, yo he hecho lo siguiente:

- en mdosql.pas he añadido una nueva propiedad pública al TMDOSQL:
Código Delphi [-]
    property UniqueCursorName:string read FCursor write FCursor;

y ahora en el Prepare:
Código Delphi [-]
procedure TMDOSQL.Prepare;
var
  stmt_len: Integer;
  res_buffer: array[0..7] of Char;
  type_item: Char;
begin
//  if FCursor = '' then
//    FCursor := Name + RandomString(8);
  if FCursor = EmptyStr then
    raise Exception.Create('Not asigned UniqueCursorName property !');

En el caso de que se te olvide asignar esa propiedad, en ejecución obtendrás una excepción muy bonita. Además no creo que se te olvide nunca...

¿qué es lo que me estoy perdiendo?
El problema, y me parece que Sick Boy tuvo la misma confusión, es que él no está utilizando componentes TMDOSQL directamente. Seguramente utiliza algún conjunto de datos derivado de TMDOCustomDataSet (le pido nos aclare cuáles ), como los que mencioné anteriormente. TMDOSQL no es un data set (al menos no uno estándar), pero sí es un componente. Puede ser utilizado de manera "suelta" (agregándolo desde la paleta a un módulo de datos), pero su utilización más frecuente es invisible e implícita, dentro de esos otros componentes que sí son conjuntos de datos (TMDOQuery, TMDOTable, etc.), como de alguna manera traté de mostrar en comentarios anteriores.

Sobre esos componentes internos TMDOSQL tenemos menos control, especialmente con los creados temporalmente dentro métodos como InternalInitFieldDefs, InternalBatchOutput y DoOnNewRecord de la clase TMDOCustomDataSet (padre de TMDOQuery, TMDOTable y TMDODataSet).

Continúo...

Última edición por Al González fecha: 15-12-2008 a las 01:18:25.
Responder Con Cita