Autor Téma: SQLite TFDSQLiteFunction - využitie globálnej premennej  (Přečteno 322 krát)

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Pre vysvetlenie som vytvoril projekt s funkciou runningTotal. Úlohu viem v pohode vyriešiť takto:
Kód: Delphi [Vybrat]
  1. var
  2.   globInt: Integer;
  3. ..
  4. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  5.          AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  6. begin
  7.   Inc( globInt, AInputs[ 0 ].AsInteger );
  8.   AOutput.AsInteger := globInt;
  9. end;
Lenže pri využití druhého volania tej istej funkcie v jednom SQL samozrejme dôjde k chybe, pretože glob. premenná je jedna a funkcie sú už dve. (viď obr.)
Ako by som naviazal globálnu premennú samostatne na každé volanie funkcie?
Je na to možné nejako použiť AUserData?
Ide o princíp. Potrebujem zložitejšiu štruktúru globálnych premenných, nejde o vyriešenie runningTotal. Ten je použitý iba pre jednoduché demo. Priložil som aj projekt, aby bolo vidieť, že pre jediné použitie funkcie, je všetko korektne nastavené.
Mimochodom volanie AfterExecute, BeforeExecute a Finalize pre FDSQLiteFunction zrejme nefunguje, resp pri volaní funkcie program cez tieto udalosti neprechádza.

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #1 kdy: 11-03-2018, 11:01:34 »
Byt bych rad pomohl, absolutne nemam tuseni o co jde. Nejspis jde o uzivatelsky definovane funkce FireDAC pro SQLite DBMS, ale co je spatne nejak nechapu. Nevim, mozna funkce chces rozlisit na zaklade AFunc.Func.Name, tezko rict...
« Poslední změna: 11-03-2018, 11:03:10 od Delfin »
I'm a soldier, so don't panic!

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #2 kdy: 11-03-2018, 11:21:55 »
Pozri prosím priložený obrázok v pôvodnom príspevku.
UDF ako taka je OK. Vyuzivam v nej vsak globalnu premennu. Co by bolo tiez OK, ak by sa funkcia v ramci SELECT pouzila len raz. Na obr. v druhej tabulke je vidiet ten vedlajsi a zly efekt. Pouzitie globalnej premennej nie je v druhom pripade korektne. Potreboval by som rozlisit prve a druhe volenie tej istej funkcie v jednom SELECT. Aby som mohol prislusne rozlisit glob. premennu.
Napr. glob[0] a glob[1] pre dve volania v jednom SELECT.
Lenze to by sa dalo iba na zaklade druheho parametra:
Kód: MySQL [Vybrat]
  1. SELECT runningTotal(val, 0), runningTotal(val, 1) FROM myTbl
Co je vsak dost neprijemne pre uzivatela, pretoze, musi strazit pocet volani tej istej funkcie v jednom SQL manualne.
Preco by som funkciu mal volat dvakrat?
Napriklad v pripade
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val) WHERE runningTotal(val) > 100
« Poslední změna: 11-03-2018, 11:29:54 od Miroslav Baláž »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2086
  • Karma: 108
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #3 kdy: 11-03-2018, 11:46:57 »
Co je vsak dost neprijemne pre uzivatela, pretoze, musi strazit pocet volani tej istej funkcie v jednom SQL manualne.
Se obavam, ze jine reseni, jak se zbavit globalni promenne, na urovni SQL dotazu neni.

Samozrejme resenim by bylo vypocitat runningTotal(val) jen jednou jako invariant a vysledek priradit do SQL promenne. Ale to uz predstavuje nejaky proceduralni postup tj. spusteni nejakeho anonymniho bloku prikazu, coz SQLite tusim nepodporuje nebo Stored procedure.
« Poslední změna: 11-03-2018, 11:49:58 od pf1957 »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2086
  • Karma: 108
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #4 kdy: 11-03-2018, 11:49:21 »
A nebo pouzit dve ruzne UDF: jednu EvalRunningTotal() a druhou FetchRunningTotal(). Ale stejne bys musel hlidat, abys nejdrive zavoval Eval... a pak teprve Fetch... kolikrat chces.

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #5 kdy: 11-03-2018, 12:07:53 »
Pre užívateľa by bolo asi najmenej mrzuté, keby musel zadať druhý parameter, asi takto:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, rowid) WHERE runningTotal(val, rowid) > 100
Rowid je prakticky automatika pre každú tabuľku SQLite. V onCalculate by som potom kontroloval počet volaní pre rovnaký rowId. Len v prvom riadku. No a potom aplikoval tento počet v cykle. Nepáči sa mi to síce, ale bez nejakého "hackovania" fireDAC to je asi jediná voľba.
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #6 kdy: 11-03-2018, 12:21:12 »
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)

Obrazek je mi jedno, popis co potrebujes a snad pomuzu ;)
I'm a soldier, so don't panic!

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #7 kdy: 11-03-2018, 12:38:32 »
Potrebujem využiť globálnu premennú pri tvorbe UDF Funkcií v SQLite.
Nedokážem zistiť koľkáty krát je mnou vytvorená funkcia volaná v rámci jedného SQL príkazu. Pretože pri jej jednom zavolaní je to jedno, ale pre dve a viac volaní tej istej funkcie to už jedno nie je.
Pre objasnenie som priložil hotový projekt a aj výstupy z neho, aby to bolo možné overiť.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2086
  • Karma: 108
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #8 kdy: 11-03-2018, 13:15:51 »
Potrebujem využiť globálnu premennú pri tvorbe UDF Funkcií v SQLite.
Nedokážem zistiť koľkáty krát je mnou vytvorená funkcia volaná v rámci jedného SQL príkazu. Pretože pri jej jednom zavolaní je to jedno, ale pre dve a viac volaní tej istej funkcie to už jedno nie je.
Zalezi, k cemu to presne ma byt, protoze muzes pouzit singleton a vypocitat soucet jen poprve a pak ho vracet, stejne jako muzes zajistit, aby kazde volani melo automaticky svoji kopii globalni promenne. Ale to nema nic spolecneho s obecnym resenim.

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #9 kdy: 11-03-2018, 13:28:05 »
Pre užívateľa by bolo asi najmenej mrzuté, keby musel zadať druhý parameter, asi takto:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, rowid) WHERE runningTotal(val, rowid) > 12
Rowid je prakticky automatika pre každú tabuľku SQLite. V onCalculate by som potom kontroloval počet volaní pre rovnaký rowId. Len v prvom riadku. No a potom aplikoval tento počet v cykle. Nepáči sa mi to síce, ale bez nejakého "hackovania" fireDAC to je asi jediná voľba.
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)
Takto to veru nejde. Vďaka WHERE sa nutne nespraví všetko v prvom riadku a tým pádom sa v prvom behu nedá zistiť počet volaní.
Takže zatiaľ jediné riešenie, ktoré funguje, je to závislé na pozornosti užívateľa:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, 0) WHERE runningTotal(val, 1) > 12
Nie je to moc pekné, ale funguje. Tu je princíp (kód v OnCalculate):
Kód: Delphi [Vybrat]
  1.   if AInputs[ 1 ].IsNull then
  2.     begin
  3.     if AInputs[ 0 ].IsNull = false then
  4.       Inc( globInt[ 0 ], AInputs[ 0 ].AsInteger );
  5.     AOutput.AsInteger := globInt[ 0 ];
  6.     end
  7.   else
  8.     begin
  9.     if AInputs[ 0 ].IsNull = false then
  10.       Inc( globInt[ AInputs[ 1 ].AsInteger ], AInputs[ 0 ].AsInteger );
  11.     AOutput.AsInteger := globInt[ AInputs[ 1 ].AsInteger ];
  12.     end;
Musím povedať, že UDF funkcie sú veľmi rýchle v prípade dát, kde jedna tabuľka má > 700 MB.
Skúšal som finty ako CTE table WITH RECURSIVE. Ale to je v SQLite zrejme len pre malé dáta. Tu ide o priepastné časové rozdiely.
Cca 4-8 x pomalší je aj self JOIN na predošlý, alebo nasledujúci riadok. A to aj pre rowId ako INT32 primary key, čo má byť v SQLite špeciálne optimalizované.
« Poslední změna: 11-03-2018, 13:56:58 od Miroslav Baláž »

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #10 kdy: 12-03-2018, 09:14:13 »
Excellent
Rated 2 times
Jo takhle. Uz Ti rozumim. Ty chces rozlisit instance jednotlivych volani v ramci jednoho SQL prikazu. To se da pres sqlite3_context. Tech bude tolik, kolikrat je v prikazu funkce volana. Tedy pro prikaz:

Kód: MySQL [Vybrat]
  1. SELECT MyFunction(Val), MyFunction(Val) FROM MyTable WHERE MyFunction(Val) > 0

Budou existovat 3 ruzne kontexty. Ve FireDAC je kontext predavan do property AOutput.Handle. Pak si muzes vyrobit treba dictionary a pocitat v nem. Napr.:

Kód: Delphi [Vybrat]
  1. type
  2.   TForm1 = class(TForm)
  3.   ...
  4.   private
  5.     FResults: TDictionary<psqlite3_context, Integer>;
  6.   end;
  7.  
  8. procedure TForm1.FormCreate(Sender: TObject);
  9. begin
  10.   FResults := TDictionary<psqlite3_context, Integer>.Create;
  11. end;
  12.  
  13. procedure TForm1.FormDestroy(Sender: TObject);
  14. begin
  15.   FResults.free;
  16. end;
  17.  
  18. procedure TForm1.FDQuery1BeforeOpen(DataSet: TDataSet);
  19. begin
  20.   FResults.Clear;
  21. end;
  22.  
  23. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  24.   AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  25. var
  26.   Value: Integer;
  27. begin
  28.   // pro kazdy kontext (kazde volani v ramci prikazu) existuje (nebo bude existovat) 1 zaznam v dictionary<kontext, hodnota sumy>
  29.   if not FResults.TryGetValue(AOutput.Handle, Value) then
  30.     Value := AInputs[0].AsInteger
  31.   else
  32.     Value := Value + AInputs[0].AsInteger;
  33.   FResults.AddOrSetValue(AOutput.Handle, Value);
  34.  
  35.   AOutput.AsInteger := Value;
  36. end;
« Poslední změna: 12-03-2018, 09:34:17 od Delfin »
I'm a soldier, so don't panic!

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #11 kdy: 12-03-2018, 10:32:26 »
..
Budou existovat 3 ruzne kontexty. Ve FireDAC je kontext predavan do property AOutput.Handle. Pak si muzes vyrobit treba dictionary a pocitat v nem. Napr.:
..
To vyzerá veľmi nádejne.. A nielen vyzerá, ale aj funguje :)
Vďaka aj za zdrojový kód v Tvojom príspevku.
« Poslední změna: 12-03-2018, 10:34:06 od Miroslav Baláž »

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #12 kdy: 12-03-2018, 10:54:06 »
A. Rovno sa spýtam aj na prípadné možné využitie parametra AUserData v FDSQLiteFunction1Calculate
Kód: Delphi [Vybrat]
  1. FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
Snáď AUserData slúži pre účely aggregate funkcií.
Nejak sa k tomu neviem dogoogliť. Veru ani ten AOutput s jeho Handle, popísaný v príspevku od Delfin, som nenašiel.

B. Ak by niekto vedel, kde je zdokumentované všetko okolo praktického využitia SQLite/FireDAC funkcií, veľmi by ma to potešilo. Docwiki mám prelezenú 100x, ale tieto veci tam nevidím.
« Poslední změna: 12-03-2018, 10:55:38 od Miroslav Baláž »

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #13 kdy: 12-03-2018, 11:04:34 »
A. Rovno sa spýtam aj na prípadné možné využitie parametra AUserData v FDSQLiteFunction1Calculate
Kód: Delphi [Vybrat]
  1. FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
Snáď AUserData slúži pre účely aggregate funkcií.
Nejak sa k tomu neviem dogoogliť. Veru ani ten AOutput s jeho Handle, popísaný v príspevku od Delfin, som nenašiel.

To asi nikdo ;D A pouziti parametru AUserData je zoufale. Misto aby vyrobili udalost napr. OnInitialize (kdyz existuje OnFinalize), priradit AUserData objekt muzes nejdrive v OnCalculate a jeste je spolecny pro vsechny kontexty (cili je spolecny pro instanci TFDSQLiteFunction). A co do nej ulozis je na Tve fantazii. Ale pouziti je zoufale, podivej (ta inicializace se mi nelibi a to ze neni mozne vytvorit samostatny objekt pro kazdy kontext je taky celkem slabe):

Kód: Delphi [Vybrat]
  1. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  2.   AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  3. begin
  4.   if not Assigned(AUserData) then
  5.     AUserData := TMyObject.Create; // neni per kontext, je spolecny pro vsechny kontexty
  6.  
  7.   if AUserData is TMyObject then
  8.     TMyObject(AUserData).DoSomething;
  9.   ...
  10. end;
  11.  
  12. procedure TForm1.FDSQLiteFunction1Finalize(AFunc: TSQLiteFunctionInstance;
  13.   var AUserData: TObject);
  14. begin
  15.   FreeAndNil(AUserData);
  16. end;
« Poslední změna: 12-03-2018, 11:13:20 od Delfin »
I'm a soldier, so don't panic!

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #14 kdy: 12-03-2018, 11:43:29 »
..
a jeste je spolecny pro vsechny kontexty (cili je spolecny pro instanci TFDSQLiteFunction).
..
Týmto parameter AUserData prakticky stráca na význame. Myslím, že Delfinom navrhnuté riešenie DictionaryAOutput.Handle, ktoré dovoľuje pracovať jednotlivo s kontextami ( rozlišujú sa viaceré volania jednej funkcie ), môže byť upravené tak, aby nahradilo všetky možnosti AUserData.
V praxi je len ťažko možné zabrániť, aby UDF funkcia nebola volaná v jednom príkaze viackrát.

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #15 kdy: 12-03-2018, 11:51:49 »
Hm, nejspis jsem se tady spletl a Delphi implementaci krivdim. sqlite3_user_data sice vyzaduje sqlite3_context, ale vracet bude pointer ktery byl predany sqlite3_create_function, takze to vypada na neschopnost SQLite definovat per kontext aplikacni data.
« Poslední změna: 12-03-2018, 11:53:47 od Delfin »
I'm a soldier, so don't panic!

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2086
  • Karma: 108
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #16 kdy: 12-03-2018, 13:49:00 »
B. Ak by niekto vedel, kde je zdokumentované všetko okolo praktického využitia SQLite/FireDAC funkcií, veľmi by ma to potešilo. Docwiki mám prelezenú 100x, ale tieto veci tam nevidím.
Takovehle zalezistosti se vzdycky hledaly ve zdrojovkach

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #17 kdy: 12-03-2018, 15:28:18 »
B. Ak by niekto vedel, kde je zdokumentované všetko okolo praktického využitia SQLite/FireDAC funkcií, veľmi by ma to potešilo. Docwiki mám prelezenú 100x, ale tieto veci tam nevidím.
Takovehle zalezistosti se vzdycky hledaly ve zdrojovkach
Áno, o to som sa tiež pokúšal, ale moc mi to nešlo. Rozhodne by sa zišli nejaké príklady.
AUserData som napríklad skúšal ako prvé, ale výsledok ma nepotešil. Ako vyššie píše Delfin, ten parameter sa moc nedá využiť.
Naopak skvelé riešenie je s využitím AOutput.Handle. Ale na to by som sám neprišiel.

Offline Miroslav Baláž

  • Plnoletý
  • ***
  • Příspěvků: 248
  • Karma: 8
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #18 kdy: 12-03-2018, 23:22:59 »
Kód: Delphi [Vybrat]
  1. ...
  2. procedure TForm1.FDSQLiteFunction1Finalize(AFunc: TSQLiteFunctionInstance; var AUserData: TObject);
  3. begin
  4.   FreeAndNil(AUserData);
  5. end;
Zdá sa, že FDSQLiteFunction1 svoju udalosť OnFinalize, ráči navštíviť iba v prípade, že je označená ako aggregate.
Alebo robím niekde chybu?
Použil som prakticky identický kód s tvojim príspevkom, ale finalize sa nevykonáva..

Offline Delfin

  • Guru
  • *****
  • Příspěvků: 862
  • Karma: 42
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #19 kdy: 13-03-2018, 06:50:19 »
Zdá sa, že FDSQLiteFunction1 svoju udalosť OnFinalize, ráči navštíviť iba v prípade, že je označená ako aggregate.
Alebo robím niekde chybu?
Použil som prakticky identický kód s tvojim príspevkom, ale finalize sa nevykonáva..

To je spravne chovani a spatny priklad z me strany. Ty AUserData objekty by se neuvolnily se skalarni funkci. Viz. Create Or Redefine SQL Functions:
Citace
The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are pointers to C-language functions that implement the SQL function or aggregate. A scalar SQL function requires an implementation of the xFunc callback only; NULL pointers must be passed as the xStep and xFinal parameters. An aggregate SQL function requires an implementation of xStep and xFinal and NULL pointer must be passed for xFunc. To delete an existing SQL function or aggregate, pass NULL pointers for all three function callbacks.

A protoze je udalost OnFinalize ve FireDAC povesena na xFinal callback, bude se spoustet jen pro agregatni funkce. On nejspis SQLite nenabizi jinou moznost. Ten mechanismus funguje tak, ze vytvoris UDF s definovanymi callbacky. A SQLite pak uz jen kompiluje SQL prikazy a pri prochazeni kurzorem vola ty callbacky. V pripade skalarnich funkci je k dispozici tedy jen xFunc callback, tj. ve FireDAC udalost OnCalculate.

Pro konstantni hodnoty parametru by bylo mozne definovat pomerne nestabilni cache objekty pomoci Function Auxiliary Data, to ale neni Tvuj pripad.

Omlouvam se za spatny priklad.
I'm a soldier, so don't panic!

 

S rychlou odpovědí můžete používat BB kódy a emotikony jako v běžném okně pro odpověď, ale daleko rychleji.

Jméno: E-mail:
Ověření:
Křestní jméno zpěváka Gotta: