Pisano 25. septembra 2026, iz izvora i iz merenja.
Postoje tri izvora istine i sva tri su mala — to je najbolja vest o ovoj arhitekturi.
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 metapodatkeOdatle 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.
cortiveo trace --app gjava.,db.,milos. --tail 8000 --stack 8 --args 3Merenja 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.
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.
--tail 8000.SQL red.systable.Package + Tabela).addColumn(...) — to je izvor kolona iz SELECT-a.SHOW CREATE TABLE.Kad se ta četiri koraka zatvore, znaš kako se baza koristi — bez čitanja 810 linija drajvera.
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):
| grupa | primeri | čemu odgovara u drugim sistemima |
|---|---|---|
| trigeri podataka | TRIGGERINSERT, TRIGGERUPDATE, TRIGGERDELETE, TRIGGERALWAYS, *AFTERALWAYS | DB trigeri / EF SaveChanges hook |
| životni ciklus ekrana | EDITPREPARATION, EDITSTART, EDITSHOWN, EDITEND, EXITREQUEST | Form.Load, OnShown, OnClosing |
| naslov i status | SETTITLE, SETTITLEINSERT, EDITEMPTY, EDITNOTEMPTY | binding na naslov |
| navigacija | MOVETOFIELD, MOVETORECORDPROCEDURE, FOCUSGAINED, SETNEXTFIELD | fokus/validacija po polju |
| akcije | SETACTION, EDITACTION, ACTIONPROCEDURE | komande / ICommand |
| podaci | DATA_LOADING, AUTO_REFRESH, DATA_REFORMAT, SUMFOOTERFINISHED | događ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.
Ako ovo praviš iznova, sistem je ovih 11 delova — ni manje ni više:
| # | stub | u GEngine-u |
|---|---|---|
| 1 | opis kolone (ime, tip, dužina, decimale, ključ, null, vidljivost) | GColumnHeader, GColumnString/Integer/... |
| 2 | opis tabele = kolone + ime + naslov + ključevi + veze | DbtObject, addColumn(...) |
| 3 | generator DDL-a iz opisa | GMySqlDatabase.dbtCreate |
| 4 | generator SQL-a (SELECT sa t0/tN join-ovima, UPDATE/DELETE po PK) | GDataDriverMySql |
| 5 | record set (redovi + kursor + get/set po imenu kolone) | GDataSource2d, GRowDataSource |
| 6 | drajver kao zamenljiv sloj (SQL / memorija / Excel / Access) | GDataDriver* |
| 7 | model filtera koji se prevodi i u WHERE i u memorijski predikat | GFilterItem, GRelation, GDataDriverMemory.rowInFilter |
| 8 | callback protokol (životni ciklus + trigeri, jedna metoda) | GCbMessage, 97 tipova |
| 9 | master-detail (detalj vezan za tekući red matične tabele) | GDetailParametersSettable, DetailMemDataSource |
| 10 | serijalizacija skupa tabela (prenosivost posla) | GGXml → .obr fajlovi |
| 11 | generator UI-ja iz opisa (grid + forma + validacija) + štampa | GContainer, 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.
| stub | šta C# daje |
|---|---|
| 1–2 opis kolona | atributi + 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 SQL | Dapper (ručni SQL, brzo) ili ADO.NET; EF Core ima svoj metadata model ali ti nameće svoj način |
| 5 record set | DataTable/DataView su bukvalno record set — DataView.RowFilter = "Kolona > 6" je tvoj filter, gotov |
| 7 filteri | DataView.RowFilter za memoriju + isti model preveden u WHERE |
| 8 callback | INotifyPropertyChanged, događaji, ili isti switch protokol — radi identično |
| 10 serijalizacija | System.Text.Json / XmlSerializer — besplatno |
| 11 UI | WinForms 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.
| stub | šta C++ daje |
|---|---|
| 1–2 | nema refleksije → kolone se deklarišu kao constexpr tabela struktura ili kroz makroe/x-macro DSL. Više teksta, ali provereno u kompajliranju |
| 5, 7, 11 | Qt je ogroman dobitak: QAbstractTableModel (record set), QSqlTableModel, QSortFilterProxyModel (filteri), QTableView (grid) — tri od jedanaest stubova dobiješ iz kutije |
| vrednost ćelije | std::variant<int64_t, double, std::string, bool, ...> = tvoj GDataType |
| 6 drajver | virtualne klase — čista Bridge |
| 8 callback | isti 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.
Ovo je istorijski način na koji su 4GL runtime-ovi i pisani (dBase, Clipper), pa nije egzotika:
struct + tabela static conststruct { enum type; union { long i; double d; char *s; }; })switch po enum-u, tačno kao GCbMessageKada 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.
| stub | šta Rust daje |
|---|---|
| vrednost ćelije | enum Value { Int(i64), Float(f64), Text(String), Date(...), Bool(bool), Null } — tagged union sa proverom u kompajliranju, bez instanceof |
| 1–2 opis kolona | derive makro (#[derive(Table)] #[column(len = 50, key)]) daje deklarativan opis kao u Javi, ali proveren u kompajliranju |
| 4 SQL | sqlx (upiti provereni prema bazi u compile-time-u!), rusqlite, tiberius (MSSQL) |
| 6 drajver | trait DataDriver + dyn — Bridge |
| 7 filteri | jedan enum Relation + funkcija koja ga prevodi u WHERE i u predikat nad redovima |
| 10 serijalizacija | serde — XML/JSON/binarno besplatno; GGXml ekvivalent je jedan #[derive(Serialize)] |
Dve zamke, iskreno:
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.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).
Ne počinji prepisivanjem. Počni tako da imaš dokaz:
GGXml to radi za podatke, a systable + addColumn za šemu.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).