Category Archives: database

Un cinguettio alternativo

Ciao a tutti cari amici di Ok, panico,

Sono 61ll35, questo è il mio secondo post.

Il primo post l’avevo dedicato alla creazione di una specie di google offline, dove utilizzavo in locale, sul mio pc, il linguaggio di programmazione PHP, affiancato al database MySql.

Tutto ciò era offline.

Vi spiego il motivo di questo post: Grazie agli stessi linguaggi di programmazione sopra citati ho realizzato un piccolo clone di twitter.

L’ho prima testato abbondantemente su localhost, poi l’ho trasferito online, su altervista, un sito di hosting gratuito.

Ora vorrei il vostro aiuto per testare questo piccolo social network…

Non vi aspettate granchè,in fatto di estetica,velocità, facilitò d’uso: comunque è un primo passo… e perdonate gli errori di gioventù.. Tenete inoltre presente che la velocità non dipende in gran parte da me: l’archiviazione su altervista è ottima, il servizio eccellente, ma la banda non sarà mai pari a quella di siti in cui si paga per avere questi servizi…

okp

Lo trovate al sito http://www.juhanpassaatwitter61ll35.altervista.org

Registratevi,loggatevi e iniziate a twittare!!

Quando vi registrerete, vi sarà detto di attivare il vostro account tramite mail: ignorate questo messaggio per il momento,in quanto, sul mio account di prova, le mail arrivavano con forte ritardo.

Se volete mandarmi un messaggio, inserite nel tweet “@61ll35”, proprio come su twitter!

Vi aspetto numerosi!

Ciao

Errata Corrige

Ebbene si, mi sono sbagliato. Nel mio articolo dove descrivevo la migrazione da Oracle a Postgres ho sbagliato il sorgente della funzione get_sysdate. Nella definizione della funzione non andavano messe le parentesi tonde (). Altrimenti Oracle non compila la funzione… Questo è il codice giusto:

create or replace function get_sysdate return date is
 Result date;
begin
 Select sysdate into Result from dual;
 return(result);
end get_sysdate;

Il classico errore di copia e incolla 😦 o almeno spero non sia la vecchiaia 😉 ho già corretto il codice nell’articolo originale… a presto e senza errori spero!

Sviluppo multipiattaforma con QT

A settembre 2011 Embarcadero annunciava il suo Delphi XE2 con la libreria firemonkey che permettevano una compilazione di un eseguibile tra piattaforme diverse. Un solo sorgente per Windows e OSX.
In realtà esiste già da tempo una libreria che permette di fare compilazioni cross-platform e si chiama QT un solo sorgente per i sitemi Linux/OSX/Windows e inoltre embedded Linux e Symbian. Così giusto per fare una qualche prova e scrivere un po’ di codice in C++ ho provato a fare un programma che si collegasse ad un database sqlite 🙂
Con le librerie Qt viene anche fornito un IDE, QT Creator, che permette di sviluppare i propri applicativi. Esiste una versione opensource per i progetti non commerciali altrimenti bisogna investire qualche euro (per sapere i prezzi bisogna contattare il loro team vendite).
Per la creazione della for ho usato il designer visuale integrato con il QT Creator. 3 bottoni, connessione al DB, esecuzione query e uscita dall’applicativo e una griglia per visualizzare i dati. La griglia è una QTableWidget (non ‘è l’oggetto consigliato per i dati che arrivano da un database, ma per la prova va benissimo).

Per usare le librerie per i database ho dovuto aggiungere sql nel file di progetto “.pro” Che di default aveva questo contenuto:

#-------------------------------------------------
#
# Project created by QtCreator 2013-02-07T11:50:15
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = DBTest
TEMPLATE = app

SOURCES += main.cpp\
 mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui

Che io ho cambiato in:

QT += core gui sql

Il file header (mainwindow.h) è il seguente:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtSql/QSqlDatabase>

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
 Q_OBJECT

  public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

  private slots:
    void on_btnDBConnection_clicked();
    void on_btnQuery_clicked();

  private:
    Ui::MainWindow *ui;
    QSqlDatabase db;
};

#endif // MAINWINDOW_H

Il file sorgente è invece questo.

#include <QMessageBox>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlRecord>
#include <QTableWidgetItem>
#include <QDir>
#include <QSqlQuery>
#include <QtSql>

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
 QMainWindow(parent),
 ui(new Ui::MainWindow)
{
 ui->setupUi(this);
}

MainWindow::~MainWindow()
{
 db.close();
 delete ui;
}

void MainWindow::on_btnDBConnection_clicked()
{
  //Connessione al DB
  db = QSqlDatabase::addDatabase("QSQLITE");
  QString path(QDir::home().path());
  path.append(QDir::separator()).append("qtTest.sqlite");
  path = QDir::toNativeSeparators(path);
  db.setDatabaseName(path);
  bool ok = db.QSqlDatabase::open();
  if (!ok)
    QMessageBox::critical(0, QObject::tr("Errore database"), db.lastError().text());
}

void MainWindow::on_btnQuery_clicked()
{
  if (db.isOpen())
  {
    QSqlQuery query("Select * from tbllocalita");

    int codNo = query.record().indexOf("cod");
    int desNo = query.record().indexOf("des");
    ui->tblData->insertColumn(0);
    ui->tblData->insertColumn(1);
    while (query.next())
    {
      QString sCod = query.value(codNo).toString();
      QString sDes = query.value(desNo).toString();
      int row = ui->tblData->rowCount();
      ui->tblData->insertRow(row);
      ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
      ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));
    }
  }
}

E’ da notare la definizione del tipo di database a cui connettersi:

db = QSqlDatabase::addDatabase("QSQLITE");

I database supportati sono i seguenti (copio e incollo dal sito):

“IBM DB2 (version 7.1 and above)Borland InterBase
MySQL
Oracle Call Interface Driver
Open Database Connectivity (ODBC) – Microsoft SQL Server and other ODBC-compliant databases
PostgreSQL (versions 7.3 and above)
SQLite version 2
SQLite version 3
SQLite version 3 for Symbian SQL Database
Sybase Adaptive Server Note: obsolete from Qt 4.7”

Trovate più informazioni sui database qui.

Dopo la scelta del tipo di database, bisognerà definire i parametri di connessione. Nel caso in questione avendo scelto sqlite si dovrà definire la path del file (l’ultima riga), ed ecco comparire alcune utili funzioni integrate in QT che permettono di otterene la path della propria home e di riscrivere i separatori delle cartelle rispettando lo standard del sistema operativo su cui gira il programma (le prime 3 righe).

QString path(QDir::home().path());
path.append(QDir::separator()).append("qtTest.sqlite");
path = QDir::toNativeSeparators(path);
db.setDatabaseName(path);

Di seguito potete vedere come nella finestra di debug le path siano corrette:

Path in ambiente windows

Path in ambiente windows

Path in ambiente Linux

Path in ambiente Linux

Una volta aperto il DB, potremo fare la nostra estrazione dei dati:

if (db.isOpen())
{
  QSqlQuery query("Select * from tbllocalita");

  int codNo = query.record().indexOf("cod");
  int desNo = query.record().indexOf("des");
  ui->tblData->insertColumn(0);
  ui->tblData->insertColumn(1);
  while (query.next())
  {
    QString sCod = query.value(codNo).toString();
    QString sDes = query.value(desNo).toString();
    int row = ui->tblData->rowCount();
    ui->tblData->insertRow(row);
    ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
    ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));
  }
}

Sempre in questo esempio la tabella era banale e aveva solo 2 colonne, ma nel caso ve ne siano molte di più si può vedere che è possibile abbinare un identificativo menmonico (una variabile di tipo intero) alla colonna fisica della query risultante in maniera da poterla usare per ottenere il valore del campo. Si è vero si poteva scrivere anche così:

QString sCod = query.value(query.record().indexOf("cod")).toString();
QString sDes = query.value(query.record().indexOf("des")).toString();

Infine i dati vengono salvati nella griglia, oggetto tbldata.

int row = ui->tblData->rowCount();
ui->tblData->insertRow(row);
ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));

La funzione insertRow inserisce una riga vuota alla posizione specificata da row, per cui nel nostro caso ci viene comodo impostarla sempre al numero di righe inserite che partono da 0.

Si compila quindi il tutto e lo stesso sorgente genera due eseguibili diversi funzionanti nello stesso modo su Windows e Linux.

Binario Linux

Binario Linux

Binario Windows

Binario Windows

Si lo so… i più bravi saranno già li a dire che con QT esisteno le QTableView a cui è possibile abbinare direttamente un modello dati di tipo QSqlQueryModel (che è in sola lettura) o QSqlTableModel (anche in scrittura). Per cui il codice in on_btnQuery_clicked diventerà molto semplicemente:

QSqlQueryModel *model = new QSqlQueryModel;
QSqlQuery query("Select cod, des from tbllocalita");

model->setQuery(query);
model->setHeaderData(0, Qt::Horizontal, tr("Codice"));
model->setHeaderData(1, Qt::Horizontal, tr("Descrizione"));

ui->tblData->setModel(model);

Speriamo che Nokia non decida in futuro di farci pagare questo gioiellino 🙂

Da Oracle a Postgresql.

Oracle è un ottimo DB si installa e ci si dimentica che c’è (beh magari una controllatina allo spazio disco ogni tanto bisogna darla). Unica controindicazione… costa! E mica poco!!!

Così per venire in aiuto alle piccole realtà, nell’azienda dove lavoro, si è deciso di fare un porting dell’applicativo su un altra base dati… Doveva essere un database il più possibile stabile e performante e magari simile ad Oracle e la scelta è ricaduta su Postgresql.
La migrazione non è stata indolore si è dovuto modificare (o meglio ringiovanire) pesantemente le query per renderle il più possibile compatibili con entrambe le base dati in maniera da avere query diverse solo in pochi casi (gestite poi con delle direttive di compilazione).
Questo è un eleco delle problematiche riscontrate e delle soluzioni adottate. Ovviamente nessuna regola è scritta nella pietra e probabilmente ci sono soluzioni migliori, ma queste hanno funzionato.

Fase 1 – Rendere standard l’SQL.

L’applicativo originale nasceva su piattaforma Oracle 8, nel 1999. Per cui si usava l’sql di Oracle che allora non rispettava lo standard ANSI. Il primo lavorone è stato rendere ANSI le query.

A) Outer Join
In Oracle un outer join può essere indicata usando la sintassi (+) nella sezione where della query. Questo implica che di seguito al campo della tabella in outer
join venga indicato il simbolo (+). Ad esempio:

Select a.campo1, b.campo2
from tabella1 a, tabella2 b
where b.campo3(+) = a.tabella2_campo3;

Usando l’SQL standard invece si scriverà:

Select a.campo1, b.campo2
from tabella1 a left join tabella2 b on b.campo3 = a.tabella2_campo3;

B) NVL
La funzione NVL in Oracle viene usata per sostituire un valore NULL in un campo con un altro valore. La sua sintassi:

NVL( string1, replace_with )

Ad esempio se non si vuole far vedere vuoto un campo si può decidere di sostituirlo con il messaggio “non selezionato” e allora viene comodo usare NVL. In sql
standard invece esiste la funzione COALESCE che restituisce la prima espressione non-NULL presente tra i suoi argomenti… per cui va benissimo per sostituire la
NVL che di parametri ne aveva solo 2.

C) DECODE
Avete bisogno di inserire un IF nel vostro SQL? In Oracle avreste usato una decode. La sua sintassi:

decode( expression , search , result [, search , result]... [, default] )

vi permette di avere un risulatato particolare a seconda del valore dell’espressione. Il limite della decode è che gestisce solo i confronti per ugualianza. Con l’sql standard si potrà invece usare il case, che ha la seguente sintassi:

SELECT CASE ("nome_di_colonna")
 WHEN "condizionale_1" THEN "risultato_1"
 WHEN "condizionale_2" THEN "risultato_2"
 ...
 [ELSE "risultato_N"]
 END
FROM "nome_della_tabella"

D) Cast dei tipi.
Si possono avere dei problemi di cast tra i tipi di dato. Oracle e Postgresql gestiscono i tipi in maniera diversa. Ad esempio concatenare dei tipi varchar in Oracle genera un tipo varchar, in postgres il capo risultante è un text (tipo di dato CLOB). Per cui una sql si questo tipo:

Select rtrim(COGNOME) || '' '' || rtrim(NOME) from tabella1

In Oracle restituirà un varchar2 in Postgresql un text per renderli compatibili basterà fare un cast per entrambi con il codice seguente:

Select cast(rtrim(COGNOME) || '' '' || rtrim(NOME) as varchar(100)) from tabella1

Fase 2 – In altri casi si è dovuto ricorrere a degli stratagemmi perché non esisteva una soluzione SQL comune ad entrambe le basi dati. In questi casi rientrano:

A) Sequence.

In Oracle per avere il valore successivo di una sequence (Usata magari per dare un volere univoco in una tabella come ad esempio una chiave primaria) si utilizza
la sintassi:

nomesequence.nextval

In postgresql bisognerebbe scrivere invece:

nextval(‘nomesequence’);

Lo stratagemma è stato quello di creare una funzione comune ad entrambi i database che restituisca il valore richiesto. Purtroppo non si può creare una funzione nextval in oracle perché la parola è riservata. Percui si è creata su entrambi i database la funzione seq_nextval(‘nomesequence’) che dato il nome della sequence, ne restituisce il valore abbinato.
Il codice PL/SQL per Oracle sarà

create or replace function seq_nextval(NomeSeq String) return binary_integer is
 Result binary_integer;
begin
 EXECUTE IMMEDIATE 'Select ' || NomeSeq || '.nextval from dual' into Result;
 return(result);
end seq_nextval;

Per Postgresql invece la funzione sarà:

create or replace function seq_nextval(NomeSeq TEXT) returns integer AS $$
declare
 Result integer;

begin
 EXECUTE 'Select nextval(''' || NomeSeq || ''')' into Result;
 return(result);
end;
$$ LANGUAGE plpgsql;

Come si può vedere le differenze nelle funzioni sono minime.

B) Data di sistema.

Lo stesso stratagemma usato per le sequence è stato usato anche per ottenere la data di sistema. In Oracle si usa la sysdate in Postgresql la now(). E’ stata quindi creata la funzione get_sysdate

create or replace function get_sysdate return date is
 Result date;
begin
 Select sysdate into Result from dual;
 return(result);
end get_sysdate;

Attenzione: modificata, come spiegato qui.

Per Postgresql la funzione invece sarà:

create or replace function get_sysdate() returns timestamp AS $$
declare
 Result timestamp;

begin
 Select now() into Result;
 return(result);
end;
$$ LANGUAGE plpgsql;

E nelle varie sql al posto di sysdate o now() si userà sempre get_sysdate(). In Oracle si potrebbe anche scrivere solo get_sysdate, ma per avere la stessa sintassi su entrambi i database bisogna utilizzarla con le parentesi aperta-chiusa.

C) Tabella DUAL.
In Oracle quando si vuole fare un’elaborazione che non ha a che fare con i dati, ad esempio ottenere la data di sistema, si scrive una select che estrae dalla tabella DUAL. Ad esempio:

Select sysdate from dual

Dual è una tabella con una sola riga e un solo campo. Il campo si chiama dummy di tipo varchar2 e il suo contenuto è una ‘X’. In Postgresql basta scrivere:

Select now()

per avere lo stesso risultato senza avere alcuna tabella di comodo. Per ovviare al problema basta creare una tabella per compatibilità chiamata Dual anche su postgresql. Quindi ce la caviamo con questo semplice SQL:

CREATE TABLE dual
(
 dummy character varying(1)
);

insert into dual values ('X');

Riguardo alle performance… non ho fatto test particolari, ma usando il “famoso” metodo occhiometrico non c’è una differenza sostanziale di tempi nell’esecuzione delle query anche sulla tabella più grossa che contiene oltre 100 milioni di record.

Insomma non siamo ancora in questa situazione:

oracle_cat_bounce

ma Postgresql è sicuramente un ottimo prodotto con caratteristiche molto, molto avanzate.