Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Firebird e Interbase (https://www.clubdelphi.com/foros/forumdisplay.php?f=19)
-   -   Mensaje de error extraño (https://www.clubdelphi.com/foros/showthread.php?t=62241)

Sick boy 12-12-2008 11:23:27

Mensaje de error extraño
 
Hola,

Hace 3 semanas que me estoy volviendo loco con un error en una base de datos Firebird 2.1. Uso Delphy 7 y MDO (Mercury Database Objects)

El error es:
Dynamic SQL ErrorSQL error code= -502 Invalid cursor declaration. Statement alreaady has a cursor XXXXXXX

Entiendo lo que dice el error, los componentes MDO me indican que se esta intentando redeclarar un cursor.

El cursor XXXXXXX esta creado hasta que cierras el programa, de modo que una vez que comienza el error, el numero XXXXXXX aparece varias veces en el error, aunque segun lo que hagas, pueden salir numeros diferentes. Lo que es seguro es que una vez da el error en un cursor, este no se libera, y continua dando errores.

Lo raro es que es:
1.- ALEATORIO. El error comienza a salir aproximadamente 45 minutos despues de comenzar a trabajar con la BBDD. No hay una sentencia en concreto que falla, puede fallar en cualquier sitio.
2.- Solo pasa en 1 base de datos, otras bases de datos iguales funcionan sin problemas.
3.- El programa que accede a la base de datos es el mismo, y llevaba 6 meses funcionando sin problemas.
4.- Si restauro la copia de seguridad en otro equipo, no consigo que aparezca el dichoso error.

El problema surgió del siguiente modo:
1.- Realicé un BackUp de la BBDD, version 2.0
2.- Desinstale Firebird 2.0 e instale 2.1 SuperServer
3.- Restauré la copia de seguridad.
4.- Arranque el programa e inicialmente estaba todo bien, ningun error en todo el proceso.
5.- Me conecté con el servidor via TCP para comprobar la conexion, y todo correcto.

Al dia siguiente, comenzó a salir este error.

He buscado y rebuscado, o no hay nada sobre el error, o son post muy antiguos.

¿Ha nadie le ha pasado????

He probado de todo (uno a uno y en este orden):
** backup y restore (ningun mensaje de error)
** Modifique el firebird.config, aumentando cached database pages a 8192
** gfix (todas las opciones que se ocurrieron, ningun error)
** Volcado de datos manual a una BBDD vacia y nueva (hice una aplicacion que lee los datos y los inserta mediante un query uno a uno, haciendo un commit cada X registros, luego ajusta los generadores y deja la BBDD clonada)

Como veis, no tengo ni idea de por donde atacar el problema

AYUDAAAAAAAAAAAAA, por favorrrrrrrrrrrrr

Quizas este equivocado, pero creo que el problema no esta en mi codigo, entiendo que de ser asi tendría problemas con todas las bases de datos, no solo con esta en concreto.

No se si via codigo hay alguna forma de "bordear" la excepcion, intentando que cuando salte el problema, los MDO utilicen el cursor existente, en lugar de intentar crearlo de nuevo.

Por otro lado, la numeracion de los cursores no es aleatoria????
Si es aleatoria, que mala suerte que mi programa intente declarar aleatoriamente siempre el mismo cursor, no se, es muy raro.

En algun sitio leí que era mejor cambiar los Tquery por TSQL, supongo que no hay mucha diferencia. Que pensais???

Por favor, ayuda, estoy desesperado.

coso 12-12-2008 11:56:05

Hola, te doy algunas pistas a ver si por alla lo solucionas.

- usa refresh despues de las operaciones con la base de datos, o bien TQuery.Autorefresh a true (o equivalente)
- TQuery.UpdateMode a upWhereChanged (o equivalente con tus componentes)

o bien cambia a ADO: con los TQuery usuales me empezaron a salir estos errores por no usar refresh ni updates. Cambie a ADO, y desaparecieron todos :D. Saludos.

Ñuño Martínez 12-12-2008 12:13:12

No repitas los mensajes :mad:

Sick boy 12-12-2008 12:30:23

gracias, ahora empiezo a probar lo que me comentas, aunque tardaré casi un dia en saber si funciona.

Cita:

usa refresh despues de las operaciones con la base de datos, o bien TQuery.Autorefresh a true (o equivalente)
Supongo que te refieres a despues de ejecutar una sentencia de INSERCION/ACTUALIZACION, o tambien de un simple SELECT????

Mas informacion sobre como trabajo:
** Para datos que sean actualizables utilizo TDataSet, en muy contadas ocasiones uso TUpdateSQL para actualizar grids con Tquerys
** ForcedRefresh esta a FALSE en Datasets y Querys. Es el valor por defecto, y asi me funcionaba sin problemas.

Cita:

o bien cambia a ADO: con los TQuery usuales me empezaron a salir estos errores por no usar refresh ni updates. Cambie a ADO, y desaparecieron todos .
Hombre, un poco drastico, pero recojo tu sugerencia y la tendré en mente.
¿Alguno más piensa que debo sustituir los MDO???


Es más facil que me equivoque yo al programar, supongo que no es un bug de Firebird, pero, ¿no podria ser un error del archivo de la base de datos?? ¿o un problema de motor mal instalado?? ¿o un conflicto con algo instalado en su windows??

Sick boy 12-12-2008 12:34:43

Cita:

No repitas los mensajes
Gracias por el consejo, no lo repeti por gusto, el señor moderador puede borrar el que sobra.

Resulta que despues de enviar el post, el navegador no me devolvio la pagina del foro, sino que me envió para descargar un archivo .php de vuestro servidor :eek::eek::eek:

Pensaba que no se habia enviado nada, asi que di para atras y volvi a enviar, esta vez si que me mostró lo esperado.

Cuando me di cuenta de que habia 2 post, intenté borrar uno, pero no tengo permisos.

Te pido disculpas si te ha molestado.

Si tienes alguna sugerencia más que añadir..... a ser posible sobre mi problema..... gracias

coso 12-12-2008 14:02:17

Cita:

...o tambien de un simple SELECT
si, tambien de un simple select. Ya te digo que al final lo cambie todo a ADO porque los problemas de recolocación del cursor saltaban cuando y como querian :confused::confused::confused: Debe haber una manera correcta de solucionarlos. Yo la desconozco :( aunque ya te digo que creo q va por el tema de update. Saludos.

Sick boy 12-12-2008 14:31:05

gracias coso

Cita:

si, tambien de un simple select.
uffff, vaya desastre.... alguno más puede confirmar esto????

Cita:

Ya te digo que al final lo cambie todo a ADO porque los problemas de recolocación del cursor saltaban cuando y como querian
ADO es igual de rapido??

Hay que cambiar muchas cosas para pasar de MDO a ADO?? encontraste problemas durante el cambio?? las transacciones se gestionan igual??

¿por que surgen solo en uno de los clientes?? ¿¿ por que a mi no me sale el problema al restaurar la copia de seguridad??

Estoy totalmente perdido :confused::confused:

Al González 12-12-2008 18:51:53

:) ¡Hola!

A raíz de este mensaje me entero que existe, al menos en Firebird e InterBase, una sentencia SQL llamada Declare Cursor (me pregunto si tendrá que ver con las recientemente mencionadas tablas temporales).

Según se desprende del mensaje de error, es como si se estuviera ejecutando dos veces un mismo Declare Cursor sin un Close entre uno y otro:

Código SQL [-]
Declare MiCursor Cursor For Select...
...
Close MiCursor  -- (como si esta sentencia SQL no fuese ejecutada)
...
Declare MiCursor Cursor For Select...

Algo que puedes hacer es buscar ese tipo de sentencias SQL en los fuentes de MDO, y seguirles la pista con el depurador. A ver cuándo se lanza el Declare y cuando se lanza el Close. No conozco los MDO, pero, considerando que son relativamente populares, quizá hagas algo mal desde tu programa, omitiéndose alguno de esos cierres de cursor.

Claro, esto mismo puede estar ocurriendo dentro de los disparadores y procedimientos almacenados, si es que dentro de ellos hay sentencias para creación, manejo y cierre de cursores.

En conclusión, es probable que se esté omitiendo uno de esos Close por algún descuido en el código de tu programa o en el código de los disparadores y SPs. La razón por la que esto ocurre tan azarosamente puede residir en alguna condición que, por no cumplirse, impida entrar al bloque de código o llamar a la rutina donde se ejecuta el cierre, o por algún error (excepción) previo no bien manejado que interrumpa la ejecución normal del programa.

Espero logres solucionarlo, apóyate en el depurador de oro de Delphi y échale un vistazo a las rutinas internas de la base de datos.

No dejes de comentarnos.

Al González. :)

Ñuño Martínez 12-12-2008 21:36:30

Cita:

Empezado por Sick boy (Mensaje 330655)
Gracias por el consejo, no lo repeti por gusto, el señor moderador puede borrar el que sobra.

Resulta que despues de enviar el post, el navegador no me devolvio la pagina del foro, sino que me envió para descargar un archivo .php de vuestro servidor :eek::eek::eek: (...)

Ah, bueno, si fue eso pues nada. Lamento la intromisión. Es que hay gente que repite los mensajes cuando se tarda en contestar.

Respecto al tema, poco más que nuestros compañeros puedo decir. En Delphi casi siempre he utilizado el método clásico del TDataTable. Sí utilicé ADO una temporada y lo encontré algo confuso, no sabría decirte. Yo creo que deberías probarlo para que puedas comprobar por ti mismo cuál te es más cómodo y cuál es más adecuado a tu proyecto.

Sick boy 12-12-2008 21:58:08

Hola AI, gracias por tu aporte.

Cita:

Algo que puedes hacer es buscar ese tipo de sentencias SQL en los fuentes de MDO, y seguirles la pista con el depurador. A ver cuándo se lanza el Declare y cuando se lanza el Close. No conozco los MDO, pero, considerando que son relativamente populares, quizá hagas algo mal desde tu programa, omitiéndose alguno de esos cierres de cursor.
Tengo localizados todos los puntos por donde pasa el programa cuando se produce el error.
Es totalmente aleatorio, pero si repaso, veo que las funciones que más se usan son las que más errores provocan.

Pongo el trazado por si veis mas que yo :eek::eek:
El numero (1463 TMDOTransaction.Call) indica la linea donde se produce el error. Ahora mismo no tengo a mano el contenido de esa linea, pero lo pondré mañana.
Código SQL [-]
exception class   : EMDOFirebirdError

exception message : Dynamic SQL ErrorSQL error code = -502Invalid cursor declaration
Statement already has a cursor 11249867 assigned.



main thread ($724):

005048cb programa.exe MDO               390 MDODataBaseError

00511d5f programa.exe MDODatabase      1463 TMDOTransaction.Call

0050bb4e programa.exe MDOSQL           1932 TMDOSQL.Call

0050bdcc programa.exe MDOSQL           2014 TMDOSQL.ExecQuery

00518061 programa.exe MDOCustomDataSet 2335 Add_Node

00518161 programa.exe MDOCustomDataSet 2359 Has_COMPUTED_BLR

005186f2 programa.exe MDOCustomDataSet 2531 TMDOCustomDataSet.InternalInitFieldDefs

005195c0 programa.exe MDOCustomDataSet 2890 TMDOCustomDataSet.InternalPrepare

0051c741 programa.exe MDOQuery          261 TMDOQuery.PrepareSQL

0051cef2 programa.exe MDOQuery          452 TMDOQuery.SetPrepared

0051c6f0 programa.exe MDOQuery          241 TMDOQuery.InternalOpen

00500021 programa.exe DB                    TDataSet.DoInternalOpen

005000da programa.exe DB                    TDataSet.OpenCursor

004fff95 programa.exe DB                    TDataSet.SetActive

004ffde0 programa.exe DB                    TDataSet.Open

008c2b63 programa.exe Unit3             860 TForm3.select_clientes

008c2960 programa.exe Unit3             816 TForm3.Timer1Timer



********************************************************************************



exception class   : EMDOFirebirdError

exception message : Dynamic SQL ErrorSQL error code = -502Invalid cursor declaration
Statement already has a cursor 19377446 assigned.



main thread ($724):

005048cb programa.exe MDO               390 MDODataBaseError

00511d5f programa.exe MDODatabase      1463 TMDOTransaction.Call

0050bb4e programa.exe MDOSQL           1932 TMDOSQL.Call

0050bdcc programa.exe MDOSQL           2014 TMDOSQL.ExecQuery

00518061 programa.exe MDOCustomDataSet 2335 Add_Node

00518161 programa.exe MDOCustomDataSet 2359 Has_COMPUTED_BLR

005186f2 programa.exe MDOCustomDataSet 2531 TMDOCustomDataSet.InternalInitFieldDefs

005195c0 programa.exe MDOCustomDataSet 2890 TMDOCustomDataSet.InternalPrepare

0051c741 programa.exe MDOQuery          261 TMDOQuery.PrepareSQL

0051cef2 programa.exe MDOQuery          452 TMDOQuery.SetPrepared

0051c6f0 programa.exe MDOQuery          241 TMDOQuery.InternalOpen

00500021 programa.exe DB                    TDataSet.DoInternalOpen

005000da programa.exe DB                    TDataSet.OpenCursor

004fff95 programa.exe DB                    TDataSet.SetActive

004ffde0 programa.exe DB                    TDataSet.Open

008f1386 programa.exe Unit2            7984 TForm2.buscar_cliente



********************************************************************************



exception class   : EMDOFirebirdError

exception message : Dynamic SQL ErrorSQL error code = -502Invalid cursor declaration
Statement already has a cursor 19377446 assigned.



main thread ($724):

005048cb programa.exe MDO               390 MDODataBaseError

00511d5f programa.exe MDODatabase      1463 TMDOTransaction.Call

0050bb4e programa.exe MDOSQL           1932 TMDOSQL.Call

0050bdcc programa.exe MDOSQL           2014 TMDOSQL.ExecQuery

00518e37 programa.exe MDOCustomDataSet 2684 TMDOCustomDataSet.InternalOpen

0051c713 programa.exe MDOQuery          245 TMDOQuery.InternalOpen

00500021 programa.exe DB                    TDataSet.DoInternalOpen

005000da programa.exe DB                    TDataSet.OpenCursor

004fff95 programa.exe DB                    TDataSet.SetActive

004ffde0 programa.exe DB                    TDataSet.Open

008e28d8 programa.exe Unit2            4950 TForm2.familias





********************************************************************************



exception class   : EMDOFirebirdError

exception message : Dynamic SQL ErrorSQL error code = -502Invalid cursor declaration
Statement already has a cursor 17264469 assigned.



main thread ($ffc):

005048cb programa.exe MDO               390 MDODataBaseError

00511d5f programa.exe MDODatabase      1463 TMDOTransaction.Call

0050bb4e programa.exe MDOSQL           1932 TMDOSQL.Call

0050bdcc programa.exe MDOSQL           2014 TMDOSQL.ExecQuery

00518e37 programa.exe MDOCustomDataSet 2684 TMDOCustomDataSet.InternalOpen

0051c713 programa.exe MDOQuery          245 TMDOQuery.InternalOpen

00500021 programa.exe DB                    TDataSet.DoInternalOpen

005000da programa.exe DB                    TDataSet.OpenCursor

004fff95 programa.exe DB                    TDataSet.SetActive

004ffde0 programa.exe DB                    TDataSet.Open

007c7fa4 programa.exe Unit14            199 TForm14.FormShow



********************************************************************************



exception class   : EMDOFirebirdError

exception message : Dynamic SQL ErrorSQL error code = -502Invalid cursor declaration
Statement already has a cursor 18422997 assigned.



main thread ($ffc):

005048cb programa.exe MDO               390 MDODataBaseError

00511d5f programa.exe MDODatabase      1463 TMDOTransaction.Call

0050bb4e programa.exe MDOSQL           1932 TMDOSQL.Call

0050bdcc programa.exe MDOSQL           2014 TMDOSQL.ExecQuery

00518061 programa.exe MDOCustomDataSet 2335 Add_Node

00518161 programa.exe MDOCustomDataSet 2359 Has_COMPUTED_BLR

005186f2 programa.exe MDOCustomDataSet 2531 TMDOCustomDataSet.InternalInitFieldDefs

005195c0 programa.exe MDOCustomDataSet 2890 TMDOCustomDataSet.InternalPrepare

0051c741 programa.exe MDOQuery          261 TMDOQuery.PrepareSQL

0051cef2 programa.exe MDOQuery          452 TMDOQuery.SetPrepared

0051c6f0 programa.exe MDOQuery          241 TMDOQuery.InternalOpen

00500021 programa.exe DB                    TDataSet.DoInternalOpen

005000da programa.exe DB                    TDataSet.OpenCursor

004fff95 programa.exe DB                    TDataSet.SetActive

004ffde0 programa.exe DB                    TDataSet.Open

008c210d programa.exe Unit3             689 TForm3.Edit16Change



*************************************************************************************

Tengo el codigo fuente de los MDO, con la linea a la que hace referencia. Si puede servir para algo lo posteo.

El problema al tratar de depurar, es que esos mismos procedimientos funcionan bien en otros equipos, incluso en el equipo ahora afectado llevaba 6 meses funcionando sin ningun problema.

Incluso con la base de datos que da este error, estos procedimientos funcionan bien la mayoria de las veces, pero en ocasiones (cada hora de trabajo o más) empieza a dar fallos.

Algunos de los forms del informe nunca se destruyen, otros se crean en ejecucion. Algunas de las querys que han dado error funcionan sin problemas desde hace años.

No es que no pueda haber un error en mi codigo, solo que es muy raro, y parece haberse propagado por todas partes.

Los MDO llevo 2 años usandolos sin problemas hasta ahora.

Lepe 13-12-2008 08:01:26

Ojalá me equivoque pero... que yo sepa MDO lleva más de 1 año o 2 sin actualizarse, así que podría tratarse de no ser compatible con Firebird 2.

En un programa de prueba, con Firebird 1.5 funcionaba de lujo, con Firebird 2, me empezaba a dar errores de la API del motor :(.

Yo elegí MDO porque estaban más avanzados que ZEOS en aquel momento, pero ahora ZEOS sigue actualizándose mientras que MDO se quedó estancado.

Lamento dar este punto de vista pesimista... pero en fin, es mi opinión.

Saludos y suerte

Sick boy 13-12-2008 11:07:11

Hola Lepe,

Si, es cierto que MDO lleva tiempo parado, bastante tiempo, y no parece que vaya a arrancar de nuevo.
Acabo de revisar su web, parece que yo empece a trabajar con ellos justo cuando dejaron de actualizarlos :(

Hasta ahora, me ha funcionado desde la 1.5 hasta la 2.1 sin ningun problema (bueno, salvo esto).

Tras documentarme un poco más sobre ADO (solo habia hecho algo hace años para acces) no creo que sea la opcion que escoja si cambio de componentes.

ZEOS son muy usados, y como dices, siguen actualizandose.
Si cambiase los MDO probablemente fuera por los ZEOS.

Una cosa de los ZEOS, por lo que he visto, no tienen componente para las transacciones, ni un dataset como el de MDO (aunque se puede suplir con un query y un updatequery).
Las transacciones las gestiona automatico??
Cambia mucho la forma de trabajo con ZEOS??

Voy a repasar mi codigo y estudiar un poco del codigo de los MDO y pongo lo que encuentre.

gracias por vuestra ayuda.

Sick boy 13-12-2008 13:53:35

Todo empieza aqui, este codigo se ejecuta sin problemas

Código:

procedure TMDOQuery.InternalOpen;
begin
  ActivateConnection();
  ActivateTransaction;
  QSelect.GenerateParamNames := FGenerateParamNames;
  SetPrepared(True);
  if DataSource <> nil then
    SetParamsFromCursor; 
  SetParams;
  inherited InternalOpen;
end;

Ahora intenta ejecutar el query
Código:

procedure TMDOCustomDataSet.InternalOpen;
var
  SetCursor: Boolean;

  function RecordDataLength(n: Integer): Long;
  begin
    result := SizeOf(TRecordData) + ((n - 1) * SizeOf(TFieldData));
  end;

begin
  SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
  if SetCursor then
    Screen.Cursor := crHourGlass;
  try
    ActivateConnection;
    ActivateTransaction;
    if FQSelect.SQL.Text = '' then
      MDOError(mdoeEmptyQuery, [nil]);
    if not FInternalPrepared then
      InternalPrepare;
  if (FQSelect.SQLType in [SQLSelect, SQLSelectForUpdate]) then
  begin
      if DefaultFields then
        CreateFields;
      BindFields(True);
      FCurrentRecord := -1;
      FQSelect.ExecQuery;

............. hay mas codigo, pero nos interesa hasta ExecQuery ........

Ahora intenta establecer el nombre del cursor con una llamada call
Código:

procedure TMDOSQL.ExecQuery;
var
  fetch_res: ISC_STATUS;
begin
  CheckClosed;
  if not Prepared then
    Prepare;
  CheckValidStatement;
  if (FBase.Transaction.AutoCommit) and
    not (FBase.Transaction.InTransaction) then
  begin
    FBase.Transaction.StartTransaction;
  end;

  try
    case FSQLType of
      SQLSelect, SQLSelectForUpdate: begin
        Call(isc_dsql_execute2(StatusVector,
                              TRHandle,
                              @FHandle,
                              Database.SQLDialect,
                              FSQLParams.AsXSQLDA,
                              nil), True);
AQUI->> Call(
          isc_dsql_set_cursor_name(StatusVector, @FHandle, PChar(FCursor), 0), True);
        FOpen := True;
        FBOF := True;
        FEOF := False;
        FRecordCount := 0;
        if FGoToFirstRecordOnExecute then
          Next;
      end;

Fcursor debe tener la informacion que buscamos, asi que hago una busqueda en el codigo y encuentro esto en el CONSTRUCTOR de TMDOSQL:
// 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
//FCursor := Name + RandomString(8);

buffff, atentos a esto: "And, in some special situation, duplicate cursornames will appear" !!!!!

Justo lo que me sucede ahora, ya que muchas querys las creo dinamicamente. Ya me gustaria saber cual es la "special situation" a la que se refieren....

Vale, ya tenemos localizado el problema, los creadores de MDO ya lo sabian, veamos como lo resolvieron....

Código:

procedure TMDOSQL.Prepare;
var
  stmt_len: Integer;
  res_buffer: array[0..7] of Char;
  type_item: Char;
begin
  if FCursor = '' then                        !! Parece que NO LO RESOLVIERON !!!
    FCursor := Name + RandomString(8);        !! Utilizan el randomstring(8) igual que en el codigo editado en el constructor !!
  CheckClosed;
  FBase.CheckDatabase;
  FBase.CheckTransaction;
  if FPrepared then
    exit;
  if (FSQL.Text = '') then
    MDOError(mdoeEmptyQuery, [nil]);
  if not ParamCheck then

........ continua ......

En mi caso, los cursores que dan errores solo lo forman 8 numeros, el "Name" parece no contener ningun valor.

Estas son las funciones que generan el nombre (numero) del cursor.
Código:

function RandomString(iLength: Integer): String;
begin
  result := '';
  while Length(result) < iLength do
    result := result + IntToStr(RandomInteger(0, High(Integer)));
  if Length(result) > iLength then
    result := Copy(result, 1, iLength);
end;

function RandomInteger(iLow, iHigh: Integer): Integer;
begin
  result := Trunc(Random(iHigh - iLow)) + iLow;
end;

Ahora mi duda es la siguiente, ¿Garantizan estas funciones numeros aleatorios para los cursores??

Si no son seguras, se pueden mejorar???

Por otro lado, si el problema no es por la generacion del numero de cursor, entonces es que un cursor se queda "pillado", pero no se como puede ser posible.

Los componentes MDO son los que se encargan de los cursores, creacion y destruccion, ¿de que modo podriamos dejar los cursores huerfanos en memoria provocando este error???

Espero que este post ayude a alguien que se encuentre en esta misma situacion, creo que he avanzado bastante en localizar el problema, un poco más y descubriremos que sucede realmente.

Sick boy 13-12-2008 13:57:01

Cita:

Una cosa de los ZEOS, por lo que he visto, no tienen componente para las transacciones, ni un dataset como el de MDO (aunque se puede suplir con un query y un updatequery).
Las transacciones las gestiona automatico??
Cambia mucho la forma de trabajo con ZEOS??
Me contesto a mi mismo.

Las transacciones de controlan con TZconnection, que es como un TDataBase + TTransaction.

Pero, y si quiero tener más de una transaccion?? Hay que asignar un TZConnection por cada transaccion??

Por lo que he leido, las transacciones se pueden gestionar automatica o manualmente.

coso 13-12-2008 14:04:20

Código:

// 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
  //FCursor := Name + RandomString(8);

que cosa mas cutre...y lo peor de todo, lo dejan a sabiendas...
yo de ti no perderia mas el tiempo con estos componentes. Ahora bien, si aun los quieres usar, puedes probar de, en el create del componente, asignar el nombre del cursor de una manera que a ti te convenga :ej, numero de componentes MDOSQL en la aplicacion o con el GetNamePath, asignando el nombre a cada MDOSQL que crees dinamicamente, o bien haciendo overload del create con un parametro de cursor.

Sick boy 13-12-2008 15:10:33

Efectivamente, es una chapuza, si al menos hubieran dicho cuales son las condiciones especiales se podria tratar de evitarlas.

Parece que tendre que cambiar de componentes, pero mientras tanto, quisiera resolver esto y ganar un poco de tiempo.

Afortunadamente, solo un cliente tiene esas condiciones especiales.

No me atrevo a meterle mano al codigo de MDO, pero esta claro que de alguna manera debo modificar el nombre del cursor para hacerlo unico, pero no se como hacerlo.

Cita:

ej, numero de componentes MDOSQL en la aplicacion o con el GetNamePath, asignando el nombre a cada MDOSQL que crees dinamicamente, o bien haciendo overload del create con un parametro de cursor.
Entiendo lo que me dices, el numero de componentes creados dinamicamente es muy variable, no se si sera buena idea.
GetNamePath parece mejor opcion.

Esto funcionaria??:
FCursor := self.getnamepath + RandomString(8);

No es que no quiera probarlo yo mismo, es que me es muy dificil reproducir el error, en mis equipos no pasa.

El overload, pues no tengo ni idea de como quedaria, en este punto necesitaria un ejemplo.

Lepe 13-12-2008 17:56:52

Yo me reservo el derecho de criticar.

Para mí, una gente que trata directamente con la API de un SGBBDD y que dan al programador una interfaz al estilo de Interbase, ya se merecen mi respeto, si además lo hacen Open Source, un aplauso para ellos. Y ya ni hablar del elegante diseño de clases, jerarquías, calidad del código, etc.

Existen componentes de pago (nada baratos) que sólo ver el código fuente se les debería caer la cara de vergüenza, al menos ese fallo, conceptualmente, no tiene mucha importancia (para Sick boy si lo tendrá porque le está amargando la existencia :p).

La generación de números aleatorios siempre han tenido ese fallo, pueden repetirse en "determinadas circunstancias". Dicho sea de paso, si la función random hiciera su trabajo bien, los MDO no fallarían :p.

Saludos

coso 13-12-2008 18:42:15

Cita:

Esto funcionaria??:
FCursor := self.getnamepath + RandomString(8);
si esta creado dinamicamente y no has asignado un nombre anteriormente, no, pues es mas o menos lo que ya estaban haciendo ellos. Yo haria algo asi: pasar FCursor a protected o a public o bien crear una propiedad con el numero de cursor que es, y hacer algo asi:

Código Delphi [-]
NCursor := 0;
for i := Application (o parent o owner).ComponentCount - 1 do
if (Application.Components[i] is TMDOQuery) 
then NCursor := Max((Application.Components[i] as TMDOQuery).NCursor + 1,NCursor);

FCursor := inttostr(NCursor);

tambien, ahora que lo pienso, puedes capturar la excepción alla mismo:

Código Delphi [-]

Libre := false;
while not Libre then 
try
FCursor := Name + RandomString(8);
Call(isc_dsql_set_cursor_name(StatusVector, @FHandle, PChar(FCursor), 0), True)
...
Libre := true;
except
end;

Lo que me sigue intrigando personalmente es que tansolo te pase en una unica tabla. Quiza deberias mirar mas profundamente las diferencias entre el diseño de la tabla que da error y el resto. Saludos.

coso 13-12-2008 18:51:47

El overload seria algo asi:

Código Delphi [-]
protected
constructor Create(AOwner : TComponent); overload;
constructor Create(AOwner : TComponent; ncursor : string); overload;
...

etc...
Tambien puedes darle un nombre por defecto al componente cuando se crea aunque seria un poco estirar el problema

Código Delphi [-]
...
if FName = '' then 
Name := 'MDOQuery_' + FormatFloat('00000000',random(99999999));

Sick boy 13-12-2008 20:37:53

Cita:

Empezado por Lepe (Mensaje 330776)
Yo me reservo el derecho de criticar.

Para mí, una gente que trata directamente con la API de un SGBBDD y que dan al programador una interfaz al estilo de Interbase, ya se merecen mi respeto, si además lo hacen Open Source, un aplauso para ellos. Y ya ni hablar del elegante diseño de clases, jerarquías, calidad del código, etc.
Saludos

Bueno, lo de chapuza lo decia por el problemon que estoy teniendo, los componentes me han funcionado muy bien hasta ahora.
Un poco chapuza dejar el codigo editado y sin corregir, al menos sabian que podia haber un problema.

He revisado el codigo de los IBX y tienen el mismo codigo, esta vez sin editar, y no veo que tengan otro mecanismo para solucionarlo.

Sobre los numeros aleatorios, ¿Cuales seran las "determinadas circunstancias"?
No es que pase solo con una base de datos, es que pasa solo en un PC, yo sigo sin poder reproducir el error en mis ordenadores.

Voy a probar una modificacion en la creacion de los numeros aleatorios, y quizas tratar de capturar la excepcion como sugiere coso.

El lunes podre probarlo en el cliente y os comento si funciona.

Por cierto, las tablas son iguales, no hay ninguna diferencia. Incluso rellene una base de datos nueva y vacia con los datos de la que daba problemas, y sigue igual.

coso 13-12-2008 21:46:49

...todo un misterio...¿no tendra algo que ver el nombre de la tabla?...bueno, ya nos diras. Saludos y suerte.

Al González 14-12-2008 09:11:55

¡Hola a todos!

Descargué la biblioteca de componentes Mercury Database Objects —MDO— (la misma versión que está usando Sick Boy, según me comentó), para echar un vistazo con mayor cercanía, específicamente sobre lo que se ha comentado de las unidades MDOSQL.pas y MDOUtils.pas (lo del nombre de cursor aleatorio, vaya), comparando diferencias con las unidades equivalentes nativas IBSQL.pas y IBUtils.pas de los IBX.


Cita:

Empezado por Sick boy (Mensaje 330758)
...intenta establecer el nombre del cursor con una llamada call
Código Delphi [-]
// En MDOSQL.pas

procedure TMDOSQL.ExecQuery;
var
  fetch_res: ISC_STATUS;
begin
  ...
        Call(
          isc_dsql_set_cursor_name(StatusVector, @FHandle, PChar(FCursor), 0),
          True);

FCursor debe tener la informacion que buscamos, asi que hago una busqueda en el codigo y encuentro esto en el CONSTRUCTOR de TMDOSQL:

Código Delphi [-]
// En MDOSQL.pas

constructor TMDOSQL.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ...
  // 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
  //FCursor := Name + RandomString(8);

buffff, atentos a esto: "And, in some special situation, duplicate cursornames will appear" !!!!!

Justo lo que me sucede ahora, ya que muchas querys las creo dinamicamente...

Es curioso esto que encontraste, por varias razones:

1. El nombre de un componente (propiedad Name) es una cadena vacía durante su construcción. A menos que el constructor realice, directa o indirectamente, una asignación de valor a la propiedad Name, no tiene sentido usarla como parte de una expresión dentro del constructor.

2. Los creadores de MDO movieron esa sentencia al método Prepare, como bien lo señalaste, pero en los IBX sí está habilitada en el constructor de TIBSQL, por lo que, en ese caso, el nombre del cursor siempre será solamente lo que devuelva la función RandomString (un número aleatorio de 8 dígitos). Un dato importante para los usuarios de IBX.

3. Esto me hace pensar, y trato de recordar si lo que voy a mencionar es cierto, que en versiones más antiguas de Delphi, al agregar un componente a un formulario o módulo de datos en tiempo de diseño, dicho componente obtenía su nombre predeterminado ("Label1", "IBSQL1") por medio del constructor Create de TComponent. Si alguien tiene Delphi 5 o anterior y algo de tiempo para comprobar ésto definiendo e instalando una clase de componente de prueba que muestre su nombre como última sentencia del constructor, se lo agradecería.

4. Los creadores de MDO movieron la sentencia al método Prepare, con el argumento de "if you create the MDOSQL dynamically, the name is not assigned in the create and the cursor is only based on a 'random' number". Es claro que se dieron cuenta de lo mismo que comenté anteriormente (desde X versión de Delphi, o quizá desde siempre, la propiedad Name no tiene valor durante la ejecución del constructor). Pero asumen, o dan por recomendado, que debe asignarse un valor a la propiedad Name del objeto antes de ejecutar la consulta para que el nombre del cursor quede más rico y sea menos probable que se repita, lo cual es sencillo si el componente es agregado en tiempo de diseño o lo instanciamos nosotros mismos y enseguida le damos valor a Name. Sin embargo, la clase común TMDOCustomDataSet, al igual que sucede en los IBX, crea varias instancias internas de estos objetos SQL sin darles valor a su propiedad Name (a no ser que lo haga en algún punto del código que no logro ver). Así pues, con el nombre del cursor (FCursor) generado en el constructor Create o en el método Prepare, éste siempre será un número de 8 dígitos, a menos que se trate de un componente TMDOSQL que nosotros mismos hayamos preparado.


Cita:

Empezado por Sick boy (Mensaje 330758)
...
Vale, ya tenemos localizado el problema, los creadores de MDO ya lo sabian, veamos como lo resolvieron....

Código Delphi [-]
// En MDOSQL.pas

procedure TMDOSQL.Prepare;
var
  stmt_len: Integer;
  res_buffer: array[0..7] of Char;
  type_item: Char;
begin
  if FCursor = '' then      // !! Parece que NO LO RESOLVIERON !!!
    FCursor := Name + RandomString(8);  // !! Utilizan el randomstring(8) igual que...

En mi caso, los cursores que dan errores solo lo forman 8 numeros, el "Name" parece no contener ningun valor.

Esto va acorde con lo que comenté en el punto #4, pero no demerita el cambio que hicieron. Cuando agregues tú mismo un componente TMDOSQL a un módulo de datos, a éste le servirá realmente ese cambio. Entiendo que en la práctica esto parece poco útil, porque principalmente se usan derivados de TMDOCustomDataSet que crean instancias internas y sin nombre de objetos TMDOSQL, pero acabo de encontrar algo al respecto que te agradará (lo menciono más abajo ;)).

Cita:

Empezado por Sick boy (Mensaje 330758)
...Estas son las funciones que generan el nombre (numero) del cursor:

Código Delphi [-]
// En MDOUtils.pas

function RandomString(iLength: Integer): String;
begin
  result := '';
  while Length(result) < iLength do
    result := result + IntToStr(RandomInteger(0, High(Integer)));
  if Length(result) > iLength then
    result := Copy(result, 1, iLength);
end;

function RandomInteger(iLow, iHigh: Integer): Integer;
begin
  result := Trunc(Random(iHigh - iLow)) + iLow;
end;

Ahora mi duda es la siguiente, ¿Garantizan estas funciones numeros aleatorios para los cursores??

Si no son seguras, se pueden mejorar???...

Son las mismas funciones en IBX. Sí garantizan números aleatorios, pero no únicos. Las probabilidades parecen ser bajas (1 en decenas de millones), pero, ahora que existen los GUIDs, podría ser preferible usar las funciones CreateGUID y GUIDToString de la unidad SysUtils.


Cita:

Empezado por Sick boy (Mensaje 330758)
...Por otro lado, si el problema no es por la generacion del numero de cursor, entonces es que un cursor se queda "pillado", pero no se como puede ser posible...

Eso necesitaría de otra línea de investigación. Pero dinos una cosa: en las máquinas donde falla, ¿cuántas veces por día u hora tu programa realiza una consulta Select sobre la base de datos? Una cifra estimada.

Cita:

Empezado por coso (Mensaje 330762)
Código:

  // 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
  //FCursor := Name + RandomString(8);

que cosa mas cutre...y lo peor de todo, lo dejan a sabiendas...
yo de ti no perderia mas el tiempo con estos componentes...

Con todo respeto, creo que fue algo precipitado ese comentario amigo Coso. Por lo que expliqué antes, me parece que fue un acierto del autor mover esa parte del código al método Prepare, que si bien no es una solución brillante, al menos hará más seguro al componente cuando lleve un nombre. 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".


Cita:

Empezado por Sick boy (Mensaje 330764)
...Ya me gustaria saber cual es la "special situation" a la que se refieren...si al menos hubieran dicho cuales son las condiciones especiales se podria tratar de evitarlas...

Creo que te sales un poco de órbita. Sólo está diciendo que por ser un simple número aleatorio (sin el nombre del objeto como prefijo) es mucho más probable que coincida con el nombre de otro cursor abierto. Entiendo lo de "special situation" como "en algunas pero muy poco frecuentes ocasiones", y reitero que no fue nada malo ese cambio en el código.


Cita:

Empezado por Sick boy (Mensaje 330764)
...Parece que tendre que cambiar de componentes...

Aun cuando ya no suelo usar este tipo de componentes especializados en un motor (tarde o temprano te enamoras de dbExpress con TClientDataSet :p), creo que cambiar de componentes sería algo aventurado en este momento.

Cita:

Empezado por Sick boy (Mensaje 330764)
...Afortunadamente, solo un cliente tiene esas condiciones especiales...

¿Será que él es el único que hace un uso extensivo de la aplicación? Vuelvo a mi pregunta de más arriba: ¿cuántas consultas realiza el programa en una sola sesión de ese cliente? Y, por casualidad, ¿no estará alguna parte del programa jugando con la variable RandSeed?


Cita:

Empezado por Sick boy (Mensaje 330764)
...No me atrevo a meterle mano al codigo de MDO, pero esta claro que de alguna manera debo modificar el nombre del cursor para hacerlo unico, pero no se como hacerlo...

Podrías modificar el código fuente de MDO, pero, para evitar conflictos de versiones cuando compartas o actualices tu código, sería más sencillo asignarle un valor a la propiedad QSelect.Name de tus componentes de consulta antes de que sea llamado el método Prepare, buscando, desde luego, que sean nombres únicos los que establezcas. Esto puedes hacerlo en el evento OnCreate del módulo de datos, o justo después de crearlos en tiempo de ejecución. Sólo toma en cuenta que la propiedad QSelect (que es el objeto TMDOSQL de la discordia), está protegida en algunas clases como TMDOQuery (al parecer no en TMDODataSet). Pero puedes usar el típico truco de molde de tipo para acceder a esa propiedad en caso de ser necesario.


Cita:

Empezado por Sick boy (Mensaje 330764)
...me es muy dificil reproducir el error, en mis equipos no pasa...

Te sugiero emplear la misma función, RandomString, pero con un valor de 1 en lugar de 8, para intentar reproducir las condiciones que generan la excepción.


Cita:

Empezado por Lepe (Mensaje 330776)
...La generación de números aleatorios siempre han tenido ese fallo, pueden repetirse en "determinadas circunstancias"...

Totalmente de acuerdo. Aunque el fallo es querer usar números aleatorios cuando lo que realmente se busca obtener son números únicos. Por ello mi sugerencia de usar GUIDs.


Cita:

Empezado por Lepe (Mensaje 330776)
...Dicho sea de paso, si la función random hiciera su trabajo bien, los MDO no fallarían :p...

Esto me llamó mucho la atención, Lepe. ¿Cómo debería hacer su trabajo la función Random?


Como punto y aparte, y a pesar del análisis anterior, yo no descarto que la causa del problema pueda ser otra cosa. Esperemos a ver qué nos trae Sick Boy...

Un abrazo sin nombre.

Al González. :)

P.D. Algo más: ¿alguien conoce el ámbito que tienen los nombres de cursores? ¿Es por conexión (programa), por máquina cliente, globales para todos los clientes...? Pienso que deberían ser por conexión/transacción, pero quisiera confirmarlo.

Lepe 14-12-2008 10:46:30

Cita:

Empezado por Al González (Mensaje 330807)
El texto no parece tener una buena redacción,

Ya sé que no es excusa, pero los creadores de MDO son portugueses, al menos el creador original.

Cita:

Empezado por Al González (Mensaje 330807)
Esto me llamó mucho la atención, Lepe. ¿Cómo debería hacer su trabajo la función Random?

Ya me metió en el fregao.... :D ... o mejor dicho, me lo busqué yo solito :D.
Si los de borland no lo han solucionado... no voy a ser yo quien lo haga :p.

No he tenido tiempo de mirar el código, amén de que no uso MDO ahora mismo. Por otra parte lo de GUID fue lo primero que pensé, pero dado que no sabía que tal rebuscado era el código (si estaba dentro de threads, secciones críticas o "cosas más raras aún") preferí ser cáuto y no decir nada. También es posible que los MDO se usarán, como bien dices, en versiones más antiguas de delphi donde no estuviese disponible el GUID.

Cita:

Empezado por Sick boy
Un poco chapuza dejar el codigo editado y sin corregir, al menos sabian que podia haber un problema.

Te pido disculpas anticipadas y espero no te lo tomes a mal Sick boy, pero echa una visual por ejemplo al código fuente de la JVCL y lo verás lleno de "todo's", preguntas del estilo "¿esto dará fallo?" y lo más gracioso:"not implemented yet" en propiedades que tienes en el inspector de objetos :mad: y frases aún peores. Lo que quiero que comprendas es que un componente estable no significa "finalizado", e incluso uno "finalizado" no quiere decir que funcionará por tiempo ilimitado, tarde o temprano saldrá una actualización de tal o cual .dll o incluso de hardware que echará por tierra el buen funcionamiento de todo componente.

Y las disculpas anticipadas, era por este comentario:
"Entonces, no usarás la JVCL ¿verdad? siguiendo tu línea ¡¡es descabellado usar la JVCL!! ¡¡ está plagado de todo's y not implemented yet!!".

En fin, no puedo aportar nada de valor al hilo y no quiero desvirtuar más de lo necesario, así que con el permiso de venia, me retiro.

... bueno... quizás sí... Sick Boy, si quieres un TMDOAutoDataset, es decir, un componente que se crea en ejecución y que dando la sql de selección genera automáticamente las demás sqls de inserción, borrado y actualización, dilo y subo el archivo.

Saludos

coso 14-12-2008 11:35:39

Cita:

Si alguien tiene Delphi 5 o anterior y algo de tiempo para comprobar ésto definiendo e instalando una clase de componente de prueba que muestre su nombre como última sentencia del constructor, se lo agradecería.
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.
Cita:

Con todo respeto, creo que fue algo precipitado ese comentario amigo Coso. Por lo que expliqué antes, me parece que fue un acierto del autor mover esa parte del código al método Prepare, que si bien no es una solución brillante, al menos hará más seguro al componente cuando lleve un nombre. 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".
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:

Sí garantizan números aleatorios, pero no únicos. Las probabilidades parecen ser bajas (1 en decenas de millones)
falso. 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.

Sobre el hecho de que solo ocurra en una maquina, y esta sea la del cliente, es lo que mas me intriga. Pense si el nombre de la tabla pueda tener algo que ver, o incluso la tabla de codigos

Sick boy 14-12-2008 12:28:24

Ole, no se por donde empezar.

Cita:

1. El nombre de un componente (propiedad Name) es una cadena vacía durante su construcción. A menos que el constructor realice, directa o indirectamente, una asignación de valor a la propiedad Name, no tiene sentido usarla como parte de una expresión dentro del constructor.
Ayer ya me di cuenta (le puse un showmessage al crear el cursor) de que la propiedad Fname no tiene ningun valor. Salvo que haya otro punto donde se creen los cursores, Fname es vacio tanto si creas el objeto en diseño o en ejecucion. Por lo tanto, todos los cursores son numeros de 8 digitos.

Efectivamente, el prefijo Fname ayudaria mucho a crear cursores unicos.

Cita:

Los creadores de MDO movieron la sentencia al método Prepare, con el argumento de "if you create the MDOSQL dynamically, the name is not assigned in the create and the cursor is only based on a 'random' number". Es claro que se dieron cuenta de lo mismo que comenté anteriormente (desde X versión de Delphi, o quizá desde siempre, la propiedad Name no tiene valor durante la ejecución del constructor). Pero asumen, o dan por recomendado, que debe asignarse un valor a la propiedad Name del objeto antes de ejecutar la consulta para que el nombre del cursor quede más rico y sea menos probable que se repita, lo cual es sencillo si el componente es agregado en tiempo de diseño o lo instanciamos nosotros mismos y enseguida le damos valor a Name. Sin embargo, la clase común TMDOCustomDataSet, al igual que sucede en los IBX, crea varias instancias internas de estos objetos SQL sin darles valor a su propiedad Name (a no ser que lo haga en algún punto del código que no logro ver). Así pues, con el nombre del cursor (FCursor) generado en el constructor Create o en el método Prepare, éste siempre será un número de 8 dígitos, a menos que se trate de un componente TMDOSQL que nosotros mismos hayamos preparado.
Bueno, 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, los cursores siempre son 8 digitos. Voy a ver si consigo darle valor a Name, creo que seria una buena solucion.

He seguido investigando en el codigo MDO, y veo que TMDOSQL no tienen por ninguna parte la propiedad Fname, mientras que la clase TMDOXSQLVar si que la tiene. Por lo que veo es la unica clase con esa propiedad.

Por ahora, no veo la forma de darle valor a Name, creo que esto mejoraria la situacion.

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.

Cita:

Cita:
Empezado por Sick boy Ver Mensaje
...Por otro lado, si el problema no es por la generacion del numero de cursor, entonces es que un cursor se queda "pillado", pero no se como puede ser posible...
Eso necesitaría de otra línea de investigación. Pero dinos una cosa: en las máquinas donde falla, ¿cuántas veces por día u hora tu programa realiza una consulta Select sobre la base de datos? Una cifra estimada.
La pregunta es ¿cuantas veces abre el mismo Select? o ¿cuantos select/insert/... se realizan??
En este caso, el cliente realiza un uso bajo de la aplicacion, hay clientes que le superan por mucho.
Aproximado, no se, quizas 500 sentencias por hora. Algunas de las operaciones habituales requieren unos 6 cursores.

Cita:

P.D. Algo más: ¿alguien conoce el ámbito que tienen los nombres de cursores? ¿Es por conexión (programa), por máquina cliente, globales para todos los clientes...? Pienso que deberían ser por conexión/transacción, pero quisiera confirmarlo.
No estoy seguro, pero ahora que tengo cursores de un digito, te puedo indicar que el cursor 1 puede habrirse 3 veces seguidas sin problemas, y quizas la cuarta de el error, asi que debe ser a nivel de transaccion, o me daria el error al salir la segunda vez.

Acabo de descubrir algo interesante.
Le he puesto transaction.name y sql.text al showmessage que me indicaba cuando un cursor nuevo es creado. Tambien he puesto un showmessage para ver los cursores que no son nuevos. Los cursores son de 1 digito.
Los cursores reutilizados funcionan bien.
Los cursores nuevos, nunca tienen nombre, y parece que el ambito es la transaccion, ya que si haces un comit, la proxima vez que se abre el cursor aparece como NUEVO y le asigna un nuevo numero sin problemas. Diferentes transacciones y mismos numeros hasta ahora todo bien.

La sorpresa es que los select internos tipo "Select F.RDB$COMPUTED_BLR, F.RDB$DEFAULT_VALUE ....." no llevan asignado un nombre de transaccion.
Cuando comienzan a abrirse estos cursores, en pocas sentencias se produce el error, por ejemplo, detecta que el cursor 1 ya esta declarado y salta la excepcion. Cada vez que vuelva a aparecer ese cursor 1 en la transaccion "sin nombre" saltará el error.
He puesto un timer que repite una sentencia SQL simple, el select mio se ejecuta bien en la transaccion que tiene asignada. Al ejecutar la sentencia interna que te devuelve los valores por defecto, campos calculados, .... el cursor asignado en los 10 ultimos select ha sido el 1 (poco aleatorio, pero bueno) y cada vez que sale el 1 da error en ese select, no en el mio.
Si aleatoriamente sale un numero distinto del 1 la sentencia funciona correctamente.

Si cierras la aplicacion y la vuelves a abrir comienza todo, por lo que creo que el ambito del cursor es a nivel de transaccion/conexion.

Sick boy 14-12-2008 12:46:21

Cita:

Te pido disculpas anticipadas y espero no te lo tomes a mal Sick boy, pero echa una visual por ejemplo al código fuente de la JVCL y lo verás lleno de "todo's", preguntas del estilo "¿esto dará fallo?" y lo más gracioso:"not implemented yet" en propiedades que tienes en el inspector de objetos y frases aún peores. Lo que quiero que comprendas es que un componente estable no significa "finalizado", e incluso uno "finalizado" no quiere decir que funcionará por tiempo ilimitado, tarde o temprano saldrá una actualización de tal o cual .dll o incluso de hardware que echará por tierra el buen funcionamiento de todo componente.

Y las disculpas anticipadas, era por este comentario:
"Entonces, no usarás la JVCL ¿verdad? siguiendo tu línea ¡¡es descabellado usar la JVCL!! ¡¡ está plagado de todo's y not implemented yet!!".

En fin, no puedo aportar nada de valor al hilo y no quiero desvirtuar más de lo necesario, así que con el permiso de venia, me retiro.

... bueno... quizás sí... Sick Boy, si quieres un TMDOAutoDataset, es decir, un componente que se crea en ejecución y que dando la sql de selección genera automáticamente las demás sqls de inserción, borrado y actualización, dilo y subo el archivo.
Creo que ya me disculpe, o no.... Bueno, no quiero empezar una polemica sobre lo bonitos o mejor comentados que estan los codigos. La gente de MDO no creo los componentes desde cero, asi que no se de quien es el merito.

Si, uso JVCL, y si, he visto las lindezas que tiene y he aprendido a vivir con ellas.
Que yo no digo que sean una mierda de componentes, es solo que esto me parece "una chapuza" por el problema concreto que estoy teniendo.
Ademas, que existan componentes peores o mejores no es excusa.

Como dice Al, mejoraron la solucion de los IBX, un punto a su favor, y si bien yo anteriormente calificaria a los componentes como "EXCELENTES", ahora entenderas que no pueden tener la misma nota, y no sabria que nota ponerles, quizas utilizarlos sea un poco arriesgado.

Cita:

falso. 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.
Si, tengo un randomize, luego repaso lo que sucede con él.

Cita:

Sobre el hecho de que solo ocurra en una maquina, y esta sea la del cliente, es lo que mas me intriga. Pense si el nombre de la tabla pueda tener algo que ver, o incluso la tabla de codigos
Esto es con diferencia lo más extraño que me ha pasado nunca, pero es asi, yo tampoco me lo creia hasta que lo vi con mis propios ojos.
Mismo nombre de tabla, misma tabla de codigos, todo igual.

Sick boy 14-12-2008 13:15:53

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??

PD: Dejar un espacio en blanco en el nombre del cursor lo trunca. Por ejemplo "cursor, portate bien" quedaria como "cursor,"

coso 14-12-2008 13:30:09

Sobre el 'name', creo que andas equivocado: después del create es cuando se le asignan los valores del inspector de propiedades al objeto, y seria muy raro que no se asignara nombre si haces, por ejemplo

Código Delphi [-]
var
  m : TMDOQuery;
begin
  m := TMDOQuery.Create(self);
  m.Name := 'test';
  ShowMessage(m.Name);
end;

si esto te muestra una cadena vacia, hay un problema gordo :confused:
Sobre lo del tiempo, usaria todas las cifras de fecha y hora : nunca son iguales

Código Delphi [-]
//fecha : 21/03/2003, hora : 13:09:23

s := Stringreplace(DateToStr(date) + TimeToStr(now),'/','',[rfReplaceAll]);
s := StringReplace(s,':','',[rfReplaceAll]);

s tendria que ser '21032003130923'

Cita:

Si se tiene algun randomize o randseed fijo en la aplicación...
corrijiendome, si se tiene un randseed. En teoria el randomize tendria que generarte cadenas nuevas

Sick boy 14-12-2008 13:59:06

OK Coso, me ha sido de mucha ayuda.

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.

Ya sabemos las "determinadas circunstancias" en las que los MDO daran problemas. Es mas, creo que los IBX tambien tendran el mismo problema.

La solucion pasa por eliminar los cursores aleatorios y convertirlos en unicos.

Lo de que la fecha y la hora no se repiten es relativo. Si el cliente modifica la fecha y hora del windows hacia atras comenzaria la pesadilla. Tambien con los cambios de horario de verano/invierno.

Lo ideal seria conseguir numeros unicos, quizas utilice GUI, si es que no se ve afectado por el randseed

Voy a hacer las pruebas activando la opcion que reinicia la semilla y os comento.

Lepe 14-12-2008 16:49:01

Yo no gastaría más tiempo en el randseed, está claro que al ejecutarlo reinicializa la semilla y es cuando empiezan los problemas.

¿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, después de eso lo reinicias:

Los GUID son creados por Microsoft y por ello no sabemos cómo están hechos, (al menos yo).

Ya puesto, puedes mezclar y el formato de fecha y hora, incluyendo milisegundos con un Int64:
Código Delphi [-]
//   Min int64 value = -9223372036854775808
//   Max int64 value = 9223372036854775807

miLong := (miLong  + 1 )
if miLong > high(int64) -1 then
  miLong := low(int64);

query.name = formatDatetime('yyyymmddhhnnsszzz', now) + inttostr(miLong);

El problema según creo entender son esos cursores que acceden a las tablas de sistema ¿no?

Suerte

Sick boy 14-12-2008 17:47:15

Lo que propone Lepe estaria bien, pero resulta que el name nunca llega hasta el codigo donde se crea el cursor.

He puesto un comprobante para que salte si al crear el cursor name<>'', y ninguno de los cursores recibe valor en name. Ni los puestos en tiempo de diseño, ni los creados en ejecucion (añadiendo query.name explicitamente), tanto querys como datasets. Creo que Al Gonzaléz me advirtió de que esto pasaria, aunque no se como lograr que funcione.

Yo pude reproducir el error con cursores de un solo digito, y las sentencias más afectadas eran las de tablas de sistema, ya que no se en que transacción estan, ni cada cuanto tiempo se abren y cierran.

Con las sentencias SQL que creo yo acostumbro a abrir la transaccion, ejecutar el SQL y cerrarla. Como los cursores creo que tienen ambito a nivel de transacion, no es facil que coincidan dos cursores abiertos con el mismo nombre.

Lepe 14-12-2008 22:38:36

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 UniqueRelationName: string read GetUniqueRelationName;
    property UniqueCursorName:string read FCursor write FCursor; <<<< esta es la linea añadida
  published
    property Database: TMDODatabase read GetDatabase write SetDatabase;

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 !');

FCursor es una variable protegida, ni propiedad ni nada, es una simple y mortar variable, pues la reuso y me quedo tan pancho.

- Guardo y cierro todo.
- Abro el paquete de los mdo que está en la carpeta "source" con nombre mdo_dX, pulso F12 para que aparezca la ventanita de compilación y pulso el botón compilar.
- Listo, Ahora ya tengo una propiedad en el TMDOSQL que se llama "UniqueCursorName:string" que desde mi programa principal puedo asignar su valor.

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?

PD: Si ahora me alguien con aquello de que esto es una chapuza más grande, pues que lo pinte de amarillito y con flores :p

Saludos

Sick boy 14-12-2008 23:55:19

Sin haberlo probado, y arriesgo de equivocarme, ¿cual seria el cursor de las sentencias que acceden a las tablas del sistema? Creo que estaria vacio.
Y en los Dataset que tienen las 4 sentencias de SELECT, INSERT, UPDATE y DELETE??

Y en los script, backup, restore??

El problema es que name llega en blanco al crear el cursor, si llegase el nombre del query (dataset, script, ...) ya lo podriamos usar como identificador, y si le añadimos la hora, o la randomstring o las dos cosas ya tendriamos un identificador unico para cualquier SQL que pase por cualquier MDO.

Yo no se como resolver lo del name, no tengo ni idea de porque no llega hasta el codigo donde se asigna al cursor.

De momento, he dejado la linea del cursor asi:
Código Delphi [-]
FCursor := Name + FormatDateTime('yyyymmddhhmmsszzz',now) + RandomString(8);
La rutina que ejecuta el randseed seguira puesta en el timer, reiniciandolo cada 5 minutos.

En cuanto lo pruebe os comento.

Al González 15-12-2008 00:32:12

¡Hola de nuevo!

Quisiera hacer algunas puntualizaciones.


Cita:

Empezado por Lepe (Mensaje 330808)
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 (Mensaje 330807)
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 (Mensaje 330808)
Si los de borland no lo han solucionado... no voy a ser yo quien lo haga :p.

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 (Mensaje 330813)
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 (Mensaje 330813)
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 (Mensaje 330813)
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 (Mensaje 330813)
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 (Mensaje 330807)
¿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 (Mensaje 330817)
...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 (Mensaje 330817)
...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 (Mensaje 330817)
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 (Mensaje 330817)
...¿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. :cool:


Cita:

Empezado por Sick boy (Mensaje 330817)
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 (Mensaje 330818)
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 (Mensaje 330818)
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 (Mensaje 330819)
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 (Mensaje 330807)
¿no estará alguna parte del programa jugando con la variable RandSeed?

Cita:

Empezado por Sick boy (Mensaje 330822)
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 (Mensaje 330822)
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 (Mensaje 330822)
La solucion pasa por eliminar los cursores aleatorios y convertirlos en unicos.

Lo mismo que vengo diciendo hace rato. ;)

Cita:

Empezado por Sick boy (Mensaje 330822)
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 (Mensaje 330846)
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 (Mensaje 330846)
¿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. :D


Cita:

Empezado por Lepe (Mensaje 330846)
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 (Mensaje 330875)
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...

Al González 15-12-2008 00:36:29

Cita:

Empezado por Sick boy (Mensaje 330880)
Y en los Dataset que tienen las 4 sentencias de SELECT, INSERT, UPDATE y DELETE??

Si te fijas, el campo (variable) FCursor solamente es utilizado cuando se trata de un Select:


Código Delphi [-]
// En MDOSQL.pas
procedure TMDOSQL.ExecQuery;
var
  fetch_res: ISC_STATUS;
begin
  ...
    case FSQLType of
      SQLSelect, SQLSelectForUpdate: begin
        Call(isc_dsql_execute2(StatusVector,
                              TRHandle,
                              @FHandle,
                              Database.SQLDialect,
                              FSQLParams.AsXSQLDA,
                              nil), True);
        Call(
          isc_dsql_set_cursor_name(StatusVector, @FHandle, PChar(FCursor), 0),
...


Cita:

Empezado por Sick boy (Mensaje 330880)
Y en los script, backup, restore??

Si son objetos TMDOSQL que hacen Selects, pasará lo mismo.


Cita:

Empezado por Sick boy (Mensaje 330880)
Yo no se como resolver lo del name, no tengo ni idea de porque no llega hasta el codigo donde se asigna al cursor.

Estoy seguro que con el cambio que te comenté de la función RandomString quedaría resuelto el problema de la repetición de nombres de cursor. Inténtalo, sólo es una variable a la que le vas sumando 1 en cada llamada. Y descarta usar el parámetro iLength.


Cita:

Empezado por Sick boy (Mensaje 330880)
La rutina que ejecuta el randseed seguira puesta en el timer, reiniciandolo cada 5 minutos.

¿Podrías mostrarnos esa rutina y decirnos cuál es su propósito?


Insisto en que todavía cabe la posibilidad de estar haciendo alguna otra cosa mal, pero esperemos llegar a una solución óptima.

Un abrazo puntualizado.

Al González. :)

Delphius 15-12-2008 01:23:10

Hola a todos,
Sobre como funcionan esos componentes no puedo opinar puesto que nunca los usé.
Si uso IBX, en D6, con Firebird 1.5 y no tengo problemas con ellos (hasta el momento).

Lo que me resulta extraño es lo que comentan sobre los números aleatorios, o mejor dicho pseudo-aleatorios.

El problema con Random es que se lo limita a 8 caracteres, y la verdad es que al limitarlo es muy probable que consigamos números repetidos. He aquí la explicación:

Internamente Random muy probablemente sea un generador lineal multiplicativo, al menos eso quisiera esperar (en la ayuda que acompaña a D6, no da información al respecto). Y si lo és, debería estar diseñado para responder del mejor modo a secuencias únicas. Al menos en teoría, se esperaría que los valores del generador sean los adecuados para garantizar que en toda la secuencia de M-1 números no exista un número repetido. Y este M es un valor bastante grande.

Si en verdad es un GLM, y desean garantizar que el número obtenido sea "único" debería tomarse todos los decimales que éste devuelve.

En realidad el generador devuelve números distribuidos uniformemente en el rango [0,1). Cuando uno llama a "RandomRange()" o a Random(Range) lo que se consigue es forzar el redondeo. Y por tanto se pierde esos valores únicos: los extensos decimales que hacen a cada número único e irrepetible en la secuencia.

Si buscan sobre generadores de números pseudoaleatorios llegarán a hilos en donde hablé al respecto.

De cualquier forma, si se quiere seguir dandole ideas de como obtener números únicos, y cuanto más grandes sean mejor, lo ideal es emplear ya sea int64 si se manejan con enteros o Extended si operan con los reales para que se puedan obtener números que no agotaríamos tan fácilmente.

Y si se desea perfeccionar, aún más, siguiendo la idea de los números aleatorios. Lo más óptimo es tener múltiples generadores concatenados. Un ejemplo es así:

numero = G1() + G2() + ... + Gn()
El programa Arenna hace uso de esta técnicas para generar números enormes.

Saludos,

Sick boy 15-12-2008 10:56:31

Hola,

Al, muchas gracias por las molestias que te has tomado y las extensas respuestas (por cierto, mi nombre es Ricardo).

Utilizo sobre todo TMDOQuery, muchos de ellos los creo y libero en tiempo de ejecucion. Tambien utilizo TMDODataset, aunque muchos menos.

Si no entiendo mal, lo de una variable que se incremente seria algo como

Código Delphi [-]
function valor_contador_cursor:string;
begin
  // CODIGO BY Lepe
  miLong := (miLong  + 1 )
  if miLong > high(int64) -1 then
    miLong := low(int64);
  result:=inttostr(miLong);
end;

Ahora una pregunta tonta, ¿donde declaro la variable miLong? En el private de TMDOSQL??

Y le pasamos el valor al cursor:
Código Delphi [-]
FCursor := Name + valor_contador_cursor;
Dejo Name porque no me molesta, y se puede utilizar sabiendo que el name corresponde a QSelect.

Cita:

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.
Es justo a lo que me referia, gracias, no lo he probado, pero con esto se podría pasar un prefijo al cursor.

La rutina que esta en el timer realiza una encriptacion o desencriptacion de un string.
Código Delphi [-]
//encriptar datos
function encriptar(aStr: String; aKey: Integer): String;
begin
   Result:='';
   RandSeed:=aKey;
   for aKey:=1 to Length(aStr) do
       Result:=Result+Chr(Byte(aStr[aKey]) xor random(256));
end;

//desencriptar datos
function desencriptar(aStr: String; aKey: Integer): String;
begin
   Result:='';
   RandSeed:=aKey;
   for aKey:=1 to Length(aStr) do
       Result:=Result+Chr(Byte(aStr[aKey]) xor random(256));
end;
aKey es la "clave" para obtener los resultados, asi que siempre es la misma, con lo que el generador de numeros aleatorios se reinicia siempre (por lo que he entendido) con la misma secuencia de numeros.

A riesgo de parecer paranoico, si despues de utilizar estas funciones ejecuto un por ejemplo RandSeed(entero_no_repetido), donde "entero_no_repetido" haga honor a su nombre y sea distinto en cada ocasion.

La verdad, creo que lo mejor puede ser la idea de que el cursor sea un "contador" y olvidarse para siempre del tema de los numeros aleatorios.
Tener un error aleatorio que se propaga por todos los procedimientos de tu programa es una pesadilla. Si os preocupais por vuestros clientes y vuestro codigo no se lo deseo a nadie.

coso 15-12-2008 11:23:32

yo abandono el hilo, creo que tienes herramientas suficientes y de sobras para solucionarlo facilmente. Tansolo estamos desgastando polemica y confusión insertada. saludos y suerte.

Sick boy 15-12-2008 11:52:13

Ok Coso, gracias por tu ayuda.

A falta de las pruebas, esto esta solucionado, acabo de poner lo que comentaba Al González y me ha parecido perfecto, elegante y simple.

Siento la polemica sobre si los componentes son buenos, malos, bien escritos, etc....

Ojo, si piensas usar MDO, IBX y posiblemente FreeIB, deberias saber como los componentes generan el famoso cursor. De lo contrario, te puede pasar como a mi, y ante la falta de documentacion sobre el dichoso error 502, empiezas a probar soluciones de todo tipo sin encontrar el motivo de los errores.

Al final, declare la variable en MDOUtils, como una variable global cualquiera, iniciandola con el valor minimo del tipo integer (si, decidi usar integer, me parece más que suficiente).
En mi caso, el programa que hago no funciona ininterrupidamente, y cada vez que se reinicia el programa el contador empezara en el valor minimo de integer, y la verdad es que dudo de que en un solo dia consiga alcanzar los numeros positivos.
Tengo clientes que hibernan el equipo (yo mismo lo hago), hay veces que el ventanucos aguanta hasta 30 dias sin fallar. Dudo de que en 30 ó 60 dias se acaben los valores de integer, en cualquier caso, si sucede se reinicia el contador.
Al reiniciar el contador se podrian repetir los cursores de nuevo?? Claro, si empiezas otra vez se pueden repetir. En mi caso, un cursor nunca estará tanto tiempo activo (Al hablaba de años), asi que para mi es seguro.

Sobre que se conecten otros equipos clientes y consuman mi contador, no creo que eso suceda.
Evidentemente Al sabe más que yo, pero creo que en esto te equivocas, ya que el programa cliente que accede utilizará su propio contador, independiente en cada cliente. Y esto no nos importa porque los cursores deben de ser unicos en el ambito de la transaccion
Si me equivoco en esto decirmelo.

Quiero dejar el programa funcionando unos dias para ver que pasa, no quiero sorpresas, y despues postearé el codigo que he modificado, por si le sirve a alguen.
Al menos ahora, no tengo que preocuparme de numeros aleatorios.

Lepe 15-12-2008 12:08:48

Si Al González confía en los GUID, para mí sobran las palabras. ¿Qué tal esto?:
Código Delphi [-]
procedure TForm1.FormCreate(Sender: TObject);
var
  guid:TGUID;
  strGuid:string;
begin
  CreateGUID(guid);
  try
    strGuid := GUIDToString(guid)
  except
    on EConvertError do
    begin
      strguid :=  FormatDateTime('yyyymmddhhmmsszzz',now) + RandomString(8);
    end;
  end;
  FCursor := strguid;
end;

Esto iría en el constructor del TMDOSQL. Puesto que lo usan internamente para las tablas de sistema, ya estaríamos usando el famoso GUID (salvo error en mi código).

Vaya, veo que Sick Boy ha implementado la solución, bueno, si falla, aquí tienes otra posibilidad.

Edito: Al final esto no me gusta. La ayuda de delphi dice que puede saltar el EConverterror al traducirlo a string y en ese caso estaríamos de vuelta con el RandomString. Muy posiblemente la solución del Integer sea la mejor.


Saludos


La franja horaria es GMT +2. Ahora son las 15:12:52.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi