PostgreSQL La base de donnees la plus sophistiquee au monde.

Forums PostgreSQL.fr

Le forum officiel de la communauté francophone de PostgreSQL

Vous n'êtes pas identifié(e).

#1 Re : Général » [PG9.6] Comment gérer au mieux les contraintes d'unicité ? » 15/11/2019 18:01:30

Beaucoup de réponse en peu de temps, merci et bravo.
Du coup, mon [edit] ci-dessus risque de passer inaperçu.


je viens de voir qu'en plus, avec la 1ère méthode (utiliser l'erreur de la contrainte) incrémentait néanmoins le compteur de ma clef primaire.


Je vais creuser le ON CONFLICT.


Merci beaucoup.
Cordialement,
Ramirez22

#2 Général » [PG9.6] Comment gérer au mieux les contraintes d'unicité ? » 15/11/2019 17:38:45

ramirez22
Réponses : 4

Bonjour,


Je me pose une question bête.
J'ai créé une table avec des contraintes d'unicité sur plusieurs colonnes.
Lors de l'INSERT d'une nouvelle ligne, qu'est-ce qui est plus "propre"?


1- Faire l'INSERT et traiter le message de défaut qui arrivera en cas de doublon
2- Faire une vérification avant de faire l'insert (SELECT ...)


La deuxième option me semble plus "normale" : on ne s'appuie pas sur un garde fou système et on gère nous même l'unicité, quitte à avoir un défaut si un INSERT est réalisé par un autre utilisateur entre le SELECT et l'INSERT.


Mais du coup, dans mon application, j'ai 2 contrôles plus ou moins identiques :
- je fais d'abord un SELECT pour vérifier si tout est OK et je traite l'erreur le cas échéant
- je fais mon INSERT
- en cas d'erreur sur la contrainte d'unicité, je traite l'erreur de la même manière


Sans compter que la deuxième solution génère le double de trafic...
[Edit]Cependant, je viens de constater que l'incrémentation automatique de mon ID se faisait même en cas de présence de doublon (dans le cas de figure 1 ci-dessus). Ce qui risque de générer quelque "trous" dans mes ID...


Est-ce que c'est selon la sensibilité de chacun ou est-ce qu'il y a des règles particulières ?


Merci de votre attention.
Cordialement,
Ramirez22.

#3 Re : Général » [PG9.6] Identification d'une connexion » 15/11/2019 17:30:01

Bonjour,


Désolé du délais de la réponse, j'avais complètement changé mon fusil d'épaule et ... j'ai zappé ce post roll
Le but était de savoir qui, de mes utilisateurs, était connecté. Mais c'est vraiment une fonction peu utile, je laisse tomber l'affaire.


Merci quand même !

#4 Général » [PG9.6] Identification d'une connexion » 25/10/2019 20:17:01

ramirez22
Réponses : 2

Bonjour,

Le titre est peu explicite, je vais développer pas de panique.

Ma base PostgreSQL ne m'appartient pas : je n'ai que très peu de droit et notamment, l'impossibilité de créer des roles.
Du coup, je suis obligé de passer par un role (créé par l'administrateur du serveur) qui possède avec la totalité des droits (alors que certains utilisateurs n'auraient pas besoin de certains droits ... mais bon, j'ai pas le choix de toute façon) sauf CREATEROLE evidemment.


La gestion des utilisateurs de mon application est donc faite par une table spécifique dans mon appli.


Afin de complexifier la chose, j'ouvre 2 connexions distinctes pour chaque utilisateur. Ceci étant pour permettre d'enregistrer des informations de débogage en cas de plantage d'une requête (le ROLLBACK effacerait les informations de débuggage ....)

Forcément, si je fais un

SELECT * FROM pg_stat_activity;

Je me retrouve avec les 2 connexions actives, mais seuls le pid, les ports et les adresses IP changent. Et j'ai pas la liste des adresses IP de chaque utilisateur (DHCP en plus ...)


L'accès à la base utilisateur ne se fait en 2 occasions :
- à la connexion pour récupérer les infos utilisateur. Si le profil utilisateur est supprimé, pas de problème car cela sera pris en compte lors de la prochaine connexion
- lors de changement du mot de passe. Et là, c'est le drame : si un administrateur a supprimé le compte, on va droit dans le mur !


J'avais imaginer mettre un verrou sur l'enregistrement avec une troisième connexion. Mais ça commence à faire beaucoup de connexions pour peu de chose. De plus, que devient un verrou si la connexion est perdue. Et ça le permet d'empêcher une suppression d'utilisateur, mais pas de savoir si l'utilisateur est connecté.


Bref, je ne vois pas trop comment faire. Si vous pouvez allumer ma lanterne SVP ...

A+

#5 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 19/10/2019 08:23:02

Bonjour,

Je viens d'essayer d'ouvrir 2 connexions avec Windev, puis de les appeler en fonction du contexte :

connexion_log, connexion_normale sont des entiers
connexion_log = SQLConnecte(AdrServ, Login, Mdp, Base, "POSTGRESQL")
<Gestion de l'erreur de connexion>

connexion_normale = SQLConnectte(AdrServ, Login, Mdp, Base, "POSTGRESQL")
<Gestion de l'erreur de connexion>
SQLChangeConnexion(connexion_normale)


... Traitement du programme ...
<En cas d'erreur:>
AjoutLog("ERREUR", Utilisateur, Message_erreur)


<Procédure globale de traitement des messages de défaut, appelée à chaque message à enregistrer dans la base>
PROCEDURE AjoutLog(sType est chaine, sUtilisateur est chaine, sMessage est chaine)
<Traitement pour vérifier les données transmises, éviter les injection SQL etc...>
     SQLChangeConnexion(connexion_log)
requete = "INSERT INTO logs (date, heure, utilisateur, type, message) VALUES (CURRENT_DATE, LOCALTIME(2),'%1','%2','%3');"
<Traitement des données variables (%1, %2...)
SQLExec(requete, resultat_requete)
<Traitement de l'erreur>
     SQLChangeConnexion(connexion_normale)
<Fin de procedure>

Et apparement ça marche : j'ai volontairement créé une erreur dans une requête dans le corps du programme, et j'ai bien eu le log, malgré un ROLLBACK sur cette première requête. De plus, plus d'erreur bloquante.


Bref, content smile


Merci dverite et rjuju pour votre aide, cela m'a permis d'approfondir mes connaissances en Windev aussi smile

#6 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 18/10/2019 15:37:24

Côté client, on suppose que vous pouvez ouvrir et maintenir deux connexions en permanence.

Il me semble que gérer 2 connexions simultanées sous Windev soit impossible. Je vais quand même m'en assurer.


L'exemple du premier message de la discussion laisse supposer qu'il y a un besoin d'annuler une transaction suite à l'impossibilité de déplacer un fichier côté client

Exact. Cela va même un peu plus loin : Il y a plusieurs fichiers (même nom, extension différente). Je contrôle d'abord si des métadonnées contenue dans le titre du fichier existent dans d'autres tables (T1, T2 et T3 par exemple) de ma base. Si oui, je les verrouille (BEGIN; SELECT FOR UPDATE ... pour chaque table) pour ne pas risquer qu'un autre utilisateur efface les données entre le contrôle et l'insertion (+LOG en fin de verrouillage). Puis j'insère les infos de ce fichier dans une table (T4) en récupérant l'ID de la ligne ajoutée (+LOG) + quelques clés étrangères (T1, T2 et T3). Je fais la copie des fichiers sur un emplacement réseau en les renommant avec le n° ID de T4. Si le transfert s'est bien passé, je COMMIT, je LOG que l'opération a été un franc succès et enfin j'efface les fichiers dans le répertoire d'import (si problème à ce moment, pas trop grave puisque les fichiers sont au bon emplacement et la base est à jour). S'il y a eu des problèmes, tout va dépendre de où cela s'est produit :


- Si c'est au moment de la recherche des meta données sur T1, T2 ou T3, il me faut faire un LOG puis ROLLBACK;. Il est nécessaire que j'ai l'info de la table en DF, mais au pire, si je perds le LOG de la première opération c'est pas trop grave (le message de log de l'erreur me permettra de retrouver facilement la source du problème).


- Si c'est au moment de l'INSERT, là encore, je peux faire ROLLBACK puis faire mon LOG. Je perds cependant 2 LOGs "process", là encore je peux m'en passer.


- Si c'est lors de la copie des fichiers, j'ai déjà réalisé un LOG indiquant la création dans la table de l'enregistrement. De plus, je LOG le transfert des fichiers 1 par 1. Comme ça, je peux savoir si c'est un fichier spécifique qui pose problème. Du coup, Si je fais un ROLLBACK, je perds ces LOG. Si je fais un COMMIT, je valide ma transaction alors que les fichiers ne sont pas déplacés.


Et c'est au niveau de cette opération que cela pose soucis. Ce n'est qu'un extrait de mon application, j'ai d'autres endroits avec les opérations multiples sur tables que je dois tracer (pour que le LOG serve à quelque chose en cas de défaut), mais qu'il ne faut pas que je conserve s'il y a eu un problème (les modif de tables hein, pas le LOG big_smile)


Donc, faire la gestion de l'erreur côté client risque d'être difficile puisque assujetti à la possibilité de connexion multiples simultanées.
Maintenant, si je veux faire du LOG côté serveur (et là encore si j'ai bien compris), il faut que j'utilise des clause exception, donc que je dialogue avec mon serveur soit avec des fonctions anonymes, soit que je crée une fois pour toute des fonctions sur mon serveur qui gère ces exceptions. C'est bien cela ?

#7 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 18/10/2019 13:53:25

Merci dverite, mais je ne comprends pas (désolé, cerveau de plus de 40 ans...)

Ici il semble que ce ne soit pas le cas, c'est-à-dire que les instructions DELETE sont envoyés individuellement par le client et que la logique IF erreur SQL est dans le client. Vous ne pouvez pas utiliser le SELECT dblink_exect() dans cette situation.

Vous avez le choix d'écrire les évènements via une connexion dédiée, par le client ou par le serveur via un dblink.

Ou alors est-ce qu'il y a une différence entre bdlink et dblink_exec ?
J'ai crus que dblink_exec correspondait en gros à dblink_connect puis dblink en enfin dblink_disconnect. Peut-être me suis-je fourvoyé.
Si je continue sur cette voie, cela signifierait qu'il faille, dès la connexion de mon appli à la BDD créer un deuxième "tunnel" via dblink_connect et si mon appli reçoit une erreur SQL, utiliser cette connexion pour exécuter un dblink ?

Je suis pas certain d'avoir bien compris, un petit éclaircissement serait le bienvenu.

Cdt,
Ramirez22

[Edit]Je reviens sur ce que j'ai dit, en relisant je crois apercevoir un début de commencement de compréhension.

La méthode du dblink est utilisable dans le cas où il appelé dans une clause EXCEPTION d'un bloc plpgsql

Je ne peux donc pas utiliser dblink tel quel.
Si je comprends bien, il faudrait que j'utilise un bloc code anonyme en gros comme ça ?

DO <code de la modification à effectuer sur mes tables
DECLARE;
BEGIN
code SQL commençant par BEGIN; pour ouvrir une transaction
END>
EXCEPTION
dblink

LANGUAGE plpgsql;

Ou est-ce que je suis en train de complètement me planter ?

#8 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 18/10/2019 09:04:30

Bonjour,

Désolé de cette absence de réponse, appelé sur des projets plus urgents...
Merci de votre aide.
Si je comprends bien, je n'ai pas le choix : il faut que je mémorise tous les évènements pour les inscrire à la fin de la transaction, car sinon ils seront effacés par le ROLLBAK (le cas échéant).
Dommage, j'espérais bien pouvoir faire une exception sur une table.


Mais je voudrais creuser un peu.
D'après vos commentaires, si je crée les fonctions qui vont bien sur le serveur, je devrait pouvoir intégrer une clause EXCEPTION qui me permettrait, en cas d'erreur, de remplir ma table LOG même en cas de ROLLBACK ?
Actuellement, une grosse partie de mes transactions se font en requête préparée. J'imagine que cela n'est pas la même chose.

Requête préparée = PREPARE blablabla;
Fonction = DECLARE etc ...

Cela me fait quelques modifs majeur de mon application...
Otez-moi d'un doute : les requêtes préparées sont valides uniquement le temps de la connexion qui les crée (actuellement, c'est comme ça que je fonctionne : à chaque connexion, je crée les fonctions préparées de l'utilisateur. Si x utilisateurs se connectent, j'aurai x requêtes préparées, chacune isolées des autre. Dès qu'une connexion disparait, ses requêtes préparées sont effacées).
Quid des fonctions ? Je les crées une fois pour toute sur mon serveur ?

Cdt

#9 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 09/10/2019 21:03:32

Bonsoir,
Bon ben y'a sans doute un problème, je n'y arrive pas...


Voici ce que je fais:

Suppression d'un enregistrement de la table 1. Obligation de réaliser un bloc transaction car je dois au préalable effacer une clef étrangère dans une table (2).

[SQL] BEGIN;
[SQL] DELETE FROM table_2 WHERE id_enregistrement = {valeur quelconque};
[Application] Si erreurSQL alors AfficheMessageErreur puis AjoutLog(message) puis [SQL]ROLLBACK; puis quitte
[Application] Sinon, 
[SQL] DELETE FROM table_1 WHERE id_enregistrement = {valeur quelconque};
[Application] Si erreurSQL alors AfficheMessageErreur puis AjoutLog(message) puis [SQL]ROLLBACK; puis quitte
[Application] Sinon,
[SQL] COMMIT;
[Application] AjoutLog("Enregistrement effacé")
...etc...

[Procédure AjoutLog]
[SQL] SELECT dblink_exec (
[SQL] $$host=localhost port=5432 dbname=database user=administrateur password=blablabla,
[SQL] $$INSERT INTO logs (date_log,heure_log,utilisateur,type_log,titre_log,message_log)
[SQL] VALUES ('%1','%2','%3','%4','%5','%6')$$
[SQL] );
[Application] Si erreurSQL Alors AfficheMessageErreur 
(forcément, inutile de tenter un nouveau message de log, le premier à planté et la procédure s’appellerait sans arrêt ...)

Il s'agit évidemment d'une transcription allégée ... big_smile
Les %1, %2 ... sont des variables que l'application remplace au moment d'envoyer la requête.


Tout se passe correctement s'il n'y a pas d'erreur (c'est une lapalissade, je vous l'accorde). La procédure fonctionne parfaitement, le message "Enregistrement effacé" est bien écrit dans ma table 'logs' (avec tous les champs dûment renseignés).


Puis j'ai volontairement créé une erreur dans le nom de la table_2 de la première requête.
J'ai bien une erreur qui est retourné par PostgreSQL (premier AfficheMessageErreur):

EIT_BASEMESSAGE <ERREUR : la relation "table2" n'existe pas at character 20>
Numéro d'erreur : <42P01> (correspond bien à 'undefined_table')

Puis la procédure AjoutLog est appelée et me génère également une erreur:

Numéro d'erreur : <25P02>
EIT_BASEMESSAGE <ERREUR: la transaction est annulée, les commandes sont ignorées jusqu'à la fin du bloc de la transaction>

Le code d'erreur correspond à in_failed_sql_transaction
Du coup, j'ai l'impression que ma transaction bloque quand même ma requête dblink_exec


J'ai essayé de saisir quelques lignes de code directement sous PGAdmin histoire de voir ce qu'il se passe.

BEGIN;
DELETE FROM table_1 WHERE id = 2;
	SELECT dblink_exec (
	$$host=localhost port=5432 dbname=database user=administrateur password=blablabla$$,
	$$INSERT INTO logs (date_log,heure_log,utilisateur,type_log,titre_log,message_log)
	VALUES ('09-10-2019','21:06:01.23','user','SUPPRESSION','Suppression','Suppression table_1 id ''2''')$$
	);
DELETE FROM table_2 WHERE id_palier = 2;
	SELECT dblink_exec (
	$$host=localhost port=5432 dbname=database user=administrateur password=blablabla$$,
	$$INSERT INTO logs (date_log,heure_log,utilisateur,type_log,titre_log,message_log)
	VALUES ('09-10-2019','21:06:01.23','user','SUPPRESSION','Suppression','Suppression table_2 id ''3''')$$
	);
COMMIT;

Dans ce cas, tout roule. La requête bdlink_exec s'exécute bien au milieu d'une transaction (qui se termine par COMMIT)
J'ai tenté un ROLLBACK à la fin pour être sûr. La requête dblink_exec s'exécute bien, les données propres au log sont bien enregistrées mais celles des autres tables n'ont pas bougées. Au poil.


J'ai tenté de faire une erreur volontaire de saisie dans la première ligne, mais l'erreur arrête immédiatement l'exécution des requêtes.
Du coup, il n'y a pas de ROLLBACK exécuté, du coup la transaction est toujours ouverte. La requête dblink_exec n'est même pas appelée (normal en PGAdmin).
Sauf que si je tente quoique ce soit par la suite, j'ai toujours l'erreur 25P02 (La transaction est annulée, les commandes sont ignorées jusqu'à la fin du bloc) qui s'affiche.
J'ai essayé (dans le même onglet) de tout virer et de faire un ROLLBACK : même erreur.
J'en déduit que la transaction reste ouverte et qu'elle est plantée.


Mais là, je sèche.


Une piste à me suggérer ?


PS : Au fait, j'espère que les termes que j'emploie sont compréhensible et corrects : je suis autodidacte et fais ce que je peut smile

#10 Re : Général » [PG9.6]Validation d'une requete au sein d'une transaction » 08/10/2019 08:51:13

Bonjour,


Merci pour l'info. Je suis en train de tester dblink_exec sans connexion permanente (paramètres de connexion directement dans la commande) et pour l'instant, j'ai réussi à écrire dans ma table.
Il a fallu trouver le bon formalisme (notamment en ce qui concerne le doublage des ' ) mais j'y suis arrivé.


Reste à tester au milieu d'une transaction et je détaillerai la méthode pour les autres personnes qui se poseraient la même question que moi.


Cdt,

#11 Général » [PG9.6]Validation d'une requete au sein d'une transaction » 07/10/2019 12:58:03

ramirez22
Réponses : 11

Bonjour,

Peut-être ce sujet a t'il déjà été évoqué, mais je n'ai pas trouvé de solution.
Ma base inclut une table dans laquelle j'enregistre tous les évènements liée à mon application.


Or, cette application nécessite la mise en œuvre de transactions en fonction du résultat de certaines actions parallèles à l'entrée de données

BEGIN
PostgreSQL : ajout d'un enregistrement dans la table 1, récupèration de l'ID de l'enregistrement 
Application : déplace un fichier en le renommant avec l'ID récupéré. Si erreur, génération d'un message de log à enregistrer dans la table 2.
Si pas d'erreur de transfert fichier : COMMIT sinon ROLLBACK

Comme j'utilise la base pour enregistrer toutes les actions (traçabilité des modifs), en cas de ROLLBACK, je perds la totalité des infos générées dans la table 2 . Du coup, difficile de débogguer.


Est-il possible de faire de sorte que l'enregistrement dans la table 2 se fasse, quel que soit le résultat final (COMIT ou ROLLBACK) ?


Merci de votre aide.

#12 Re : Général » [PG10] Requête préparée, INSERT nombre variable de ligne par tableau » 15/03/2019 08:30:58

Bonjour,


C'est à ce moment que je réalise l'immensité de l'abime de ma méconnaissance de PostgreSQL roll


Merci beaucoup. Je prends 2 aspirines et j'essaye de comprendre comme ça marche big_smile
Pour l'instant, j'ai décomposé ma requête en 2 (de plus, cela me permet de récupérer l'ID pour un autre traitement ...), mais pour ma culture, je regarderai quand même votre syntaxe.

Merci encore.

#13 Re : Général » [PG10] Requête préparée, INSERT nombre variable de ligne par tableau » 10/03/2019 18:04:03

Je ne suis pas sur que l'on se comprenne bien tongue


Aussi, histoire d'être plus compréhensible (j'avoue que parfois ...) un petit exemple ne sera pas de trop.
Je viens de faire une base de test avec 2 tables.

CREATE TABLE public.test_document (
    id_doc bigint NOT NULL DEFAULT nextval('test_id_doc_seq'::regclass),
    code text COLLATE pg_catalog."default" NOT NULL,
    titre text COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT "Test_document_pkey" PRIMARY KEY (id_doc))

CREATE TABLE public.test_liaison(
    id_doc bigint,
    id_type bigint)

Et je voudrais réaliser cette action :

DO $$
	DECLARE
		tableau bigint[] := '{2,9,5}';
	BEGIN
		INSERT INTO test_document (code,titre) VALUES ('A', '') RETURNING id_doc AS id_nouveau_doc;
		FOR i IN 1..array_upper(tableau,1) LOOP
			INSERT INTO test_liaison (id_doc, id_type) VALUES (id_nouveau_doc, tableau[i]);
		END LOOP;
	END;
$$ LANGUAGE plpgsql;

Mais cela ne fonctionne pas :

ERROR:  ERREUR:  la requête n'a pas de destination pour les données résultantes
CONTEXT:  fonction PL/pgsql inline_code_block, ligne 5 à instruction SQL
SQL state: 42601

En gros, je voudrais que ce groupe de commande :
- crée un enregistrement sur la première table avec les valeurs passées "VALUES ('A', '')" que j'adapterai depuis mon code appelant
- créé un nombre d'enregistrement dans la deuxième table correspondant au nombre de valeurs dans la variable de type tableau 'tableau' et comme valeurs, l'ID créé précédemment et la valeur correspondante de la variable 'tableau' (suis-je clair hmm ?)


J'ai tenté d'ajouter

id_nouveau_doc bigint;

dans les déclarations, mais aucun changement.

Si je supprime le RETURNING, plus d'erreur (j'aurais tendance à penser que c'est normal puisque DO est censé renvoyer VOID), mais forcément, la table test_liaison n'a pas la valeur d'ID_doc souhaitée. Du coup, je ne vois pas comment faire.


J'avoue que cela ne vaut pas vraiment le coup de se creuser la tête pour si peu.
Je vais envoyer la première requête :

INSERT INTO test_document (code,titre) VALUES ('A', '') RETURNING id_doc AS id_nouveau_doc;

Puis récupérer dans mon code la valeur de l'ID renvoyée et lancer cette fois-ci :

DO $$
	DECLARE
		tableau bigint[] := '{2,9,5}';
	BEGIN
		FOR i IN 1..array_upper(tableau,1) LOOP
			INSERT INTO test_liaison (id_doc, id_type) VALUES (id_nouveau_doc, tableau[i]);
		END LOOP;
	END;
$$ LANGUAGE plpgsql;

Avec comme paramètres le tableau et l'id_nouveau_doc.

Certes, cela fait 2 requêtes, mais cela ne va pas surcharger le réseau big_smile


C'est juste que j'aurais voulu savoir et comprendre.


Merci en tout cas pour l'intervention. Et si tu vois des "solutions" à ma problématique, je suis preneur à titre de curiosité malgré tout.

Cordialement,
Ramirez22

#14 Re : Général » [PG10] Requête préparée, INSERT nombre variable de ligne par tableau » 09/03/2019 09:07:33

Je suis en train de me demander si je ne cherche pas la complexité là où il n'y en a pas...


En gros, j'ai créé une table pour test avec 2 colonne bigint, puis j'ai créé cette fonction:

CREATE OR REPLACE FUNCTION insertion(
	idcol bigint,
	tableau bigint[])
    RETURNS void
    LANGUAGE 'plpgsql'
    AS $$
        BEGIN
	FOR i IN 1..array_upper(tableau,1)
	    LOOP
		INSERT INTO test (colonne1, colonne2) VALUES (idcol, tableau[i]);
	    END LOOP;
        END;
$$;

Puis je l'ai lancée

SELECT insertion (1, '{1,2,4,5}');

Et j'obtiens bien ma table

colonne 1  |  colonne 2
     1     |     1
     1     |     2
     1     |     4
     1     |     5

Mais je me demande quel est le gain réel et si ça vaut vraiment le coup de se creuser la tête pour adapter cette fonction à mon besoin ci-dessus.


En gros, avec cette fonction, j'envoie une fois pour toute les infos du document avec un array contenant les ID du type de documents, et la fonction créé autant de ligne dans la table documents_types que nécessaire.
Mais quel est réellement le gain par rapport à : créer mon document et récupérer son ID, puis envoyer autant de requête que nécessaire pour créer les lignes de la table documents_types ? La charge réseau ?


De plus, l'avantage des requêtes préparées, c'est qu'elles étaient supprimées à la fermeture de la session utilisateur. Ce qui n'est pas le cas des fonctions...
Je ne sais pas si cela aura des conséquences à terme ...


Merci de vos éclaircissements.

#15 Général » [PG10] Requête préparée, INSERT nombre variable de ligne par tableau » 08/03/2019 17:20:27

ramirez22
Réponses : 6

Bonjour,


Malgré mon expérience grandissante dans la compréhension du SQL et de PostgreSQL (bon OK, j'en rajoute un peu hmm ), je n'arrive pas à trouver un début de piste de réflexion.


Je voudrais faire une requête préparée et lui passer quelques valeurs text et un tableau (array) de x bigint.
Ma fonction actuelle (sans le tableau) a cette tête :

PREPARE ajout_nouveau_document_inconnu(text,text,text,text,bigint) AS 
WITH nouveau_doc AS (
    INSERT INTO documents (
    code, 
    titre, 
    commentaires, 
    creation_par,
    date_creation) 
    VALUES ($1, $2, $3, $4, CURRENT_DATE)
    RETURNING id_document)
INSERT INTO documents_types (id_document, id_type) 
    SELECT nouveau_doc.id_document, $5 
    FROM nouveau_doc 
RETURNING id_document;

La table documents_types est une table de liaison et il peut y avoir plusieurs types pour un document.
Or avec cette requête, je ne peut que saisir le premier type ($5).
Je suis ensuite obligé de faire un INSERT INTO documents_types pour chaque types et ça envoi un nombre conséquent de requêtes.


J'aurais voulu, à la place du dernier bigint de la fonction préparée, transmettre un tableau de bigint avec les ID de chaque types de doc, et que la fonction gère d'elle-même les INSERT INTO en fonction:
- du id_document récupéré lors de la création du-dit document dans la table documents
- du ou des bigint contenu(s) dans le tableau transmis


Mais perso, je sèche...

Un peu d'aide serait la bienvenue (même si c'est pour me dire "pô possible !")

Merci à vous, bonne fin de journée

#16 Re : Général » [PG10] Stockage de fichiers » 01/12/2018 02:02:55

Bonsoir.


Suite et fin de mes pérégrinations.
Je dis fin, car comme Jacques BREL, je n'irai pas plus loin big_smile


L'utilisation de la base en tant que stockage de fichier est un échec (dans mon cas). Pour la majeur partie des fichiers, cela se passe bien. A partir d'une taille (grosso merdo 10-15 Mo), le temps de transfert en écriture devient vraiment trop long. Sans parler de la lecture qui impose un téléchargement complet du fichier avant de pouvoir l'ouvrir par l'ActiveX Adobe. L'utilisation via un partage réseau permet de commencer à afficher les premières pages du fichier alors qu'il n'est pas encore complétement chargé = moins de latence pour l'utilisateur.


J'ai installé un service SMB sur mon serveur avec quelques droits bien sentis (bon OK, je me suis fais aider big_smile) et mon application arrive à afficher les fichiers, même les plus gros, dans des délais raisonnables.


Pour des questions de sécurité, les échanges devront être chiffrés, ce qui implique un temps de chargement plus long. Quand je parle des échanges, je parle des échanges au niveau de SMB, mais également au niveau des requêtes SQL (SSL ? SSH ?)


L'idéal pour les fichiers serait un chiffrement/déchiffrement directement réalisé par l'application cliente : même un piratage du serveur ne servirait pas à grand chose dans ce cas. Mais bon, il faut que je regarde ce que ça donne avec les fonctions intégrées à Windev.


En tout cas, merci de votre soutiens, j'ai appris plein de choses grâce à vous.
Cordialement,
Ramirez

#17 Re : Général » [PG10] Stockage de fichiers » 28/11/2018 13:09:47

Suite de mes essais.


Histoire de voir, j'ai quand même essayé de passer directement par un bytea et de stocker directement le(s) fichier(s) dans la base.
L'envoi du fichier est assez lent, mais comparable à ce que j'ai déjà vu.
La lecture du fichier passe par une opération d'enregistrement sur le disque dur local pour pourvoir ensuite l'ouvrir avec l'AtciveX. Tout roule, et c'est assez rapide à mon sens.


Je vais essayer de lancer une saisie multiple d'enregistrements, en variant taille et nombre de fichiers.
Puis je testerai la lecture histoire de voir les performance.


Je vous tiens au jus...

#18 Re : Général » [PG10] Stockage de fichiers » 28/11/2018 10:51:34

dverite a écrit :

Un 0 à côté de chaque octet est typique de l'encodage UTF-16 que Windows utilise préférentiellement pour l'Unicode:
https://fr.wikipedia.org/wiki/UTF-16
Ca peut vouloir dire que le binaire aurait été pris à tort pour du texte et passé dans un mauvais filtre.


Rhaaaa ! Je vais pas y arriver yikes


Bon, café, on va reprendre tout depuis le début.
J'ai du mal à envoyer mes données via une requête SQL par Windev. J'utilise l'argument {WDMemoBinaire('<Fichier>')}pour pouvoir envoyer les données "brutes", mais il se passe quelque chose pendant la com'.

Entête fichier PDF 'conforme' (envoyé par le client)
%PDF-1.6
%Þ­¾ï
6 0 obj
<<  /Length 39463 /Filter /FlateDecode /DecodeParms
<<  /Predictor 1

Entête fichier PDF 'non conforme' (reçu par le serveur)
%PDF-1.6\012%\336\255\276\357\0126 0 obj\012<<  /Length 39463 /Filter /FlateDecode /DecodeParms\012<<  /Predictor 

On voit bien que le fichier a été interprété (CR devient \012 par exemple). De plus, la taille du fichier a quasiment triplée. Le fichier est bien sur illisible.


Du coup, j'ai regardé les types de variables en entrée. pg_file_write demande des données sous forme text alors que j'envoie des données binaires. Il y a peu-être quelque chose à voir là dedans. Mais je ne vois pas comment envoyer mes données autrement que sous la forme binaire avec Windev ...


Il existe bien un pg_read_binary_file(), mais pas l'inverse ...
Je creuse ...

#19 Re : Général » [PG10] Stockage de fichiers » 28/11/2018 10:12:51

Bonjour.


Suite de mes pérégrinations. Je pense abandonner définitivement ce type de transfert.
Je viens de réaliser des tests avec mon application et avec des fichiers PDF de toute tailles (de moins de 1Mo à plus de 20 Mo) et de tout type (vectoriel et raster).
Hélas, les performances ne sont pas au rendez-vous (sachant qu'en plus, durant la phase de développement / test, je suis sur un serveur virtuel sur mon PC via virtualbox. Je suis donc affranchi des lenteurs occasionnées par le réseau...).


Je n'avais pas précisé que la version 1.0 de mon application utilisait une version HFSQL Classic (sorte de SQLite version PCSOFT, en mode fichier). La base de donnée et les données étaient sur un disque partagé sur un réseau RLE. Fonctionnement au poil, mais une crainte de voir des problèmes se pointer dès que plusieurs utilisateurs tentaient d’accéder en écriture en même temps aux enregistrements. D'où le besoin du serveur SQL pour, entre autre, sécuriser tout ça.


Dans la version client-serveur, le transfert vers la base se passe plutôt bien. Pas (trop) de ralentissement par rapport à la version d'avant pour les fichiers jusqu'à 2Mo. Par contre, cela commence à se gâter au delà.
L'opération inverse (lecture depuis la base) est plus problématique : je suis obligé d'enregistrer le fichier sur le disque "local" avant de l'ouvrir avec un ActiveX PDF pour l'afficher.
En effet, je n'ai accès qu'à un activeX adobe accrobat reader, puisqu'il est installé par défaut sur toutes les machines. Je n'ai pas la possibilité d'installer un autre viewer ActiveX car je n'ai pas les droits admin sur les machines clientes. Les fonctions de cet ActiveX sont très peu documentées (je n'ai trouvé que peu de choses sur le Net) et il semble qu'il ne soit possible que d'ouvrir un fichier (et non d'envoyer directement le flux de données dans l'ActiveX pour l'afficher.
Le cumul de ces opérations est là encore pas trop sensible pour les petits fichiers, mais plante lamentablement pour les gros (quand je dis plante, c'est que l'application se fige pendant le temps de chargement, qui est très long). Je pourrais faire un traitement en parallèle pour continuer à travailler sur l'application pendant le chargement du fichier, mais de toute façon, l'utilisateur n'a rien d'autre à faire : s'il a sélectionné un fichier, c'est pour le voir !


Bref, tout ça pour rien (ou presque, le fait d'apprendre est toujours gratifiant). Il ne me reste plus qu'à voir comment mettre en oeuvre un serveur de fichier sur mon Windows Server, et de le blinder au niveau sécurité d'accès.


Il est fort probable que les lenteurs proviennent du passage par un LO dans PostgreSQL. Il faudrait que je teste avec des fonctions plus directes comme pg_read_binary_file() et pg_file_write(), mais j'avoue commencer à perdre un peu en motivation sad
Allez, un dernier coup de collier tongue


Merci à tous pour vos interventions.

#20 Re : Général » [PG10] Stockage de fichiers » 27/11/2018 19:02:33

Bonjour,


J'ai testé avec pg_read_binary_file() et pg_file_write(). J'obtiens enfin le bon résultat mais cela nécessite que le rôle qui se connecte aie le privilège de super-utilisateur. Cela me pose des problèmes d'ordre "éthique", car même les administrateurs de l'application n'ont pas ce genre de droits ...


J'ai fais des essais avec external_file et ça marche pas trop mal non plus. Tout se fait via external_file et je n'ai pas de soucis au niveau des droits.
Je vais essayer de repartir sur une base vierge et recommencer afin de valider les manipulations (à force de faire des modifs, on n'est plus trop sur de ce qui fonctionne vraiment big_smile), et surtout de l'implanter dans mon application pour voir si l'intégration ne pose pas de problème (en version stand-alone actuellement).


Pour ceux que ça intéresse :

// Requêtes d'écriture du fichier sur le serveur. %1=fichier à transmettre, %2=nom du fichier de destination
gsRequeteSQL = "SELECT external_file.writeEfile({WDMemoBinaire('%1')}, ('<Répertoire_destination>', '%2'));"

// Complétion de la requête avec les valeurs variables (SAI_fichier = chemin + nom + extension du fichier)
gsRequeteSQL = ChaîneConstruit(gsRequeteSQL, SAI_Fichier, fExtraitChemin(SAI_Fichier,fFichier+fExtension))

// Émission de la requête, traitement de l'erreur
SI SQLExec(gsRequeteSQL,gResultatSQL) = Faux ALORS 
	SQLInfoGene(gResultatSQL)
	Erreur(SQL.MesErreur)
	SQLFerme(gResultatSQL)
SINON
	Info("Envoyé")
FIN

//  -------------------------------------------------------------------------------------------
// Requête de lecture
gsRequeteSQL = "SELECT external_file.readefile(('<Repertoire_destination>', '%1'));"

// Là encore complétion de la requête avec les variables 
gsRequeteSQL = ChaîneConstruit(gsRequeteSQL, fExtraitChemin(SAI_Fichier,fFichier+fExtension))

// Émission de la requête et traitement de l'erreur
SI SQLExec(gsRequeteSQL,gResultatSQL) = Faux ALORS 
	SQLInfoGene(gResultatSQL)
	Erreur(SQL.MesErreur)
	SQLFerme(gResultatSQL)
SINON
	Info("Reçu")
	SQLAvance(gResultatSQL)
// Récupération des valeurs en format binaire et envoi dans l'image à afficher
	IMG_image = SQLLitMémo(gResultatSQL,1)
	SQLFerme(gResultatSQL)
FIN

J'espère que cela pourra aider certaines personnes.


Il ne restera plus qu'à gérer l'effacement des fichiers. En effet, il peut arriver qu'un fichier doive être effacé (obsolescence du fichier, erreur ...). Et là, external_file ne propose pas cette fonction. Il faudra voir si je peux octroyer les droits Super-utilisateur aux administrateur de l'application... A voir.


Bonne soirée.

#21 Re : Général » [PG10] Stockage de fichiers » 26/11/2018 22:47:41

Hello !


Merci pour toute vos intervention, mais là ça commence à devenir complexe pour moi : script untrusted, compilation C ... vite, une aspirine big_smile
Blague à part, j'ai eu l'occasion de tester un peu external_file.
Pour l'instant, j'arrive à envoyer un fichier PDF depuis mon application vers le serveur, qui le stocke bien là ou je lui dit... (sympa le serveur smile).
L'ouverture du fichier directement sur le serveur fonctionne bien (le fichier PDF est intègre).
Par contre, le fonctionnement dans l'autre sens est un peu moins ... fonctionnel. Je me retrouve avec quelques NULL qui trainent à côté de chaque "caractères" (une petite ouverture sous notepad++) et la taille n'a rien à voir avec l'original (je perds 99 % des données). Bref, je n'arrive pas à récupérer mon fichier PDF.


En plus, je programme en Windev, ce qui complexifie encore plus la chose yikes


En résumé, Windev n'est certainement pas le langage le plus utilisé, postgreSQL sur Windows Server idem ... j'aime les défis big_smile !!!
Ce soir un peu crevé, mais demain je vais y arriver !


Je crois que je vais jeter un oeil quand même à  pg_read_binary_file() et pg_file_write... histoire de voir si cela m'inspire.


Bonne soirée.

#22 Re : Général » [PG10] Stockage de fichiers » 25/11/2018 23:14:52

Merci beaucoup pour ces informations précises et surtout très bien explicitées.
Je n'avais aucune idée de tout ces détails et j'avoue que cela m'éclaire grandement.


Fort de ces explications, et par rapport à l'utilisation que j'en ai, il est désormais évident qu'il vaut mieux partir sur un serveur de fichiers en parallèle de ma base plutôt que d'enregistrer les fichier directement dans celle-ci.
Je suis également en train de creuser l'extension external_file, qui pourrait bien être le meilleur des 2 mondes : le fichier est sauvegardé sur un espace disque "en clair" et PostgreSQL sert de "passerelle" : pas besoin de serveur de fichier (donc pas de ports supplémentaire ouvert etc ...), droits utilisateurs ...


Je teste et vous dit ce qu'il en est. Si par contre vous avez un REX à partager, je suis également preneur.


Bonne nuit.

#23 Re : Général » [PG10] Supprimer ROLE après déconnexion » 25/11/2018 19:55:29

rjuju a écrit :

Le plus simple serait quand même d'appliquer le changement de profil directement, et de gérer correctement les erreurs dans l'application.  Cela devrait être de toutes façons fait, vous n'êtes probablement pas à l'abri de toute autre erreur lors de l'exécution d'une requête, autant que votre application ne plante pas.


Vous avez sans aucun doute raison. C'est du boulot que de traiter toutes les erreurs possibles, mais la stabilité de l'application ne doit pas être mise en cause.
Du coup, je suis parti sur un blocage de l'opération de suppression du ROLE si celui-ci est connecté. Un symbole dans la liste des users permet de voir d'un coup d’œil si c'est le cas. De plus, l'admin a accès au mail de l'utilisateur (je réfléchi à la possibilité de mettre également le téléphone), comme ça il n'a plus qu'a le prévenir pour que ce dernier se déconnecte.


Je vais peut-être également donner la possibilité à l'administrateur de forcer la déconnexion de l'utilisateur, mais j'hésite encore. C'est un peu brutal ... big_smile


Bonne soirée

#24 Re : Général » [PG10] Stockage de fichiers » 25/11/2018 19:47:51

Bonjour et merci de ce premier retour.


gleu a écrit :

et tout ça sur un serveur Windows.

Pas le choix hélas ... yikes


gleu a écrit :

Pour moi, c'est à tester avec une base contenant la volumétrie cible, ie au minimum les 500 Go.

Pas faux, je vais essayer de mettre ça en œuvre, mais je ne sais pas quand ni trop comment pour l'instant big_smile Encore un peu de lecture en perspective ...


gleu a écrit :

Surtout qu'on parle là de 100000 objets à 5 Mo de moyenne, 60 dans le pire des cas. Ce qui veut dire une instance comprise entre 500 Go et 6 To, en ne comptant que les documents. Et je ne prends en compte qu'un seul fichier par ligne, alors qu'apparemment la source peut aussi être stockée, ce qui veut dire plus ou moins doubler la volumétrie précédente..

Je n'ai pas encore de vision claire de la volumétrie, mais je devrais avoir une estimatif grossier assez rapidement. Je reviendrai d'ici peu pour compléter ces infos.


Mais pour compléter ma maigre connaissance de PostgreSQL, pourquoi est-il déconseillé de stocker des données de fichier ?
Pour éviter de surcharger le serveur (pendant qu'il envoie le fichier, il ne peut pas traiter d'autre requête) ? Mais alors dans ce cas, mettre un serveur de fichiers sur le même serveur physique ne va pas changer grand chose (les ressources restent "limitées" aux capacités du serveur physique). Ou est-ce uniquement pour que les opérations de sauvegarde prennent moins de ressources ? D'ailleurs, qu'appelle t'on "opérations de maintenance" ? D'autres tâches que la sauvegarde ?


Désolé si mes questions paraissent bêtes, mais ma spécialité reste l'automatisme industriel alors ... wink


Merci encore, bonne soirée.

#25 Général » [PG10] Stockage de fichiers » 25/11/2018 12:27:28

ramirez22
Réponses : 16

Bonjour,


Je reviens vers vous pour des conseils et des précisions.
Malgré le temps passé entre mon dernier post et celui-ci, je n'ai guère avancé dans ma connaissance profonde de PostgreSQL, donc prière d'employer un langage connu du Vulgum Pecus big_smile


Ma base permet une recherche de plans, en fonction de nombreux paramètres.
Ces plans sont dit 'sensibles' par ma société, et je dois évidemment essayer de sécuriser tout ça. mais c'est un autre sujet.
Je dispose d'un serveur sous Windows sever 2012 et d'un postgreSQL 10 installé. La plupart des ports de comm' ont été bloqués.


La table principale de la base contiendra plusieurs dizaines de milliers de lignes (50 000 à 100 000 à vue de nez) et chaque ligne est associée à un fichier pdf et potentiellement un autres fichier (les sources comme un fichier Word, AutoCAD etc ...). La taille des fichiers peut monter à 50-60 Mo dans le pire des cas. Sinon, ça tourne autour des 5 Mo en moyenne.


J'ai lu un peu de tout et son contraire sur le Net concernant la 'gestion' des fichiers par les SGBDD:
- "surtout pas de fichier, c'est pas fait pour ça"
- "pas de problème, sauf si les fichiers sont gros ou s'il y en a beaucoup"
- "vas-y fonce, c'est tout bon"


Bref, il y a de quoi péter un câble RJ45 yikes
Comme d'habitude, la vérité doit se trouver au milieu de tout ça, et je compte un peu sur vous pour éclairer ma lanterne (sans vouloir lancer un Troll sur le sujet).
Et d'abord, c'est quoi un fichier trop gros ? Et à partir de combien on considère qu'il y en a beaucoup ? yikes


Bref, à priori, il existe deux modes de gestion, résumé en 2 types de données :
- le LargeObjet ou BLOB
- le bytea

J'ai également entendu parler de l'extension 'external_file' qui serait utile pour (à ce que j'ai compris) référencer et manipuler des fichiers physiquement stockés ailleurs que dans la base.


Sinon, il y a encore la possibilité de laisser la gestion des fichiers à l'OS (ce qui est un peu son boulot après tout) et de faire référence au chemin uniquement dans la base (c'est ce qui est actuellement réalisé). Mais cela nécessite de prévoir un serveur de fichier, d'ouvrir quelques ports supplémentaires etc ...

L'avantage de passer par la BDD, c'est que je n'ajoute pas d'ouverture de port, je bénéficie du chiffrement de la connexion directement par le SGBDD (SSL ou SSH) et que mes données ne sont pas facilement accessibles (nécessité d'avoir un ROLE permettant l'accès à ces données). Par contre, quid de la charge du serveur et du besoin en bande passante ? A ce sujet, j'ai omis de préciser qu'actuellement il y a 30 ROLES avec LOGIN et ultérieurement, cela pourrait monter à plus de 100. Par contre, le nombre de personnes connectées simultanément ne devrait pas excéder la dizaine sauf cas particulier et rarissime.
L'inconvénient, c'est que la sauvegarde de la base prendra un quart de siècle, mais ça c'est pas un soucis. Par contre, si l'application n'est pas utilisable parce qu'il y a trop de latence entre les requêtes ou que le fichier PDF met 3 plombes à se charger, c'est rédhibitoire.


A vos claviers big_smile et merci d'avance de vos éclaircissements.

Cdt,
Ramirez

Pied de page des forums

Propulsé par FluxBB