Review per il team

Differenze tra asqueel e GenroPy

asqueel confrontato con il layer SQL di GenroPy (gnrpy/gnr/sql/): metodi, attributi e macro che un progetto chiama o dichiara.

Data 20 ottobre 2026 GenroPy commit 95da66ee asqueel commit fbe0b45a

La pagina ha quattro blocchi: Model, Query, Read-Write e Adapters. Ogni blocco ha quattro rami:

Ogni scheda mostra GenroPy sopra e asqueel sotto. Le note stanno in fondo alla scheda. I file citati stanno nel blocco chiuso «File». L'identificativo di ogni voce (per esempio A1.4) rimanda all'inventario verificato docs/design/112-legacy-inventory.md. Il codice degli esempi viene dal capitolo «Differences from GenroPy» del manuale.

Model

Manca

column(default=) temporanea A1.7

In GenroPy scrivevi

tbl.column('status', default='new')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:938 attributo memorizzato; usato da defaultValues in gnrpy/gnr/sql/gnrsqltable/record.py:600

In asqueel

Dichiarato (src/asqueel/elements.py:274); la scrittura della tabella è rifiutata se una colonna ha default (src/asqueel/model_source.py:251). Prova: insert solleva UnsupportedFeatureError 'write execution of c.default is not available in this release'

Motivazione. #28; docs/guide/limitations.md:42

Alternative. (a) newrecord con default come nel legacy (b) tradurlo in sql_default lato database (c) lasciarlo fuori: valorizzare in trigger_onInserting

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:938, gnrpy/gnr/sql/gnrsqltable/record.py:600
  • asqueel: src/asqueel/elements.py:274, src/asqueel/model_source.py:251

localized / variant / ext_<pkg> temporanea A1.10

In GenroPy scrivevi

tbl.column('description', localized=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:919 e :958 (handleLocalizedColumn), :935 variant, :948 ext_config

In asqueel

Ciascuno solleva UnsupportedFeatureError che nomina l'attributo, su tabella (variant, ext_<pkg>) e su colonna (localized, variant, ext_<pkg>): src/asqueel/builder.py:405-411. Prova: 'app.t.c: localized is not available in this release'

Motivazione. Assenti, raise ora e poi da valutare: decisione utente 2026-10-17; docs/guide/legacy-features.md:107-109

Alternative. (a) eseguire gli hook del package come nel legacy (b) tradurli in opzioni dichiarative nel livello applicativo (c) lasciarli fuori: dichiarare a mano le colonne generate

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:919, gnrpy/gnr/sql/gnrsqlmodel/model.py:958, gnrpy/gnr/sql/gnrsqlmodel/model.py:935, gnrpy/gnr/sql/gnrsqlmodel/model.py:948
  • asqueel: src/asqueel/builder.py:405-411

sysFields(tbl, ...) sostituita A1.14

In GenroPy scrivevi

def config_db(self, pkg):
    tbl = pkg.table('note', pkey='id')
    self.sysFields(tbl)

In GenroPy ottenevi

gnrpy/gnr/app/gnrdbo.py:324 metodo chiamato in config_db

In asqueel scrivi

tbl = pkg.table('note', pkey='id')
tbl.options().sys_fields()

In asqueel ottieni

options().sys_fields(...) (src/asqueel/model_elements.py:90); espansione src/asqueel/model_expansion.py:125 (id, __ins_ts, __mod_ts, __del_ts, __ins_user, __mod_user, __is_draft, __syscode); scrittura eseguita (src/asqueel/model_source.py:237): trigger_setTSNow (src/asqueel/table_triggers.py:50), trigger_setCurrentUser (src/asqueel/table_triggers.py:57) da db.currentUser (src/asqueel/runtime.py:158). Prova: insert valorizza id di 22 caratteri, __ins_ts e __mod_ts

Motivazione. Forma sysFields sostituita da options(): decisione utente 2026-10-17; scrittura: #29; docs/guide/legacy-features.md:110-117

File
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:324
  • asqueel: src/asqueel/model_elements.py:90, src/asqueel/model_expansion.py:125, src/asqueel/model_source.py:237, src/asqueel/table_triggers.py:50, src/asqueel/table_triggers.py:57, src/asqueel/runtime.py:158

sysFields(hierarchical=...) sostituita A1.15

In GenroPy scrivevi

self.sysFields(tbl, hierarchical='code')

In GenroPy ottenevi

gnrpy/gnr/app/gnrdbo.py:326 parametro; attributo hierarchical impostato in gnrpy/gnr/app/gnrdbo.py:437

In asqueel scrivi

tbl.options().sys_fields()
tbl.options().hierarchical('code')

In asqueel ottieni

options().hierarchical (src/asqueel/option_elements.py:14); espansione src/asqueel/model_expansion.py:226, richiede pkey id di size 22 (:234), linked_to e depth sollevano UnsupportedFeatureError (:230); scrittura con HierarchicalHandler (src/asqueel/hierarchical.py:29) chiamato da trigger_hierarchical_before/after (src/asqueel/table_triggers.py:61); lettura di child_count, hlevel, root_id

Motivazione. Dichiarazione con options().hierarchical, lettura e scrittura da realizzare subito: decisione utente 2026-10-17; #30; docs/guide/legacy-features.md:118-139. JSON del modello: #18

File
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:326, gnrpy/gnr/app/gnrdbo.py:437
  • asqueel: src/asqueel/option_elements.py:14, src/asqueel/model_expansion.py:226, src/asqueel/hierarchical.py:29, src/asqueel/table_triggers.py:61

sysFields(counter=) / sysFields_counter sostituita A1.16

In GenroPy scrivevi

self.sysFields(tbl, counter=True)

In GenroPy ottenevi

gnrpy/gnr/app/gnrdbo.py:326; gnrpy/gnr/app/gnrdbo.py:556

In asqueel scrivi

tbl.options().auto_counter()

In asqueel ottieni

options().auto_counter (src/asqueel/option_elements.py:22); espansione src/asqueel/model_expansion.py:199-209 (colonna _row_count, order_by della tabella). La scrittura è rifiutata salvo su tabella gerarchica (src/asqueel/model_source.py:239-242). Prova: insert su tabella con auto_counter solleva 'write execution of option auto_counter is not available in this release'

Motivazione. Forma sysFields sostituita da options(): decisione utente 2026-10-17; valorizzazione del contatore in scrittura: #31; docs/guide/legacy-features.md:140-144

File
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:326, gnrpy/gnr/app/gnrdbo.py:556
  • asqueel: src/asqueel/option_elements.py:22, src/asqueel/model_expansion.py:199-209, src/asqueel/model_source.py:239-242

Mixin di tabella e di package (tableMixin, packageMixin, _plugins, mixin=) temporanea A1.18

In GenroPy scrivevi

model.tableMixin('sales.invoice', InvoiceMixin)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:516, :525; gnrpy/gnr/sql/gnrsqlmodel/obj.py:58-63

In asqueel

db.tableMixin e db.packageMixin sollevano UnsupportedFeatureError (src/asqueel/runtime.py:53, :57); mixin= sulla tabella solleva (src/asqueel/builder.py:407); la classe Table di main.py solleva (src/asqueel/package_layout.py:55). Restano una sola classe per tabella: x_table_class (src/asqueel/object_tree.py:166; docs/guide/hooks.md:3) o la Table del layout unita a SqlTable (src/asqueel/package_layout.py:75)

Motivazione. Assenti, raise ora e poi da valutare: decisione utente 2026-10-17; docs/guide/legacy-features.md:145-147

Alternative. (a) comporre le classi mixin come nel legacy (b) una sola sottoclasse SqlTable per tabella più contributi (c) lasciarli fuori: x_table_class o Table del layout

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:516, gnrpy/gnr/sql/gnrsqlmodel/model.py:525, gnrpy/gnr/sql/gnrsqlmodel/obj.py:58-63
  • asqueel: src/asqueel/runtime.py:53, src/asqueel/runtime.py:57, src/asqueel/builder.py:407, src/asqueel/package_layout.py:55, src/asqueel/object_tree.py:166, src/asqueel/package_layout.py:75

rowcaption / newrecord_caption (proprietà) temporanea A1.23

In GenroPy scrivevi

caption = tblobj.rowcaption

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:240 ricade su caption_field e poi su pkey; gnrpy/gnr/sql/gnrsqlmodel/table.py:255

In asqueel

Attributi di table() dichiarati (src/asqueel/elements.py:172-173); le proprietà sollevano UnsupportedFeatureError (src/asqueel/object_tree.py:334). Prova: 'app.t: rowcaption is not available in this release'

Motivazione. Assenti, raise ora e poi da valutare: decisione utente 2026-10-17; docs/guide/legacy-features.md:148-150

Alternative. (a) proprietà con lo stesso fallback del legacy (b) lettura da attributes con un helper unico (c) lasciarle fuori: attributes.get('rowcaption')

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:240, gnrpy/gnr/sql/gnrsqlmodel/table.py:255
  • asqueel: src/asqueel/elements.py:172-173, src/asqueel/object_tree.py:334

virtual_columns / starColumns() temporanea A1.24

In GenroPy scrivevi

names = list(tblobj.virtual_columns.keys())
cols = tblobj.starColumns()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:359 include colonne locali, formulaColumn_* (:405) e variant (:440); gnrpy/gnr/sql/gnrsqlmodel/table.py:479 starColumns pubblico

In asqueel

virtual_columns elenca solo le virtuali dichiarate (src/asqueel/object_tree.py:217); le colonne di formulaColumn_<x>() stanno in dynamic_columns (src/asqueel/object_tree.py:225) e column() le risolve; starColumns solleva UnsupportedFeatureError (src/asqueel/object_tree.py:334); l'espansione di '*' è interna al compilatore (src/asqueel/compiler.py:805)

Motivazione. Assenti, raise ora e poi da valutare: decisione utente 2026-10-17; docs/guide/legacy-features.md:151-152

Alternative. (a) proprietà complete come nel legacy (b) solo colonne dichiarate più starColumns pubblico (c) lasciarli fuori: dichiarare le colonne nella ricetta

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:359, gnrpy/gnr/sql/gnrsqlmodel/table.py:405, gnrpy/gnr/sql/gnrsqlmodel/table.py:440, gnrpy/gnr/sql/gnrsqlmodel/table.py:479
  • asqueel: src/asqueel/object_tree.py:217, src/asqueel/object_tree.py:225, src/asqueel/object_tree.py:334, src/asqueel/compiler.py:805

subtable() temporanea A1.25

In GenroPy scrivevi

tbl.subtable('open', condition='$status = :s', condition_s='open')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:685; formula subtable_<nome> (:800); variante di package con maintable (:704)

In asqueel

Nessun elemento subtable: t.subtable(...) solleva AttributeError; default_subtable come attributo di tabella solleva ValueError (src/asqueel/builder.py:365); opzione di query subtable rifiutata (src/asqueel/application_table.py:45)

Motivazione. Discorso subtable dichiarato non ancora realizzato: decisione utente 2026-10-17; docs/guide/legacy-features.md:153-155

Alternative. (a) variante di package e filtro come nel legacy (b) solo filtro in query su subtable dichiarata (c) lasciarlo fuori: where con la condizione della subtable

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:685, gnrpy/gnr/sql/gnrsqlmodel/model.py:800, gnrpy/gnr/sql/gnrsqlmodel/model.py:704
  • asqueel: src/asqueel/builder.py:365, src/asqueel/application_table.py:45

_override (virtuale che sostituisce una colonna fisica omonima) temporanea A3.8

In GenroPy scrivevi

tbl.formulaColumn('total', sql_formula='$net + $vat', _override=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1007-1019: errore salvo _override=True, che toglie la colonna fisica

In asqueel

_override rifiutato dalla grammatica: «Element 'formulaColumn' does not accept '_override'» (prova); fisica contro virtuale dà 'conflicting element kinds' (src/asqueel/builder.py:194-197, tests/test_virtual_column_redefinition.py:93-106)

Motivazione. docs/design/33-structure-rectification-plan.md:305-309 (decisione D4 del 2 ottobre 2026): rinviato alla composizione fra package, con la sintassi legacy

Alternative. (a) accettare _override come nel legacy (b) solo nella composizione fra package, come prevede D4 (c) lasciarlo fuori: rinominare la virtuale

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1007-1019
  • asqueel: src/asqueel/builder.py:194-197, tests/test_virtual_column_redefinition.py:93-106

pyColumn (py_method, default pyColumn_<nome>) temporanea A3.12

In GenroPy scrivevi

tbl.pyColumn('age', py_method='calc_age', dtype='L')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1291-1303; gnrpy/gnr/sql/gnrsqldata/compiler.py:442-446 NULL in SQL e registrazione; gnrpy/gnr/sql/gnrsqldata/query.py:257-280 calcolo Python sulle righe

In asqueel

src/asqueel/elements.py:388 solo dichiarazione; selezionarla dà UnsupportedFeatureError 'pyColumn is not available in this release' (src/asqueel/compiler.py:214, tests/test_specific_errors.py:62, prova)

Motivazione. nessuna issue. docs/guide/limitations.md:296, docs/guide/formulas.md:246-247

Alternative. (a) NULL in SQL e callback dopo il fetch, come nel legacy (b) callback dichiarata solo sul record e non nelle query (c) lasciarla fuori: calcolo applicativo dopo fetch()

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1291-1303, gnrpy/gnr/sql/gnrsqldata/compiler.py:442-446, gnrpy/gnr/sql/gnrsqldata/query.py:257-280
  • asqueel: src/asqueel/elements.py:388, src/asqueel/compiler.py:214, tests/test_specific_errors.py:62

toolColumn temporanea A3.14

In GenroPy scrivevi

tbl.toolColumn('print_link', tool='print', dtype='T')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1194-1211 (toolFormula)

In asqueel

Assente dalla grammatica: chiamarla dà AttributeError «'SourceBagNode' object has no attribute 'toolColumn'» (prova); non compare fra i tag di src/asqueel/builder.py:66-70

Motivazione. docs/design/44-core-release-plan.md:113: esclusa da questa release per decisione esplicita

Alternative. (a) portarla come nel legacy (b) spostarla nel livello applicazione o UI (c) lasciarla fuori: formulaColumn con l'espressione esplicita

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1194-1211
  • asqueel: src/asqueel/builder.py:66-70

Colonne varianti: variant= con variantColumn_<v>(), sqlparams[nome]={field:...} temporanea A3.25

In GenroPy scrivevi

tbl.column('date', dtype='D', variant='year')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:440-471 (variantColumn_*), :343-356 (variante creata dai sqlparams)

In asqueel

variant= rifiutato su column (src/asqueel/builder.py:405-410) e dalla grammatica di formulaColumn (prova); i metodi variantColumn_* non vengono letti; un nome passato solo nei sqlparams dà 'Unknown column' (src/asqueel/compiler.py:139, prova)

Motivazione. nessuna issue. docs/guide/limitations.md:208, :296 ('Formula variants ... not implemented')

Alternative. (a) ricalcolare le varianti per ambiente come nel legacy (b) espanderle una volta al render del modello (c) lasciarle fuori: dichiarare formulaColumn esplicite

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:440-471, gnrpy/gnr/sql/gnrsqlmodel/table.py:343-356
  • asqueel: src/asqueel/builder.py:405-410, src/asqueel/compiler.py:139

Percorso verso molti @relation_name.col (JOIN che moltiplica le righe) sostituita A4.9

In GenroPy scrivevi

tbl.query(columns='$id, @invoices.amount').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:571 manyrelation, :665 registra la join, :1059 DISTINCT automatico

In asqueel scrivi

tbl.query(columns='$id, @invoices.aggr(count() AS n)').fetch()

In asqueel ottieni

src/asqueel/compiler.py:377-379 UnsupportedFeatureError '<rel>: a path through the many relation <rel> is not available in this release' (prova); con one_one il percorso diventa LEFT JOIN (src/asqueel/compiler.py:363-364, prova); al suo posto @rel.aggr(...) (riga A4.19)

Motivazione. docs/design/41-deferred-features.md:192 (decisione del 16 ottobre 2026): niente JOIN verso molti, niente righe moltiplicate, niente DISTINCT implicito; al loro posto aggr. Vale il codice su docs/guide/queries.md:380-387 e docs/guide/limitations.md:23, che descrivono una LEFT JOIN

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:571, gnrpy/gnr/sql/gnrsqldata/compiler.py:665, gnrpy/gnr/sql/gnrsqldata/compiler.py:1059
  • asqueel: src/asqueel/compiler.py:377-379, src/asqueel/compiler.py:363-364

joinColumn e relazione su colonna virtuale temporanea A4.11

In GenroPy scrivevi

tbl.joinColumn('customer_code', ...)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1067 joinColumn; gnrpy/gnr/sql/gnrsqlmodel/columns.py:273-279 relazione virtuale con relation_path '@nome.col'

In asqueel

joinColumn rifiutato alla chiamata con 'joinColumn is not available in this release' (src/asqueel/builder.py:126-129, prova). La relazione su colonna virtuale esiste: weak di default (src/asqueel/builder.py:441-446), join sull'espressione (src/asqueel/compiler.py:428, prova). Il caricatore legacy traduce join_column in virtualRelation più aliasColumn (src/asqueel/legacy_source.py:77-88), ma una virtualRelation non si risolve come percorso verso uno (src/asqueel/compiler.py:354)

Motivazione. decisione utente 2026-10-17: assente, raise ora, poi da valutare. nessuna issue. docs/guide/limitations.md:209

Alternative. (a) aggiungere joinColumn come nel legacy: colonna virtuale, relazione e relation_path automatico (b) far tradurre joinColumn in formulaColumn(...).relation(...) più aliasColumn (c) lasciarla fuori: il progetto scrive a mano formulaColumn con relation e aliasColumn

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1067, gnrpy/gnr/sql/gnrsqlmodel/columns.py:273-279
  • asqueel: src/asqueel/builder.py:126-129, src/asqueel/builder.py:441-446, src/asqueel/compiler.py:428, src/asqueel/legacy_source.py:77-88, src/asqueel/compiler.py:354

joinConditions, setJoinCondition, kwargs jc_* di query temporanea A4.13

In GenroPy scrivevi

tbl.query(columns='@lines.amount', joinConditions={...}).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:191 setJoinCondition; gnrpy/gnr/sql/gnrsqltable/query.py:134, :157 jc_kwargs; gnrpy/gnr/sql/gnrsqldata/compiler.py:653, :702 aggiungono la condizione, one_one rende verso uno una join verso molti

In asqueel

query(joinConditions=...) rifiutato con 'query option ... is not available in this release' (src/asqueel/application_table.py:44); setJoinCondition solleva (src/asqueel/application_table.py:80-81); jc_<nome> rifiutati per nome (src/asqueel/application_table.py:397-399); prova su tutti e tre

Motivazione. decisione utente 2026-10-17: assente, raise ora, poi da valutare. nessuna issue. docs/guide/limitations.md:215, :220

Alternative. (a) implementarlo come nel legacy: dict per relazione con '$tbl', parametri e one_one (b) esprimere la condizione con una virtualRelation filtrata, usabile solo in aggr (c) lasciarlo fuori: condizione nella WHERE o in una subQueryColumn

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:191, gnrpy/gnr/sql/gnrsqltable/query.py:134, gnrpy/gnr/sql/gnrsqltable/query.py:157, gnrpy/gnr/sql/gnrsqldata/compiler.py:653, gnrpy/gnr/sql/gnrsqldata/compiler.py:702
  • asqueel: src/asqueel/application_table.py:44, src/asqueel/application_table.py:80-81, src/asqueel/application_table.py:397-399

relationDict (nomi simbolici delle colonne in query) temporanea A4.14

In GenroPy scrivevi

tbl.query(columns='$cust', relationDict={'cust': '@customer_id.name'})

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:146 parametro, :491 uso

In asqueel

query(relationDict=...) rifiutato con 'query option ... is not available in this release' (src/asqueel/application_table.py:44, prova); src/asqueel/compiler.py:125 cpl.relationDict contiene solo i path risolti in uscita

Motivazione. decisione utente 2026-10-17: assente, raise ora, poi da valutare. nessuna issue

Alternative. (a) accettare relationDict come nel legacy, con i nomi simbolici precompilati (b) usare alias AS nelle columns (c) lasciarlo fuori: aliasColumn nel modello

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:146, gnrpy/gnr/sql/gnrsqldata/query.py:491
  • asqueel: src/asqueel/application_table.py:44, src/asqueel/compiler.py:125

getRelation, getRelationBlock, getJoiner, resolveRelationPath, relations_one, relations_many, manyRelationsList, oneRelationsList temporanea A4.16

In GenroPy scrivevi

joiner = tblobj.getJoiner('sales.customer')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:648, :696, :709, :717, :730, :755, :859, :881

In asqueel

getRelation, getJoiner, relations_one, relations_many, manyRelationsList sollevano 'not available in this release' su DbTableObj e su SqlTable (src/asqueel/object_tree.py:334-336). getRelationBlock, resolveRelationPath, oneRelationsList danno AttributeError generico (prova). Esiste solo il metodo privato src/asqueel/table_crud.py:167 _relations_many

Motivazione. decisione utente 2026-10-17: assente, raise ora, poi da valutare. nessuna issue. docs/guide/limitations.md:210-213. Tre metodi non hanno il rifiuto con il nome

Alternative. (a) portarli come nel legacy su DbTableObj, sopra la Bag relations (b) definire una sola API di introspezione nuova sopra relations (c) lasciarli fuori: il progetto legge table.relations e il joiner

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:648, gnrpy/gnr/sql/gnrsqlmodel/table.py:696, gnrpy/gnr/sql/gnrsqlmodel/table.py:709, gnrpy/gnr/sql/gnrsqlmodel/table.py:717, gnrpy/gnr/sql/gnrsqlmodel/table.py:730, gnrpy/gnr/sql/gnrsqlmodel/table.py:755, gnrpy/gnr/sql/gnrsqlmodel/table.py:859, gnrpy/gnr/sql/gnrsqlmodel/table.py:881
  • asqueel: src/asqueel/object_tree.py:334-336, src/asqueel/table_crud.py:167

relationExplorer temporanea A4.17

In GenroPy scrivevi

tree = tblobj.relationExplorer()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/utils.py:456; gnrpy/gnr/sql/gnrsql/query.py:378

In asqueel

relationExplorer solleva 'not available in this release' su DbTableObj e su SqlTable (src/asqueel/object_tree.py:335, prova)

Motivazione. decisione utente 2026-10-17: assente, raise ora, poi da valutare. nessuna issue. docs/guide/limitations.md:212

Alternative. (a) portarlo come nel legacy, sopra RelationTreeResolver (b) esporre solo l'albero relations e lasciare l'attraversamento al chiamante (c) lasciarlo fuori, come funzione della UI e non del layer SQL

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/utils.py:456, gnrpy/gnr/sql/gnrsql/query.py:378
  • asqueel: src/asqueel/object_tree.py:335

aliasTable / table_alias (alias di percorso) temporanea A4.18

In GenroPy scrivevi

tbl.aliasTable('customer', relation_path='@invoice_id.@customer_id')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1305; gnrpy/gnr/sql/gnrsqldata/compiler.py:490 espande table_aliases dentro il percorso

In asqueel

Nessun elemento aliasTable: la chiamata dà AttributeError generico (prova); i nodi aliasTable e table_alias di una source sono rifiutati (src/asqueel/configuration.py:121-123). Il caricatore legacy traduce table_aliases in virtualRelation(relation=...) (src/asqueel/legacy_source.py:98-100), che si risolve solo dentro aggr: @alias.col dà 'Relation @alias not found' (src/asqueel/compiler.py:354, prova)

Motivazione. docs/design/41-deferred-features.md:73 (decisione del 13 ottobre 2026: per ora aliasTable esce dalla grammatica); docs/design/42-configuration-grammar.md:552-557: virtualRelation solo verso molti

Alternative. (a) portare aliasTable come nel legacy: l'alias si espande nel percorso (b) far risolvere virtualRelation(relation=...) anche nei percorsi verso uno (c) lasciarlo fuori: scrivere il percorso completo @a.@b.col

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1305, gnrpy/gnr/sql/gnrsqldata/compiler.py:490
  • asqueel: src/asqueel/configuration.py:121-123, src/asqueel/legacy_source.py:98-100, src/asqueel/compiler.py:354

Diverso

package() / sqlschema / sqlprefix / sqlname A1.1

In GenroPy scrivevi

pkg = model.package('sales', sqlschema='sales')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:602 package(name, sqlschema, ...); gnrpy/gnr/sql/gnrsqlmodel/obj.py:200 prefisso = nome package; gnrpy/gnr/sql/gnrsqlmodel/table.py:151 sqlschema con tenant_schema (riga 154)

In asqueel scrivi

pkg = root.packages().package('sales', sqlschema='crm', sqlprefix='c')

In asqueel ottieni

src/asqueel/model_elements.py:38 package(name, sqlschema, sqlprefix, name_full, path, ...) sotto db.packages(); schema del package src/asqueel/object_tree.py:150 (default 'public', 'main' su SQLite); prefisso src/asqueel/object_tree.py:155; sqlschema/sqlname della tabella src/asqueel/object_tree.py:256 e :260. Prova: package 'sales' dà sales_inv in public; sqlprefix='c' e sqlname='customers' danno customers in crm. tenant_schema non esiste: l'attributo multi_tenant della tabella è rifiutato come fuori grammatica (src/asqueel/builder.py:365)

Motivazione. Regole di schema e prefisso come nel legacy: docs/guide/legacy-features.md:61-66. Un modello con tag schema non imposta il prefisso: #25. multi_tenant e tenant_schema fuori perimetro per ora: decisione utente 2026-10-17

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:602, gnrpy/gnr/sql/gnrsqlmodel/obj.py:200, gnrpy/gnr/sql/gnrsqlmodel/table.py:151
  • asqueel: src/asqueel/model_elements.py:38, src/asqueel/object_tree.py:150, src/asqueel/object_tree.py:155, src/asqueel/object_tree.py:256, src/asqueel/object_tree.py:260, src/asqueel/builder.py:365

table(name, pkey=, ..., **kwargs) A1.2

In GenroPy scrivevi

tbl = pkg.table('category', pkey='id', hierarchical='code', my_attr=1)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:643 accetta lastTS, rowcaption, sqlname, sqlschema e qualsiasi attributo in kwargs

In asqueel scrivi

tbl = pkg.table('category', pkey='id', name_long='Category')

In asqueel ottieni

src/asqueel/elements.py:166 firma chiusa (pkey, caption_field, name_*, rowcaption, newrecord_caption, order_by, lookup, sqlname, sqlschema, deletable, lastTS, logicalDeletionField, draftField, protectionColumn, x_table_class) più la famiglia partition_*; ogni altra chiave solleva ValueError che nomina attributo ed elemento (src/asqueel/builder.py:365). Prova: hierarchical, xtd, multi_tenant, group_x, x_custom, my_attr rifiutati

Motivazione. Regola «ogni cosa fuori grammatica raise»: decisione utente 2026-10-17; docs/guide/legacy-features.md:67-72

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:643
  • asqueel: src/asqueel/elements.py:166, src/asqueel/builder.py:365

partition_<campo>='path' A1.4

In GenroPy scrivevi

tbl = pkg.table('invoice', pkey='id', partition_company_id='company_id')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/columns.py:104-105 partitionParameters estratti dagli attributi partition_*

In asqueel scrivi

tbl = pkg.table('invoice', pkey='id', partition_company_id='company_id')

In asqueel ottieni

Famiglia di attributi partition_ della tabella (src/asqueel/elements.py:164); filtro getPartitionCondition in src/asqueel/compiler.py:876: current_<path> dà $<campo> = :env_current_<path>, altrimenti allowed_<path> dà IS NULL o IN, altrimenti nessun filtro; conta il primo attributo. Manca il ripiego sulla colonna __allowed_partition. Le scritture non leggono la partizione, come nel legacy (prova: insert con current_tenant='a' lascia tenant NULL)

Motivazione. Sintassi legacy sopra il meccanismo esistente: decisione utente 2026-10-17. Ripiego __allowed_partition non portato: docs/guide/legacy-features.md:32-37. Partizioni nelle scritture proposte: #15

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/columns.py:104-105
  • asqueel: src/asqueel/elements.py:164, src/asqueel/compiler.py:876

Attributi UI senza '_' (format, values, print_width, encrypted, ...) A1.9

In GenroPy scrivevi

tbl.column('status', values='o:Open,c:Closed', format='#', print_width=10)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:938 qualsiasi kwarg viene memorizzato

In asqueel scrivi

tbl.column('status', validate_values='o:Open,c:Closed', print_width=10)

In asqueel ottieni

format e print_width dichiarati (src/asqueel/elements.py:272), letti da attributes e da column.print_width (src/asqueel/object_tree.py:348, senza setter); encrypted solleva UnsupportedFeatureError (src/asqueel/builder.py:415); ogni altro attributo (values, cell_*, template_*, x_*) solleva ValueError (src/asqueel/builder.py:365). values si scrive validate_values

Motivazione. Regola «ogni cosa fuori grammatica raise» e sintassi legacy per A1.9: decisione utente 2026-10-17; docs/guide/legacy-features.md:73-76

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:938
  • asqueel: src/asqueel/elements.py:272, src/asqueel/object_tree.py:348, src/asqueel/builder.py:415, src/asqueel/builder.py:365

colgroup() A1.11

In GenroPy scrivevi

grp = tbl.colgroup('addr', name_long='Address', colgroup_label='Addr')
grp.column('street')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:809; group 'nome.NNN' più colgroup_label e colgroup_name_long (:843-848); col_* qualsiasi; attributo di tabella group_<nome> (:831)

In asqueel scrivi

grp = tbl.colgroup('addr', name_long='Address', col_dtype='T')
grp.column('street')

In asqueel ottieni

src/asqueel/model_elements.py:26; group 'nome.NNN' (src/asqueel/builder.py:496); col_* solo per i parametri dichiarati di column (src/asqueel/builder.py:500 e :520). Prova: le colonne ricevono group addr.001 e addr.002 e il dtype da col_dtype; nessun group_<nome> sulla tabella, nessun colgroup_label o colgroup_name_long sulle colonne; colgroup_label come argomento solleva ValueError

Motivazione. docs/guide/legacy-features.md:77-79; attributi mancanti: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:809, gnrpy/gnr/sql/gnrsqlmodel/model.py:843-848, gnrpy/gnr/sql/gnrsqlmodel/model.py:831
  • asqueel: src/asqueel/model_elements.py:26, src/asqueel/builder.py:496, src/asqueel/builder.py:500, src/asqueel/builder.py:520

compositeColumn() A1.12

In GenroPy scrivevi

tbl.compositeColumn('identity', columns='code,year', static=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1078 virtual_column con composed_of, dtype JS, static=True; pkeys espanse da gnrpy/gnr/sql/gnrsqlmodel/table.py:191

In asqueel scrivi

tbl.compositeColumn('identity', columns='code,year', unique=True)

In asqueel ottieni

src/asqueel/elements.py:408 nella collezione composites, con unique=; dtype JS (src/asqueel/object_tree.py:367); pkey risolta sui membri (src/asqueel/model_source.py:148); letta come valore con la formula '[a, b]' (src/asqueel/compiler.py:219). Prova: fetch di $k dà '[1, 2]' e where $k=:k trova la riga. static= rifiutato come attributo sconosciuto

Motivazione. Sintassi legacy: decisione utente 2026-10-17; lettura come valore: #26; docs/guide/legacy-features.md:80-82. static= rifiutato: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1078, gnrpy/gnr/sql/gnrsqlmodel/table.py:191
  • asqueel: src/asqueel/elements.py:408, src/asqueel/object_tree.py:367, src/asqueel/model_source.py:148, src/asqueel/compiler.py:219

index() A1.13

In GenroPy scrivevi

tbl.index('code,year', 'doc_code_year', True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1326 index(columns, name, unique); accetta una lista (:1343); nome di default '<tabella>_<colonne>_key' (:1346)

In asqueel scrivi

tbl.index(['code', 'year'], name='doc_code_year', unique=True)

In asqueel ottieni

src/asqueel/elements.py:446 index(columns, name, unique, method, where, tablespace, with_options); lista o tupla unita con ',' e nome di default legacy (src/asqueel/builder.py:458-464, src/asqueel/common.py:88). Un solo argomento posizionale (src/asqueel/builder.py:170): index('a', 'b', True) solleva TypeError. method, where, tablespace, with_options sono in più rispetto al legacy

Motivazione. Firma legacy: decisione utente 2026-10-17; docs/guide/legacy-features.md:47-50. Argomenti posizionali oltre columns: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1326, gnrpy/gnr/sql/gnrsqlmodel/model.py:1343, gnrpy/gnr/sql/gnrsqlmodel/model.py:1346
  • asqueel: src/asqueel/elements.py:446, src/asqueel/builder.py:458-464, src/asqueel/common.py:88, src/asqueel/builder.py:170

config_db / config_db_<pkg> / config_db_custom A1.17

In GenroPy scrivevi

class Table(object):
    def config_db_custom(self, pkg):
        pkg.table('invoice').column('note')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:127, :130, :140 eseguiti da build() sui mixin di tabella

In asqueel scrivi

class Table:
    def config_db(self, pkg):
        tbl = pkg.table('invoice', pkey='id')

In asqueel ottieni

Table.config_db(self, pkg) di model/<tabella>.py eseguito in ordine alfabetico da src/asqueel/package_layout.py:69; config_db_<pkg> e config_db_custom su Table sollevano UnsupportedFeatureError (src/asqueel/package_layout.py:66-68). Accanto resta la ricetta main(self, root) (src/asqueel/builder.py:123) con override tramite builder.contribution (src/asqueel/builder.py:181)

Motivazione. Personalizzazione di livello applicativo, layout provvisorio: decisione utente 2026-10-18; docs/guide/legacy-features.md:83-93

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:127, gnrpy/gnr/sql/gnrsqlmodel/model.py:130, gnrpy/gnr/sql/gnrsqlmodel/model.py:140
  • asqueel: src/asqueel/package_layout.py:69, src/asqueel/package_layout.py:66-68, src/asqueel/builder.py:123, src/asqueel/builder.py:181

model.table('pkg.tbl') / model.column() / tblobj.column(name) A1.19

In GenroPy scrivevi

model.table('nope.t')
model.table('sales.nope')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:545 package sconosciuto: None; gnrpy/gnr/sql/gnrsqlmodel/table.py:514 colonna semplice sconosciuta: None

In asqueel scrivi

model.table('nope.t')
model.table('sales.nope')

In asqueel ottieni

model.table con package sconosciuto restituisce None (src/asqueel/object_tree.py:465); tabella sconosciuta di un package noto: ValueError 'Unknown table' (src/asqueel/object_tree.py:143), nel legacy GnrSqlMissingTable; colonna sconosciuta: None (src/asqueel/object_tree.py:298); model.column con package sconosciuto: ValueError (src/asqueel/object_tree.py:473), nel legacy AttributeError su None. Percorsi @ e AliasColumnWrapper come nel legacy

Motivazione. None per nome sconosciuto come legacy: decisione utente 2026-10-17; docs/guide/legacy-features.md:42-44. Classe delle eccezioni diversa: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:545, gnrpy/gnr/sql/gnrsqlmodel/table.py:514
  • asqueel: src/asqueel/object_tree.py:465, src/asqueel/object_tree.py:143, src/asqueel/object_tree.py:298, src/asqueel/object_tree.py:473

pkey / pkeys A1.20

In GenroPy scrivevi

tbl = pkg.table('doc', pkey='identity')
tbl.compositeColumn('identity', columns='code,year')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:210 una sola colonna; gnrpy/gnr/sql/gnrsqlmodel/table.py:191 pkeys espande composed_of

In asqueel scrivi

tbl = pkg.table('doc', pkey='identity')
tbl.compositeColumn('identity', columns='code,year')

In asqueel ottieni

pkey restituisce il nome della compositeColumn o la colonna (src/asqueel/object_tree.py:252); pkeys è la lista dei membri o [pkey] (src/asqueel/object_tree.py:264); pkey='a,b' su tabella di package solleva ValueError (src/asqueel/model_source.py:145). Prova: pkey 'k', pkeys ['a', 'b']. Senza pkey: None e [] (legacy '' e lista vuota con log critico)

Motivazione. Chiave composta come legacy: decisione utente 2026-10-17; #23; docs/guide/legacy-features.md:51-52. pkey assente None invece di '': nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:210, gnrpy/gnr/sql/gnrsqlmodel/table.py:191
  • asqueel: src/asqueel/object_tree.py:252, src/asqueel/object_tree.py:264, src/asqueel/model_source.py:145

name_long / name_short / name_full A1.22

In GenroPy scrivevi

tblobj.name_long = 'Invoices'

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/obj.py:109-125: name_long ricade su name_short; name_full ricade su name_long; ci sono i setter

In asqueel scrivi

label = tblobj.name_long

In asqueel ottieni

src/asqueel/object_tree.py:88-97 stessi ripieghi (name_short su name_long e poi sul nome; name_full su name_long); name_plural su name_long (src/asqueel/object_tree.py:284); name_full attributo di package, tabella e colonna. Nessun setter: prova, assegnare name_long solleva AttributeError

Motivazione. Ripieghi legacy: decisione utente 2026-10-17; docs/guide/legacy-features.md:94-95. Setter assenti: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/obj.py:109-125
  • asqueel: src/asqueel/object_tree.py:88-97, src/asqueel/object_tree.py:284

Tipi custom del package (custom_type_<dtype>) A1.26

In GenroPy scrivevi

class Package(GnrDboPackage):
    def custom_type_money(self):
        return dict(dtype='N', size='14,2')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/columns.py:126-133 hook del package

In asqueel scrivi

pkg.options().type('money', dtype='N', size='14,2')

In asqueel ottieni

Nel layout legacy Package.custom_type_<dtype>() dà gli attributi della colonna, dtype preso dal risultato e attributi espliciti vincenti (src/asqueel/package_layout.py:76-91); nella ricetta options().type(name, dtype, size, ...) (src/asqueel/model_elements.py:84) risolto in src/asqueel/model_source.py:115. Un dtype sconosciuto solleva ValueError (prova: 'app.t.p: unknown dtype'), nel legacy resta come stringa

Motivazione. Hook legacy: decisione utente 2026-10-17; docs/guide/legacy-features.md:96-100. Errore sul dtype sconosciuto: regola «ogni cosa fuori grammatica raise», decisione utente 2026-10-17

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/columns.py:126-133
  • asqueel: src/asqueel/package_layout.py:76-91, src/asqueel/model_elements.py:84, src/asqueel/model_source.py:115

column(sqldefault=, extra_sql=, generated_expression=) A1.27

In GenroPy scrivevi

tbl.column('qty', dtype='L', sqldefault='0')

In GenroPy ottenevi

Kwargs di colonna letti dal migratore: chiavi gnrpy/gnr/sql/gnrsqlmigration/structures.py:68-69, filtro in gnrpy/gnr/sql/gnrsqlmigration/orm_extractor.py:490

In asqueel scrivi

tbl.column('qty', dtype='L', sql_default='0', sql_sqlite_default='1')

In asqueel ottieni

Colonna sql_default, sql_extra, sql_generatedExpression (src/asqueel/elements.py:266-268) e varianti di dialetto sql_pg_* e sql_sqlite_* che vincono sul loro dialetto (src/asqueel/common.py:35); proiettati verso asqueel-migration con le chiavi legacy sqldefault, extra_sql, generated_expression (src/asqueel/common.py:28). Prova: sqldefault, extra_sql, generated_expression e sql_mysql_default rifiutati

Motivazione. Nomi con prefisso sql_ e varianti per dialetto: decisione utente 2026-10-17; docs/guide/legacy-features.md:55-57

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmigration/structures.py:68-69, gnrpy/gnr/sql/gnrsqlmigration/orm_extractor.py:490
  • asqueel: src/asqueel/elements.py:266-268, src/asqueel/common.py:35, src/asqueel/common.py:28

Cartella di package nel layout legacy: main.py con Package, model/<tabella>.py con Table A1.28

In GenroPy scrivevi

class Package(GnrDboPackage):
    def config_attributes(self):
        return dict(name_long='Sales')

In GenroPy ottenevi

gnrpy/gnr/app/gnrapp.py:640 importa main.py; :654 config_attributes() sopra gli attributi del package; :679-680 tabelle da model/ in ordine alfabetico (loadTableMixinDict)

In asqueel scrivi

pkg = root.packages().package('sales', path='/path/to/sales')

In asqueel ottieni

package(name, path=<cartella>) (src/asqueel/model_elements.py:38) caricato da src/asqueel/package_layout.py:41: config_attributes() sotto gli attributi scritti sull'elemento, Table.config_db(pkg) in ordine alfabetico, classe di runtime Table più SqlTable. Package.config_db, onBuildingDbobj e la Table di main.py sollevano UnsupportedFeatureError (src/asqueel/package_layout.py:55-60); custom.py, _packages e plugin non sono letti

Motivazione. Layout provvisorio, da rivedere: decisione utente 2026-10-18; docs/guide/configuration.md:162-170

File
  • GenroPy: gnrpy/gnr/app/gnrapp.py:640, gnrpy/gnr/app/gnrapp.py:654, gnrpy/gnr/app/gnrapp.py:679-680
  • asqueel: src/asqueel/model_elements.py:38, src/asqueel/package_layout.py:41, src/asqueel/package_layout.py:55-60

formulaColumn (sql_formula, select, exists, dtype) A3.1

In GenroPy scrivevi

tbl.formulaColumn('total', sql_formula='#t', select=dict(...), select_t=dict(...))

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1265 (dtype di default 'A', :1271); gnrpy/gnr/sql/gnrsqldata/compiler.py:400-401: sql_formula prevale, select/exists usati solo se sql_formula manca

In asqueel scrivi

tbl.formulaColumn('label', sql_formula='$code')

In asqueel ottieni

src/asqueel/elements.py:346; senza dtype vale 'A' (src/asqueel/object_tree.py:369, prova); src/asqueel/model.py:49-52 vuole esattamente uno fra sql_formula, select, exists e rifiuta sql_formula insieme a select/exists; :62-63 select_<nome>/exists_<nome> senza sql_formula danno errore; :64-66 un select_<nome> non usato nella formula dà errore (prova)

Motivazione. decisione utente 2026-10-17: dtype 'A' come legacy, errore se sql_formula è passata con select/exists (tests/test_formula_columns_legacy.py:100, :114). I controlli su select_<nome> non usato o senza sql_formula non hanno decisione: nessuna issue. docs/guide/formulas.md:22, :211-214

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1265, gnrpy/gnr/sql/gnrsqlmodel/model.py:1271, gnrpy/gnr/sql/gnrsqldata/compiler.py:400-401
  • asqueel: src/asqueel/elements.py:346, src/asqueel/object_tree.py:369, src/asqueel/model.py:49-52, src/asqueel/model.py:62-66, tests/test_formula_columns_legacy.py:100, tests/test_formula_columns_legacy.py:114

subQueryColumn (query=, mode=) A3.10

In GenroPy scrivevi

tbl.subQueryColumn('codes', query=dict(table='app.invoice', columns='$code',
                   where='$customer_id=#THIS.id'), mode='json')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1213-1262: json (:1233) jsonb_agg, xml (:1247) xmlagg; altri mode: SELECT semplice

In asqueel scrivi

tbl.subQueryColumn('codes', query=dict(table='app.invoice', columns='$code',
                   where='$customer_id=#THIS.id'), mode='json')

In asqueel ottieni

src/asqueel/elements.py:368; src/asqueel/compiler.py:183-199: query= come legacy, mode solo None, 'json', 'xml'; un altro mode dà ValueError che rimanda ad aggr; mode='xml' con query stringa rifiutato; :341-345 json/xml tramite l'adapter (src/asqueel/adapters/postgres.py:113); su SQLite json/xml rifiutati prima dell'esecuzione (src/asqueel/adapters/sqlite.py:191-192, prova)

Motivazione. decisione utente 2026-10-17: query=dict(...) e mode json/xml come legacy, tolti i mode inventati join/to_json/to_xml; aggr è la riga A3.21. Rifiuto degli altri mode: tests/test_subquery_aggr_and_macros.py:154. docs/guide/formulas.md:271-273, :288-292

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1213-1262, gnrpy/gnr/sql/gnrsqlmodel/model.py:1233, gnrpy/gnr/sql/gnrsqlmodel/model.py:1247
  • asqueel: src/asqueel/elements.py:368, src/asqueel/compiler.py:183-199, src/asqueel/compiler.py:341-345, src/asqueel/adapters/postgres.py:113, src/asqueel/adapters/sqlite.py:191-192, tests/test_subquery_aggr_and_macros.py:154

compositeColumn letta come valore A3.13

In GenroPy scrivevi

tbl.compositeColumn('identity', columns='code,year')
tblobj.query(columns='*').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1078-1118 (static=True di default :1079, dtype JS :1112), :1120-1157 formula stringa '["v1", v2]'

In asqueel scrivi

tbl.compositeColumn('identity', columns='code,year')
tblobj.query(columns='$identity').fetch()

In asqueel ottieni

src/asqueel/compiler.py:200-202 e :219-230 stessa formula stringa del legacy; dtype JS (src/asqueel/object_tree.py:366-367); pkey composta letta e filtrata con la stessa formula (tests/test_formula_columns_legacy.py:75, :80). Differenza: src/asqueel/elements.py:408 non accetta static, e '*' non include la compositeColumn (src/asqueel/compiler.py:805-808, prova)

Motivazione. decisione utente 2026-10-17 (A3.13 subito, come legacy; collegata a #26). L'esclusione da '*' e il rifiuto di static non hanno decisione: nessuna issue. docs/guide/formulas.md:177-184

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1078-1118, gnrpy/gnr/sql/gnrsqlmodel/model.py:1120-1157
  • asqueel: src/asqueel/compiler.py:200-202, src/asqueel/compiler.py:219-230, src/asqueel/object_tree.py:366-367, src/asqueel/elements.py:408, src/asqueel/compiler.py:805-808, tests/test_formula_columns_legacy.py:75, tests/test_formula_columns_legacy.py:80

Colonne dinamiche: metodi formulaColumn_<x>() A3.15

In GenroPy scrivevi

def formulaColumn_labels(self):
    return dict(name='loud', sql_formula='upper($code)', dtype='T')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/table.py:405-427 (formulaColumn_*), :369-383 virtuali in cache in currentEnv; un nome già dichiarato viene sostituito (:379-381)

In asqueel scrivi

def formulaColumn_labels(self):
    return dict(name='loud', sql_formula='upper($code)', dtype='T')

In asqueel ottieni

src/asqueel/object_tree.py:224-245 chiama i metodi senza argomenti, dict o lista di dict, attributi in cache in currentEnv['_virtual_columns_<pkg>_<tbl>']; src/asqueel/compiler.py:158-159 le legge dopo le virtuali dichiarate. Differenza: un nome già dichiarato dà ValueError 'redefines a declared column' (src/asqueel/object_tree.py:239-240, prova)

Motivazione. decisione utente 2026-10-17 (da implementare come legacy); tests/test_formula_columns_legacy.py:64, :69; docs/guide/formulas.md:156-175. L'errore sul nome già dichiarato non ha decisione: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:405-427, gnrpy/gnr/sql/gnrsqlmodel/table.py:369-383
  • asqueel: src/asqueel/object_tree.py:224-245, src/asqueel/compiler.py:158-159, tests/test_formula_columns_legacy.py:64, tests/test_formula_columns_legacy.py:69

relation(): destinazione 'pkg.tbl.col' / 'tbl.col', anche verso un altro package A4.1

In GenroPy scrivevi

tbl.column('customer_id').relation('customer.id')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/columns.py:153 _fillRelatedColumn completa 'tbl.col' con il package corrente; gnrpy/gnr/sql/gnrsqlmodel/model.py:256 richiede pkg.table.column

In asqueel scrivi

tbl.column('customer_id').relation('customer.id')

In asqueel ottieni

src/asqueel/model_source.py:155-157 completa 'tbl.col' con il package corrente quando il primo nome non è un package; src/asqueel/object_tree_relations.py:75-82 legge 'x.y' come pkg.tbl quando x è un package e prende la pkey del target; src/asqueel/validators.py:261-265 rifiuta 'tbl' da solo

Motivazione. Stessa risoluzione a 3 e a 2 parti nei casi comuni. In più 'pkg.tbl' punta alla pkey, documentato in docs/grammar.md:416. Nel legacy un percorso a 2 parti è sempre tbl.col: con un package che ha lo stesso nome di una tabella del package corrente asqueel legge pkg.tbl (prova: 'app.code' dentro il package sal cerca il package app). Per questa ambiguità nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/columns.py:153, gnrpy/gnr/sql/gnrsqlmodel/model.py:256
  • asqueel: src/asqueel/model_source.py:155-157, src/asqueel/object_tree_relations.py:75-82, src/asqueel/validators.py:261-265

relation(mode='foreignkey'/'relation'/'insensitive') A4.2

In GenroPy scrivevi

tbl.column('customer_id').relation('customer.id', mode='foreignkey')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1357 mode='relation' di default; gnrpy/gnr/sql/gnrsqlmodel/model.py:268-269 foreignkey e case_insensitive derivati da mode

In asqueel scrivi

tbl.column('customer_id').relation('customer.id', foreign_key=True)

In asqueel ottieni

src/asqueel/elements.py:470 relation(to, foreign_key=, case_insensitive=); mode= rifiutato dalla grammatica chiusa (prova); in un package foreign_key vale True di default e False con cnd (src/asqueel/builder.py:447-451); weak_relation senza FK (src/asqueel/semantic_elements.py:24, src/asqueel/builder.py:438-440); il caricatore legacy traduce mode (src/asqueel/legacy_source.py:115-120). case_insensitive=True non produce lower() nella join, come nel legacy, perché il compilatore confronta il valore con 'Y' (src/asqueel/compiler.py:422, prova)

Motivazione. docs/design/42-configuration-grammar.md:526-528: relation crea la FK, weak_relation no; docs/guide/legacy-features.md:316-319. Nel legacy il default mode='relation' non crea FK; in asqueel il default dentro un package la crea

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1357, gnrpy/gnr/sql/gnrsqlmodel/model.py:268-269
  • asqueel: src/asqueel/elements.py:470, src/asqueel/builder.py:447-451, src/asqueel/semantic_elements.py:24, src/asqueel/builder.py:438-440, src/asqueel/legacy_source.py:115-120, src/asqueel/compiler.py:422

onDelete, onUpdate, onDelete_sql, onUpdate_sql, deferred (solo dichiarazione) A4.6

In GenroPy scrivevi

tbl.column('customer_id').relation('customer.id', onDelete_sql='C')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:1366-1370, onUpdate_sql='cascade' di default; deferred automatico con setnull in gnrpy/gnr/sql/gnrsqlmodel/model.py:276

In asqueel scrivi

tbl.column('customer_id').relation('customer.id', onDelete_sql='C')

In asqueel ottieni

src/asqueel/elements.py:471-473 onDelete, onUpdate, onDelete_sql, onUpdate_sql, deferred; src/asqueel/object_tree_relations.py:35-39 nel joiner, onUpdate_sql 'cascade' di default; src/asqueel/model_source.py:167-171 deferred con setnull; abbreviazioni SQL in src/asqueel/common.py:15; on_delete= rifiutato (prova). Il joiner conserva le azioni in minuscolo e senza spazi, 'no action' diventa None (src/asqueel/object_tree_relations.py:18-25; prova: 'C' diventa 'c'); il legacy le conserva come scritte

Motivazione. decisione utente 2026-10-17 (nomi e significati legacy); docs/guide/legacy-features.md:297-300. La FK proiettata è quella del legacy (prova: ON DELETE CASCADE ON UPDATE SET NULL, DEFERRABLE INITIALLY DEFERRED con setnull). La normalizzazione dei valori nel joiner non ha motivazione registrata: nessuna issue. Gli effetti in scrittura sono dell'area 5

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:1366-1370, gnrpy/gnr/sql/gnrsqlmodel/model.py:276
  • asqueel: src/asqueel/elements.py:471-473, src/asqueel/object_tree_relations.py:35-39, src/asqueel/model_source.py:167-171, src/asqueel/common.py:15, src/asqueel/object_tree_relations.py:18-25

Condizioni di join della relazione: cnd, join_on, between A4.12

In GenroPy scrivevi

tbl.column('rate_id').relation('rate.id', cnd='#IN_RANGE(...)')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqlmodel/model.py:222-224; gnrpy/gnr/sql/gnrsqldata/compiler.py:609-616 sostituiscono la condizione di chiave, #IN_RANGE espanso dentro cnd

In asqueel scrivi

tbl.column('customer_id').relation('customer.id', cnd='$ccode = @customer_id.code')

In asqueel ottieni

src/asqueel/elements.py:483-484 join_on, cnd, between dichiarati; src/asqueel/compiler.py:410-420 sostituiscono la condizione di chiave, join_on prima di cnd (prova); cnd senza FK (src/asqueel/builder.py:448-451, prova); #IN_RANGE dentro cnd rifiutato con 'macro #IN_RANGE is not available in this release' (src/asqueel/compiler.py:429, prova); aggr rifiuta queste relazioni (src/asqueel/compiler.py:604-607). Vale il codice sul commento in src/asqueel/object_tree_relations.py:44, che le dice non eseguite

Motivazione. decisione utente 2026-10-17 (sintassi legacy); docs/guide/legacy-features.md:308-312. Il rifiuto di #IN_RANGE dentro cnd non ha motivazione registrata: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:222-224, gnrpy/gnr/sql/gnrsqldata/compiler.py:609-616
  • asqueel: src/asqueel/elements.py:483-484, src/asqueel/compiler.py:410-420, src/asqueel/builder.py:448-451, src/asqueel/compiler.py:429, src/asqueel/compiler.py:604-607

Nuovo

subQueryColumn(aggr=...) A3.21

In asqueel scrivi

lines = dict(table='app.invoice', where='$customer_id=#THIS.id')
customer.subQueryColumn('total', query=lines, aggr='sum($amount)', dtype='N')
customer.subQueryColumn('lines', query=lines, aggr='count()', dtype='L')

In asqueel ottieni

src/asqueel/elements.py:368-369; src/asqueel/compiler.py:193-198: una chiamata della grammatica .aggr() (count, sum, avg, min, max, join, to_json, to_xml) sostituisce le colonne della query; un solo aggregato; aggr con mode dà ValueError; l'order_by della query ordina join, to_json, to_xml (prova)

Motivazione. decisione utente 2026-10-17 (estensione di subQueryColumn); tests/test_subquery_aggr_and_macros.py:53, :58, :139, :146; docs/guide/formulas.md:275-286

File
  • GenroPy: none
  • asqueel: src/asqueel/elements.py:368-369, src/asqueel/compiler.py:193-198, tests/test_subquery_aggr_and_macros.py:53, tests/test_subquery_aggr_and_macros.py:58, tests/test_subquery_aggr_and_macros.py:139, tests/test_subquery_aggr_and_macros.py:146

exists_<nome>=dict(...) in formulaColumn A3.22

In asqueel scrivi

customer.formulaColumn('has_big', sql_formula='#big', dtype='B',
    exists_big=dict(table='app.invoice', where='$customer_id=#THIS.id AND $amount > 100'))

In asqueel ottieni

src/asqueel/elements.py:345 famiglia exists_; src/asqueel/model.py:54-61 select_<nome> ed exists_<nome> con lo stesso nome danno errore; src/asqueel/compiler.py:254 #<nome> diventa EXISTS (<sottoquery>)

Motivazione. decisione utente 2026-10-17 (estensione approvata); tests/test_formula_columns_legacy.py:54, :59, :122; docs/guide/formulas.md:186-190

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:398
  • asqueel: src/asqueel/elements.py:345, src/asqueel/model.py:54-61, src/asqueel/compiler.py:254, tests/test_formula_columns_legacy.py:54, tests/test_formula_columns_legacy.py:59, tests/test_formula_columns_legacy.py:122

@rel.aggr(...) e @rel.order_by('...').aggr(...) A4.19

In asqueel scrivi

db.table('sales.invoice').query(
    columns='$id, @lines.aggr(count() AS line_count, sum($amount) AS amount)')

In asqueel ottieni

src/asqueel/compiler.py:452 expand_many_operations, :523 many_aggr, :627 subquery correlata; src/asqueel/_many_aggregates.py:21 count, sum, avg, min, max, join, to_json, to_xml; order_by ordina join, to_json, to_xml (prova); solo in proiezione: where e order_by rifiutati (src/asqueel/compiler.py:526, prova)

Motivazione. docs/guide/queries.md:413-451; docs/design/101-many-aggregation-decisions.md; to_xml solo su PostgreSQL (docs/guide/queries.md:451)

File
  • GenroPy: none
  • asqueel: src/asqueel/compiler.py:452, src/asqueel/compiler.py:523, src/asqueel/compiler.py:627, src/asqueel/_many_aggregates.py:21, src/asqueel/compiler.py:526

virtualRelation (relation= derivata, table= diretta, var_*, ask) A4.20

In asqueel scrivi

customer.virtualRelation('direct_invoices', table='app.invoice',
    condition='$customer_id=:var_customer', var_customer='$id')

In asqueel ottieni

src/asqueel/semantic_elements.py:39; src/asqueel/compiler.py:536 ricerca, :566 derivazioni relation= e forma table= (prova su entrambe); ask rifiutato (src/asqueel/compiler.py:577, prova); usabile solo dentro aggr: @nome.col dà 'Relation @nome not found' (prova)

Motivazione. docs/design/109-derived-virtual-relations.md; docs/design/110-direct-virtual-relations.md; docs/design/108-deferred-virtual-relation-ask.md (ask rinviato)

File
  • GenroPy: none
  • asqueel: src/asqueel/semantic_elements.py:39, src/asqueel/compiler.py:536, src/asqueel/compiler.py:566, src/asqueel/compiler.py:577

Relazione ereditata dall'aliasColumn (@alias.col senza .relation() sull'alias) A4.21

In asqueel scrivi

row.aliasColumn('customer_id', '@invoice_id.customer_id')

In asqueel ottieni

src/asqueel/object_tree_relations.py:127-182 registra una relazione weak sull'alias; src/asqueel/elements.py:332-333 (prova: @alias.name attraversa due join)

Motivazione. docs/design/100-alias-relation-inheritance.md:15-18; docs/guide/models.md:166-185; tests/object_tree/test_alias_relations.py:34

File
  • GenroPy: none
  • asqueel: src/asqueel/object_tree_relations.py:127-182, src/asqueel/elements.py:332-333, tests/object_tree/test_alias_relations.py:34

Uguale

  • lastTS / logicalDeletionField / draftField A1.3
  • column(): dtype, size, notnull, unique, indexed, sqlname, comment, group, sql_value A1.5
  • Abbreviazione 'nome::dtype' (colonne fisiche e virtuali) A1.6
  • validate_* A1.8
  • columns / relations A1.21
  • sql_formula=True con sql_formula_<nome>(attr); formule sysFields __protecting_reasons / __invalid_reasons A3.2
  • select= / exists= / select_<nome> con #<nome>, cast, subquery_<nome>() A3.3
  • var_* nelle formule A3.4
  • aliasColumn (relation_path, dtype e metadati propri) A3.6
  • Ridefinizione di una virtuale con tipo diverso (alias, formula, subQuery, py, bagItem) A3.7
  • static=True e '*' A3.9
  • bagItemColumn (bagcolumn, itempath, dtype) A3.11
  • relation(relation_name=...), nome dell'inversa A4.3
  • Nome inverso di default e one_one='*' A4.4
  • one_name, many_name, one_group, many_group, many_order_by A4.5
  • Percorso verso uno @fkey_col.col in columns, where, order_by A4.7
  • Percorso multi-hop @a.@b.c A4.8
  • Relazione su chiave composta (compositeColumn) A4.10
  • Bag relations (table.relations, model.relations) A4.15
  • eager_one, eager_many, external_relation, resolver_kwargs, onDuplicate (attributi della relazione conservati nel joiner) A4.22

Query

Manca

columns di default / columns=None temporanea A2.1

In GenroPy scrivevi

rows = tblobj.query(columns=None).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:157 columns None o vuoto diventa '*'

In asqueel scrivi

rows = db.table('sales.invoice').query().fetch()

In asqueel

src/asqueel/application_table.py:383 default '*'; columns=None e columns='' sollevano UnsupportedFeatureError 'columns=None is not available in this release' (src/asqueel/application_table.py:385-386, prova)

Motivazione. decisione utente 2026-10-17 (rimandato, per ora raise); rifiuto elencato in docs/guide/limitations.md:219

Alternative. (a) trattare None e '' come '*', come il legacy (b) rifiutare None con un errore esplicito e documentarlo (c) lasciarlo fuori: il chiamante omette columns

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:157
  • asqueel: src/asqueel/application_table.py:383
  • asqueel: src/asqueel/application_table.py:385-386
  • asqueel: docs/guide/limitations.md:219

'*@rel', '*@rel1.@rel2', '*prefix_', '*nome', '*@rel.(a,b)' eliminata A2.3

In GenroPy scrivevi

rows = tblobj.query(columns='*@customer_id').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:746 expandMultipleColumns espande le forme con prefisso e relazione; :776 *@rel1.@rel2

In asqueel scrivi

rows = db.table('sales.invoice').query(columns='$id, @customer_id.name, @customer_id.city').fetch()

In asqueel

src/asqueel/compiler.py:816-818 UnsupportedFeatureError "column wildcard '*@customer_id' is not available in this release; use * or explicit columns" (prova)

Motivazione. docs/design/41-deferred-features.md:226 (decisione del 15 ottobre 2026: solo * e colonne esplicite); docs/adattamenti-legacy.md:178-182 (forme rimosse; genropy#623 e genropy#1504)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:746
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:776
  • asqueel: src/asqueel/compiler.py:816-818
  • asqueel: docs/design/41-deferred-features.md:226
  • asqueel: docs/adattamenti-legacy.md:178-182

condizioni env_<tabella>_condition_* temporanea A2.10

In GenroPy scrivevi

db.updateEnv(env_sales_invoice_condition_open='$closed IS NOT TRUE')
rows = db.table('sales.invoice').query().fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:966 condizioni lette da currentEnv e aggiunte alla WHERE

In asqueel scrivi

rows = db.table('sales.invoice').query(where='$closed IS NOT TRUE').fetch()

In asqueel

Nessuna lettura delle condizioni; una chiave env_<tabella>_condition_* nell'ambiente solleva UnsupportedFeatureError all'esecuzione (src/asqueel/runtime.py:433-437, prova)

Motivazione. decisione utente 2026-10-17 (differenza da discutere con il team, proposta di eliminazione); docs/guide/limitations.md:200-201

Alternative. (a) leggere le condizioni dall'ambiente come nel legacy (b) condizioni dichiarate nel modello come policy di riga (c) lasciarlo fuori: where esplicita nel chiamante

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:966
  • asqueel: src/asqueel/runtime.py:433-437
  • asqueel: docs/guide/limitations.md:200-201

ignoreTableOrderBy, _storename, locale, mode, checkPermissions, aliasPrefix temporanea A2.15

In GenroPy scrivevi

rows = tblobj.query(columns='$id', ignoreTableOrderBy=True).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/query.py:130-133 (mode è accettato ma non passato a SqlQuery); gnrpy/gnr/sql/gnrsqldata/compiler.py:912 order_by della tabella

In asqueel scrivi

rows = db.table('sales.invoice').query(columns='$id', order_by='$id').fetch()

In asqueel

src/asqueel/application_table.py:42-48 in _UNSUPPORTED: UnsupportedFeatureError "query option 'ignoreTableOrderBy' is not available in this release" (src/asqueel/application_table.py:400-403, prova). L'order_by della tabella è applicato (src/asqueel/compiler.py:707-708) ma non si può disattivare. subtable: riga A1.25

Motivazione. decisione utente 2026-10-17 (rimandate con raise; storename fuori dal livello basso). Vale il codice su docs/guide/queries.md:126 e docs/guide/limitations.md:289-290 ('nessun ordinamento di default')

Alternative. (a) accettare tutte le opzioni con la semantica legacy (b) accettare solo ignoreTableOrderBy, ignorare mode come fa il legacy (c) lasciarle fuori: order_by esplicito, filtro nella where

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:130-133
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:912
  • asqueel: src/asqueel/application_table.py:42-48
  • asqueel: src/asqueel/application_table.py:400-403
  • asqueel: src/asqueel/compiler.py:707-708

record(pkey= / where=, for_update, ignoreMissing) sostituita A2.21

In GenroPy scrivevi

record = tblobj.record(pkey=10, ignoreMissing=True).output('dict')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/record.py:309; gnrpy/gnr/sql/gnrsqldata/record.py:224 pkey composta come stringa '[...]'; :353 Bag vuota se ignoreMissing

In asqueel scrivi

row = db.table('sales.invoice').query(where='$id = :id', id=10).fetchRecord(ignoreMissing=True)

In asqueel ottieni

src/asqueel/application_table.py:426-427 record() solleva UnsupportedFeatureError 'record is not available in this release; use query(...).fetchRecord()' (prova); al suo posto query(...).fetchRecord() (riga A2.27)

Motivazione. decisione utente 2026-10-17 (record() esce da questa release e torna con la sua revisione; al suo posto fetchRecord()); docs/guide/queries.md:573-574

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:309
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:224
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:353
  • asqueel: src/asqueel/application_table.py:426-427
  • asqueel: docs/guide/queries.md:573-574

record: selettori kwargs, ignoreDuplicate, virtual_columns, lazy/eager temporanea A2.22

In GenroPy scrivevi

record = tblobj.record(code='INV-7', ignoreDuplicate=True, virtual_columns='$total').output('dict')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/record.py:305 where dai kwargs colonna=valore; :360-366 scarta i duplicati cancellati, poi ignoreDuplicate; gnrpy/gnr/sql/gnrsqldata/compiler.py:1105 virtual_columns

In asqueel scrivi

row = db.table('sales.invoice').query(columns='*, $total', where='$code = :code', code='INV-7').fetchRecord()

In asqueel

record() solleva errore (src/asqueel/application_table.py:426-427); lazy, eager, virtual_columns, ignoreMissing, ignoreDuplicate come opzioni di query sono in _UNSUPPORTED (src/asqueel/application_table.py:47) e sollevano UnsupportedFeatureError (prova)

Motivazione. decisione utente 2026-10-17 (tutto il discorso record è rimandato alla sua revisione; per ora raise)

Alternative. (a) portare tutti i parametri con la semantica legacy, compreso il filtro sui duplicati cancellati (b) solo selettori kwargs e virtual_columns, senza ignoreDuplicate (c) lasciarli fuori: where esplicita in query(...).fetchRecord()

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:305
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:360-366
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:1105
  • asqueel: src/asqueel/application_table.py:426-427
  • asqueel: src/asqueel/application_table.py:47

record mode / output(mode) temporanea A2.23

In GenroPy scrivevi

bag = tblobj.record(pkey=10).output('bag')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/record.py:263 out_<mode>: bag (con resolver), dict, record, json, template, sample, newrecord

In asqueel scrivi

row = db.table('sales.invoice').query(where='$id = :id', id=10).fetchRecord()
data = dict(row)

In asqueel

Nessun oggetto record: record() solleva errore (src/asqueel/application_table.py:426-427); fetchRecord() restituisce solo la riga NamedList

Motivazione. decisione utente 2026-10-17 (output bag/json rimandati alla revisione di record); newrecord: #28; docs/guide/queries.md:573-574

Alternative. (a) aggiungere bag con resolver e gli altri modi legacy (b) aggiungere bag senza resolver e json (c) lasciarli fuori: la riga di fetchRecord() o dict(riga)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:263
  • asqueel: src/asqueel/application_table.py:426-427
  • asqueel: docs/guide/queries.md:573-574

Copia memorizzata del record e sua rilettura (refresh) eliminata A2.24

In GenroPy scrivevi

record = tblobj.record(pkey=10)
first = record.output('dict')
again = record.output('dict')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/record.py:313 _result in cache, senza metodo di ricarica

In asqueel scrivi

row = db.table('sales.invoice').query(where='$id = :id', id=10).fetchRecord()

In asqueel

Nessun oggetto record e nessuna cache: fetchRecord() esegue la query a ogni chiamata

Motivazione. decisione utente 2026-10-17 (copia memorizzata e refresh() non si tengono; per rileggere si usano i resolver)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/record.py:313

readColumns, countRecords, recordAs, cachedRecord/tableCachedData, db.query(table) temporanea A2.25

In GenroPy scrivevi

name = tblobj.readColumns(pkey=10, columns='$name')
total = tblobj.countRecords()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:447, :476; gnrpy/gnr/sql/gnrsqltable/record.py:346, :183, :241; gnrpy/gnr/sql/gnrsql/query.py:253

In asqueel scrivi

row = db.table('sales.customer').query(columns='$name', where='$id = :id', id=10).fetchRecord()
total = db.table('sales.customer').query(exclude_logical_deleted=False, exclude_draft=False).count()

In asqueel

src/asqueel/application_table.py:376-379 readColumns, countRecords, recordAs, cachedRecord sollevano UnsupportedFeatureError; db.query in src/asqueel/runtime.py:60; tableCachedData dà AttributeError (prova)

Motivazione. decisione utente 2026-10-17 (rimandati con raise); docs/guide/limitations.md:214-218

Alternative. (a) portare gli helper con firme e cache legacy (b) solo readColumns e recordAs, senza cache (c) lasciarli fuori: query(...).fetch() e query(...).fetchRecord()

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:447
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:476
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:346
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:183
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:241
  • GenroPy: gnrpy/gnr/sql/gnrsql/query.py:253
  • asqueel: src/asqueel/application_table.py:376-379
  • asqueel: src/asqueel/runtime.py:60
  • asqueel: docs/guide/limitations.md:214-218

sqlWhereFromBag / whereTranslator / opTranslate temporanea A2.26

In GenroPy scrivevi

where, sqlArgs = tblobj.sqlWhereFromBag(wherebag)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/query.py:91, :95, :85 WHERE costruita da una Bag tramite l'adapter

In asqueel scrivi

rows = db.table('sales.invoice').query(where='$total >= :minimum', minimum=100).fetch()

In asqueel

src/asqueel/application_table.py:380-381 sqlWhereFromBag e whereTranslator sollevano UnsupportedFeatureError; db.whereTranslator in src/asqueel/runtime.py:61; opTranslate dà AttributeError (prova)

Motivazione. decisione utente 2026-10-17 (rimandati con raise); docs/guide/limitations.md:214-218

Alternative. (a) portare il whereTranslator dell'adapter (b) un traduttore ridotto da mapping a where con parametri legati (c) lasciarlo fuori: where scritta dal chiamante

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:91
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:95
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:85
  • asqueel: src/asqueel/application_table.py:380-381
  • asqueel: src/asqueel/runtime.py:61
  • asqueel: docs/guide/limitations.md:214-218

Macro del dialetto #TSQUERY, #TSRANK, #TSHEADLINE, #VECQUERY, #VECRANK temporanea A3.20

In GenroPy scrivevi

rows = tblobj.query(columns='$id', where='#TSQUERY($body, :search)', search='invoice').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:421 (formule), :964 (WHERE); gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:13-46

In asqueel scrivi

rows = db.table('app.doc').query(columns='$id', where="to_tsvector($body) @@ plainto_tsquery(:search)", search='invoice').fetch()

In asqueel

Rifiutate con UnsupportedFeatureError 'macro #<NOME> is not available in this release' (src/asqueel/compiler.py:649-656); tests/test_specific_errors.py:109; tests/object_tree/test_phase4_macros.py:80-83

Motivazione. decisione utente 2026-10-17: rimandate, raise con un messaggio che nomina la macro

Alternative. (a) portare il MacroExpander del dialetto come nel legacy (b) funzioni esplicite del dialetto PostgreSQL con parametri legati (c) lasciarle fuori: SQL full-text esplicito in sql_formula o where

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:421
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:964
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:13-46
  • asqueel: src/asqueel/compiler.py:649-656
  • asqueel: tests/test_specific_errors.py:109
  • asqueel: tests/object_tree/test_phase4_macros.py:80-83

#PREF(path[,default]) temporanea A3.24

In GenroPy scrivevi

tbl.formulaColumn('vat', sql_formula='$amount * #PREF(vat_rate,0.22)')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:315-319 (preferenza del package), applicata in :428 come testo letterale

In asqueel scrivi

tbl.formulaColumn('vat', '$amount * :env_vat_rate', dtype='N')

In asqueel

Rifiutata con UnsupportedFeatureError 'macro #PREF is not available in this release' (src/asqueel/compiler.py:649-656); tests/test_specific_errors.py:109; tests/object_tree/test_phase4_macros.py:80-83

Motivazione. decisione utente 2026-10-17: rimandata, raise con un messaggio che nomina la macro

Alternative. (a) espandere come nel legacy, testo letterale (b) tradurla in un parametro legato letto da un provider di preferenze (c) lasciarla fuori: usare var_* o :env_nome

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:315-319
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:428
  • asqueel: src/asqueel/compiler.py:649-656
  • asqueel: tests/test_specific_errors.py:109
  • asqueel: tests/object_tree/test_phase4_macros.py:80-83

Diverso

addPkeyColumn / colonna 'pkey' automatica A2.4

In GenroPy scrivevi

rows = tblobj.query(columns='$name').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/query.py:130 default True; gnrpy/gnr/sql/gnrsqldata/compiler.py:949 aggiunge $pkey AS pkey se la query non aggrega

In asqueel scrivi

rows = db.table('sales.customer').query(columns='$name', addPkeyColumn=True).fetch()

In asqueel ottieni

src/asqueel/compiler.py:738-739 aggiunge 'pkey' solo con addPkeyColumn=True; default False (src/asqueel/compiler.py:692); prova: senza opzione la riga non ha 'pkey'

Motivazione. docs/adattamenti-legacy.md:118-126 (decisione del 2 ottobre 2026: la colonna la aggiunge l'adattatore legacy); docs/guide/limitations.md:236-237

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:130
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:949
  • asqueel: src/asqueel/compiler.py:738-739
  • asqueel: src/asqueel/compiler.py:692
  • asqueel: docs/adattamenti-legacy.md:118-126
  • asqueel: docs/guide/limitations.md:236-237

kwargs come parametri di query A2.8

In GenroPy scrivevi

rows = tblobj.query(where='$total >= :minimum', minimun=100).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:162 sqlparams.update(kwargs): una chiave non usata viene ignorata

In asqueel scrivi

rows = db.table('sales.invoice').query(where='$total >= :minimum', minimun=100).fetch()

In asqueel ottieni

src/asqueel/application_table.py:408 le kwargs diventano parametri; una kwarg non usata resta parametro, la query parte e src/asqueel/application_table.py:103-112 emette UnusedQueryParameterWarning (src/asqueel/contracts.py:31) con il nome della kwarg e i parametri usati (prova)

Motivazione. decisione utente 2026-10-17 (come legacy, più un warning dedicato che nomina la kwarg); docs/guide/queries.md:276-279

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:162
  • asqueel: src/asqueel/application_table.py:408
  • asqueel: src/asqueel/application_table.py:103-112
  • asqueel: src/asqueel/contracts.py:31
  • asqueel: docs/guide/queries.md:276-279

for_update A2.12

In GenroPy scrivevi

rows = tblobj.query(columns='$customer_id', group_by='$customer_id', for_update=True).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:176 forza bagFields; FOR UPDATE nella SQL

In asqueel scrivi

rows = db.table('sales.invoice').query(columns='$id', for_update='NOWAIT').fetch()

In asqueel ottieni

src/asqueel/compiler.py:729 forza bagFields come il legacy; accetta True, 'NOWAIT', 'SKIP LOCKED' (src/asqueel/compiler.py:694-695); rifiuta for_update con distinct o group_by alla costruzione della query (src/asqueel/application_table.py:406-407, prova)

Motivazione. decisione utente 2026-10-17 (errore anticipato: PostgreSQL non blocca righe raggruppate); docs/guide/queries.md:483-484; modi NOWAIT/SKIP LOCKED: riga A8.11

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:176
  • asqueel: src/asqueel/compiler.py:729
  • asqueel: src/asqueel/compiler.py:694-695
  • asqueel: src/asqueel/application_table.py:406-407
  • asqueel: docs/guide/queries.md:483-484

ignorePartition e filtro di partizione A2.14

In GenroPy scrivevi

tbl = pkg.table('document', pkey='id', partition_organization_id='organization')
rows = db.table('app.document').query().fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:971 getPartitionCondition; ripiego su __allowed_partition in gnrpy/gnr/sql/gnrsqltable/columns.py:85-100

In asqueel scrivi

with db.tempEnv(current_organization=10):
    rows = db.table('app.document').query().fetch()

In asqueel ottieni

src/asqueel/compiler.py:876-891 current_<path>, poi allowed_<path>; valori falsi e contesto mancante non filtrano (prova); manca il ripiego su __allowed_partition

Motivazione. decisione utente 2026-10-17 (per ora come legacy, revisione da discutere); il ripiego mancante è scritto in docs/guide/limitations.md:196-197, nessuna issue; docs/guide/legacy.md:48-54

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:971
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/columns.py:85-100
  • asqueel: src/asqueel/compiler.py:876-891
  • asqueel: docs/guide/limitations.md:196-197
  • asqueel: docs/guide/legacy.md:48-54

#THIS, #IN_RANGE, #PERIOD A3.17

In GenroPy scrivevi

tbl.column('tier_id').relation('app.tier.id', cnd='#IN_RANGE($total,@tier_id.low,@tier_id.high)')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:74, :417, :424 (#THIS); :426, :614, :962 (#IN_RANGE in formule, cnd di relazione, WHERE); :963 (#PERIOD solo nel WHERE)

In asqueel scrivi

owner.formulaColumn('within', '#IN_RANGE($id,:low,:high)', var_low=0, var_high=9, dtype='B')

In asqueel ottieni

src/asqueel/compiler.py:258 (#THIS espanso prima della scoperta dei riferimenti), :323 (#THIS nel where delle sottoquery); :261, :746 (#IN_RANGE in formule e WHERE); :747, :827-858 (#PERIOD solo nel WHERE, decodifica di genro_toolbox con workdate e locale). Differenza: #IN_RANGE nella cnd di una relazione dà 'macro #IN_RANGE is not available in this release' (src/asqueel/compiler.py:429, prova)

Motivazione. nessuna issue per #IN_RANGE nella cnd. tests/object_tree/test_phase4_macros.py:49-78

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:74
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:614
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:963
  • asqueel: src/asqueel/compiler.py:258
  • asqueel: src/asqueel/compiler.py:827-858
  • asqueel: src/asqueel/compiler.py:429
  • asqueel: tests/object_tree/test_phase4_macros.py:49-78

#ENV(nome[,fallback]) A3.18

In GenroPy scrivevi

tbl.formulaColumn('who', sql_formula='#ENV(user)')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:321-350 (env diretto, fallback, env_<nome>() della tabella, altrimenti il testo 'Not found <nome>'), applicato in :427 come testo letterale senza raddoppiare gli apici

In asqueel scrivi

with db.tempEnv(user="O'Hara"):
    rows = db.table('app.customer').query(columns='$who').fetch()

In asqueel ottieni

src/asqueel/compiler.py:239-240 e :266-277: stessa sequenza (chiave, fallback, env_<nome>()), solo nella sql_formula di una formulaColumn. Differenze: apici raddoppiati nel valore; senza chiave né metodo ValueError che nomina la chiave; #ENV altrove dà ValueError (src/asqueel/compiler.py:651-652, prova)

Motivazione. decisione utente 2026-10-17 (#ENV subito come legacy); tests/test_subquery_aggr_and_macros.py:66-95; docs/guide/formulas.md:303-317. Apici raddoppiati ed errori espliciti: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:321-350
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:427
  • asqueel: src/asqueel/compiler.py:239-240
  • asqueel: src/asqueel/compiler.py:266-277
  • asqueel: src/asqueel/compiler.py:651-652
  • asqueel: tests/test_subquery_aggr_and_macros.py:66-95
  • asqueel: docs/guide/formulas.md:303-317

#BAG / #BAGCOLS nelle colonne A3.19

In GenroPy scrivevi

rows = tblobj.query(columns='$id, #BAG(@customer_id.payload) AS doc').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/compiler.py:69-70, :1003-1004, :1283-1316; decodifica Bag dopo il fetch in gnrpy/gnr/sql/gnrsqldata/query.py:282-300

In asqueel scrivi

rows = db.table('app.customer').query(columns='$id,#BAG($payload) AS doc', order_by='$id').fetch()

In asqueel ottieni

src/asqueel/compiler.py:716-718 e :279-286 espansi nelle colonne della query principale; src/asqueel/application_table.py:142-150 handleBagColumns dopo fetch, fetchRecord e cursore; tests/test_subquery_aggr_and_macros.py:106, :114. Differenze: un path di relazione (@rel.col) dà ValueError; #BAG fuori dalle colonne dà ValueError (src/asqueel/compiler.py:653-654)

Motivazione. decisione utente 2026-10-17 (#BAG e #BAGCOLS subito come legacy). Il rifiuto del path di relazione non ha decisione: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:69-70
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/compiler.py:1283-1316
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:282-300
  • asqueel: src/asqueel/compiler.py:716-718
  • asqueel: src/asqueel/compiler.py:279-286
  • asqueel: src/asqueel/application_table.py:142-150
  • asqueel: src/asqueel/compiler.py:653-654
  • asqueel: tests/test_subquery_aggr_and_macros.py:106

Nuovo

query(...).fetchRecord(ignoreMissing=False) A2.27

In asqueel scrivi

customer = db.table('sales.customer')
first = customer.query(where='$id = :id', id=1).fetchRecord()
missing = customer.query(where='$id = :id', id=99).fetchRecord(ignoreMissing=True)
lowest = customer.query(order_by='$id').fetchRecord()

In asqueel ottieni

src/asqueel/application_table.py:222-242: una riga restituisce la riga nel formato di fetch(); nessuna riga RecordNotFoundError (:25) o None con ignoreMissing; più righe RecordMultipleRowsError (:29); con order_by esplicito compila con LIMIT 1 e prende la prima (prova); usato dalla rilettura con lock di delete (src/asqueel/table_crud.py:79-84) e da insertOrUpdate (src/asqueel/table_crud.py:66-67)

Motivazione. decisione utente 2026-10-17 (terminale semplice al posto di record()); docs/guide/queries.md:562-574

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:309
  • asqueel: src/asqueel/application_table.py:222-242
  • asqueel: src/asqueel/table_crud.py:79-84
  • asqueel: src/asqueel/table_crud.py:66-67
  • asqueel: docs/guide/queries.md:562-574

#TEMPLATE('${n} ...', espressione AS n, ...) A3.23

In asqueel scrivi

query = model.table('app.person').query(
    "#TEMPLATE('${i}: ${n}', $id AS i, $name AS n) AS tag",
    where="#TEMPLATE('${n}!', $name AS n) = :value", sqlparams={'value': 'Ada!'})

In asqueel ottieni

src/asqueel/compiler.py:65-102 espansione nella concatenazione del dialetto (src/asqueel/adapters/base.py:98, src/asqueel/adapters/sqlite.py:206); NULL diventa testo vuoto; $$ è un $ letterale; usata in sql_formula (:246) e in colonne, where, order_by, group_by, having (:712-714); tests/test_template_macro.py:71, :76, :84, :98

Motivazione. decisione utente 2026-10-17 (proposta #TEMPLATE: va subito nelle macro; marcatore $)

File
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:1250
  • asqueel: src/asqueel/compiler.py:65-102
  • asqueel: src/asqueel/adapters/base.py:98
  • asqueel: src/asqueel/adapters/sqlite.py:206
  • asqueel: tests/test_template_macro.py:71

Uguale

  • espansione '*' e bagFields A2.2
  • nome automatico (colToAs) A2.5
  • $col, $pkey, @rel verso uno in columns/where/order_by A2.6
  • sqlparams e parametri simbolici ($col/@rel nel valore) A2.7
  • parametro :env_nome A2.9
  • distinct, group_by, having, limit, offset A2.11
  • excludeLogicalDeleted (anche 'mark'), excludeDraft A2.13
  • fetch() A2.16
  • fetchAsDict, fetchAsBag, fetchGrouped, fetchPkeys, fetchAsJson, cursor, servercursor, serverfetch, iterfetch A2.17
  • count() A2.18
  • compiled, sqltext, sqlparams, test() A2.19
  • Riferimenti nelle formule: $col, @rel.col, :param, :env_* A3.16

Read-Write

Manca

raw_insert / raw_update / raw_delete e insertMany sostituita A5.17

In GenroPy scrivevi

tbl.raw_insert(record)
tbl.raw_update(record, old_record=old)
tbl.insertMany(records)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:67, :77, :86, :91; gnrpy/gnr/sql/gnrsql/write.py:180-220 senza trigger ma con _onDbChange(_raw=True); insertMany (gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:821) assegna newPkeyValue alle chiavi mancanti (:829) e usa le colonne del primo record

In asqueel scrivi

customer.sql_insert([{"id": 11, "name": "Grace"}, {"id": 12, "name": "Alan"}])
customer.sql_update({"id": 11, "name": "Grace Hopper"})
customer.sql_delete({"id": 12})

In asqueel ottieni

Assenti (prova: AttributeError). Al loro posto src/asqueel/application_table.py:434-477 sql_insert (record o lista), sql_update, sql_delete: solo SQL, nessun _onDbChange, nessun controllo require_write_capabilities (prova: sql_insert riuscito su una tabella con colonna default); chiavi mancanti assegnate con checkPkey, interi consecutivi nella lista (src/asqueel/application_table.py:453-468); lista: un executemany per gruppo di record consecutivi con le stesse colonne (src/asqueel/write_compiler.py:136-150); tests/write_compiler/test_write_compiler.py:266, :259; tests/test_record_writes_legacy.py:103

Motivazione. decisione utente 2026-10-17: restano sql_insert, sql_update, sql_delete; niente insertMany, sql_insert con una lista assegna la pkey mancante come legacy. docs/guide/writes.md:163-190. Vale il codice su docs/adattamenti-legacy.md:48-51, :55-56, :75-78 e :93 (raw_* con hook DB)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:67
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:180-220
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:821
  • asqueel: src/asqueel/application_table.py:434-477
  • asqueel: src/asqueel/write_compiler.py:136-150
  • asqueel: tests/write_compiler/test_write_compiler.py:266

sql_deleteSelection(where, _pkeys) sostituita A5.18

In GenroPy scrivevi

tbl.sql_deleteSelection(where='$total < :minimum', minimum=10)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:280-295 legge le pkey con lock, poi DELETE ... IN :pkeyList senza trigger

In asqueel scrivi

deleted = tbl.query(where="$total < :minimum", minimum=10).sql_delete()

In asqueel ottieni

Assente (prova: AttributeError). Al suo posto query(...).sql_delete(): src/asqueel/application_table.py:244-249, un solo DELETE ... WHERE (pk) IN (SELECT della query) con filtri, ordine e limit della query (src/asqueel/write_compiler.py:227-231, :275-282); restituisce il numero di righe (src/asqueel/application_table.py:251-255); group_by e having rifiutati; tests/write_compiler/test_write_compiler.py:227, :253

Motivazione. decisione utente 2026-10-17: query(...).sql_delete() resta tra le novità, in blocco con un solo statement senza trigger; decisione utente 2026-10-15: sql_deleteSelection tolto dal core. docs/guide/writes.md:192-209

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:280-295
  • asqueel: src/asqueel/application_table.py:244-255
  • asqueel: src/asqueel/write_compiler.py:227-231
  • asqueel: tests/write_compiler/test_write_compiler.py:253

deleteSelection (con trigger) sostituita A5.20

In GenroPy scrivevi

tbl.deleteSelection(where='$status = :s', s='closed')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:297-324 query con lock, poi delete(r) per ogni riga; restituisce la selezione

In asqueel scrivi

rows = tbl.query(where="$status = :s", s="closed").delete()

In asqueel ottieni

Assente (prova: AttributeError). Al suo posto query(...).delete(): src/asqueel/application_table.py:264-272 rilegge le righe con tutte le colonne fisiche, bagFields e for_update (src/asqueel/application_table.py:292-302), poi delete() per ogni riga con la chiave come stringa, così una riga già tolta da una cascata è saltata; restituisce le righe; filtri di default della query (bozze e righe cancellate logicamente escluse), mentre deleteSelection le include per default (prova); group_by e having rifiutati; tests/test_selection_writes_legacy.py:82

Motivazione. decisione utente 2026-10-17: deleteSelection sostituito al basso livello da query(...).delete(), che cancella record per record con il ciclo di scrittura. docs/guide/writes.md:134-149

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:297-324
  • asqueel: src/asqueel/application_table.py:264-272
  • asqueel: src/asqueel/application_table.py:292-302
  • asqueel: tests/test_selection_writes_legacy.py:82

batchUpdate(updater, where/pkey/_pkeys, _raw_update, autocommit, ...) temporanea A5.21

In GenroPy scrivevi

tbl.batchUpdate({'note': 'Reviewed'}, where='$total >= :m', m=100, autocommit=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:349-387 e :389-436 query con lock, updater dict o callable, update() per riga, autocommit e deferredTotalize

In asqueel

Assente (prova: AttributeError). query(...).update(updater) copre l'updater dict o callable (riga A5.31); query(...).sql_update() scrive senza hook (riga A5.19)

Motivazione. #32; decisione utente 2026-10-17: batchUpdate è API di alto livello, fuori da questa release. docs/guide/limitations.md:52

Alternative. (a) portarlo come nel legacy, sopra query(...).update() (b) aggiungere a query(...).update() solo le opzioni _raw_update e autocommit (c) lasciarlo fuori: query(...).update() o query(...).sql_update()

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:349-387
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:389-436

recordToUpdate(pkey, ...) (RecordUpdater) temporanea A5.22

In GenroPy scrivevi

with tbl.recordToUpdate(pkey) as record:
    record['status'] = 'closed'

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:345; gnrpy/gnr/sql/gnrsqltable/helpers.py:190 context manager: legge con lock, poi update, insert (insertMissing) o delete (pkey False), anche raw

In asqueel

Assente (prova: AttributeError)

Motivazione. #17; decisione utente 2026-10-17: recordToUpdate è API di alto livello, fuori da questa release

Alternative. (a) portare RecordUpdater come nel legacy (b) un context manager ridotto: solo update e delete di una riga esatta (c) lasciarlo fuori: query(..., for_update=True).fetchRecord() e update(record, old_record)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:345
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/helpers.py:190

newrecord(assignId, _fromRecord, **kwargs), defaultValues, extendDefaultValues, recordCopy temporanea A5.24

In GenroPy scrivevi

record = tbl.newrecord(assignId=True, name='Ada')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/record.py:161-177; :600 defaultValues; :543 extendDefaultValues (defaultFrom); :141 recordCopy

In asqueel

Assenti (prova: AttributeError); una colonna con default o default_from blocca la scrittura (src/asqueel/model_source.py:251, prova: 'write execution of n.default is not available in this release')

Motivazione. #28; docs/guide/limitations.md:42

Alternative. (a) portarli come nel legacy, con Bag e defaultFrom (b) newrecord che restituisce un dict con i soli default dichiarati (c) lasciarli fuori: valori iniziali nel chiamante o in trigger_onInserting

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:161-177
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:600
  • asqueel: src/asqueel/model_source.py:251

touchRecords(_pkeys, method, ...) temporanea A5.25

In GenroPy scrivevi

tbl.touchRecords(where='$status = :s', s='open')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/triggers.py:52-100 riscrive o applica un metodo a una selezione

In asqueel

Assente (prova: AttributeError)

Motivazione. nessuna issue. decisione utente 2026-10-15: per ora non si fa

Alternative. (a) portarlo come nel legacy, con _notUserChange (b) un terminale della query che riesegue il ciclo update sulle righe (c) lasciarlo fuori: query(...).update() con un updater vuoto

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/triggers.py:52-100

Effetti di _onDbChange: totalizer_<x>, logChanges, notify temporanea A5.26

In GenroPy scrivevi

tbl = pkg.table('invoice', pkey='id', logChanges=True, notify=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/write.py:50-85 updateTotalizers, onLogChange e change log; :87-131 NOTIFY 'dbevent'

In asqueel

Nessun _onDbChange (prova: AttributeError sul database). totalizer_<x>, logChanges e notify rifiutati dalla grammatica della tabella come attributi non dichiarati (prova)

Motivazione. nessuna issue. decisione utente 2026-10-17: totalizer_<x> da decidere, per ora assenti; ogni dichiarazione rinviata solleva errore. docs/guide/hooks.md:120-128

Alternative. (a) eseguirli dopo ogni scrittura come nel legacy (b) solo NOTIFY e change log, senza totalizer (c) lasciarli fuori: trigger after dell'applicazione o trigger del database

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:50-85
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:87-131
  • asqueel: docs/guide/hooks.md:120-128

check_updatable / check_deletable / isDraft / islocked_write / islocked_delete / hasProtectionColumns temporanea A5.29

In GenroPy scrivevi

if tbl.check_deletable(record):
    tbl.delete(record)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/triggers.py:152, :186-231 protect_* senza eccezione, readOnly della tabella, campo bozza

In asqueel

Assenti (prova: AttributeError). La proprietà draftField esiste (src/asqueel/object_tree.py:280)

Motivazione. nessuna issue. decisione utente 2026-10-17: check_updatable e check_deletable sono API di alto livello, fuori da questa release; isDraft e islocked_* compaiono nell'elenco delle funzioni da tenere come legacy e non sono realizzati

Alternative. (a) portarli come nel legacy (b) solo check_updatable e check_deletable sopra protect_* (c) lasciarli fuori: il chiamante intercetta le eccezioni di protect_*

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/triggers.py:152
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/triggers.py:186-231
  • asqueel: src/asqueel/object_tree.py:280

Helper di tabella: setColumns, changePrimaryKeyValue, empty, fillFromSqlTable, duplicateRecord e copia/incolla temporanea A5.30

In GenroPy scrivevi

tbl.setColumns(pkey, status='closed')
tbl.duplicateRecord(pkey, howmany=2)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:438, :96, :269, :273; gnrpy/gnr/sql/gnrsqltable/copy.py:251 duplicateRecord con onDuplicating, :107 onCopyRecord, :163 onPasteRecord

In asqueel

Assenti (prova: AttributeError)

Motivazione. nessuna issue (recordCopy: #28). decisione utente 2026-10-17: fuori da questa release

Alternative. (a) portarli come nel legacy (b) solo setColumns ed empty, senza copia e incolla (c) lasciarli fuori: query(...).fetchRecord(), update, insert e query(...).sql_delete()

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:438
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/copy.py:251

storename, use_store, store multipli ('*' o lista), currentStorename, connection, connectionKey, get_dbname temporanea A6.7

In GenroPy scrivevi

with db.tempEnv(storename='customer_a'):
    tbl.query().fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/env.py:131, :139, :204; gnrpy/gnr/sql/gnrsql/connections.py:59, :104-121; esecuzione parallela su più store in gnrpy/gnr/sql/gnrsql/execute.py:137-145

In asqueel

Uno storename in ambiente diverso da '_main_db' solleva UnsupportedFeatureError "environment key storename=...: database stores are not available in this release" (src/asqueel/runtime.py:123-126; prova con 'other' e '*'); execute(storename=...) solleva 'storename is not available in this release' (:203-206). use_store, currentStorename, connection, connectionKey, get_dbname assenti (prova)

Motivazione. decisione utente 2026-10-17: questa release è il livello basso; storename e store multipli sono fuori e sollevano errore. Nessuna issue. docs/guide/transactions.md:50

Alternative. (a) store come nel legacy: connessione per (storename, connectionName) ed esecuzione su più store (b) solo selezione di un singolo store, senza esecuzione multi-store (c) lasciarlo fuori: un AsqueelDb per database fisico o una connessione con nome dichiarata

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/env.py:131
  • GenroPy: gnrpy/gnr/sql/gnrsql/connections.py:104-121
  • GenroPy: gnrpy/gnr/sql/gnrsql/execute.py:137-145
  • asqueel: src/asqueel/runtime.py:123-126
  • asqueel: src/asqueel/runtime.py:203-206

autoCommit, dbevents, systemDbEvent, GnrMissedCommitException, tblobj.dbevents / notifyDbUpdate temporanea A6.14

In GenroPy scrivevi

tbl.notifyDbUpdate(record)
db.autoCommit()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:210-233; gnrpy/gnr/sql/gnrsql/helpers.py:152; gnrpy/gnr/sql/gnrsqltable/triggers.py:45-50

In asqueel

Assenti su db e su tabella: hasattr restituisce False (prova)

Motivazione. nessuna issue. decisione utente 2026-10-15: asqueel solo database; dbevents e notifyDbEvent si riprendono con l'applicazione

Alternative. (a) portarli come nel legacy, con dbevents in currentEnv per connessione (b) metodi vuoti nel core, riempiti dal livello applicativo (c) lasciarli fuori: deferAfterCommit per le notifiche applicative

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:210-233
  • GenroPy: gnrpy/gnr/sql/gnrsql/helpers.py:152
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/triggers.py:45-50

NOTIFY: attributo di tabella notify, db.notify, db.listen temporanea A6.16

In GenroPy scrivevi

db.listen('dbevent', cb)
db.notify('dbevent', payload)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/write.py:87-131 _dbNotify invia NOTIFY 'dbevent' con il payload; gnrpy/gnr/sql/gnrsql/transactions.py:289-305 listen e notify delegati all'adapter

In asqueel

db.listen e db.notify assenti (prova). L'attributo di tabella notify è rifiutato dalla grammatica con ValueError "Element 'table' does not accept 'notify'" (prova). src/asqueel/db_write.py:5 esclude _onDbChange e NOTIFY dal ciclo di scrittura

Motivazione. decisione utente 2026-10-17: db.listen e db.notify rimandati, previsti come capability dell'adapter PostgreSQL nel catalogo delle capability. Nessuna issue

Alternative. (a) _dbNotify e listen/notify come nel legacy, solo PostgreSQL (b) NOTIFY registrato con deferAfterCommit nel livello applicativo (c) lasciarlo fuori: il progetto chiama NOTIFY con db.execute

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:87-131
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:289-305
  • asqueel: src/asqueel/db_write.py:5

Diverso

insert(record) A5.1

In GenroPy scrivevi

record = tbl.insert({'id': 3, 'name': 'Katherine'})

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:57 delega a db.insert e restituisce il record; gnrpy/gnr/sql/gnrsql/write.py:134-168 ciclo: checkPkey, protect_validate, trigger di campo, trigger di tabella, trigger di package (:157), contatori, dbo_onInserting (:159-160), protect_draft, INSERT, _onDbChange (:165), trigger after

In asqueel scrivi

record = customer.insert({"id": 3, "name": "Katherine"})

In asqueel ottieni

src/asqueel/table_crud.py:31-34 delega a db.insert e restituisce il record; src/asqueel/db_write.py:21-33 stesso ordine senza trigger di package, dbo_onInserting e _onDbChange; una tabella con dichiarazioni non eseguite è rifiutata prima degli hook (src/asqueel/db_write.py:22, src/asqueel/model_source.py:231); tests/write_compiler/test_write_cycle.py:130

Motivazione. docs/guide/writes.md:34-37; docs/guide/hooks.md:120-128 (niente _onDbChange, trigger di package non eseguiti); docs/guide/limitations.md:31-45 (rifiuti). Dettagli nelle righe A5.7, A5.8, A5.16, A5.26

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:57
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:134-168
  • asqueel: src/asqueel/table_crud.py:31-34
  • asqueel: src/asqueel/db_write.py:21-33
  • asqueel: src/asqueel/model_source.py:231

update(record, old_record=None, pkey=None) A5.2

In GenroPy scrivevi

tbl.update(record, old_record=old)
tbl.update(record, pkey='["a", "2026-01-01::D"]')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:118-148, storename del package in tempEnv (:129-136); gnrpy/gnr/sql/gnrsql/write.py:223-265; pkey composta come stringa serializzata (gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:867)

In asqueel scrivi

customer.update(record, old_record)
customer.update({"id": 3, "name": "Katherine Johnson"})

In asqueel ottieni

src/asqueel/table_crud.py:36-47; src/asqueel/db_write.py:35-54; chiave da old_record, poi pkey, poi record (src/asqueel/write_compiler.py:186-193); pkey composta come stringa serializzata con toTypedJSON, tupla o mapping rifiutati con ValueError (src/asqueel/write_compiler.py:253-264); nessuno storename; storename e altri kwargs accettati e ignorati (prova: update(record, foo=1, storename='x') riuscito); riga mancante: nessun errore, come il legacy (prova); tests/write_compiler/test_write_cycle.py:146; tests/test_record_writes_legacy.py:66

Motivazione. decisione utente 2026-10-17: pkey composta come stringa serializzata legacy (realizzata), storename fuori e kwargs in più rifiutati; il codice accetta e ignora gli uni e gli altri (src/asqueel/db_write.py:35). docs/guide/writes.md:66-77 e :211-218

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:118-148
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:223-265
  • asqueel: src/asqueel/table_crud.py:36-47
  • asqueel: src/asqueel/write_compiler.py:186-193
  • asqueel: src/asqueel/write_compiler.py:253-264
  • asqueel: tests/write_compiler/test_write_cycle.py:146

delete(record) e attributo deletable A5.3

In GenroPy scrivevi

tbl.delete(pkey)
pkg.table('invoice', pkey='id', deletable='admin')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:103-115 stringa letta con lock e ignoreMissing, dict passato com'è; gnrpy/gnr/sql/gnrsql/write.py:294-305 deletable anche come tag di permesso; WHERE col nome di modello (gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:890); nessun controllo delle righe cancellate

In asqueel scrivi

db.table("sales.line").delete({"id": 101})

In asqueel ottieni

src/asqueel/table_crud.py:49-58 stringa riletta con for_update fuori dai filtri di bozza, cancellazione logica e partizione (src/asqueel/table_crud.py:78-85), stringa mancante: ritorno senza errore; dict cancellato com'è, senza rilettura; src/asqueel/db_write.py:68-72 il DELETE deve togliere una riga, altrimenti RecordNotFoundError (prova su dict mancante); WHERE col sqlname (src/asqueel/write_compiler.py:266-273); deletable solo bool (src/asqueel/elements.py:177), una stringa è rifiutata dal validatore (prova); tests/write_compiler/test_write_cycle.py:171, :176, :189, :194; tests/test_record_writes_legacy.py:115

Motivazione. decisione utente 2026-10-17: delete come legacy, deletable come tag di permesso fuori da questa release (rifiutato). decisione utente 2026-10-15 (provvisoria): errore se il DELETE non toglie esattamente una riga. docs/guide/writes.md:101-105

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:103-115
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:294-305
  • asqueel: src/asqueel/table_crud.py:49-58
  • asqueel: src/asqueel/db_write.py:68-72
  • asqueel: src/asqueel/elements.py:177
  • asqueel: tests/write_compiler/test_write_cycle.py:171

protect_draft e dbo_onInserting / dbo_onUpdating / dbo_onDeleting A5.7

In GenroPy scrivevi

def dbo_onUpdating(self, record, old_record=None, **kwargs):
    ...

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/write.py:159-163 (dbo_onInserting, poi protect_draft), :256-257 dbo_onUpdating, :311-312 dbo_onDeleting; in update protect_draft è chiamato da gnrpy/gnr/app/gnrdbo.py:1079 dbo_onUpdating

In asqueel scrivi

def protect_draft(self, record):
    return record.get("status") != "confirmed"

In asqueel ottieni

src/asqueel/db_write.py:29 e :43-44 protect_draft chiamato direttamente, in update prima dei contatori; src/asqueel/db_write.py:77-82 solo se draftField e il metodo esistono; la cascata dell'archiviazione logica, che il legacy esegue in dbo_onUpdating, è nella stessa posizione (src/asqueel/db_write.py:45-48); nessun dbo_* chiamato

Motivazione. decisione utente 2026-10-17: la parte dbo è sostituita da options(); decisione utente 2026-10-16: del contenuto dbo restano protect_draft e la cascata dell'archiviazione logica nelle posizioni legacy. Vale il codice su docs/adattamenti-legacy.md:90 ('protect_draft ... Non portato')

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:159-163
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:1079
  • asqueel: src/asqueel/db_write.py:29
  • asqueel: src/asqueel/db_write.py:43-48
  • asqueel: src/asqueel/db_write.py:77-82

currentTrigger e trigger stack (event, table, record, old_record, parent) A5.9

In GenroPy scrivevi

trigger = self.currentTrigger
parent = trigger.parent

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/triggers.py:107-110; gnrpy/gnr/sql/gnrsql/helpers.py:80-82 push e pop senza try/finally: dopo un'eccezione l'elemento resta; elemento in gnrpy/gnr/sql/gnrsql/helpers.py:280

In asqueel scrivi

trigger = self.currentTrigger
parent = trigger.parent

In asqueel ottieni

src/asqueel/triggers.py:9-17 stessi campi, nessun level; pop in finally (src/asqueel/triggers.py:52-57); src/asqueel/runtime.py:231-242; src/asqueel/application_table.py:429-432; tests/write_compiler/test_write_cycle.py:139. I comandi sql_* non entrano nello stack, come i raw legacy

Motivazione. decisione utente 2026-10-15: stack come il legacy, pop anche su eccezione come chiede genropy#1688. Vale il codice su docs/guide/hooks.md:68-71, che documenta un campo level inesistente (prova: hasattr False)

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/triggers.py:107-110
  • GenroPy: gnrpy/gnr/sql/gnrsql/helpers.py:80-82
  • asqueel: src/asqueel/triggers.py:9-17
  • asqueel: src/asqueel/triggers.py:52-57
  • asqueel: tests/write_compiler/test_write_cycle.py:139

self.exception(nome, record=, msg=) A5.10

In GenroPy scrivevi

raise self.exception('protect_update', record=record, msg='locked')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/table.py:92-118: nome cercato in EXCEPTIONS, rowcaption da recordCaption (:109), localizer del db (:116)

In asqueel scrivi

raise self.exception("protect_update", record=record, msg="locked")

In asqueel ottieni

src/asqueel/table_triggers.py:67-77: stesso EXCEPTIONS (src/asqueel/errors.py:69-77), nome sconosciuto ValueError; rowcaption è il valore della pkey (src/asqueel/table_triggers.py:74-76); nessun localizer. Prova: 'The record 1 in table app.t is not updatable: locked'

Motivazione. decisione utente 2026-10-17: self.exception come legacy. Caption dalla pkey al posto di recordCaption: nessuna motivazione registrata. docs/guide/hooks.md:49-52

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/table.py:92-118
  • asqueel: src/asqueel/table_triggers.py:67-77
  • asqueel: src/asqueel/errors.py:69-77

deleteRelated: onDelete raise / cascade / setnull / ignore, 'azione:messaggio' A5.11

In GenroPy scrivevi

tbl.column('invoice_id').relation('sales.invoice.id', onDelete='c')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:182-229: forme brevi r, c, n, i; default 'raise' per mode foreignkey (:189-191) mai raggiunto, perché relations_many contiene solo joiner con mode 'M' (gnrpy/gnr/sql/gnrsqlmodel/table.py:702): default effettivo ignore; query con subtable='*' e salto dei dbstores (:201, :208); messaggio '!!Record referenced...'

In asqueel scrivi

columns.column("invoice_id").relation("sales.invoice.id", onDelete="cascade")

In asqueel ottieni

src/asqueel/table_crud.py:185-207 stesse azioni, default ignore (:188), messaggio dopo ':' (tests/write_compiler/test_write_cycle.py:200); forme brevi rifiutate dal validatore con 'invalid onDelete' (src/asqueel/validators.py:297-301, prova); nessun subtable né dbstore; tests/write_compiler/test_write_cycle.py:176

Motivazione. decisione utente 2026-10-17: deleteRelated e onDelete come legacy; decisione utente 2026-10-16: grafie legacy raise, cascade, setnull, ignore (le forme brevi non hanno usi nei repo locali). docs/guide/writes.md:107-110. Vale il codice su docs/adattamenti-legacy.md:91 ('Nessun ciclo Python equivalente')

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:182-229
  • asqueel: src/asqueel/table_crud.py:185-207
  • asqueel: src/asqueel/validators.py:297-301
  • asqueel: tests/write_compiler/test_write_cycle.py:176

updateRelated: onUpdate raise / cascade A5.12

In GenroPy scrivevi

tbl.column('invoice_id').relation('sales.invoice.id', onUpdate='raise')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:150-180: con chiave invariata esce dal ciclo con return (:156-157); raise usa il nome 'update', che non è in EXCEPTIONS (:172); setnull ignorato; old_record None dà AttributeError

In asqueel scrivi

columns.column("invoice_id").relation("sales.invoice.id", onUpdate="raise")

In asqueel ottieni

src/asqueel/table_crud.py:222-243: continue alla relazione successiva (:231-232); raise con 'save' (:236-238, prova: SaveException 'Record referenced in table app.u'); setnull ignorato come nel legacy (prova); forme brevi rifiutate dal validatore (src/asqueel/validators.py:297-301); senza old_record ValueError esplicito (src/asqueel/table_crud.py:228-229, tests/write_compiler/test_write_cycle.py:207); cascade in tests/write_compiler/test_write_cycle.py:163

Motivazione. decisione utente 2026-10-16 (provvisoria): correggere i difetti legacy segnalati in genropy#1686 e genropy#1687. docs/guide/writes.md:72-77

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:150-180
  • asqueel: src/asqueel/table_crud.py:222-243
  • asqueel: tests/write_compiler/test_write_cycle.py:207
  • asqueel: tests/write_compiler/test_write_cycle.py:163

checkPkey / newPkeyValue / pkeyValue (generazione della chiave) A5.13

In GenroPy scrivevi

record = tbl.insert({'name': 'Ada'})

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/record.py:620; gnrpy/gnr/sql/gnrsqltable/utils.py:75 e :92: pkey composta con toTypedJSON (:88), max+1 per L/I/R (:106), getUuid (:110, :134), pkey_columns, __syscode con sysRecord_<codice> (:120)

In asqueel scrivi

record = customer.insert({"name": "Ada"})

In asqueel ottieni

src/asqueel/table_crud.py:96-144 stesso ordine delle strategie; composta con toTypedJSON come il legacy (src/asqueel/table_crud.py:125); UUID da genro_toolbox get_uuid (:132, :143); __syscode senza sysRecord_<codice> (:138-141); una pkey con sql_default resta al database (src/asqueel/table_crud.py:104, :111-114). Prova: chiave 1, 2 su intero, 'Z0G7...' di 22 caratteri su testo, 'ABC_______' da __syscode su size 10; tests/test_record_writes_legacy.py:50, :57

Motivazione. Algoritmo UUID: docs/design/40-model-and-operation-recipes.md:206-208 (decisione del 16 ottobre: id ordinabile nel tempo); docs/guide/writes.md:39-43; pkey con sql_default: docs/guide/writes.md:184-187. sysRecord_<codice>: nessuna motivazione registrata

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/record.py:620
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/utils.py:75
  • asqueel: src/asqueel/table_crud.py:96-144
  • asqueel: tests/test_record_writes_legacy.py:50

Valore Bag in una colonna X A5.15

In GenroPy scrivevi

tbl.insert({'id': 1, 'data': Bag(dict(a=1))})

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:742-747 la Bag diventa XML (toXml) prima del binding

In asqueel scrivi

tbl.insert({"id": 1, "data": Bag(dict(a=1))})

In asqueel ottieni

src/asqueel/write_compiler.py:117-118 la Bag diventa to_xml() di genro_bag, la Bag vuota NULL; tests/test_record_writes_legacy.py:92. Prova: una Bag con a=1 è scritta come '<a>1</a>', senza la radice GenRoBag e gli attributi di tipo del toXml legacy

Motivazione. decisione utente 2026-10-17: Bag in colonne X come legacy; il formato del bagfield dipende da genro-bag#72. docs/guide/writes.md:220-224

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:742-747
  • asqueel: src/asqueel/write_compiler.py:117-118
  • asqueel: tests/test_record_writes_legacy.py:92

insertOrUpdate(record) A5.23

In GenroPy scrivevi

result = tbl.insertOrUpdate(record)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:252-263 cerca la pkey con lock; insert se manca, altrimenti update con la prima riga

In asqueel scrivi

result = customer.insertOrUpdate(record)

In asqueel ottieni

src/asqueel/table_crud.py:60-70 cerca la pkey con for_update e i filtri di default della query (fetchRecord con ignoreMissing), poi insert o update con la riga letta come old_record; pkey composta come stringa serializzata (src/asqueel/application_table.py:411-424); restituisce il record anche dopo l'update, dove il legacy restituisce None (gnrpy/gnr/sql/gnrsqltable/crud.py:263); tests/test_record_writes_legacy.py:75, :82

Motivazione. decisione utente 2026-10-17: insertOrUpdate come legacy. Valore restituito dopo l'update: nessuna motivazione registrata. docs/guide/writes.md:79-82

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:252-263
  • asqueel: src/asqueel/table_crud.py:60-70
  • asqueel: src/asqueel/application_table.py:411-424
  • asqueel: tests/test_record_writes_legacy.py:75

Cascata dell'archiviazione logica: onArchivingRecord / archiveRelatedRecords A5.27

In GenroPy scrivevi

def onArchivingRecord(self, record=None, archive_ts=None):
    self.archiveRelatedRecords(record=record, archive_ts=archive_ts)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/copy.py:70-100 batchUpdate del campo di cancellazione logica sulle figlie con onDelete cascade; avviata da gnrpy/gnr/app/gnrdbo.py:1088 quando cambia __del_ts

In asqueel scrivi

old = dict(invoice.query(columns="*", where="$id=:pk", pk="o1").fetchRecord())
invoice.update({**old, "__del_ts": datetime.now()}, old_record=old)

In asqueel ottieni

src/asqueel/table_crud.py:209-220 archiveRelatedRecords: relazioni con onDelete cascade la cui tabella figlia ha logicalDeletionField, scritte con query(...).update() al posto di batchUpdate, quindi ricorsiva; chiamata da src/asqueel/db_write.py:45-48 quando un update con old_record cambia il campo; nessun hook onArchivingRecord da ridefinire (prova: AttributeError), nessun salto dei dbstore, nessun restoreUnifiedRecord; tests/test_selection_writes_legacy.py:97, :107, :116

Motivazione. decisione utente 2026-10-17: cascata come legacy, con una nota di valutazione nella scheda. Assenza di onArchivingRecord: nessuna motivazione registrata. docs/guide/writes.md:116-132

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/copy.py:70-100
  • GenroPy: gnrpy/gnr/app/gnrdbo.py:1088
  • asqueel: src/asqueel/table_crud.py:209-220
  • asqueel: src/asqueel/db_write.py:45-48
  • asqueel: tests/test_selection_writes_legacy.py:97

currentEnv (lettura e assegnazione), updateEnv(_excludeNoneValues), clearCurrentEnv A6.2

In GenroPy scrivevi

db.currentEnv = {'user': 'ada'}
db.updateEnv(workdate=today)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/env.py:63-71 dict per thread, il setter accetta qualsiasi valore; :105 updateEnv; :59 clearCurrentEnv

In asqueel scrivi

db.currentEnv = {"user": "ada"}
snapshot = db.current_env

In asqueel ottieni

src/asqueel/runtime.py:147-166; src/asqueel/environment.py:108-118 dict per thread, il setter solleva TypeError se il valore non è un dict (prova: 'currentEnv must be a dictionary'). current_env in più restituisce una copia in sola lettura senza le code differite e _pendingExceptions (src/asqueel/environment.py:100-106)

Motivazione. decisione utente 2026-10-17: currentEnv, updateEnv e clearCurrentEnv come legacy; docs/guide/environment.md:57-63 e :109-114 (current_env come snapshot). Nessuna motivazione registrata per il TypeError del setter

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/env.py:63-71
  • asqueel: src/asqueel/runtime.py:147-166
  • asqueel: src/asqueel/environment.py:100-118

workdate / locale A6.3

In GenroPy scrivevi

locale = db.locale

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/env.py:75-86 workdate con fallback a date.today(); :90-101 locale con fallback a defaultLocale(), che valida l'identificativo e ricade su en_GB (gnrpy/gnr/core/gnrlocale.py:308-313)

In asqueel scrivi

locale = db.locale

In asqueel ottieni

src/asqueel/runtime.py:168-184; src/asqueel/environment.py:120-134 stesso fallback per workdate; locale da GNR_LOCALE o locale.getlocale() senza validazione. Prova: con locale di sistema 'C' asqueel restituisce 'C'; un identificativo 'xx_INVALID' resta tale

Motivazione. decisione utente 2026-10-17: workdate e locale come legacy. Mancata validazione dichiarata in docs/guide/environment.md:69-72 e docs/guide/limitations.md:253-254; nessuna motivazione registrata

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/env.py:75-101
  • GenroPy: gnrpy/gnr/core/gnrlocale.py:308-313
  • asqueel: src/asqueel/environment.py:120-134

Connessioni per thread e per nome, aperte alla prima istruzione A6.6

In GenroPy scrivevi

with db.tempEnv(connectionName='worker'):
    tbl.insert(record)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/connections.py:79-102 chiave (storename, connectionName) per thread; ogni nome usa gli stessi parametri fisici dello store (adapter.connect(storename), :97)

In asqueel scrivi

with db.tempEnv(connectionName="worker"):
    customer.insert(record)

In asqueel ottieni

src/asqueel/runtime.py:38-42 e :128-140 stato per thread e per nome; apertura a :452-456. Un nome può avere una connessione dichiarata con un altro database fisico (src/asqueel/application.py:78-86; src/asqueel/configuration.py:306-320); con dichiarazioni esplicite un nome non dichiarato dà ValueError 'Unknown named connection' (:319, prova). Le operazioni sulla connessione devono avvenire nel thread che l'ha creata (src/asqueel/runtime.py:414-416)

Motivazione. decisione utente 2026-10-17: connessioni per thread e nome come legacy. Dichiarazione per nome descritta in docs/guide/cli.md:81-86; nessuna motivazione registrata per il database fisico per nome e per il controllo del thread

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/connections.py:79-102
  • asqueel: src/asqueel/runtime.py:128-140
  • asqueel: src/asqueel/configuration.py:306-320
  • asqueel: src/asqueel/runtime.py:414-416

db.commit() A6.8

In GenroPy scrivevi

def trigger_onInserted(self, record):
    self.db.commit()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:47-93: per ogni connessione non committata con lo stesso connectionName (tutti gli store) esegue deferToCommit, eccezioni pendenti, commit, deferAfterCommit, poi onDbCommitted; consentito anche dentro un trigger

In asqueel scrivi

db.commit()

In asqueel ottieni

src/asqueel/runtime.py:264-267 e :520-551 stesso ordine sulla sola connessione selezionata, poi onDbCommitted (riga A6.15). Dentro una scrittura o un hook, commit sulla connessione in uso solleva TransactionStateError (:259-262, prova); su un'altra connessione con nome è consentito (prova)

Motivazione. decisione utente 2026-10-15: commit e rollback rifiutati sulla connessione in uso dentro un trigger e consentiti su un'altra, per il deadlock segnalato in genropy#1621 (provvisoria)

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:47-93
  • asqueel: src/asqueel/runtime.py:259-267
  • asqueel: src/asqueel/runtime.py:520-551

db.rollback() / rollbackAll() A6.9

In GenroPy scrivevi

db.rollback()
db.rollbackAll()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:252-264 rollback della sola connessione, code differite ed eccezioni pendenti restano in currentEnv e partono al commit successivo; :266-287 rollbackAll le svuota

In asqueel scrivi

db.rollback()

In asqueel ottieni

src/asqueel/runtime.py:296-298; _finish svuota code differite ed eccezioni pendenti della connessione (:494-518, :409-412), come rollbackAll. rollbackAll assente (prova): rollback ha già il suo effetto sull'unico store. Rifiutato dentro una scrittura come il commit (:259-262). Prova: callback registrata e poi rollback non parte al commit successivo

Motivazione. decisione utente 2026-10-16: correzione di genropy#1689 (rollback e callback differite); docs/guide/transactions.md:79

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:252-287
  • asqueel: src/asqueel/runtime.py:296-298
  • asqueel: src/asqueel/runtime.py:494-518

Rollback automatico su errore SQL A6.10

In GenroPy scrivevi

db.execute('INSERT INTO bad_table VALUES (1)')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/execute.py:154-159 registra l'errore, chiama self.rollback() e rilancia

In asqueel scrivi

db.execute("INSERT INTO bad_table VALUES (1)")

In asqueel ottieni

src/asqueel/runtime.py:463-465 e :469-478 rollback e rilancio dell'errore originale; lo stesso per un errore durante la lettura dal cursore (:631-640). Se il rollback fallisce la connessione viene scartata (:506-513). Svuota anche le code differite (riga A6.9). Prova: errore SQL dopo un insert, la riga non resta

Motivazione. decisione utente 2026-10-17: rollback automatico su errore SQL come legacy. docs/guide/transactions.md:54-55 e :62-64; docs/guide/low-level-runtime.md:33-35 (errore in lettura)

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/execute.py:154-159
  • asqueel: src/asqueel/runtime.py:463-478
  • asqueel: src/asqueel/runtime.py:631-640

Errore Python in un trigger o hook di scrittura A6.11

In GenroPy scrivevi

try:
    tbl.insert(record)
except ValueError:
    db.commit()

In GenroPy ottenevi

La transazione resta valida: insert/update/delete sono solo avvolti da in_triggerstack (gnrpy/gnr/sql/gnrsql/helpers.py:56-85; gnrpy/gnr/sql/gnrsql/write.py:133-134); il commit successivo salva la scrittura interrotta

In asqueel scrivi

try:
    customer.insert(record)
except ValueError:
    db.rollback()

In asqueel ottieni

src/asqueel/runtime.py:300-318 _write_operation marca la connessione rollback-only; commit, deferToCommit e nuove istruzioni rifiutati finché non si chiama rollback (:428-431; prova: 'Connection is rollback-only; call rollback before reuse')

Motivazione. decisione utente 2026-10-15 (provvisoria): rollback-only, con il possibile difetto legacy segnalato in genropy#1696; docs/guide/legacy.md:78-82

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/helpers.py:56-85
  • asqueel: src/asqueel/runtime.py:300-318
  • asqueel: src/asqueel/runtime.py:428-431

deferredRaise(exception) A6.13

In GenroPy scrivevi

self.db.deferredRaise(ValueError('stock below zero'))

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:131-137 lista globale in currentEnv['_pendingExceptions']; :84-87 commit solleva GnrException e non svuota la lista: ogni nuovo commit la rilancia

In asqueel scrivi

self.db.deferredRaise(ValueError("stock below zero"))

In asqueel ottieni

src/asqueel/runtime.py:254-257 e :373-377 (TypeError se non è un'eccezione, prova); lista per connessione con nome in currentEnv['_pendingExceptions__main_db_<connectionName>'] (:348-355); :530-536 DeferredCommitError, lista svuotata e connessione rollback-only. Prova: secondo commit dà TransactionStateError, rollback la svuota

Motivazione. decisione utente 2026-10-16: correzione di genropy#1690; lista per connessione descritta in docs/guide/transactions.md:75-79

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:131-137
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:84-87
  • asqueel: src/asqueel/runtime.py:373-377
  • asqueel: src/asqueel/runtime.py:530-536

db.execute(sql, sqlargs, cursor, cursorname, dbtable, storename, _adaptArguments) A6.17

In GenroPy scrivevi

cursor = db.execute('SELECT id FROM t', cursorname='*', storename='a')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/execute.py:47-56 firma; restituisce il cursore (:164); cursorname '*' crea un cursore server (:128-131)

In asqueel scrivi

cursor = db.execute("SELECT id FROM t", cursorname="*")
rows = cursor.fetchall()

In asqueel ottieni

src/asqueel/runtime.py:195-216 stessa firma, restituisce SqlCursor (:586-655: description, rowcount, arraysize, fetchone, fetchmany, fetchall, iterazione, close; righe NamedList lette per nome). sql è una stringa o una CompiledQuery. cursorname apre un cursore server su PostgreSQL ('*' genera il nome, :211-212), SQLite lo ignora (prova). dbtable accettato senza effetto. storename, cursor e _adaptArguments=False sollevano UnsupportedFeatureError (:203-206, prova)

Motivazione. decisione utente 2026-10-17: db.execute che restituisce il cursore come legacy; docs/guide/low-level-runtime.md:25-35. Store fuori da questa release (riga A6.7); nessuna motivazione registrata per il rifiuto di cursor e _adaptArguments

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/execute.py:47-56
  • asqueel: src/asqueel/runtime.py:195-216
  • asqueel: src/asqueel/runtime.py:586-655

autocommit (execute(autocommit=True), connessione autocommit) A6.18

In GenroPy scrivevi

db.execute(sql, autocommit=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/execute.py:161-162 commit dopo l'istruzione; connessione con autoCommit in gnrpy/gnr/sql/adapters/gnrpostgres3.py:41 e :54

In asqueel scrivi

db.execute(sql, autocommit=True)

In asqueel ottieni

execute(autocommit=True) chiama db.commit() dopo l'istruzione (src/asqueel/runtime.py:214-215; prova: la riga resta dopo closeConnection). Una connessione autocommit è rifiutata: connect_kwargs autocommit dà ValueError (:76-77, :454-455, prova), anche nell'adapter PostgreSQL (src/asqueel/adapters/postgres.py:32-33)

Motivazione. decisione utente 2026-10-17: autocommit come legacy; docs/guide/low-level-runtime.md:31-32. Nessuna motivazione registrata per il rifiuto della connessione autocommit

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/execute.py:161-162
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:41
  • asqueel: src/asqueel/runtime.py:214-215
  • asqueel: src/asqueel/runtime.py:76-77
  • asqueel: src/asqueel/adapters/postgres.py:32-33

db.closeConnection() A6.21

In GenroPy scrivevi

db.closeConnection()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/connections.py:41-57 rollback e chiusura di tutte le connessioni del thread; gli errori sono ignorati

In asqueel scrivi

db.closeConnection()

In asqueel ottieni

src/asqueel/runtime.py:320-346 rollback e chiusura di tutte le connessioni del thread, chiusura dei cursori aperti (:485-492); il database resta usabile (prova). Dentro una scrittura solleva TransactionStateError (:326-327, prova); il primo errore di pulizia viene rilanciato dopo aver chiuso le altre (:335-346, prova)

Motivazione. decisione utente 2026-10-17: closeConnection come legacy; docs/guide/transactions.md:142-147. Nessuna motivazione registrata per la propagazione degli errori e per il rifiuto dentro una scrittura

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/connections.py:41-57
  • asqueel: src/asqueel/runtime.py:320-346

tblobj.lock(mode, nowait) / adapter.lockTable A6.25

In GenroPy scrivevi

tbl.lock(mode='SHARE', nowait=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:481-487; PostgreSQL scrive 'NO WAIT' (gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:319-325); SQLite non fa nulla (gnrpy/gnr/sql/adapters/gnrsqlite.py:390-392)

In asqueel scrivi

customer.lock(mode="SHARE", nowait=True)

In asqueel ottieni

src/asqueel/table_crud.py:72-76 table.lock(mode='ACCESS EXCLUSIVE', nowait=False); src/asqueel/write_compiler.py:233-247: PostgreSQL LOCK ... IN <mode> MODE con NOWAIT; un modo non PostgreSQL dà ValueError (prova); SQLite prende il lock di scrittura con un UPDATE senza effetto (prova)

Motivazione. decisione utente 2026-10-17: tblobj.lock come legacy; decisione utente 2026-10-16: correzione di genropy#1692 per NOWAIT; docs/guide/writes.md:228-232

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:481-487
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:319-325
  • asqueel: src/asqueel/table_crud.py:72-76
  • asqueel: src/asqueel/write_compiler.py:233-247

for_update con modo stringa ('NOWAIT', 'SKIP LOCKED') A6.26

In GenroPy scrivevi

tbl.query(where='$id = :id', id=1, for_update='SKIP LOCKED').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:717-723 una stringa viene aggiunta dopo FOR UPDATE OF; SQLite restituisce '' (gnrpy/gnr/sql/adapters/gnrsqlite.py:140-141)

In asqueel scrivi

customer.query(where="$id = :id", id=1, for_update="SKIP LOCKED").fetch()

In asqueel ottieni

src/asqueel/compiler.py:694-695 for_update vale True, 'NOWAIT' o 'SKIP LOCKED', altrimenti ValueError (prova); PostgreSQL aggiunge il modo dopo FOR UPDATE OF (src/asqueel/adapters/base.py:138-139); SQLite rifiuta un modo stringa con UnsupportedFeatureError 'for_update=NOWAIT is not available on SQLite' (src/asqueel/adapters/sqlite.py:215-218, prova). for_update=True: riga A2.12

Motivazione. decisione utente 2026-10-17: for_update con modo stringa come legacy; rifiuto esplicito di una capability di dialetto assente (regola della decisione su una riga di migrazione). docs/guide/queries.md:621-626

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:717-723
  • asqueel: src/asqueel/compiler.py:694-695
  • asqueel: src/asqueel/adapters/sqlite.py:215-218

Transazioni e lock su SQLite A6.27

In GenroPy scrivevi

tbl.query(where='$id = :id', id=1, for_update=True).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrsqlite.py:80 connessione sqlite3 con transazione implicita di default; for_update non produce nulla (:140-141); lockTable vuoto (:390-392)

In asqueel scrivi

customer.query(where="$id = :id", id=1, for_update=True).fetch()

In asqueel ottieni

src/asqueel/adapters/sqlite.py:54-57 isolation_level='DEFERRED': BEGIN prima della prima INSERT, UPDATE o DELETE, le letture non aprono transazione (prova); :111-121 for_update prende il lock di scrittura (BEGIN IMMEDIATE, o un UPDATE senza effetto in una transazione aperta); FK attive di default (:61, riga A8.8)

Motivazione. decisione utente 2026-10-17: transazione SQLite come legacy, differita, lock solo con for_update; docs/guide/sqlite.md:59-66

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:80
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:140-141
  • asqueel: src/asqueel/adapters/sqlite.py:54-57
  • asqueel: src/asqueel/adapters/sqlite.py:111-121

setConstraintsDeferred() A6.28

In GenroPy scrivevi

db.setConstraintsDeferred()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:243-250 chiama il metodo del cursore se esiste; PostgreSQL SET CONSTRAINTS all DEFERRED (gnrpy/gnr/sql/adapters/gnrpostgres3.py:347-348); il cursore SQLite non lo ha, nessun effetto

In asqueel scrivi

db.setConstraintsDeferred()

In asqueel ottieni

src/asqueel/runtime.py:278-280; PostgreSQL SET CONSTRAINTS ALL DEFERRED nella transazione corrente (src/asqueel/adapters/base.py:219-221); SQLite PRAGMA defer_foreign_keys = ON (src/asqueel/adapters/sqlite.py:93-95)

Motivazione. decisione utente 2026-10-17: setConstraintsDeferred come legacy; docs/guide/low-level-runtime.md:77-79. Effetto su SQLite senza motivazione registrata oltre alle FK attive (riga A8.8)

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:243-250
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:347-348
  • asqueel: src/asqueel/runtime.py:278-280
  • asqueel: src/asqueel/adapters/sqlite.py:93-95

analyze() / vacuum(table, full) A6.29

In GenroPy scrivevi

db.analyze()
db.vacuum(full=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:307-318 delegati all'adapter; gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:929-948 ANALYZE nella transazione corrente, VACUUM [FULL] ANALYZE su una connessione autocommit separata, lo stesso testo anche su SQLite

In asqueel scrivi

db.analyze()
db.vacuum(full=True)

In asqueel ottieni

src/asqueel/runtime.py:282-294; analyze nella transazione corrente (src/asqueel/adapters/base.py:223-225). vacuum con lavoro pendente nel thread solleva TransactionStateError (prova); PostgreSQL VACUUM [FULL] ANALYZE su una connessione autocommit separata (src/asqueel/adapters/postgres.py:42-49); SQLite VACUUM sul file principale e su ogni schema collegato, poi ANALYZE (src/asqueel/adapters/sqlite.py:97-109)

Motivazione. decisione utente 2026-10-17: analyze e vacuum come legacy; docs/guide/low-level-runtime.md:71-76 e docs/guide/sqlite.md:53-55

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:307-318
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:929-948
  • asqueel: src/asqueel/runtime.py:282-294
  • asqueel: src/asqueel/adapters/postgres.py:42-49
  • asqueel: src/asqueel/adapters/sqlite.py:97-109

db.currentUser A6.31

In GenroPy scrivevi

user = self.db.currentUser

In GenroPy ottenevi

gnrpy/gnr/app/gnrapp.py:537-538 GnrSqlAppDb: user di currentEnv, altrimenti l'utente della pagina corrente; usato da trigger_setCurrentUser (gnrpy/gnr/app/gnrdbo.py:1021)

In asqueel scrivi

user = self.db.currentUser

In asqueel ottieni

src/asqueel/runtime.py:157-159 solo il valore user di currentEnv; nessun ricorso alla pagina. Usato dal trigger dei campi utente (src/asqueel/table_triggers.py:59)

Motivazione. decisione utente 2026-10-17: questa release è il livello basso, il livello applicativo (pagina) viene dopo; docs/guide/legacy-features.md:110-114

File
  • GenroPy: gnrpy/gnr/app/gnrapp.py:537-538
  • asqueel: src/asqueel/runtime.py:157-159
  • asqueel: src/asqueel/table_triggers.py:59

Nuovo

RETURNING dei valori decisi dal database A5.16

In asqueel scrivi

record = customer.insert({"name": "Ada"})
created = record["created_ts"]

In asqueel ottieni

src/asqueel/write_compiler.py:284-309 colonne con sql_value, sql_generatedExpression, sql_default omessa o dtype serial; src/asqueel/db_write.py:84-89 scritte nel record prima dei trigger after; anche sql_insert di un solo record (src/asqueel/application_table.py:446-450); solo con la capability returning_insert, dichiarata da entrambi gli adapter (src/asqueel/adapters/sqlite.py:32-35, src/asqueel/adapters/postgres.py:20-23): RETURNING su PostgreSQL e su SQLite (prova); tests/write_compiler/test_write_compiler.py:73; tests/write_compiler/test_write_cycle.py:130

Motivazione. docs/guide/writes.md:45-47; decisione utente 2026-10-15 (provvisoria). Vale il codice su docs/guide/writes.md:48-49 e docs/guide/limitations.md:71-72, che dicono SQLite senza RETURNING. Su un adapter senza capability il RETURNING cade senza errore (src/asqueel/write_compiler.py:305): #21

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:797
  • asqueel: src/asqueel/write_compiler.py:284-309
  • asqueel: src/asqueel/db_write.py:84-89
  • asqueel: tests/write_compiler/test_write_compiler.py:73

query(...).sql_update(values) A5.19

In asqueel scrivi

changed = db.table("sales.invoice").query(
    where="$total >= :minimum", minimum=100,
).sql_update({"note": "Reviewed"})

In asqueel ottieni

src/asqueel/application_table.py:257-262 un solo UPDATE ... WHERE (pk) IN (SELECT della query) (src/asqueel/write_compiler.py:206-225); group_by e having rifiutati (src/asqueel/application_table.py:304-309); parametri condivisi fra query e valori rifiutati (src/asqueel/write_compiler.py:275-278); tests/write_compiler/test_write_compiler.py:227, :246

Motivazione. docs/guide/writes.md:192-209; decisione utente 2026-10-17: sql_update e sql_delete della query tenuti tra le novità

File
  • asqueel: src/asqueel/application_table.py:257-262
  • asqueel: src/asqueel/write_compiler.py:206-225
  • asqueel: tests/write_compiler/test_write_compiler.py:246

query(...).update(updater) A5.31

In asqueel scrivi

pkeys = db.table("sales.invoice").query(
    where="$total >= :minimum", minimum=100,
).update({"note": "Reviewed"})

In asqueel ottieni

src/asqueel/application_table.py:274-290 rilegge le righe con tutte le colonne fisiche, bagFields e for_update (src/asqueel/application_table.py:292-302), applica l'updater dict o callable a una copia della riga e chiama update(record, old_record=<riga letta>); restituisce le pkey aggiornate; group_by e having rifiutati; tests/test_selection_writes_legacy.py:66, :74

Motivazione. decisione utente 2026-10-17: per simmetria con query(...).delete(), aggiornamento record per record con il ciclo di scrittura, accanto a query(...).sql_update(). docs/guide/writes.md:134-161

File
  • asqueel: src/asqueel/application_table.py:274-302
  • asqueel: tests/test_selection_writes_legacy.py:66

session(db, **env): unità di lavoro fuori da una richiesta A6.30

In asqueel scrivi

from asqueel import session

with session(db, user="scheduler"):
    db.table("sales.customer").insert({"id": 5, "name": "Batch"})
    db.commit()

In asqueel ottieni

src/asqueel/runtime.py:666-691: all'ingresso clearCurrentEnv e updateEnv(**env), all'uscita closeConnection anche quando il corpo solleva; una sessione annidata sullo stesso thread solleva TransactionStateError (prova); esportata da asqueel (src/asqueel/__init__.py:53). L'host prende un database aprendo la sessione (src/asqueel/hosting.py:110-115)

Motivazione. decisione utente 2026-10-19: nome session(db) e sessione annidata come errore; docs/guide/transactions.md:102-124

File
  • GenroPy: gnrpy/gnr/web/gnrwebpage.py:161
  • GenroPy: gnrpy/gnr/web/gnrwsgisite.py:1542
  • asqueel: src/asqueel/runtime.py:666-691
  • asqueel: src/asqueel/__init__.py:53

Uguale

  • trigger_onInserting / onInserted / onUpdating / onUpdated / onDeleting / onDeleted; trigger_assignCounters / trigger_releaseCounters A5.4
  • Trigger di campo: onInserting, onUpdating, onDeleting, onInserted, onUpdated, onDeleted, trigger_table sulla colonna A5.5
  • protect_update / protect_delete / protect_validate A5.6
  • Dati del record in scrittura: chiavi '@', 'pkey', virtuali e sconosciute scartate; None omesso in INSERT; UPDATE di tutte le chiavi presenti; sql_value sempre emesso A5.14
  • Policy di riga in scrittura: delete fisico, cancellazione logica con update, partizione e bozza non applicate A5.28
  • tempEnv(**kwargs) / temp_env A6.1
  • setLocale() (locale applicato all'adapter) A6.4
  • connectionName in ambiente, currentConnectionName, usingMainConnection, '_main_connection' A6.5
  • deferToCommit / deferAfterCommit (_deferredBlock, _deferredId, deferredCommitRecursion, onCommittingStep) A6.12
  • onDbCommitted() A6.15
  • Parametri :env_* e preparazione degli argomenti in execute A6.19

Adapters

Manca

Inserimento multiplo (insertMany, executemany) sostituita A8.14

In GenroPy scrivevi

tblobj.insertMany(records)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:86 insertMany; gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:821 assegna la pkey mancante con newPkeyValue (:827-829) ed esegue cursor.executemany fuori da db.execute

In asqueel scrivi

db.table('app.counter').sql_insert([{'text': 'a'}, {'text': 'b'}])

In asqueel ottieni

insertMany assente (prova). Al suo posto sql_insert con una lista (src/asqueel/application_table.py:434-450): un executemany per gruppo di record consecutivi con le stesse colonne (src/asqueel/write_compiler.py:136-151), senza RETURNING; le pkey mancanti sono assegnate prima (src/asqueel/application_table.py:453-468), interi consecutivi in una lista (prova: [1, 2, 10, 11]); executemany dell'adapter in src/asqueel/adapters/base.py:200-205 e src/asqueel/adapters/sqlite.py:129-133; test tests/write_compiler/test_write_compiler.py:259, tests/test_record_writes_legacy.py:103

Motivazione. decisione utente 2026-10-17: solo sql_insert con record o lista, niente insertMany, pkey mancante assegnata come legacy; docs/guide/writes.md:184-188. Interi consecutivi invece dello stesso valore per ogni record (difetto legacy corretto): docs/guide/legacy-features.md:408-413

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:86
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:821
  • asqueel: src/asqueel/application_table.py:434-450
  • asqueel: src/asqueel/application_table.py:453-468
  • asqueel: src/asqueel/write_compiler.py:136-151
  • asqueel: src/asqueel/adapters/base.py:200-205
  • asqueel: src/asqueel/adapters/sqlite.py:129-133
  • asqueel: tests/write_compiler/test_write_compiler.py:259
  • asqueel: tests/test_record_writes_legacy.py:103

db.listen / db.notify (LISTEN e NOTIFY) temporanea A8.17

In GenroPy scrivevi

db.notify('orders', payload='42')
db.listen('orders', timeout=10, onNotify=handle)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:289 e :298; gnrpy/gnr/sql/adapters/gnrpostgres3.py:155 e :180; gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1295 listen_connection, :1311 poll_notifications; SQLite: notify vuoto e listen che attende soltanto (gnrpy/gnr/sql/adapters/gnrsqlite.py:299, :315)

In asqueel

Assenti: db.listen, db.notify e i metodi corrispondenti dell'adapter non esistono (prova). src/asqueel/db_write.py:5 esclude NOTIFY dal ciclo di scrittura; riga A6.16

Motivazione. decisione utente 2026-10-17: rimandati, previsti come capability dell'adapter PostgreSQL nel catalogo delle capability

Alternative. (a) db.listen e db.notify come nel legacy (b) solo notify nella transazione corrente, listen nel livello applicativo (c) lasciarli fuori: db.execute('NOTIFY canale, :payload') e una connessione psycopg dedicata

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:289
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:298
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:155
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:180
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1295
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1311
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:299
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:315
  • asqueel: src/asqueel/db_write.py:5

createDb / dropDb / dbExists / dump / restore / importRemoteDb sostituita A8.19

In GenroPy scrivevi

db.createDb('shop')
db.dump('/backup/shop.sql')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/schema.py:266, :275, :302, :315; gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:417, :435, :486, :335 (pg_dump), :439; gnrpy/gnr/sql/adapters/gnrpostgres3.py:146 (pg_restore o psql); SQLite crea e cancella il file (gnrpy/gnr/sql/adapters/gnrsqlite.py:319, :329); il modello chiama createDb in gnrpy/gnr/sql/gnrsqlmodel/model.py:496

In asqueel scrivi

plan = db.migration_plan()
db.migrate()

In asqueel ottieni

Metodi assenti su db e adapter (prova). Al loro posto asqueel-migration: db.migration_plan() e db.migrate() (src/asqueel/application.py:104-125) e la CLI asqueel db plan e db apply; l'adapter PostgreSQL di asqueel-migration crea il database mancante (docs/guide/cli.md:115-117); su SQLite la connessione crea il file e la cartella (src/asqueel/adapters/sqlite.py:51-53)

Motivazione. decisione utente 2026-10-17: API di amministrazione del database affidate solo alla migrazione (asqueel-migration); asqueel non le espone

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/schema.py:266
  • GenroPy: gnrpy/gnr/sql/gnrsql/schema.py:275
  • GenroPy: gnrpy/gnr/sql/gnrsql/schema.py:302
  • GenroPy: gnrpy/gnr/sql/gnrsql/schema.py:315
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:417
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:435
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:486
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:335
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:439
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:146
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:319
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:329
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/model.py:496
  • asqueel: src/asqueel/application.py:104-125
  • asqueel: src/asqueel/adapters/sqlite.py:51-53
  • asqueel: docs/guide/cli.md:115-117

Altri adapter legacy: mssql, mysql, db2_400, fourd, postgres8000 temporanea A8.23

In GenroPy scrivevi

db = GnrSqlDb(implementation='mssql', dbname='shop')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrmssql.py:102, gnrpy/gnr/sql/adapters/gnrmysql.py:48, gnrpy/gnr/sql/adapters/gnrdb2_400.py:113, gnrpy/gnr/sql/adapters/gnrfourd.py:84, gnrpy/gnr/sql/adapters/gnrpostgres8000.py:91

In asqueel

Assenti: un'implementazione diversa da sqlite, postgres e postgres3 solleva ModuleNotFoundError al caricamento (src/asqueel/runtime.py:68-73; prova con 'mssql'). Il pacchetto di migrazione ha adapter MySQL e SQL Server solo per il DDL (docs/guide/cli.md:141-144)

Motivazione. decisione utente 2026-10-17: fuori da questa release, per regola; per gli adapter vale la proposta di traduzione con sqlglot

Alternative. (a) portare gli adapter legacy come moduli asqueel.adapters.<nome> (b) compilare solo PostgreSQL e tradurre negli altri dialetti con sqlglot, come nella proposta (c) lasciarli fuori: PostgreSQL o SQLite

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrmssql.py:102
  • GenroPy: gnrpy/gnr/sql/adapters/gnrmysql.py:48
  • GenroPy: gnrpy/gnr/sql/adapters/gnrdb2_400.py:113
  • GenroPy: gnrpy/gnr/sql/adapters/gnrfourd.py:84
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres8000.py:91
  • asqueel: src/asqueel/runtime.py:68-73
  • asqueel: docs/guide/cli.md:141-144

Diverso

Scelta del backend e db.adapter: implementation ('sqlite', 'postgres', 'postgres3', 'nome:modulo'), currentImplementation A8.1

In GenroPy scrivevi

db = GnrSqlDb(implementation='postgres', dbname='shop')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/db.py:97 default 'sqlite'; gnrpy/gnr/sql/gnrsql/db.py:151-156 carica gnr.sql.adapters.gnr<implementation> oppure il modulo dopo ':'; gnrpy/gnr/sql/gnrsql/db.py:272 l'ambiente può cambiare adapter con currentImplementation

In asqueel scrivi

db = Database(implementation='postgres')
with db.tempEnv(currentImplementation='postgres'):
    adapter = db.adapter

In asqueel ottieni

src/asqueel/runtime.py:67-73 carica asqueel.adapters.<implementation> oppure il modulo dopo ':' e costruisce SqlDbAdapter(db); src/asqueel/runtime.py:81-86 db.adapter segue currentEnv['currentImplementation'] e carica l'adapter alla prima richiesta; default 'sqlite' (src/asqueel/configuration.py:57, src/asqueel/runtime.py:64); 'postgres' e 'postgres3' caricano lo stesso adapter psycopg 3 (src/asqueel/adapters/postgres3.py:2); un altro nome solleva ModuleNotFoundError (prova con 'postgresql' e 'mssql'); le connessioni nominate usano il dialetto del db (src/asqueel/configuration.py:373-374); test tests/adapters/test_db_adapter.py:10, :23, :53

Motivazione. decisione utente 2026-10-17: scelta del backend come legacy. Unica differenza: 'postgres' carica l'adapter psycopg 3, nel legacy psycopg2 (docs/design/83-legacy-realignment-r3.md:44-53); docs/guide/adapters.md:8-16

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/db.py:97
  • GenroPy: gnrpy/gnr/sql/gnrsql/db.py:151-156
  • GenroPy: gnrpy/gnr/sql/gnrsql/db.py:272
  • asqueel: src/asqueel/runtime.py:67-73
  • asqueel: src/asqueel/runtime.py:81-86
  • asqueel: src/asqueel/configuration.py:57
  • asqueel: src/asqueel/adapters/postgres3.py:2
  • asqueel: src/asqueel/configuration.py:373-374
  • asqueel: tests/adapters/test_db_adapter.py:10
  • asqueel: docs/guide/adapters.md:8-16

Parametri :nome nel testo SQL e driver PostgreSQL (prepareSqlText) A8.2

In GenroPy scrivevi

db.execute("SELECT ':id' AS v", {'id': 1})

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrpostgres.py:166 :nome diventa %(nome)s, binding di psycopg2; gnrpy/gnr/sql/adapters/gnrpostgres3.py:107 :nome diventa {nome} e gnrpy/gnr/sql/adapters/gnrpostgres3.py:337 interpola i valori nel testo con sql.SQL().format; SQLite paramstyle named (gnrpy/gnr/sql/adapters/gnrsqlite.py:60)

In asqueel scrivi

db.execute("SELECT ':id' AS v", {'id': 1})

In asqueel ottieni

Solo psycopg 3. src/asqueel/adapters/postgres.py:51-54 espande le collezioni come gnrpostgres3, poi :nome diventa %(nome)s e REGEXP diventa ~* come gnrpostgres; binding lato server del cursore psycopg (src/asqueel/adapters/postgres.py:36-40); SQLite lascia :nome (src/asqueel/adapters/sqlite.py:146-154); \: protetto (src/asqueel/adapters/base.py:149, :156); parametro mancante a None (src/asqueel/adapters/base.py:153-154). Prova: SELECT ':id' con id=1 restituisce '$1' su PostgreSQL e ':id' su SQLite

Motivazione. docs/design/83-legacy-realignment-r3.md:44-53: algoritmo postgres3 per le collezioni, senza l'interpolazione di sql.SQL().format, perché SQL e mapping restano separati. Effetto registrato in docs/design/84-legacy-realignment-r4.md:54-61 (':id' dentro una stringa dà '$1'). '%' letterale scritto '%%': docs/guide/queries.md:329 e #19

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres.py:166
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:107
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:337
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:60
  • asqueel: src/asqueel/adapters/postgres.py:51-54
  • asqueel: src/asqueel/adapters/postgres.py:36-40
  • asqueel: src/asqueel/adapters/sqlite.py:146-154
  • asqueel: src/asqueel/adapters/base.py:149
  • asqueel: src/asqueel/adapters/base.py:153-156
  • asqueel: docs/guide/queries.md:329

Collezioni dopo IN :param (adaptTupleListSet) A8.3

In GenroPy scrivevi

tblobj.query(where='$id IN :ids', ids=[1, 2]).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:208 SQLite: nome0, nome1; gnrpy/gnr/sql/adapters/gnrpostgres3.py:65 PostgreSQL: nome_0, nome_1, lista vuota sostituita da TRUE o FALSE

In asqueel scrivi

db.table('app.item').query('$id', where='$id IN :ids', ids=[1, 2]).fetch()

In asqueel ottieni

SQLite: src/asqueel/adapters/base.py:164-172 stesso algoritmo (nome0, nome1; la lista vuota diventa IN (), che non restituisce righe). PostgreSQL: src/asqueel/adapters/postgres.py:56-81 stesso algoritmo di gnrpostgres3 (nome_0, nome_1; lista vuota sostituita da TRUE o FALSE dopo una colonna "tN"."col", :67-69), con \b che fa corrispondere solo il nome intero del parametro (:80). Collisioni dei nomi generati conservate (tests/adapter_driver/test_legacy_preparation.py:151); un nome prefisso di un altro non lo cattura più (tests/adapter_driver/test_legacy_preparation.py:160); confronto con gli originali in tests/adapter_driver/test_legacy_preparation.py:43; tests/object_tree/test_minimal_select.py:270

Motivazione. Correzione dei prefissi :ids/:ids2 con \b su PostgreSQL: docs/design/83-legacy-realignment-r3.md:12-13; collisioni conservate: docs/design/83-legacy-realignment-r3.md:83-86

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:208
  • GenroPy: gnrpy/gnr/sql/adapters/gnrpostgres3.py:65
  • asqueel: src/asqueel/adapters/base.py:164-172
  • asqueel: src/asqueel/adapters/postgres.py:56-81
  • asqueel: tests/adapter_driver/test_legacy_preparation.py:43
  • asqueel: tests/adapter_driver/test_legacy_preparation.py:151
  • asqueel: tests/adapter_driver/test_legacy_preparation.py:160
  • asqueel: tests/object_tree/test_minimal_select.py:270

ILIKE su SQLite A8.5

In GenroPy scrivevi

tblobj.query(where='$name ILIKE :pattern', pattern='ali%')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrsqlite.py:115 replace('ILIKE','LIKE') e replace('ilike','like') su tutto il testo: cambia anche nomi e stringhe che contengono 'ilike'; 'IlIkE' resta invariato

In asqueel scrivi

db.table('app.customer').query('$name', where='$name IlIkE :pattern', pattern='ali%')

In asqueel ottieni

src/asqueel/adapters/sqlite.py:20-24 e :150 sostituiscono solo l'operatore ILIKE, in qualsiasi grafia, fuori da stringhe, identificatori quotati e commenti; prova: 'IlIkE' diventa 'lIkE', 'ilike' in una stringa e col_ilike restano invariati; tests/sqlite/test_ilike.py:32, :39, :45

Motivazione. nessuna issue; docs/guide/legacy-features.md:704-705 registra la differenza senza motivazione. docs/guide/sqlite.md:81-82 dice ILIKE non tradotto: vale il codice

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:115
  • asqueel: src/asqueel/adapters/sqlite.py:20-24
  • asqueel: src/asqueel/adapters/sqlite.py:150
  • asqueel: tests/sqlite/test_ilike.py:32
  • asqueel: tests/sqlite/test_ilike.py:39
  • asqueel: tests/sqlite/test_ilike.py:45
  • asqueel: docs/guide/legacy-features.md:704-705

Apertura della connessione SQLite: PRAGMA foreign_keys, timeout, opzioni A8.8

In GenroPy scrivevi

db = GnrSqlDb(implementation='sqlite', dbname='/data/shop.db')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrsqlite.py:80 timeout=20.0 e detect_types; nessun PRAGMA foreign_keys: le FK non valgono

In asqueel scrivi

db.connection(name='/data/shop.db', implementation='sqlite',
              options={'foreignKeys': False, 'timeout': 5})

In asqueel ottieni

src/asqueel/adapters/sqlite.py:56-57 detect_types PARSE_DECLTYPES e PARSE_COLNAMES come legacy; src/asqueel/adapters/sqlite.py:49 timeout 20.0 di default, modificabile insieme a cached_statements (src/asqueel/adapters/sqlite.py:46-48); src/asqueel/adapters/sqlite.py:61 PRAGMA foreign_keys = ON, OFF con l'opzione di connessione foreignKeys=False (src/asqueel/configuration.py:352-353, :392); PostgreSQL rifiuta foreignKeys (src/asqueel/adapters/postgres.py:27-28); test tests/test_sqlite_legacy_connection.py:82, :93, :106

Motivazione. decisione utente 2026-10-17: FK attive di default, disattivabili con un'opzione della connessione; timeout 20 s e detect_types come legacy; docs/guide/sqlite.md:21-29. Opzioni timeout e cached_statements modificabili: nessuna issue

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:80
  • asqueel: src/asqueel/adapters/sqlite.py:46-49
  • asqueel: src/asqueel/adapters/sqlite.py:56-57
  • asqueel: src/asqueel/adapters/sqlite.py:61
  • asqueel: src/asqueel/configuration.py:352-353
  • asqueel: src/asqueel/configuration.py:392
  • asqueel: src/asqueel/adapters/postgres.py:27-28
  • asqueel: tests/test_sqlite_legacy_connection.py:82
  • asqueel: tests/test_sqlite_legacy_connection.py:93
  • asqueel: tests/test_sqlite_legacy_connection.py:106
  • asqueel: docs/guide/sqlite.md:21-29

Tipi dei valori su SQLite (converter e adapter registrati) A8.9

In GenroPy scrivevi

tblobj.insert({'id': 1, 'price': Decimal('9.90'), 'day': date(2026, 1, 1)})

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrsqlite.py:80 PARSE_DECLTYPES; gnrpy/gnr/sql/adapters/gnrsqlite.py:543-544 Decimal scritto come float, numeric letto come Decimal; :552 date letta come date; :579 bool; :596 timestamp

In asqueel scrivi

db.table('app.item').insert({'id': 1, 'price': Decimal('9.90'), 'day': date(2026, 1, 1)})

In asqueel ottieni

src/asqueel/adapters/sqlite.py:76-87 registra i converter date (solo la parte data), time, timestamp, numeric, boolean e bool; src/asqueel/adapters/sqlite.py:135-144 scrive date, datetime e time come stringa ISO (tzinfo conservato) e Decimal come stringa. Prova: D, DH, H, N, B letti come date, datetime, time, Decimal, bool; test tests/sqlite/test_sqlite_legacy_values.py:38

Motivazione. decisione utente 2026-10-17: valori convertiti al dtype, come legacy. Differenze residue: Decimal scritto come stringa (legacy float), colonna boolean convertita (legacy solo bool), tzinfo conservato (legacy lo toglie): docs/guide/legacy-features.md:672-676 e docs/guide/sqlite.md:83-88

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:80
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:543-544
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:552
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:579
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:596
  • asqueel: src/asqueel/adapters/sqlite.py:76-87
  • asqueel: src/asqueel/adapters/sqlite.py:135-144
  • asqueel: tests/sqlite/test_sqlite_legacy_values.py:38
  • asqueel: docs/guide/sqlite.md:83-88

Transazione e for_update su SQLite A8.10

In GenroPy scrivevi

tblobj.record(pkey, for_update=True).output('bag')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/gnrsqlite.py:140-141 _selectForUpdate restituisce '': nessun lock; transazione implicita differita del modulo sqlite3 (gnrpy/gnr/sql/adapters/gnrsqlite.py:80)

In asqueel scrivi

db.table('app.item').query('$id', for_update=True).fetch()

In asqueel ottieni

src/asqueel/adapters/sqlite.py:54-57 isolation_level DEFERRED: BEGIN differito prima della prima INSERT, UPDATE o DELETE, le letture non aprono transazione, come legacy; src/asqueel/adapters/sqlite.py:111-121 con for_update BEGIN IMMEDIATE se nessuna transazione è aperta, altrimenti un UPDATE senza effetto sulla tabella; nessun FOR UPDATE nel testo (src/asqueel/adapters/sqlite.py:221-222); test tests/sqlite/test_sqlite_legacy_values.py:51, :65

Motivazione. decisione utente 2026-10-17: transazione differita come legacy, lock di scrittura solo con for_update; docs/guide/sqlite.md:59-66

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:140-141
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:80
  • asqueel: src/asqueel/adapters/sqlite.py:54-57
  • asqueel: src/asqueel/adapters/sqlite.py:111-121
  • asqueel: src/asqueel/adapters/sqlite.py:221-222
  • asqueel: tests/sqlite/test_sqlite_legacy_values.py:51
  • asqueel: tests/sqlite/test_sqlite_legacy_values.py:65
  • asqueel: docs/guide/sqlite.md:59-66

for_update con modo ('NOWAIT', 'SKIP LOCKED') A8.11

In GenroPy scrivevi

tblobj.query(for_update='SKIP LOCKED').fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:721-723 FOR UPDATE OF t0 seguito dalla stringa del modo; usato con 'SKIP LOCKED' in gnrpy/gnr/web/gnrtask.py:99

In asqueel scrivi

db.table('app.note').query(columns='$id', for_update='SKIP LOCKED').fetch()

In asqueel ottieni

src/asqueel/compiler.py:694-695 accetta solo True, 'NOWAIT' e 'SKIP LOCKED', ValueError per ogni altro valore (prova con 'FOO'); src/asqueel/adapters/base.py:139 PostgreSQL aggiunge il modo dopo FOR UPDATE OF t0; src/asqueel/adapters/sqlite.py:217-218 SQLite rifiuta i modi stringa con UnsupportedFeatureError (prova con 'SKIP LOCKED'); test tests/test_record_writes_legacy.py:136, :141

Motivazione. decisione utente 2026-10-17: for_update con modo stringa dentro, come legacy; i modi fuori dall'insieme e i modi su SQLite (legacy nessun lock) sollevano per la regola utente 2026-10-17 «ogni cosa fuori grammatica raise»; docs/guide/legacy-features.md:185-188

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:721-723
  • GenroPy: gnrpy/gnr/web/gnrtask.py:99
  • asqueel: src/asqueel/compiler.py:694-695
  • asqueel: src/asqueel/adapters/base.py:139
  • asqueel: src/asqueel/adapters/sqlite.py:217-218
  • asqueel: tests/test_record_writes_legacy.py:136
  • asqueel: tests/test_record_writes_legacy.py:141
  • asqueel: docs/guide/legacy-features.md:185-188

lock() di tabella (lockTable) A8.12

In GenroPy scrivevi

tblobj.lock(mode='SHARE', nowait=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:481 lock(mode, nowait); gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:319 LOCK ... IN <mode> MODE, con 'NO WAIT' non valido (:321); SQLite nessuna istruzione (gnrpy/gnr/sql/adapters/gnrsqlite.py:390)

In asqueel scrivi

db.table('app.note').lock(mode='SHARE', nowait=True)

In asqueel ottieni

src/asqueel/table_crud.py:72-76 table.lock(mode='ACCESS EXCLUSIVE', nowait=False) esegue lock_table nella transazione corrente; src/asqueel/write_compiler.py:233-245 su PostgreSQL LOCK ... IN <mode> MODE con NOWAIT, modo controllato su LOCK_MODES (src/asqueel/write_compiler.py:23), ValueError per un altro modo; su SQLite la riserva di scrittura con un UPDATE senza effetto (prova: UPDATE "app"."app_item" SET "id"="id" WHERE 0); test tests/test_record_writes_legacy.py:127, tests/write_compiler/test_write_compiler.py:146

Motivazione. decisione utente 2026-10-17: tblobj.lock dentro, come legacy; NOWAIT al posto di NO WAIT (difetto legacy corretto) e riserva di scrittura su SQLite: docs/guide/legacy-features.md:404-405, docs/guide/writes.md:226-232

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:481
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:319
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:321
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:390
  • asqueel: src/asqueel/table_crud.py:72-76
  • asqueel: src/asqueel/write_compiler.py:23
  • asqueel: src/asqueel/write_compiler.py:233-245
  • asqueel: tests/test_record_writes_legacy.py:127
  • asqueel: tests/write_compiler/test_write_compiler.py:146
  • asqueel: docs/guide/writes.md:226-232

unaccentFormula, mask_field_sql, ageAtDate (espressioni SQL del dialetto) A8.16

In GenroPy scrivevi

self.db.adapter.mask_field_sql('$email', mode='email')
self.db.adapter.ageAtDate('$born', timeUnit='year')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1291 unaccent($campo); :1333 unaccent nel whereTranslator; gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:248 e gnrpy/gnr/sql/adapters/gnrsqlite.py:401 mask_field_sql; gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:681 ageAtDate; usate da gnrpy/gnr/sql/gnrsqltable/columns.py:297, :344, :381

In asqueel scrivi

self.db.adapter.mask_field_sql('$email', mode='email')
self.db.adapter.unaccentFormula('$name')

In asqueel ottieni

src/asqueel/adapters/postgres.py:126-128 unaccentFormula come legacy; su SQLite il campo invariato (src/asqueel/adapters/base.py:235-237); mask_field_sql con le espressioni legacy (src/asqueel/adapters/postgres.py:130-184, src/asqueel/adapters/sqlite.py:230-287), un modo non valido solleva ValueError (src/asqueel/adapters/base.py:45-50); ageAtDate come legacy su PostgreSQL (src/asqueel/adapters/base.py:239-246), un timeUnit sconosciuto solleva ValueError, su SQLite UnsupportedFeatureError (src/asqueel/adapters/sqlite.py:227-228); db.whereTranslator rifiutato (src/asqueel/runtime.py:61); colonne variante: riga A3.15; test tests/adapters/test_adapter_functions.py:45, :52, :64, :98

Motivazione. decisione utente 2026-10-17: unaccentFormula, mask_field_sql e ageAtDate dentro, come legacy; modo e timeUnit non validi (legacy ripiega su '2-4' e su day) e ageAtDate su SQLite sollevano per la regola utente 2026-10-17 «ogni cosa fuori grammatica raise»; docs/guide/legacy-features.md:695-699

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1291
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:1333
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:248
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:401
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:681
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/columns.py:297
  • asqueel: src/asqueel/adapters/postgres.py:126-184
  • asqueel: src/asqueel/adapters/sqlite.py:227-287
  • asqueel: src/asqueel/adapters/base.py:45-50
  • asqueel: src/asqueel/adapters/base.py:235-246
  • asqueel: src/asqueel/runtime.py:61
  • asqueel: tests/adapters/test_adapter_functions.py:45
  • asqueel: tests/adapters/test_adapter_functions.py:52
  • asqueel: tests/adapters/test_adapter_functions.py:64
  • asqueel: tests/adapters/test_adapter_functions.py:98

db.analyze() / db.vacuum(table, full) A8.18

In GenroPy scrivevi

db.vacuum(table='app.app_item', full=True)

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/transactions.py:307 e :311; gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:929 ANALYZE, :933 VACUUM su una connessione autocommit dedicata

In asqueel scrivi

db.commit()
db.vacuum(table='app.app_item', full=True)

In asqueel ottieni

src/asqueel/runtime.py:282-284 analyze() esegue ANALYZE nella transazione corrente (src/asqueel/adapters/base.py:223-225), come legacy; src/asqueel/runtime.py:286-294 vacuum(table='', full=False) solleva TransactionStateError se il thread ha lavoro pendente, poi delega all'adapter: su PostgreSQL VACUUM [FULL] ANALYZE [table] su una connessione autocommit separata (src/asqueel/adapters/postgres.py:42-49); su SQLite VACUUM su main e su ogni schema allegato, poi ANALYZE [table], su una connessione separata (src/asqueel/adapters/sqlite.py:97-109); test tests/test_execute_and_transactions.py:73, tests/test_read_write_final_touch.py:118, tests/test_sqlite_legacy_connection.py:123

Motivazione. decisione utente 2026-10-17: analyze e vacuum dentro, come legacy; rifiuto con lavoro pendente e forma SQLite (il legacy emette VACUUM ANALYZE, che SQLite rifiuta): docs/guide/legacy-features.md:534-541

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:307
  • GenroPy: gnrpy/gnr/sql/gnrsql/transactions.py:311
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:929
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:933
  • asqueel: src/asqueel/runtime.py:282-294
  • asqueel: src/asqueel/adapters/base.py:223-225
  • asqueel: src/asqueel/adapters/postgres.py:42-49
  • asqueel: src/asqueel/adapters/sqlite.py:97-109
  • asqueel: tests/test_execute_and_transactions.py:73
  • asqueel: tests/test_read_write_final_touch.py:118
  • asqueel: tests/test_sqlite_legacy_connection.py:123
  • asqueel: docs/guide/legacy-features.md:534-541

Capability dell'adapter (CAPABILITIES, has_capability) A8.20

In GenroPy scrivevi

if db.adapter.has_capability('SCHEMAS'):
    pass

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:87 e :343; gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:114 MIGRATIONS, VECTOR, SCHEMAS; gnrpy/gnr/sql/adapters/gnrsqlite.py:56 SCHEMAS; ADMINISTER se ci sono gli eseguibili (gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:164-165); letta da gnrpy/gnr/sql/gnrsqlmodel/table.py:176

In asqueel scrivi

adapter = PostgresAdapter(db, capabilities=PostgresAdapter.capabilities - {'returning_insert'})

In asqueel ottieni

src/asqueel/adapters/base.py:58 attributo capabilities; src/asqueel/adapters/postgres.py:20-24 e src/asqueel/adapters/sqlite.py:32-36 capability di SQL (select, insert, update, delete, returning_insert, returning_update, returning_delete, left_join, limit, offset, default_values, for_update), riducibili con l'opzione capabilities del costruttore (src/asqueel/adapters/base.py:60-69) e verificate da _require (src/asqueel/adapters/base.py:71-73); nessun has_capability e nessuna capability SCHEMAS, MIGRATIONS, VECTOR, ADMINISTER (prova); test tests/adapter_dialect/test_postgres.py:46

Motivazione. decisione utente 2026-10-17: proposta di catalogo delle capability per tema, da discutere prima di implementarla; docs/guide/limitations.md:222-224 (dichiarare una capability non la implementa); il compilatore di scrittura non le verifica tutte: #21

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:87
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:343
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:114
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:56
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:164-165
  • GenroPy: gnrpy/gnr/sql/gnrsqlmodel/table.py:176
  • asqueel: src/asqueel/adapters/base.py:58-73
  • asqueel: src/asqueel/adapters/postgres.py:20-24
  • asqueel: src/asqueel/adapters/sqlite.py:32-36
  • asqueel: tests/adapter_dialect/test_postgres.py:46
  • asqueel: docs/guide/limitations.md:222-224

compileSql: LIMIT e OFFSET per dialetto A8.21

In GenroPy scrivevi

tblobj.query(limit=0, offset=10).fetch()

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:715-716 LIMIT e OFFSET aggiunti solo se valorizzati: limit=0 viene omesso; su SQLite un OFFSET senza LIMIT dà errore di sintassi (prova con sqlite3)

In asqueel scrivi

db.table('app.item').query('$id', limit=0, offset=10).fetch()

In asqueel ottieni

src/asqueel/adapters/base.py:133-137 limit e offset falsi non emettono clausola, come legacy (prova: limit=0 restituisce tutte le righe); src/asqueel/adapters/base.py:117-121 un limit o offset non intero o negativo solleva ValueError; src/asqueel/adapters/sqlite.py:219-220 OFFSET senza LIMIT aggiunge LIMIT 9223372036854775807 (prova)

Motivazione. decisione utente 2026-10-17: limit=0 come legacy. OFFSET senza LIMIT su SQLite e controllo degli interi: nessuna issue. docs/guide/queries.md:127 dice che limit=0 non restituisce righe: vale il codice

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:715-716
  • asqueel: src/asqueel/adapters/base.py:117-121
  • asqueel: src/asqueel/adapters/base.py:133-137
  • asqueel: src/asqueel/adapters/sqlite.py:219-220

Tipi SQL dei dtype (revTypesDict, columnSqlType) e lettura inversa (typesDict) A8.22

In GenroPy scrivevi

tbl.column('note', dtype='X')
tbl.column('code', dtype='A', size='10')

In GenroPy ottenevi

gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:1133 columnSqlType (size senza ':' dà C, 'n:m' dà A); gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:146 tipi PostgreSQL; gnrpy/gnr/sql/adapters/gnrsqlite.py:50 tipi SQLite (X blob, DH timestamp, H time, O assente); gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:123 typesDict

In asqueel scrivi

tbl.column('note', dtype='X')
tbl.column('code', dtype='A', size='10')

In asqueel ottieni

Il DDL lo produce asqueel-migration 0.3 con i nomi di tipo legacy: PostgreSQL come revTypesDict, SQLite character varying(n), character(n), timestamp, time, blob per O. src/asqueel/projection.py:15-30 proietta X, Z, P e A o C senza size come T senza cambiare la colonna del modello. Prova: A senza size e X diventano text su entrambi i dialetti; DH timestamp su SQLite e timestamp without time zone su PostgreSQL; O blob e bytea. Codici dtype in src/asqueel/elements.py:62-65; lettura inversa PostgreSQL con le voci di typesDict (src/asqueel/importers.py:210-225); test tests/test_sqlite_legacy_connection.py:139, :152

Motivazione. decisione utente 2026-10-17: tipi come legacy anche su SQLite, trasformazione di X/Z/P/A senza size in T solo nel DDL. Il codice proietta X come text anche su SQLite, dove gnrsqlite.revTypesDict dà blob: docs/guide/legacy-features.md:686-691

File
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbaseadapter.py:1133
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:146
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:50
  • GenroPy: gnrpy/gnr/sql/adapters/_gnrbasepostgresadapter.py:123
  • asqueel: src/asqueel/projection.py:15-30
  • asqueel: src/asqueel/elements.py:62-65
  • asqueel: src/asqueel/importers.py:210-225
  • asqueel: tests/test_sqlite_legacy_connection.py:139
  • asqueel: tests/test_sqlite_legacy_connection.py:152
  • asqueel: docs/guide/legacy-features.md:686-691

Nuovo

Versione minima di SQLite A8.13

In asqueel scrivi

db.connection(name='/data/shop.db', implementation='sqlite')

In asqueel ottieni

src/asqueel/adapters/sqlite.py:39-40 connect() solleva RuntimeError sotto SQLite 3.35, la versione che introduce RETURNING; RETURNING delle scritture: riga A5.16

Motivazione. decisione utente 2026-10-17 (versione minima SQLite 3.35: si tiene); docs/guide/sqlite.md:5

File
  • GenroPy: gnrpy/gnr/sql/adapters/gnrsqlite.py:1
  • asqueel: src/asqueel/adapters/sqlite.py:39-40
  • asqueel: docs/guide/sqlite.md:5

Uguale

  • REGEXP / ~* e funzione regexp su SQLite A8.4
  • IS TRUE / IS FALSE su SQLite (_booleanSubCb) A8.6
  • Schemi SQLite come file allegati (ATTACH) e schema main A8.7
  • db.adapter.string_agg(campo, separatore) nelle formule del progetto A8.15

Da implementare

Funzioni che una decisione registrata dice che saranno implementate.

Query

selection() e API SqlSelection (output, sort, totalize, freeze, frozenSelection) A2.20

In GenroPy scrivevi

selection = tblobj.query(columns='$id, $total').selection()
selection.sort('total:d')
bag = selection.output('grid')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqldata/query.py:446; gnrpy/gnr/sql/gnrsqldata/selection.py:256 output, :493 freeze, :558 sort, :731 totalize; gnrpy/gnr/sql/gnrsqltable/query.py:107

In asqueel

src/asqueel/application_table.py:315-316 solleva UnsupportedFeatureError 'selection(): Selection and Bag output are not available in this release' (prova)

Motivazione. decisione utente 2026-10-17 (da realizzare, prossimo step dopo la pulizia); docs/guide/limitations.md:238

File
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/query.py:446
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/selection.py:256
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/selection.py:493
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/selection.py:558
  • GenroPy: gnrpy/gnr/sql/gnrsqldata/selection.py:731
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/query.py:107
  • asqueel: src/asqueel/application_table.py:315-316
  • asqueel: docs/guide/limitations.md:238

Read-Write

Trigger di package trigger_<evento>_<pkg> e avoid_trigger_<pkg> A5.8

In GenroPy scrivevi

class Table(object):
    def trigger_onInserting_mypkg(self, record):
        record['code'] = record['code'].upper()

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsqltable/crud.py:696-710 per ogni package dell'applicazione, saltati con avoid_trigger_<pkg> nell'env; chiamati in gnrpy/gnr/sql/gnrsql/write.py:157, :168, :255, :265, :309, :318

In asqueel

Assenti dal ciclo (src/asqueel/db_write.py:6 nel docstring); un metodo trigger_on<Evento>_<pkg> nella classe della tabella è rifiutato con UnsupportedFeatureError (src/asqueel/object_tree.py:173-181, prova); avoid_trigger_<pkg> non esiste

Motivazione. decisione utente 2026-10-17: da implementare, ma solo dopo una valutazione; fino ad allora la dichiarazione solleva errore. docs/guide/hooks.md:124-128

File
  • GenroPy: gnrpy/gnr/sql/gnrsqltable/crud.py:696-710
  • GenroPy: gnrpy/gnr/sql/gnrsql/write.py:157
  • asqueel: src/asqueel/object_tree.py:173-181
  • asqueel: src/asqueel/db_write.py:6

Commento SQL con l'utente, sql_audit, debugger A6.20

In GenroPy scrivevi

with db.tempEnv(sql_comment='nightly import'):
    db.execute('SELECT 1')

In GenroPy ottenevi

gnrpy/gnr/sql/gnrsql/execute.py:105 e :120 antepone '-- <sql_comment o user>'; gnrpy/gnr/sql/gnrsql/helpers.py:88-117 log di audit con il tempo; debugger a :149-152

In asqueel

Nessun commento né audit; le chiavi d'ambiente sql_comment e sql_details sollevano UnsupportedFeatureError all'esecuzione (src/asqueel/runtime.py:433-437; prova). debugger assente (prova)

Motivazione. decisione utente 2026-10-17: sql_comment e chiavi d'ambiente simili da includere, non subito, resi più fruibili (per esempio JSONL); fino ad allora errore. docs/guide/limitations.md:200-201

File
  • GenroPy: gnrpy/gnr/sql/gnrsql/execute.py:105
  • GenroPy: gnrpy/gnr/sql/gnrsql/helpers.py:88-117
  • asqueel: src/asqueel/runtime.py:433-437

Migrazione

asqueel non modifica da sé la struttura del database. Per il DDL usa il pacchetto separato asqueel-migration, installato con asqueel[migration].

  • project_tree() traduce il modello asqueel nel formato che asqueel-migration legge.
  • db.migration_plan() restituisce il piano e non cambia niente; db.migrate() lo applica.
  • Entrambi accettano le opzioni allow_removals, foreignKeys, force e backup.
  • La riga di comando offre le stesse due operazioni: asqueel db plan e asqueel db apply, con --allow-removals, --no-foreign-keys, --force e --backup.

Le differenze del layer di migrazione sono descritte in asqueel-migration.

API di basso e alto livello

Questa release è il livello basso dell'API SQL.

  • In questa release: query() con fetch() e i suoi derivati, fetchRecord(), query().update() e query().delete() (riga per riga, attraverso il ciclo di scrittura), query().sql_update() e query().sql_delete() (un solo statement, senza trigger), insert(), update(), delete() e le scritture sql_* di una tabella.
  • Più avanti, nel livello alto: record() con la sua API ricca, selection() (il prossimo passo), batchUpdate(), recordToUpdate(), check_updatable() e check_deletable().

Una funzione di alto livello chiamata in questa release solleva un errore che la nomina.

Proposte

Queste proposte sono in discussione. Nessuna è implementata.

Traduzione con sqlglot. asqueel compilerebbe solo SQL PostgreSQL e lo tradurrebbe negli altri dialetti con sqlglot, attraverso il suo albero sintattico. Un solo compilatore servirebbe tutti i dialetti, e dialetti come MySQL, SQL Server e DuckDB diventerebbero raggiungibili. Punti aperti: semantica che non si traduce (FOR UPDATE su SQLite, jsonb_agg, xmlagg, tsvector), conservazione dei parametri della query, costo del parsing, una nuova dipendenza.

Elemento figlio .sql(...). Gli attributi sql_* di una colonna sarebbero raggruppati in un solo elemento figlio .sql(...) invece di stare sparsi sulla colonna.

Catalogo delle capabilities. Ogni adapter dichiarerebbe le capabilities che ha, organizzate per tema: alter column, indici gin, RETURNING, aggregati JSON e XML, ricerca full-text, vettori, schemi, migrazione, amministrazione. Una chiamata che richiede una capability che il dialetto non ha solleverebbe un errore che nomina la capability.

Modalità di connessione async. asqueel gestisce le connessioni come GenroPy: l'ambiente si rilegge a ogni esecuzione, e non esistono db.close(), with db: né acquire(). Modalità di connessione alternative, per esempio per codice async, sono un possibile studio futuro.