Mondi su mondi, sistemi di sistemi.

OR Mappers: altre testimonianze

Wednesday, March 31st, 2010

Vedo che anche altri sono più o meno del mio parere1.

Mi sembra interes­sante anche SQLAlchemy, credo varrà la pena di dargli un’occhiata.

Core Data, sì o no?

Wednesday, March 3rd, 2010

Premessa doverosa: non ho mai usato Core Data anche se ho usato in lungo e in largo l’EOF, fino a lasciarlo perdere. I post1 di Brent Simmons in cui dice di aver rinunciato a usare Core Data sull’iPhone per problemi di pre­stazioni, quindi, non poteva sfuggirmi, tanto più che ha generato diverse reazioni interes­santi2.

Quello che mi interessa discutere non è la scelta in sé, che è solo buon senso applicato alla programmazione, ovvero, lavora sempre al più alto livello di astrazione pos­sibile, scendi sotto solo se sei obbligato.

Quello che continuo a non capire è come si possa sostenere che una cosa come update newsItems set read = 1 where... stia a un livello di astrazione più basso di un loop in Objective-C o qualche altro linguaggio.

Non mi sfuggono gli aspetti problematici del fare interagire un programma con un database né che secondo alcuni Core Data non è un database o un ORM3, ma è proprio questo arguire intorno al cosa sia Core Data mi rende sospettoso perché queste discus­sioni sembrano spostare il problema nell’àmbito delle aspettative sbagliate.

Secondo la documentazione ufficiale:

The technically correct way to describe Core Data is as an object-graph management and persistence framework. In down-to-earth terms, this means that Core Data organizes the application’s model layer into a set of defined in-memory data objects. Core Data tracks changes to these objects and can reverse those changes on demand, such as when a user performs an undo command. Then, when it is time to save changes to your application’s data, Core Data takes care of archiving the objects to a persistent store. (…)

Core Data builds on some of the concepts of enterprise-class database application frameworks, such as the Enterprise Objects Framework in WebObjects. However, make no mistake, Core Data is not an object-relational database access framework. Instead, it uses concepts from the database world to take application data management to a new level.

Ora, se diamo per buona questa descrizione — e non vedo perché non dovremmo — bisogna chiedersi: le aspettative di Brent Simmons erano ragionevoli? Credo di sì, perché se “usi dei concetti dal mondo dei database” non è strano aspettarsi un modo efficiente di fare un update. Giusto?

PostgreSQL 8.4: parametri di default e overloading

Wednesday, November 11th, 2009

Tempo fa, quando spiegavo le novità di PostgreSQL 8.4 nelle funzioni, avvertivo anche che la combinazione dei para­metri di default con l’overloading degli argomenti delle funzioni poteva condurre ad ambiguità.

Mi ero però limitato alla parte negativa, mentre è proprio grazie ai default che diventa pos­sibile sbarazzarsi di funzioni dallo scopo identico, che variano solo per il numero di argomenti.

In questo post si spiegano i vantaggi (piuttosto ovvi, in verità) di questo approccio e si accennano alle novità in arrivo per la versione 8.5.

Diciamo no a MySQL

Thursday, October 15th, 2009

Pensierini della sera

Nei momenti di sciatteria mi vien da pensare che in fondo, per la maggior parte degli usi, MySQL va più che bene. Sì, d’accordo, PostgreSQL è un’altra cosa ma sono differenze che spesso non si vedono.

Poi mi capitano cose come quella che segue e capisco che è una sciocchezza.

Il fatto

C’è questa applicazione PHP+MySQL da mettere in sicurezza, prima della riscrittura da zero. Provo alcune modifiche in locale e, essendo tutto a posto, riporto gli aggiornamenti sul server di deployment.

Verifico che funzioni tutto e noto alcune stranezze per cui inizio a fare tutte le pos­sibili verifiche ma sembra tutto a posto, fin quando provo l’SQL a mano e vedo che una query dà errore dicendo che la tabella è inesistente (????).

A questo punto guardo la documentazione di MySQL e scopro che:

In MySQL, databases correspond to directories within the data directory. Each table within a database corresponds to at least one file within the database directory (and pos­sibly more, depending on the storage engine). Consequently, the case sensitivity of the underly­ing operating system plays a part in the case sensitivity of database and table names. This means database and table names are not case sensitive in Windows, and case sensitive in most varieties of Unix. One notable exception is Mac OS X, which is Unix-based but uses a default file system type (HFS+) that is not case sensitive.

A quel punto capisco: sto sviluppando su Mac OS X e il server è Linux.

La morale della storia

Che dire? tutto questo è ridicolo. Non so cosa dica lo standard SQL: se il riferimento ai nomi debba esser case sensitive o meno ma che una query smetta di funzionare a causa del sistema operativo sottostante è veramente un delirio. Non vedo l’ora di mandare in pensione questa applicazione!

Configurazione nei database

Monday, September 14th, 2009

Tanto tempo fa… Un prologo

Qualche anno fa mi ero messo a scrivere un framework di autenticazione e autorizzazione per WebObjects. L’obiettivo era quello di creare un sistema integrato in cui i principali aspetti della sicurezza fos­sero affrontati e messi in collegamento con il DirectToWeb. Fu un mezzo fallimento.

Funzionava, sì, ma era troppo macchinoso da impostare e utilizzare. Uno dei motivi fu quello di voler usare un database per memorizzare le configurazioni, obbligandomi anche a pre­vedere un’interfaccia di gestione per aspetti che è quasi sempre pre­feribile affrontare con un buon editor di testo.

Flash forward

L’altro ieri mi trovavo a mettere le mani su un’applicazione fatta in Joomla (oh, the horror… Risparmio le imprecazioni per un codice che grida vendetta).

Anche Joomla usa un database dietro le quinte per archiviare le sue configurazioni e consente un approccio semplice ad alcune delle funzioni di CMS ma rende le operazioni di staging molto, troppo complicate: non posso provare una configurazione in locale, fare il commit nella repository e poi fare il deployment ma devo replicare le modifiche sul database di produzione; inoltre non ho nes­sun controllo di versione se non facendo il dump delle tabelle.

Anche in WordPress, come segnalavo qualche giorno fa, alcune operazioni concettualmente semplici e standard sono più complicate del previsto.

Un anti-pattern?

A questo punto, comincio a sospettare seriamente che questa soluzione sia in realtà un anti-pattern bello e buono; forse dovremmo essere un po’ più parsimoniosi nell’uso dei database.

PostgreSQL: passare le informazioni di sessione dall’applicazione

Tuesday, September 8th, 2009

Nelle applicazioni che sfruttano una connes­sione persistente verso il database e implementano al loro interno la logica di autenticazione e autorizzazione, può pre­sentarsi la neces­sità di pas­sare al database delle informazioni relative all’utente dell’applicazione, che sarà diverso da quello usato per connettersi al database.

In molti casi, se si tratta solo di inserire le informazioni di auditing su poche tabelle, è sufficiente che sia l’applicazione a pas­sare esplicitamente queste informazioni, durante il salvataggio dei record. Se l’auditing va fatto su tutte le tabelle si può progettare il codice in modo da rendere l’operazione più o meno trasparente.

Non sempre questo è pos­sibile — le modifiche al codice sarebbero troppo onerose o impos­sibili — né adeguato. Ad esempio, quando vogliamo tracciare eventuali attività svolte al di fuori del controllo dell’applicazione. In questi casi la soluzione dev’essere trovata al livello del database.

Il problema diventa quindi come pas­sare in modo trasparente i dati sull’utente dell’applicazione. Una pos­sibilità consiste nell’usare le cosiddette opzioni custom in combinazione con le trigger.

Al login sull’applicazione verrà chiamata una funzione che imposta il valore dell’opzione custom:

CREATE OR REPLACE FUNCTION begin_sess(staffid text)
RETURNS void AS $$ BEGIN
    PERFORM set_config(
        'mysess.curr_user',
        coalesce(staffid,''),
        false
    );
END; $$ LANGUAGE 'plpgsql' VOLATILE

Con questa informazione, basta agganciare una trigger ad ogni tabella che ci interessa:

...
DECLARE
   curr_user	staff.staff_id%TYPE;
BEGIN
   SELECT current_setting('mysess.curr_user') INTO curr_user;
...

Se il valore di mysess.curr_user fosse vuoto, ad es. perché l’operazione viene fatta direttamente sul database, basterà ripiegare sull’utente a livello del database.

Le informazioni riportate qui sono state ricavate più o meno pedis­sequamente da questo thread: Audit Trigger puzzler.

Vincoli di unicità con i NULL

Monday, June 29th, 2009

Anche se non amo i NULL, questo “trucco” che ho letto sulla mailing list di PostgreSQL è troppo carino per non citarlo.

Qual è il problema?

Come dice il titolo, vogliamo imporre un vincolo di unicità su più colonne, una delle quali può essere NULL. In PostgreSQL, se abbiamo tre colonne A, B e C — con C che può essere NULL — e creiamo uno UNIQUE INDEX con tutte e tre, posso inserire due record con valori duplicati per A e B.

Salvati dagli indici parziali

La soluzione, una volta tanto, è semplice: basta creare un indice parziale:
CREATE INDEX idx ON table_name(A,B) WHERE (C IS NULL);

pgTAP: un esempio concreto di TDD

Wednesday, May 27th, 2009

Qualche giorno fa parlavo di pgtap e accennavo alla pos­sibilità di usarlo per usare il TDD direttamente con il database, sfruttando il fatto che PostgreSQL consente l’uso di transazioni anche per
i comandi di definizione delete database.

Questo è un grosso vantaggio perché posso definire nuovi oggetti dello schema senza che eventuali errori si propaghino al di fuori della ses­sione corrente.

NB: L’esempio che segue è prolisso e probabilmente simile a innumerevoli altri esempi sulle virtù del “test infected” ma mi sembra comunque degno di nota che si possa usare questo approccio anche all’interno di un database. Bene, cominciamo:

\set ECHO
\set QUIET 1
\pset format unaligned
\pset tuples_only true
\pset pager
\set ON_ERROR_ROLLBACK 1
\set ON_ERROR_STOP true
\set QUIET 1

Dopo aver impostato alcuni para­metri per psql, proviamo a definire un nuovo domain che verrà poi utilizzato da una nuova tabella.

Ho installato pgtap in uno schema a sé stante, in modo che le sue funzioni non si mischino con quelle dello schema “vero”.

Visto che vogliamo sviluppare in modalità TDD, creiamo subito il test per verificare la pre­senza del domain, che ovviamente fallirà.

BEGIN;
SET search_path TO tap, public;
SELECT plan(1);
SELECT has_domain( 'new_domain', 'verifichiamo la presenza del domain');
SELECT * FROM finish();

ROLLBACK;

1..1
not ok 1 - verifichiamo la presenza del domain
# Failed test 1: "verifichiamo la presenza del domain"
# Looks like you failed 1 test of 1

Aggiungiamo il domain:

BEGIN;

CREATE DOMAIN new_domain int CHECK (VALUE > 0 AND VALUE < 5);

SET search_path TO tap, public;
SELECT plan(1);
SELECT has_domain( 'new_domain', 'verifichiamo la presenza del domain');
SELECT * FROM finish();

ROLLBACK;

1..1
ok 1 - verifichiamo la presenza del domain

Funziona! Adesso tocca alla tabella:

BEGIN;

CREATE DOMAIN new_domain int CHECK (VALUE > 0 AND VALUE < 5);

SET search_path TO tap, public;
SELECT plan(2);
SELECT has_domain( 'new_domain', 'verifichiamo la presenza del domain');
SELECT has_table( 'new_table', 'verifichiamo la presenza di new_table');
SELECT * FROM finish();

ROLLBACK;

1..2
ok 1 - verifichiamo la presenza del domain
not ok 2 - verifichiamo la presenza di new_table
# Failed test 2: "verifichiamo la presenza di new_table"
# Looks like you failed 1 test of 2

Solita storia: facciamo in modo di pas­sare anche questo secondo test.

BEGIN;

CREATE DOMAIN new_domain int CHECK (VALUE > 0 AND VALUE < 5);
CREATE TABLE new_table (col new_domain);

SET search_path TO tap, public;
SELECT plan(2);
SELECT has_domain( 'new_domain', 'verifichiamo la presenza del domain');
SELECT has_table( 'new_table', 'verifichiamo la presenza di new_table');
SELECT * FROM finish();

ROLLBACK;

Aggiungiamo qualche test “perimetrale”:

BEGIN;

CREATE DOMAIN new_domain int CHECK (VALUE > 0 AND VALUE < 5);
CREATE TABLE new_table (col new_domain);

SET search_path TO tap, public;
SELECT plan(4);
SELECT has_domain( 'new_domain', 'verifichiamo la presenza del domain');
SELECT has_table( 'new_table', 'verifichiamo la presenza di new_table');
SELECT col_type_is('new_table', 'col', 'new_domain');
SELECT col_hasnt_default('new_table', 'col');
SELECT * FROM finish();

ROLLBACK;

Uno degli aspetti interes­santi è che pos­siamo testare l’efficacia della validazione del domain separatamente dal suo uso effettivo in qualche tabella. Nell’esempio che segue creiamo una tabella che ha il solo scopo di testare gli insert:

BEGIN;

CREATE DOMAIN new_domain int CHECK (VALUE > 0 AND VALUE < 5);
CREATE TEMP TABLE dummy (col new_domain);

SET search_path TO tap, public;
SELECT plan(3);
SELECT throws_ok(
    'INSERT INTO dummy (col) values(5)',
    '23514',
    'value for domain new_domain violates check constraint "new_domain_check"',
    'Non sono consentiti valori superiori a 4'
);
SELECT throws_ok(
    'INSERT INTO dummy (col) values(0)',
    '23514',
    'value for domain new_domain violates check constraint "new_domain_check"',
    'Non sono consentiti valori inferiori a 0'
);
SELECT lives_ok(
    'INSERT INTO dummy (col) values(1)',
    '1 è un valore consentito'
);
SELECT * FROM finish();

ROLLBACK;

1..3
ok 1 - Non sono consentiti valori superiori a 4
ok 2 - Non sono consentiti valori inferiori a 0
ok 3 - 1 è un valore consentito

Una volta che siamo soddisfatti delle modifiche basterà estrarre gli statement DML e sottometterli, lasciando alla nostra suite di test.

Test-driven design con PostgreSQL: pgTAP

Friday, May 15th, 2009

Fino ad ora ho sempre testato il database attraverso un qualche strumento esterno, come i vari JUnit, TestNG o tcltest.

Sono librerie che funzionano bene e che sono familiari a chiunque pratichi un minimo di TDD ma non particolarmente tagliate per testare i database (con l’eccezione forse di TestNG, che non uso da anni): vuoi perché bisogna pre­vedere delle fixture elaborate che vanno poi eliminate; vuoi perché, a ben guardare, bisogna attraversare tutta una serie di strati software anche solo per verificare un constraint.

Così ho fatto una ricerca e ho trovato due utilità per PostgreSQL — pgTAP e PGUnit — che permettono di creare delle suite di test eseguite direttamente nel database.

I vantaggi sono diversi: oltre all’accesso più diretto e pre­stazioni pre­sumibilmente migliori, la gestione delle fixture risulta semplificata perché basta fare il rollback alla fine dei test; inoltre, dato che in PostgreSQL i comandi DDL sono transazionali, diventa pos­sibile testare le modifiche al database istantaneamente, senza doverlo fare in modo permanente.

Per ora ho scelto pgTAP. Mette a disposizione una serie di funzioni che pos­sono venire molto comode per scrivere i test.

Le prime impres­sioni sono buone e penso che il miglioramento sarà anche più marcato quando potrò disegnare direttamente da zero uno schema in modalità TDD.

PostgreSQL 8.4: window functions

Tuesday, May 12th, 2009

Le window functions permettono di usare valori aggregati direttamente sulle singole righe e senza dover utilizzare la clausola GROUP BY.

Prima della versione 8.4 avrei dovuto scrivere una cosa del genere (usando l’esempio in documentazione):


SELECT
    depname,
    empno,
    salary, avg_salary
FROM
    empsalary
    JOIN (
        SELECT
            depname,
            avg(salary) as avg_salarys
        FROM empsalary
        GROUP BY depname
    ) AS avg_salaries using(depname);


Oltre a semplificare le query, pos­siamo anche andare oltre e, con la funzione rank() “clas­sificare” i record della tabella in base al loro salario o calcolare dei totali progres­sivi; il tutto espresso in modo molto naturale.

Se penso alle contorsioni da fare in SQL “normale” (o comunque una versione sufficientemente vecchia) per fare cose del genere…

« Voci Precedenti