Cortiveo i GEngine - prirucnik

↑ Nazad

Pisano 25. septembra 2026, iz izvora i iz merenja.

Deo 1: Kako razumeti upotrebu SQL baze

Postoje tri izvora istine i sva tri su mala — to je najbolja vest o ovoj arhitekturi.

1a. Sav SQL nastaje u jednoj klasi

gjava/data/GDataDriverMySql.java — 810 linija i tu je sve. Oblici koje program ume da napravi:

:105  String text = "SELECT ";
:121  text += " FROM " + mName + " AS t0 ";
:130  text += " LEFT OUTER JOIN " + j.mJoinedDataSourceName + " AS t" + (i+1) + " ON t" + ...
:147  text += " WHERE ";                    // iz filtera (3 sloja)
:194  text += " ORDER BY ";                 // iz sortiranja/RDC
:453  text  = "UPDATE " + mName + " SET ";
:474  text += " WHERE " + ch.getName() + "=" + ...      // po primarnom kljucu
:493  text  = "DELETE FROM " + mName;
:535  final String s = "INSERT IGNORE INTO " + pTable.getName() + " VALUES ";
:388  final String text = "SELECT * FROM " + mName + " LIMIT 1";   // samo da dobije metapodatke

Odatle se čita ceo ugovor sa bazom: tabela je t0, svaka linkovana (lookup) tabela je t1, t2… spojena LEFT OUTER JOIN-om, WHERE dolazi iz filtera, ORDER BY iz sortiranja, a upis i brisanje idu isključivo po primarnom ključu. Zato su svi upiti u tragu ličili jedan na drugi:

SELECT t0.Id, t0.Firma, concat(trim(t1.Opis)), t0.Opis, t0.Verzija ...
  FROM definition AS t0 LEFT OUTER JOIN firma AS t1 ON t1.Id = t0.Firma ...

concat(trim(...)) je generisan za lookup kolonu — vrednost se ne čita iz matične tabele nego iz spojene. Nijedan od tih upita nije napisan rukom.

1b. Šta se stvarno izvršava — Cortiveo

cortiveo trace --app gjava.,db.,milos. --tail 8000 --stack 8 --args 3

Merenja koja su već napravljena (vidi tracer): klik na meni = 0 SQL, posao ide kroz invokeLater; otvaranje ekrana = 7 upita; štampa = po jedan SELECT na definiciju. Uz svaki upit ide lanac pozivalaca, pa se vidi koji ekran ga je tražio.

1c. Šta je u bazi

SELECT TABLE_NAME, TABLE_ROWS, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE();
SHOW CREATE TABLE definitionprintl;

Odatle H/L parovi, FK-ovi ON UPDATE CASCADE, InnoDB, utf8_unicode_ci.

Vežba koja daje pun krug (uradi jednom i znaš sistem)

  1. Izaberi jedan ekran. Pokreni tracer sa --tail 8000.
  2. Klikni ga. Iz loga izvadi svaki SQL red.
  3. Za svaki upit nađi tabelu → nađi Java klasu te tabele (systable.Package + Tabela).
  4. U toj klasi pročitaj addColumn(...) — to je izvor kolona iz SELECT-a.
  5. Uporedi sa SHOW CREATE TABLE.

Kad se ta četiri koraka zatvore, znaš kako se baza koristi — bez čitanja 810 linija drajvera.


Deo 2: Callback sistem

gjava/common/GCbMessageType.java ima 97 tipova poruka. Sve ide kroz jednu metodu:

public GCbMessage callback(GCbMessage pMessage)
{
    switch (pMessage.getId()) {
        case TRIGGERALWAYS: ...        // pre svakog snimanja
        case SETACTION: ...            // koje akcije ekran nudi
        case EDITACTION:               // korisnik pokrenuo akciju
            pMessage.consume();        // pojedi komandu - dalje ne ide
    }
    return super.callback(pMessage);
}

Grupe poruka (po imenima):

grupaprimeričemu odgovara u drugim sistemima
trigeri podatakaTRIGGERINSERT, TRIGGERUPDATE, TRIGGERDELETE, TRIGGERALWAYS, *AFTERALWAYSDB trigeri / EF SaveChanges hook
životni ciklus ekranaEDITPREPARATION, EDITSTART, EDITSHOWN, EDITEND, EXITREQUESTForm.Load, OnShown, OnClosing
naslov i statusSETTITLE, SETTITLEINSERT, EDITEMPTY, EDITNOTEMPTYbinding na naslov
navigacijaMOVETOFIELD, MOVETORECORDPROCEDURE, FOCUSGAINED, SETNEXTFIELDfokus/validacija po polju
akcijeSETACTION, EDITACTION, ACTIONPROCEDUREkomande / ICommand
podaciDATA_LOADING, AUTO_REFRESH, DATA_REFORMAT, SUMFOOTERFINISHEDdogađaji data source-a

Zašto tako: jedna ulazna točka znači da potklasa ne mora ništa da registruje — nasledi i pokrij callback. Nema listener liste, nema odjave, nema propuštenog removeListener. consume() daje Chain of Responsibility: ko obradi poruku, zaustavi je.

Šta to košta: ne vidi se statički ko obrađuje šta — svaki switch je skriven u potklasi. Alat za to je runtime: cortiveo trace --call Klasa.callback --args 2 pokaže koja klasa je dobila koju poruku i u kom redu.


Deo 3: Šta bi se moralo ponoviti (spisak stubova)

Ako ovo praviš iznova, sistem je ovih 11 delova — ni manje ni više:

#stubu GEngine-u
1opis kolone (ime, tip, dužina, decimale, ključ, null, vidljivost)GColumnHeader, GColumnString/Integer/...
2opis tabele = kolone + ime + naslov + ključevi + vezeDbtObject, addColumn(...)
3generator DDL-a iz opisaGMySqlDatabase.dbtCreate
4generator SQL-a (SELECT sa t0/tN join-ovima, UPDATE/DELETE po PK)GDataDriverMySql
5record set (redovi + kursor + get/set po imenu kolone)GDataSource2d, GRowDataSource
6drajver kao zamenljiv sloj (SQL / memorija / Excel / Access)GDataDriver*
7model filtera koji se prevodi i u WHERE i u memorijski predikatGFilterItem, GRelation, GDataDriverMemory.rowInFilter
8callback protokol (životni ciklus + trigeri, jedna metoda)GCbMessage, 97 tipova
9master-detail (detalj vezan za tekući red matične tabele)GDetailParametersSettable, DetailMemDataSource
10serijalizacija skupa tabela (prenosivost posla)GGXml → .obr fajlovi
11generator UI-ja iz opisa (grid + forma + validacija) + štampaGContainer, GCell, GPrintJob

Stub 11 je 70% posla u svakom jeziku. Stubovi 1–7 su nedelju-dve za jednog čoveka u bilo kom od jezika ispod.


Deo 4: Port po jezicima — iskreno, sa razlozima

C# — najlakši, i kulturno najbliži

stubšta C# daje
1–2 opis kolonaatributi + refleksija: [Column(Length=50, Key=true)] na property; čita se u runtime-u. Ovo je najbliža stvar GColumnString statičkim poljima, samo deklarativnija
4 SQLDapper (ručni SQL, brzo) ili ADO.NET; EF Core ima svoj metadata model ali ti nameće svoj način
5 record setDataTable/DataView su bukvalno record set — DataView.RowFilter = "Kolona > 6" je tvoj filter, gotov
7 filteriDataView.RowFilter za memoriju + isti model preveden u WHERE
8 callbackINotifyPropertyChanged, događaji, ili isti switch protokol — radi identično
10 serijalizacijaSystem.Text.Json / XmlSerializer — besplatno
11 UIWinForms DataGridView + BindingSource generiše grid iz metapodataka; WPF sa DataTemplate. Najbliže Oracle Forms iskustvu

Procena: stubovi 1–10 u nekoliko nedelja; UI sloj je najveći, ali WinForms data binding radi pola posla. Rizik: EF Core te vuče u svoj model — za ovaj obrazac je bolji Dapper + sopstveni opis kolona.

C++ — izvodljivo, i najbrže; metapodaci su ručni

stubšta C++ daje
1–2nema refleksije → kolone se deklarišu kao constexpr tabela struktura ili kroz makroe/x-macro DSL. Više teksta, ali provereno u kompajliranju
5, 7, 11Qt je ogroman dobitak: QAbstractTableModel (record set), QSqlTableModel, QSortFilterProxyModel (filteri), QTableView (grid) — tri od jedanaest stubova dobiješ iz kutije
vrednost ćelijestd::variant<int64_t, double, std::string, bool, ...> = tvoj GDataType
6 drajvervirtualne klase — čista Bridge
8 callbackisti switch ili std::function mapa

Procena: sa Qt-om realno; bez Qt-a UI je posao za sebe. Glavna nagrada: amplifikacija poziva koju smo izmerili (175 000 poziva po štampi kroz getColumn) u C++-u je praktično besplatna — ista arhitektura, red veličine brža.

Čist C — moguće, ali 3–5× više koda

Ovo je istorijski način na koji su 4GL runtime-ovi i pisani (dBase, Clipper), pa nije egzotika:

Kada ima smisla: kad ti treba C ABI kao granica (biblioteka koju zove više jezika) ili embedded. Kada nema: za poslovnu aplikaciju sa 44 tabele i formama — C# i C++ daju isto za trećinu koda.

Rust — dobra podloga, dve prave zamke

stubšta Rust daje
vrednost ćelijeenum Value { Int(i64), Float(f64), Text(String), Date(...), Bool(bool), Null } — tagged union sa proverom u kompajliranju, bez instanceof
1–2 opis kolonaderive makro (#[derive(Table)] #[column(len = 50, key)]) daje deklarativan opis kao u Javi, ali proveren u kompajliranju
4 SQLsqlx (upiti provereni prema bazi u compile-time-u!), rusqlite, tiberius (MSSQL)
6 drajvertrait DataDriver + dyn — Bridge
7 filterijedan enum Relation + funkcija koja ga prevodi u WHERE i u predikat nad redovima
10 serijalizacijaserde — XML/JSON/binarno besplatno; GGXml ekvivalent je jedan #[derive(Serialize)]

Dve zamke, iskreno:

  1. Graf objekata. GEngine je pun deljenih promenljivih referenci: master ↔ detalj ↔ ekran ↔ callback. Doslovan port u Rust-u se bori sa borrow checker-om. Rešenje nije Rc<RefCell<...>> svuda, nego model sa indeksima (arena: tabele u Vec, veze kao TableId/RowId) — to je idiomatski Rust i usput uklanja ciklične reference koje u Javi niko ne vidi.
  2. UI. Za aplikaciju sa gustim grid-ovima i formama Rust još nema Qt/WinForms ekvivalent (egui, slint, gtk-rs su upotrebljivi, ali nijedan nije Oracle Forms).

Zato je najpametniji Rust plan hibridan — i to je već pola naše arhitekture: model i drajveri u Rust-u, izloženi kroz C ABI (Cortiveo to već radi: cortiveo-model + cortiveo-core → cortiveo_core.dll + include/cortiveo.h), a UI u čemu god (C#, Qt, web).


Deo 5: Prvi korak koji se može izmeriti

Ne počinji prepisivanjem. Počni tako da imaš dokaz:

  1. Izvezi model iz Java klasa u jezik-neutralan oblik (JSON/TOML). Da je model serijalizabilan već je dokazano — GGXml to radi za podatke, a systable + addColumn za šemu.
  2. U novom jeziku napravi generator DDL-a iz tog modela.
  3. Uporedi izlaz sa SHOW CREATE TABLE iz žive baze — kolona po kolonu.

Kad DDL iz Rust-a/C#-a postane identičan onome što MySQL prijavljuje za svih 44 tabele, model je veran. To je merljiva prekretnica, a ne osećaj — i tek posle nje ima smisla praviti record set, filtere i UI.

Golden master za sve dalje: stara aplikacija. Isti upit, isti izlaz, ista štampa — a Cortiveo je alat koji to poredi (vidi Faze 5 i 6 u docs/roadmap.md).

Izvor: 10-kako-razumeti-i-ponoviti.md