Carnet Wiki

Indexer-date-32bits

L’indexation de la date dans le plugin Indexer se fait au format UNIX_TIMESTAMP, ce qui n’est pas sans causer quelques soucis.

1. Le problème

Sur les 4 installations de Sphinx que j’ai, le champ date est stocké au format unix, à savoir le nombre de secondes écoulé depuis EPOCH (1er janvier 1970).

Cette date est calculée via dans lib/Indexer/Storage/Sphinx.php via date = strtotime($doc->date).

Pour les dates antérieures au 1er janv. 1970 PHP donne un nombre négatif (alors que MySQL va donner 0) ; donc ici, on a date = nombre négatif.

Mais le champ sphinx au format attr_timestamp est un entier de 32 bits non signé, donc les nombres négatifs sont représentés en ajoutant le bit 2^32.

2. La solution trouvée pour le moment

Tant que sphinx stocke ses dates en 32 bits (cf forum sphinx-search), il faut retrancher 2^32 = 4294967296 à la valeur que nous renvoie sphinx, pour retrouver notre nombre négatif… On le fait pour les nombres > 2^31 = 2147483648, c’est-à-dire postérieurs au 19 janvier 2038.

Ce cutoff est choisi de façon un peu arbitraire, et permet d’avoir la plage de dates suivante :
-  de 0 à 2147483648 = de 1er janv. 1970 à 19 janvier 2038
-  de 2147483649 à 4294967295 = de 13 décembre 1901 au 31 décembre 1969.

Du coup dans le squelette on pourra utiliser la fonction suivante pour trier selon un ordre qui donne une priorité à la date, soit une priorité absolue (si tri par date), soi une priorité relative (si tri par « pertinence », on veut quand même donner un bonus aux plus récents) :

[(#REM)

	Définition de la fonction de score (tri des résultats)

	On bidouille la date car le UNIX_TIMESTAMP
	est stupidement impossible avant 1er janvier 1970.

        Cf. https://contrib.spip.net/Indexer-date-32bits

]

[(#ENV{tri}|=={date}|ou{[(#RECHERCHE|strlen|=={0})]}?{
	#SET{select,'*, IF (date > 2147483648, date-4294967296, date) as dateu'}
	#SET{tri,dateu}
	#SET{sens_tri,1}
,
	#SET{select,'*, WEIGHT() / (100+SQRT(SQRT(NOW()-(IF (date > 2147483648, date-4294967296, date))))) as poids'}
	#SET{tri,poids}
	#SET{sens_tri,1}
})]

3. Une meilleure solution ?

Si on veut pouvoir encoder une plage de dates plus large, il va falloir soit passer en 64 bits, soit utiliser un format de champ qui permette des nombres négatifs, soit oublier les secondes et diviser la date par 60. À moins d’une autre idée ??

4. Utiliser YEARMONTHDAY(date)

Il semble qu’on puisse utiliser YEARMONTHDAY(date) pour calculer une formule, et cela fonctionne de 1902 à 2038 sans rien changer à la structure de la base sphinx. C’est déjà un peu plus convaincant que la formule ci-dessus.

Avant la boucle :

	#SET{select, 'YEARMONTHDAY(date) as odate, *'}
	#SET{tri, odate}

et plus bas, dans la boucle :

{select #GET{select}}
{par #GET{tri}}{inverse}
{par date}{inverse}

5. Passer les dates en BIGINT

Dans la définition standard on a jusqu’ici proposé rt_attr_timestamp = date.

Comme le souligne Barry Hunter ce rt_attr_timestamp n’est rien d’autre qu’un uint 32 bits, et on peut le remplacer par un bigint en indiquant à la place rt_attr_bigint     = date.

La conséquence cependant est qu’on perd l’usage de la fonction YEAR(), et que les facettes d’année par exemple devront passer par un calcul ad hoc

{facet annee, IF(date>0, IDIV(date,31514400)+1970, IDIV(date,31514400)+1969) as year ORDER BY year DESC}

au lieu de

{facet annee, YEAR(date) ORDER BY date DESC}

et il faut réutiliser cette formule partout où on utiliserait YEAR()… en attendant que sphinx le fasse en natif (feature request #2608).

Pour cela le plus « simple » est de coder dans le squelette :
[(#SET{yeardate,"IF(date>0, IDIV(date,31514400)+1970, IDIV(date,31514400)+1969)"})]

et d’utiliser #GET{yeardate} dans les critères etc.

Indexer les dates à part. À noter 31514400 = 364.75 * 24 * 3600 = une année « moyenne » en secondes. Il y a donc un peu de dérive puisque ça ne gère pas parfaitement les années bisextiles, les leap seconds…

Du coup il faut éventuellement indexer l’année dans un champ à part, pour faire des facettes « qui tombent juste » — les bugs pouvant se produire sur les dates genre 1er janvier.

C’est ce que fait le plugin, qui indexe properties.ymd = { year: 2005, yearmonth: 200512, yearmonthday: 20051201, u: 1133395200, datetime: '2005-12-01 13:02:29' }. Ces valeurs sont enregistrées sous forme de nombres entiers et non pas de chaînes (sauf datetime évidemment), ce qui permet aux facettes et aux comparaisons (avant… après…) de fonctionner. Ouf !

Le code des filtres et facettes d’année devient alors :

filtres :

{filter #ENV{annee},  'IN(properties.ymd.year, @valeurs)' }
{filter #ENV{date_debut}, 'properties.ymd.year >= @valeurs' }
{filter #ENV{date_fin}, 'properties.ymd.year <= @valeurs' }

facette :

{facet annee, properties.ymd.year ORDER BY properties.ymd.year DESC }
Fil - Mise à jour :10 février 2017 à 09h57min