[spip-dev] Encore un problème avec PostgreSQL

Bonjour,

J'ai encore un soucis lié à mon utilisation de PostgreSQL, sur les
pages de statistiques.

Sur une page de type

.../spip/ecrire/?exec=statistiques_visites&id_article=47

J'obtiens l'erreur suivante :

SQL error 1000
errcode: 1000 : function abstime(double precision, unknown) does not
exist LINE 1: SELECT SUM(visites) AS n, ABSTIME( EXTRACT(epoch FROM
date),... ^ HINT: No function matches the given name and argument
types. You might need to add explicit type casts.

SELECT SUM(visites) AS n,
       ABSTIME( EXTRACT(epoch FROM date),'%Y-%m') AS d
FROM spip_visites_articles
WHERE date > (NOW() -INTERVAL '2700 DAY')
  AND id_article=47
GROUP BY d, ABSTIME( EXTRACT(epoch FROM date),'%Y-%m')

En corrigeant la requete à la main, ça donnerait ceci :

SELECT SUM(visites) AS n,
       TO_CHAR(date,'YYYY-MM') AS d
FROM spip_visites_articles
WHERE date > (NOW() -INTERVAL '2700 DAY')
  AND id_article=47
GROUP BY d, TO_CHAR(date,'YYYY-MM')

J'ai changé 3 choses :

1. ABSTIME n'existe pas sur mon PostgreSQL (8.3.11 si ça peut aider)

2. TO_CHAR a l'air de faire ce qu'il faut, mais il prends directement
   la date, si je j'essaye TO_CHAR(EXTRACT(epoch FROM date),
   'YYYY-MM'), le formattage ne se fait pas, la requette renvoit la
   chaine 'YYYY-MM' invariablement.

3. La chaine de formattage est différente, c'est '%Y-%m' en MySQL, et
   'YYYY-MM' en PostgreSQL.

Le hack suivant a l'air de faire l'affaire, mais il y a sans doute
plus propre :

--- a/ecrire/req/pg.php
+++ b/ecrire/req/pg.php
@@ -563,6 +563,11 @@ function spip_pg_frommysql($arg)
                            'CAST(substring(\1, \'^ *[0-9]+\') as int)',
                            $res);

+ $res = preg_replace('/FROM_UNIXTIME\s*[(]\s*UNIX_TIMESTAMP\s*[(]\s*([^()]*)\s*[)],\s*([^()]*)\s*[)]/',
+ 'TO_CHAR(\1, \2)', $res);

Ça n'inspire personne ?

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:

si bien sûr !
et par là même : merci à toi pour ces propositions de patch.

juste encore un peu de test et, aussi, un peu de temps... :slight_smile:

Arriver à faire qqch de portable dans la gestion des dates vu la disparité des serveurs SQL
fait que ce code est un bricolage pas satisfaisant.
Au dela du cas qui te pose problème, ça semble dire d'une manière plus générale qu'il faut abandoner ABSTIME,
mais après une recherche certes rapide, je n'ai pas vu dans la doc de PG où il est dit explicitement qu'elle est obsolète et par quoi il faut la remplacer. L'as-tu vu ? J'ajoute que SPIP n'a jamais été opérationnel pour PG avant sa version 8,
donc pas besoin de se rajouter la contrainte supplémentaire d'être compatible avec d'anciennes versions.

Committo,Ergo:Sum

"Committo,Ergo:sum" <esj@rezo.net> writes:

je suppose que c'est assez
facile de faire une fonction sql_timestamp_yy_mm() qui renvoit ce
qu'il faut selon la base de donnée utilisée, mais je n'arrive pas à
comprendre comment faire ça dans le code de SPIP.

Arriver à faire qqch de portable dans la gestion des dates vu la disparité des serveurs SQL
fait que ce code est un bricolage pas satisfaisant.
Au dela du cas qui te pose problème, ça semble dire d'une manière
plus générale qu'il faut abandoner ABSTIME,
mais après une recherche certes rapide, je n'ai pas vu dans la doc
de PG où il est dit explicitement qu'elle est obsolète et par quoi
il faut la remplacer. L'as-tu vu ?

En fait, ce n'était pas tout à fait vrai quand j'ai dit que ABSTIME
n'existait pas sur mon postgreSQL. Le message exact est : "function
abstime(double precision, unknown) does not exist" et il existe en
fait une fonction abstime, avec des arguments différents. Par contre,
elle ne fait pas grand chose d'intéressant : L'ancienne doc est ici :

  PostgreSQL: Documentation: 7.0: Date/Time Functions

et la nouvelle dit simplement :

  PostgreSQL: Documentation: 8.3: Date/Time Key Words
  "The key word ABSTIME is ignored for historical reasons: In very old
  releases of PostgreSQL, invalid values of type abstime were emitted
  as Invalid Abstime. This is no longer the case however and this key
  word will likely be dropped in a future release."

Donc, circulez, y'a rien à voir du côté de ABSTIME. Je ne sais pas
pourquoi ce bout de code a été introduit, mais il me semble vraiment
que l'auteur voulait écrire TO_CHAR à la place.

J'ajoute que SPIP n'a jamais été opérationnel pour PG avant sa
version 8,

Oui, j'utilise la 8.3.

En fait, même avec mon patch, je n'ai plus d'erreur SQL, mais j'ai
quand même un comportement bizare. Je ne sais pas à quoi doit
ressembler exactement la page de statistiques, mais pour l'instant, ça
ressemble à ça :

  http://www-verimag.imag.fr/~moy/tmp/stats-screenshot.png

=> Le graphique dépasse de son cadre, et quand je passe ma souris sur
les bares de l'histograme, je me rend compte que les dates ne sont pas
triées par ordre chronologique.

En fait, ce n'était pas tout à fait vrai quand j'ai dit que ABSTIME
n'existait pas sur mon postgreSQL. Le message exact est : "function
abstime(double precision, unknown) does not exist" et il existe en
fait une fonction abstime, avec des arguments différents. Par contre,
elle ne fait pas grand chose d'intéressant : L'ancienne doc est ici :

http://www.postgresql.org/docs/7/static/functions2876.htm

et la nouvelle dit simplement :

PostgreSQL: Documentation: 8.3: Date/Time Key Words
"The key word ABSTIME is ignored for historical reasons:

Attention ce sont deux choses différentes: un mot-clé et une fonction, ça n'a rien à voir.
Le pb est que ABSTIME est une fonction PG unaire et que FROM_UNIXTIME est une fonction MySQL binaire,
mon traducteur mysql->pg fait n'importe quoi ici.

En fait, même avec mon patch, je n'ai plus d'erreur SQL, mais j'ai
quand même un comportement bizare.

Je pense avoir résolu le pb en éliminant FROM_UNIXTIME de le version MySQL de départ:
http://trac.rezo.net/trac/spip/changeset/15983
peux-tu essayer ?

Committo,Ergo:Sum

"Committo,Ergo:sum" <esj@rezo.net> writes:

En fait, ce n'était pas tout à fait vrai quand j'ai dit que ABSTIME
n'existait pas sur mon postgreSQL. Le message exact est : "function
abstime(double precision, unknown) does not exist" et il existe en
fait une fonction abstime, avec des arguments différents. Par contre,
elle ne fait pas grand chose d'intéressant : L'ancienne doc est ici :

PostgreSQL: Documentation: 7.0: Date/Time Functions

et la nouvelle dit simplement :

PostgreSQL: Documentation: 8.3: Date/Time Key Words
"The key word ABSTIME is ignored for historical reasons:

Attention ce sont deux choses différentes: un mot-clé et une
fonction, ça n'a rien à voir.

Effectivement, par contre, je ne trouve aucune trace de documentation
de la fonction abstime dans la doc de PostgreSQL 8.

Je pense avoir résolu le pb en éliminant FROM_UNIXTIME de le version MySQL de départ:
http://trac.rezo.net/trac/spip/changeset/15983
peux-tu essayer ?

Ça ne suffit pas. Il reste un appel à DATE_FORMAT dans la requete, et
PG ne connait pas ça.

J'ai tenté d'ajouter ça en imitant le code existant :

diff --git a/ecrire/req/pg.php b/ecrire/req/pg.php
index 0034a02..18c9fb9 100644
--- a/ecrire/req/pg.php
+++ b/ecrire/req/pg.php
@@ -596,6 +596,7 @@ function spip_pg_frommysql($arg)
        $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)\"([^\"]*)\"/", '\1\'\2\'', $res);
        $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m%d\'[)]/', 'to_number(to_char(\1, \'YYYYMMDD\'), \'8\')', $res);
        $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m\'[)]/', 'to_number(to_char(\1, \'YYYYMM\'),\'6\')', $res);
+ $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y-%m\'[)]/', 'to_number(to_char(\1, \'YYYY-MM\'),\'6\')', $res);
        $res = preg_replace('/DATE_SUB\s*[(]([^,]*),/', '(\1 -', $res);
        $res = preg_replace('/DATE_ADD\s*[(]([^,]*),/', '(\1 +', $res);
        $res = preg_replace('/INTERVAL\s+(\d+\s+\w+)/', 'INTERVAL \'\1\'', $res);

Mais ça ne marche doublement pas :

* D'une part, je ne comprends pas ce qu'est censé faire le
  to_number(..., '6'), mais il ne marche pas, même sur des requetes
  simples :

  moy=> SELECT to_number(to_char(date, 'YYYY-MM'),'6') AS d FROM spip_visites;
  ERROR: invalid input syntax for type numeric: " "

  C'est le '6' qui est en cause pour l'erreur de syntaxe, si je met un
  '9' à la place, je n'ai plus d'erreur de (mais j'ai une colonne de 2
  comme résultats, c'est pas super interessant). Mais vu qu'on est en
  train de remplacer un « DATE_FORMAT » MySQL, qui renvoit une string,
  pourquoi tenter une re-conversion en integer ?

* D'autre part, spip_pg_groupby fait des siennes : la ligne

  $join = preg_replace('/\w+\(\s*([^(),\']*),\s*\'[^\']*\'[^)]*\)/','\\1', $join);

  tente de faire quelque chose d'intelligent avec
  to_number(to_char(date, 'YYYY-MM'),'6') et c'est l'échec, le
  résultat est une clause « GROUP BY d, to_number(date,'6') » et une
  erreur de syntaxe puisque to_number ne peut pas prendre une date en
  argument 1.

Si j'applique ceci (i.e. sans le to_number), je n'ai plus d'erreur
SQL (mais je ne suis pas certain que le résultat soit correct pour
autant. En particulier, mon site est nouveau, il n'y a des visites que
pour le mois d'aout, donc je trouverai sans doute d'autres bugs après
le 1er septembre :wink: )

--- a/ecrire/req/pg.php
+++ b/ecrire/req/pg.php
@@ -596,6 +596,7 @@ function spip_pg_frommysql($arg)
        $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)\"([^\"]*)\"/", '\1\'\2\'', $res);
        $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m%d\'[)]/', 'to_number(to_char(\1, \'YYYYMMDD\'), \'8\')', $res);
        $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m\'[)]/', 'to_number(to_char(\1, \'YYYYMM\'),\'6\')', $res);
+ $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y-%m\'[)]/', 'to_char(\1, \'YYYY-MM\')', $res);
        $res = preg_replace('/DATE_SUB\s*[(]([^,]*),/', '(\1 -', $res);
        $res = preg_replace('/DATE_ADD\s*[(]([^,]*),/', '(\1 +', $res);
        $res = preg_replace('/INTERVAL\s+(\d+\s+\w+)/', 'INTERVAL \'\1\'', $res);

http://trac.rezo.net/trac/spip/changeset/15986

devrait retomber sur ce qu'il faut.

Committo,Ergo:Sum

"Committo,Ergo:sum" <esj@rezo.net> writes:

Je pense avoir résolu le pb en éliminant FROM_UNIXTIME de le version MySQL de départ:
http://trac.rezo.net/trac/spip/changeset/15983
peux-tu essayer ?

Ça ne suffit pas. Il reste un appel à DATE_FORMAT dans la requete,

http://trac.rezo.net/trac/spip/changeset/15986

devrait retomber sur ce qu'il faut.

Non, ça ne suffit pas :

SQL error 1000
errcode: 1000 : function to_number(date, unknown) does not exist LINE
4: GROUP BY d, to_number(date,'6') ^ HINT: No function matches the
given name and argument types. You might need to add explicit type
casts.
SELECT SUM(visites) AS n, to_number(to_char(date, 'YYYYMM'),'6') AS d
FROM spip_visites WHERE date > (NOW() -INTERVAL '2700 DAY') GROUP BY
d, to_number(date,'6')

cf. mon message précédent pour les détails :

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:

* D'une part, je ne comprends pas ce qu'est censé faire le
to_number(..., '6')

Malheureusement la doc de PostGres est vraiment pas claire non plus:

Visiblement cette partie du traducteur Mysql->pg n'a jamais marché et on ne le découvre que maintenant.

, mais il ne marche pas, même sur des requetes
simples :

moy=> SELECT to_number(to_char(date, 'YYYY-MM'),'6') AS d FROM spip_visites;
ERROR: invalid input syntax for type numeric: " "

C'est le '6' qui est en cause pour l'erreur de syntaxe, si je met un
'9' à la place, je n'ai plus d'erreur

ce n'est donc pas une erreur de syntaxe mais une erreur de sémantique,
mais il n'y a que des exemples abscons dans la doc, pas de description du 2e argument
que j'avais donc mal devinée.

de (mais j'ai une colonne de 2
comme résultats, c'est pas super interessant). Mais vu qu'on est en
train de remplacer un « DATE_FORMAT » MySQL, qui renvoit une string,
pourquoi tenter une re-conversion en integer ?

parce qu'ensuite, dans certains cas, on fait des comparaisons arithmétiques.

Faut essayer de trouver des infos hors la doc officielle sur cette fonction si on veut s'en sortir.

Committo,Ergo:Sum

"Committo,Ergo:sum" <esj@rezo.net> writes:

* D'une part, je ne comprends pas ce qu'est censé faire le
to_number(..., '6')

Malheureusement la doc de PostGres est vraiment pas claire non plus:
PostgreSQL: Documentation: 8.3: Data Type Formatting Functions

Je n'avais pas compris non plus ce que to_integer devait faire. Par
contre, le fait que ce soit documenté dans « Data Type Formatting
Functions » me fait intuiter que ce n'est pas ce qu'on cherche.
Visiblement, c'est l'inverse de to_char ou quelque chose comme ça.

, mais il ne marche pas, même sur des requetes
simples :

moy=> SELECT to_number(to_char(date, 'YYYY-MM'),'6') AS d FROM spip_visites;
ERROR: invalid input syntax for type numeric: " "

C'est le '6' qui est en cause pour l'erreur de syntaxe, si je met un
'9' à la place, je n'ai plus d'erreur

ce n'est donc pas une erreur de syntaxe mais une erreur de
sémantique,

Je ne fais que répéter ce que dit le message d'erreur.

de (mais j'ai une colonne de 2
comme résultats, c'est pas super interessant). Mais vu qu'on est en
train de remplacer un « DATE_FORMAT » MySQL, qui renvoit une string,
pourquoi tenter une re-conversion en integer ?

parce qu'ensuite, dans certains cas, on fait des comparaisons
arithmétiques.

Mais en quoi est-ce différent de MySQL ?

La requete MySQL renvoit une string, pourquoi faire différent en
PSQL ?

En PSQL, les comparaisons string/entier donnent ceci :

moy=> select '10' > '9'; ?column? ---------- f (1 row)

moy=> select '10' > 9;
?column? ----------
t (1 row)

=> Les comparaisons string/string se font par ordre lexicographique,
et les comparaisons string/integer font un cast implicite.

En MySQL, j'ai pile les mêmes résultats.

Faut essayer de trouver des infos hors la doc officielle sur cette
fonction si on veut s'en sortir.

Ce que tu veux faire a l'air d'être tout simplement le résulat d'un
cast (je connais mal SQL, je tatonne un peu ...) :

moy=> select CAST('10' as INT);
int4

C'est le '6' qui est en cause pour l'erreur de syntaxe, si je met un
'9' à la place, je n'ai plus d'erreur

ce n'est donc pas une erreur de syntaxe mais une erreur de
sémantique,

Je ne fais que répéter ce que dit le message d'erreur.

oui oui, c'est le laconisme de la doc qui est en cause:
mettre un chiffre plutôt qu'un autre ne peut être une erreur de syntaxe,
sauf si ce chiffre a ici une signification particuière.

=> Les comparaisons string/string se font par ordre lexicographique,
et les comparaisons string/integer font un cast implicite.

En MySQL, j'ai pile les mêmes résultats.

Là ça me surprend: la personne qui avait commencé le portage de SPIP en PG m'avait dit
avoir butté sur l'absence de Cast implicite, et j'étais effectivement tombé dessus,
d'où la gymnastique to_char/to_number. Y aurait-il un mode strict désactivable en PG ?
Ou un cas particulier en ce qui concerne les dates ?
En tout cas peux-tu essayer en virant les to_number ?

Committo,Ergo:Sum

"Committo,Ergo:sum" <esj@rezo.net> writes:

=> Les comparaisons string/string se font par ordre lexicographique,
et les comparaisons string/integer font un cast implicite.

En MySQL, j'ai pile les mêmes résultats.

Là ça me surprend: la personne qui avait commencé le portage de SPIP en PG m'avait dit
avoir butté sur l'absence de Cast implicite, et j'étais effectivement tombé dessus,
d'où la gymnastique to_char/to_number. Y aurait-il un mode strict désactivable en PG ?
Ou un cas particulier en ce qui concerne les dates ?

Aucune idée :-(.

En tout cas peux-tu essayer en virant les to_number ?

Avec juste ça, je n'ai plus d'erreur de SQL sur la page de
statistiques :

  $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m%d\'[)]/', 'to_char(\1, \'YYYYMMDD\')', $res);
  $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m\'[)]/', 'to_char(\1, \'YYYYMM\')', $res);

(mais je ne suis pas certain que la fonctionalité soit correcte, ceci
dit. Si j'ai bien compris, la requete qui me pose problème est là pour
trouver les mois où il y a eu des visites, et pour moi, il n'y en a
qu'un pour l'instant)

Bon alors on adopte=
http://trac.rezo.net/trac/spip/changeset/15990

Committo,Ergo:Sum