"Databázově typový" programovací jazyk rubrika: Programování: Jiné

8 rmaslo
položil/-a 16.3.2019
 
upravil/-a 17.3.2019

Zdravím,

nedávno tu byla diskuze o různých programovacích jazycích (řešila se hlavně typovost a takové věci), přišlo mi, že dost lidí tu má zajímavé zkušenosti proto bych se chtěl zeptat na jednu věc. Nezná někdo jazyk, který by si typy přejímal přímo z db (dle položených dotazů) a pracoval s nimi?

Popíšu přesněji co bych si pod tím představuji.

Mějme třeba nějakou tabulku definovanou třeba takto:

CREATE TABLE firmy (
id bigint(20) NOT NULL AUTO_INCREMENT,
nazev varchar(50) NOT NULL,
ico varchar(8) NOT NULL,
dic varchar(10) DEFAULT NULL)

Na tuto tabulku v tomto jazyce položím dotaz

$data = RunSQL("SELECT * FROM firmy WHERE id = $promenna");

a pak udělám třeba:

if ($data.dic) then $data.dic = 'CZx' + $data.ico //preklep misto 'CZ' jsem napsal 'CZx'

a tady očekávám, že mi kompilátor zařve něco ve smyslu:
Do proměnné dic která je varchar(10) chceš uložit ico což je varchar(8) a 'CZx' což je varchar(3). To se tam nevejde.

Takže od jazyka a jeho kompilátoru očekávám:

  1. Jazyk má nějaké "podivné" typy s parametrem délka.
  2. Kompilátor umí rozparsovat ten SQL a porozumí mu. (SQL je vždy pohromadě tj. neskládá se a v nějakých speciálních uvozovkách)
  3. Kompilátor si umí šáhnout si do information_schema a zjistit db typy a jejich délky.
  4. Kompilátor zjistí, že 'CZx' + $data.ico je vlastně varchar(3) + varchar(8) což je varchar(11) a to se do varchar(10) nevejde.
    PS: To, že $promenna ošetří na SQL injection považuji za samozřejmé...

A s tou "délkou typu" bych chtěl pracovat i ve smyslu

echo '<INPUT value='+SafeHTML($data.nazev)+' maxlegth='+TypeOf($data.nazev)->length+'>';

PS: Píšu to v takovém pseudokódu podobnému PHP, snad je to srozumitelné "+" je spojení řetězců, "." je index pole, '....' je string, "...." je SQL string, "$" začíná proměnná, "->" je volání metody.
PS2: Ještě lepší než jazyk (a kompilátor) by byl transpiler do PHP :-).
Ale samozřejmě budu rád za každý odkaz mířící tímto směrem, stejně si myslím, že nakonec si ho budu muset vytvořit sám a každá inspirace dobrá.

Komentáře

  • pavel.stehule : Přesně tohle umí nativní jazyky uložených procedur 17.3.2019
  • rmaslo : @pavel.stehule To moc dobře vím :-), proto jsem tam dal i ten druhý příklad s tím echem, aby bylo jasné, že to potřebuji i "vně" db, protože ze SP budu těžko vytvářet HTML výstup. Jinak souhlas, mnoho věcí z db je mi inspirací. 17.3.2019
odkaz
8 rmaslo
odpověděl/-a 2.4.2019
 
upravil/-a 2.4.2019

Protože se tu objevily hlasy, že to povyšuje dynamický jazyk na "umělou inteligenci" tak nejprve popíšu co teď mám a co tedy bez problémů lze.

Jak mi to dnes funguje:
Do databáze položím libovolný SQL dotaz. Z jeho metaintormací (získaných z mysqli_fetch_fields) zjistím pro jednotlivá pole OrgTable a OrgName a pokud jsou, tak si z Inf.Sch. k OrgTable zjistím PK a zjistím jestli jsou hodnoty tohoto PK také ve výstupním dotazu. Pokud tam PK je, tak vím, že znám adresu (Tabulka, PK, HodnotaPK, Field) daného políčka v db a můžu ho vykreslit jako editovatelné.
Pokud OrgTable a OrgField není či není PK, tak editovat logicky nepůjde a pro zobrazení se řídím jen údaji z metainformací, hlavně z type, decimals a flag.
Samozřejmě v zobrazovacích gridech (např. Browse, Edit, či v na šablonách založených zobrazeních, atd...) můžu stanovit další upřesnění (třeba zakázat editaci i když by to v principu šlo, udělat fitry, řazení - co se pak předřadí před to SQL, atd...)

Dále se budu zabývat editovatelným polem:
Z Inf.Sch. zjistím typ (string, číslo, datum, boolean, text) a podle toho zvolím typ prvku (input type ceckbox, date, atd...) zjistím jak formátovat, nastavím maxlenght, required (z NotNull), atd... To, že to má být případně combo (ve smyslu HTML SELECT) zjistím z relací. První (tj. skrytý datový) sloupec komba je to kam ta relace vede a bývá to PK toho číselníku, druhý (zobrazovaný) sloupec komba poznám podle speciálního jména indexu.
Do jména HTML inputu (či selectu) uložím jeho adresu (takže má název třeba: firmy-id-8-ico).

Po tom co uživatel zmáčkne např. "Save" tak z jednotlivých přišlých inputů, zjistím ty změněné, vyseparuji z nich adresu, odformátuji je a validuji (dle údajů z Inf.Sch.). Pak z nich složím záznamy (tj. stejná tabulka a hodnotaPK) pro aktualizace, zkontroluji jestli má uživatel práva pro zápis (ve smyslu Insert, Update, Delete). To samozřejmě předpokládá nějaké tabulky s uživateli, jich skupinami a právy těch skupin.

Před vlastní aktualizací ještě na ty data zavolám (existuje-li) funkci $Tabulka.'_BeforeUpdate'. Nezruší-li $Tabulka.'_BeforeUpdate' aktualizaci tak složím SQL dotaz a jeho spuštěním aktualizuji data. Po něm volám (existuje-li) funkci $Tabulka.'_AfterUpdate'. V těch _Before/_After + Update/Insert/Delete je aplikační logika (teoreticky by ani nemusely být pokud bych místo nich používal trigery v DB).
Dynamicky to funguje velice dobře, používám to asi 10 let a mám na tom založených několik informačních systémů v různých podnicích. Ale jsem schopen jen dynamických (tj. za běhu programu) kontrol. Např. kontroluji jestli nepřednastavuji vlastnosti nějakého políčka, které z dotazu nepřišlo - to je typický překlep.


Nyní jak bych k tomu chtěl přidat nějaké "statické kontroly".
K čemu jsem zatím došel:
Je potřeba si uvědomit, že celá ta "umělá inteligence" podle které se to zobrazení dat chová, je dnes závislá na metainformacích z mysqli_fetch_fields, které v stádiu překladu nezískám (pouštění nějakých "fake select dotazů LIMIT 0" při kompilaci jsem zavrhl).
Ok, při kompilaci si "musím rozebrat dotaz" a vědět výstupní typy jak jeho polí (z Inf.Schema), tak použitých SQL funkcí. Tj. chci zjistit zhruba to, co bych získal z metainformací.

První co si člověk musí uvědomit je to, že pokud používá dotazy typu "SELECT * FROM $_GET['Table']" tak prostě staticky žádné informace získat nejdou. Tedy musím se rozhodnout, že buď takovéto dotazy nesmím používat nebo se naopak ta kontrola musí nechat vypnout. Tohle je rozhodující věc ohledně "tvrdosti" této typové kontroly.
Ono, když to člověk zkoumá podrobněji, tak zjistí, že výstupní typy dotazu určuje to co je v SELECT a ve FROM a to musí být staticky napsáno v kódu. Naopak WHERE, ORDER, HAVING, atd vliv nemá a může se měnit zcela libovolně a být závislé třeba na URL. To jestli je dotaz zapsán pomocí SQL nebo pomocí nějakých Query Builderů je z hlediska co lze zjistit zcela irelevantní.
Já osobně se rozhoduji pro "měkčí", "vypínatelné kontroly" protože součástí mého systému je v podstatě i jednoduchý Adminer a protože občas opravdu používám stejný formulář pro různé tabulky (např. číselníky). Ale samozřejmě každému může vyhovovat něco jiného.

Další věc ke které jsem zatím došel je to, že umět rozebrat "jakýkoliv" SQL dotaz je nekonečná práce (db jazyky se vylepšují a dialektů je spousta). Proto se naopak rozhoduji, že použiji nějaký "jednodušší, svůj SQL" (inspirace LINQ je jasná), který budu překládat. Protože dotazy, které já budu z klienta (View) pokládat jsou vlastně takové, kterými se klient ptá aplikačního serveru na data a ty zas tak složité nejsou, protože to je odraz nějakých filtrů, řazení, agregací, stránkování a dalších klientských požadavků. Ty opravdu "složité dotazy" počítající třeba ziskovost zakázek, (kde se využijí i specifické rysy serverového SQL, proměnné, atd...) patří dovnitř aplikačního serveru a mohu je tam mít uloženy třeba jako db view (a tím vidět jejich výstupní typy v INFORMATION_SCHEMA.COLUMNS)


Takže ještě z hlediska MVC:

  1. Čerpání dat z modelu: Tabulky budou vystavené, ptám se do nich přes "mé jednoduché SQL", složitější logika modelu bude v uložených SQL, která se budou ukládat do DB View, která také vystavím. Případná "ne db data" budu taky mapovat na SQL, třeba v podobě temp. tabulek, jejichž struktura bude předepsána jejich ne-temp variantama.
  2. Zpracování dat ve View. Všechna mi tam polezou přes "mé jednoduché SQL", kterým (pokud jsou staticky zapsaná) rozumím i v době překladu a jsem z nich schopen určit typy a udělat i statické kontroly. Primární data "mé jednoduché SQL" čerpá z Inf.Sch. Pokud SQL nemá statickou SELECT s FROM část, tak není co kontrolovat.
  3. Zpracování dat v controleru. Tam já toho většinou moc individuálně neprogramuji, validace a odformátování se udělá samo, dle typů z Inf.Sch. A pak mám nějakých cca 50 předpřipravených akcí controleru (které si vždy zkoppíruju) z těchto skupin: Předej na aplikační server (tj Save, Delete); Skupina příkazy stránky (jdi na stránku xy, jdi na ní v módu zobraz jeden záznam, jdi na ní v módu nový záznam, atd...); Skupina příkazy gridu (jdi na stránku nn, předchozí, další, počet záznamů, řazení dle soupce xy, filtr dle sloupce, kombinované fitry, agregace (SUM, atd...) sloupce XY, pořadí a kotvení sloupců) a s tím si většinou vystačím.
  4. Zpracování dat v zápisové části modelu (tj. v těch Before/After+Update/Insert/Delete). Tam už je situace v podstatě jednoduchá, protože v těch datových procedurách jsou data rozčleněna dle řádků jednotlivých tabulek (do kterých se zapisují), takže se snadno i staticky zjistí jejich typy z Inf. Sch. a jestli tam není nějaký sloupec navíc atd...

Toto je tedy první nástřel toho jak bych to chtěl udělat. Všem děkuji za různé inspirativní návrhy a budu rád, když napíšete pokud se Vám na tom něco nezdá.

Komentáře

  • pavel.stehule : místo exekuce SELECT ... LIMIT 0 pro zjištění výsledku by (na Postgresu) bylo možné vytvořit prepare statement, a z něj si zjistit položky a typy výsledku. Na většině db je struktura výsledku určená v době plánování, a není nutné vykonání příkazu. 2.4.2019
  • harrison314 : K tej "umelej inteligencii"... cakal som, ze tam bude aj nejaka aplikacna logika, ale v tychto pripadoch ziadna nie je (podobny typ aplikacii som nerobil uz cez 7 rokov). CO sa tyka vyhod dynamicky typovanych jazykov, tak v tomto priapde ziade nie su, to iste napisem aj s tym, ze ako staticky typ pouzijem objekt. To ako to robis teraz si len povzdychnem... 2.4.2019
  • rmaslo : @pavel.stehule: Mně se na tom nelíbí hlavně to množství. Řeknu zkompilovat/zkontrolovat aplikaci a ono to položí stovky dotazů (nebo prepare statement), to mi nepřijde dobré a oproti jednorázovému slíznutí a nacachování Inf.Sch. v tom vidím dost rozdíl. Ale možná je to pocit a do db First to patří? 2.4.2019
  • rmaslo : @harrison314: Aplikační logika, je na straně zápisu v After* a Before* funkcích, na straně čtení mám uložená View, případně mohu vytvořit pomocnou tabulku (třeba protože server neumí CROSS JOIN). Zatím jsem nenarazil na případ, kdy by toto nestačilo. Ale pokud nějaký takový existuje, rád se poučím, nechtěl bych, aby mne po realizaci něco takového zaskočilo. 2.4.2019
  • harrison314 : @rmaslo: napriklad uz len zlozitejsia validacia, kde nestaci typ a maximalna dlzka, ale vyzaduje siahnut do inej tabulky, alebo zavvolat sluzbu, transfrormacia dat,... To nehovorim ani o potencialnych problemov z OverPostingom, tym, ze takato aplikacia vykeca viac ako by mala. User frendly error hendlingom, alebo nejakych vypoctoch. A akykolvek rozumny bezpecak by ta s tym poslal kade lahsie. 2.4.2019
  • pavel.stehule : Olíznout si komplet information schema může být náročnější než vygenerovat prepare statement a podívat se na výsledek. Samozřejmě, že by záleželo na poměru. V každém případku zjistit strukturu výsledku pro netriviální dotaz už může být docela komplikovanější úloha. Použitím prepare statementu si navíc můžu zvalidovat dotaz, a nemusím syntaktickou/sémantickou kontrolu implementovat v aplikaci (což, jak jsi zmiňovat pro netriviální SQL, není jednoduché). Kompilace a plánování dotazu je záležitost cca jednotek ms, takže pokud by jich byly stovky, tak se pořád bavíme o řádu sekund, a spíš méně. 2.4.2019
  • v6ak : Z toho, co píšete, mám pocit, že chcete dost dynamický přístup. Staticky typovaný jazyk si budete potřebovat nějak „zdynamičnit“ – ať už pomocí object.getString("fieldName"), nebo pomocí reflexe. Alternativou by mohlo být nějaké generování kódu, kterým byste se té reflexe zbavil – a teď neřeším, jestli budete generovat přímo soubor se zdrojákem, AST, nebo třeba bytecode. Tím ostatně vyřešíte i „SELECT * FROM $table“. Generování kódu umí zmíněná Scala například pomocí maker – to umí při kompilaci dogenerovat kód k ručně napsanému kódu. Nebo kód můžete generovat pomocí buildtoolu do separátních souborů (které ale nepatří do VCS a nesmíte je ručně editovat, jinak si je přepíšete), což je sice méně mocné, ale zase nezáleží tolik na jazyce. Většina přístupů si ale při změně schématu vyžádá rebuild. Výjimkou je ta nejdynamičtější varianta row.getString("fieldName"), kde ale taky ztratíte nejvíce typové kontroly. 2.4.2019
  • v6ak : Ohledně zpracování SQL snad 100% souhlas s Pavlem Stěhulem: Vidím to jako premature optimization za cenu psaní si celkem komplexního (a nejspíš vždy nedokonalého) parseru a analyzátoru SQL, což mi navíc nepřijde jako úplně parser-friendly jazyk. Pokud výkon začne být problém, můžete se pustit do cacheování – pokud se SQL ani struktura DB nezmění, lze vytáhnout výsledek z cache. Strukturu DB bych doporučoval verzovat s kódem. 2.4.2019
  • Taco : @rmaslo: SQL je docela dost staticky typovaný jazyk. IMHO jediný výrazný problém vidím ve spojení tvého kódu a databázového stroje. Jak zajistit, aby schema nebylo nečekaně upraveno. 3.4.2019
  • Kit : @Taco: DB schéma může být upraveno kdykoli a je nutné s tím počítat. U dynamických jazyků reflexí, u statických jazyků rekompilací ORM. 3.4.2019
  • harrison314 : Zabranit uprave schemi sa da velmi dobre suportnou zmluvou a licencnymi podmienkami ;) 3.4.2019
  • v6ak : „Jak zajistit, aby schema nebylo nečekaně upraveno.“ – úplně stejně, jako to (ne)řeší snad většina ostatních aplikací – staticky i dynamicky typovaných. Pokud nečekaně upravím schéma DB nebo různé další zásadní věci prostředí, pak od aplikace nemohu čekat, že bude fungovat. Možná jste se chtěl zeptat na něco jiného – jak se aplikace přizpůsobí plánované změně schématu. Tady se nabízejí dvě možnosti, které jsem uváděl: a. překompilovat, b. použít ten jazyk dostatečně dynamickým způsobem, ve stylu JdbcRowSet. 3.4.2019
  • harrison314 : Kit: DB schema je sucast aplikacie, ak si ju niekto lubovolne upravi, je to uz jeho problem a nie moj. Dokonca dana aplikacia by sa v tom pripade nemala ani nastartovat (ale az taky extremista nie som). Vies v normalnych firmach maju na to, aby si zalozili dve databazove instancie a nepouzivali datbazu pre moju aplikaciu na nieco ine. 3.4.2019
  • Kit : @harrison314: Tento přístup nechápu. Do DB mi data sype hromada nezávislých aplikací, klidně od různých dodavatelů. Jiná hromada aplikací z ní může dělat různé reporty. 3.4.2019
  • v6ak : @Kit: A každá si upraví schéma podle sebe? Pokud ano, tak vás obdivuju, jak s tím dovedete pracovat. Pokud ne a je v tom nějaký pořádek, pak taková aplikace ani nemusí být schopna nastartovat, ledaže byste chtěl pálit hromadu času řešením situací, kdy vám provozovatel aplikace háže klacky pod nohy. Já ten čas radši využiju jinak. 3.4.2019
  • harrison314 : @Kit: Ja by som nieco hentake nikdy nedovolil uz z principu, lebo si vazim svojich pouzivatelov mojich systemov a este viac si vazim vlastne nervy a cas. Pricom @rmaslo tu riesi presne opacny problem, chce staticky typovy jazyk pre databazu. (No mne stale pride, ze dane problemy by mu riesil ststicky typovany jazyk pre aplikaciu a ORM-ko). 3.4.2019
  • Kit : Datacentrické zpracování je právě o tom, že data jsou v databázi v nějakém schématu a popisují stav. Aplikace toto schéma nemění, ale pouze s databází podle tohoto schématu pracují. Funguje to bez problémů. Pokud nějaká aplikace spadne, je vždy chyba v té aplikaci. O integritu dat se stará jen ta databáze. 3.4.2019
  • Taco : @v6ak: špatně jsi mne pochopil. Jak může aplikace zjistit, že se to schéma změnilo a že má recompilovat? U aplikace a knihovny je to jednoduché: při startu checkne verzi, když nesedí chcipne (třeba). Ale u databázové aplikace se může schéma změnit kdykoliv během běhu. A ověřovat verzi při každé transakci, no nevím. 3.4.2019
  • v6ak : No já nevím, odkud tyto požadavky berete. Strukturu DB bych někde verzoval (například jako migrační skripty v Gitu). Pak není problém zjistit, že se mi něco změnilo a je potřeba to překompilovat, v případě vývoje spolu s jejich spuštěním. Nebo to odporuje některému z požadavků @rmaslo? Pokud ano, vždy tu je k dispozici fallback na „ostatní vyřeš přes mapu (slovník)“, což umožňuje v rámci staticky typovaného jazyka kombinovat statický a dynamický přístup… 3.4.2019
  • Taco : @v6ak: Neštvi mě, a přečti si co píšu :-) Vůbec neodpovídáš na mou poznámku. 4.4.2019
  • v6ak : @Taco tak nějak odpovídám – myslím, že to deploynutá aplikace především nepotřebuje zjišťovat. 4.4.2019
  • harrison314 : @Kit: dalsie famtasticke trvdenia, chcelo by to dokazy. 4.4.2019
  • harrison314 : @Taco: Nejako nechapem dovodu, preco by ti mal zakaznik menit DB schemu pod rukami? Ak robis integraciu systemov cez DB, richlo sa toho zbav, uz na skole mi vraveli, ze na prvy pohlada to vyzera dobre, ale prinasa to len problemy, a na tie som narazil vzdy ked som to viddel v praxy (bud sa DB schema stane nemenna, co brani rozumnemu rozvoju aplikacii, alebo sa meni a potom treba upravovat N aplikacii, prisom polku z nich vytvorili ludia, co vo firme uz nepracuju,...). Integracia cez databazu je v 99,99% pripadov zle inzinierske rozhodnutie. 4.4.2019
  • Taco : @harrison314: Kdo mluví o zákazníkovi? ••• Co myslíš tím integrace systému přes DB? 4.4.2019
  • Taco : @v6ak: pokud stačí, že se “domluvíme”, ze do Db se mimo deploy proces nehrabe, tak pak máš samozřejmě pravdu. 4.4.2019
  • Kit : @harrison314: Databáze není součástí aplikace, obě komponenty žijí samostatným životem. Podstatné je zdokumentované rozhraní. Pokud se nemění rozhraní, může se databázové schéma měnit dle potřeby. 4.4.2019
  • harrison314 : @Taco: Tak kto ti meni databazu pod rukami? Mas azda sabotera? Integracia cez databazu, ze k tej istej DB sa pripaja viac aplikacii a DB sluzi ako platforma na zdielanie a vymenu dat. 4.4.2019
  • harrison314 : @Kit: Ak sa nemeni rozhranie tak vsteko je OK, ale ty si tvrdil nieco odlisne a to, ze vdaka dynamickemu jazyku mozes menit schemu (a v 90% pripadoch je ta schema rozhranie DB). 4.4.2019
  • Kit : @harrison314: Schéma je interní záležitostí databáze, rozhraní je externí. 4.4.2019
  • rmaslo : Ad. struktura: V mých případech mám databázi pod kontrolou, tj. nikdo cizí by mi neměl měnit strukturu. Nicméně, pokud bych potřeboval detekovat, jestli nenastala změna struktury asi bych použil třeba něco ve smyslu: "SELECT SUM(CRC32(CONCAT_WS("|", TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, ...))) AS database_crc FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE()". Podobným způsobem si kontroluji shodnost tabulek na nějakém vzdáleném serveru a pokud nenastane shoda přijde něco jako metoda binárního dělení přes id. 7.4.2019

Pro zobrazení všech 6 odpovědí se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.