Écrire un langage spécifique à un domaine pour sélectionner des lignes dans une table

StackOverflow https://stackoverflow.com/questions/140026

Question

J'écris un serveur que je m'attends à faire fonctionner par de nombreuses personnes différentes, avec lesquelles je n'aurai pas toutes les contacts directs. Les serveurs vont communiquer les uns avec les autres dans un cluster. Une partie des fonctionnalités du serveur implique la sélection d'un petit sous-ensemble de lignes dans une table potentiellement très volumineuse. Le choix exact des lignes sélectionnées nécessitera quelques ajustements et il est important que la personne qui exécute le cluster (par exemple, moi-même) puisse mettre à jour les critères de sélection sans que chaque administrateur de serveur ne déploie une nouvelle version du serveur. .

Écrire simplement la fonction en Python n'est pas vraiment une option, car personne ne voudra installer un serveur qui télécharge et exécute du code Python arbitraire au moment de l'exécution.

Ce dont j'ai besoin, ce sont des suggestions sur la manière la plus simple d'implémenter un langage spécifique au domaine pour atteindre cet objectif. Le langage doit être capable d'évaluer une expression simple, d'interroger des index de table et d'effectuer une itération dans les lignes renvoyées. La facilité d'écriture et de lecture du langage est secondaire à la facilité de mise en œuvre. Je préférerais également ne pas avoir à écrire un optimiseur de requête complet, donc quelque chose qui spécifie explicitement quels index interroger serait idéal.

L’interface avec laquelle elle devra compiler aura des fonctionnalités similaires à celles exportées par le magasin de données App Engine: vous pouvez rechercher des plages séquentielles sur n’importe quel index de la table égalité), puis filtrez la ligne renvoyée par une expression booléenne. Vous pouvez également concaténer plusieurs ensembles de résultats indépendants.

Je réalise que cette question ressemble beaucoup à celle que je demande pour SQL. Cependant, je ne veux pas exiger que le magasin de données sauvegardant ces données soit une base de données relationnelle, et je ne veux pas la surcharge de tenter de réimplémenter SQL moi-même. Je n'ai également affaire qu'à une seule table avec un schéma connu. Enfin, aucune jointure ne sera requise. Une solution beaucoup plus simple serait de loin préférable.

Modifier: Description étendue pour dissiper certaines idées fausses.

Était-ce utile?

La solution

Construction d’un DSL à interpréter par Python.

Étape 1. Générez les classes et les objets d'exécution. Ces classes auront toutes les boucles de curseur et les instructions SQL et tout ce traitement algorithmique caché dans leurs méthodes. Vous ferez un usage intensif de la Commande et Stratégie , modèles de conception permettant de créer ces classes. La plupart des choses sont une commande, les options et les choix sont des stratégies de plug-in. Examinez le design de l'API Task d'Apache Ant - c'est un bon exemple.

Étape 2. Vérifiez que ce système d'objets fonctionne réellement. Assurez-vous que le design est simple et complet. Vos tests construisent les objets Command et Strategy, puis exécutent l'objet Command de niveau supérieur. Les objets Command feront le travail.

À ce stade, vous avez en grande partie terminé. Votre run-time est juste une configuration d'objets créés à partir du domaine ci-dessus. [Ce n'est pas aussi facile qu'il y paraît. Il faut prendre soin de définir un ensemble de classes pouvant être instanciées, puis "se parler entre elles". faire le travail de votre application.]

Notez que ce que vous aurez n’exigera rien de plus que des déclarations. Quel est le problème avec procédural? Dès que vous commencez à écrire un DSL avec des éléments procéduraux, vous constatez que vous avez besoin de plus en plus de fonctionnalités jusqu'à ce que vous ayez écrit Python avec une syntaxe différente. Pas bien.

De plus, les interprètes de langage procédural sont tout simplement difficiles à écrire. L'état d'exécution et l'étendue des références sont simplement difficiles à gérer.

Vous pouvez utiliser le langage Python natif et cesser de vous inquiéter de "sortir du bac à sable". En effet, c’est comme ça que vous allez tout tester en utilisant un court script Python pour créer vos objets. Python sera le DSL.

["Mais attendez", dites-vous, "si j’utilise simplement Python comme moyen de pouvoir exécuter des tâches arbitraires sur DSL". Dépend de ce qui est sur PYTHONPATH et sys.path. Consultez le module site pour savoir comment contrôler ce qui est disponible.]

Un DSL déclaratif est le plus simple. C'est entièrement un exercice de représentation. Un bloc de Python qui ne fait que définir les valeurs de certaines variables est agréable. C'est ce que Django utilise.

Vous pouvez utiliser le ConfigParser comme langage de représentation de votre exécution. configuration temporelle des objets.

Vous pouvez utiliser JSON ou YAML en tant que langage permettant de représenter votre configuration d’objets au moment de l’exécution. Des analyseurs syntaxiques prêts à l'emploi sont totalement disponibles.

Vous pouvez également utiliser XML. Il est plus difficile de concevoir et d'analyser, mais cela fonctionne bien. Les gens adorent ça. C'est ainsi que Ant et Maven (et de nombreux autres outils) utilisent la syntaxe déclarative pour décrire les procédures. Je ne le recommande pas, car c'est une douleur verbeuse au cou. Je recommande simplement d'utiliser Python.

Vous pouvez aussi vous lancer dans l'introspection, inventer votre propre syntaxe et écrire votre propre analyseur.

Autres conseils

Je pense que nous aurons besoin d'un peu plus d'informations ici. Faites-moi savoir si l’un des éléments suivants est basé sur des hypothèses incorrectes.

Tout d'abord, comme vous l'avez fait remarquer vous-même, il existe déjà un DSL pour la sélection de lignes dans des tables arbitraires - il s'appelle "SQL". Puisque vous ne voulez pas réinventer le code SQL, je suppose que vous n'avez besoin d'interroger que depuis une seule table avec un format fixe.

Si tel est le cas, vous n’avez probablement pas besoin de mettre en œuvre une DSL (bien que ce soit certainement une solution); si vous êtes habitué à l'orientation d'objet, il peut être plus facile de créer un objet filtre.

Plus précisément, un " Filtre " collection contenant un ou plusieurs objets SelectionCriterion. Vous pouvez les implémenter pour hériter d'une ou plusieurs classes de base représentant les types de sélections (Range, LessThan, ExactMatch, Like, etc.). Une fois ces classes de base en place, vous pouvez créer des versions héritées spécifiques à la colonne et appropriées à cette colonne. . Enfin, en fonction de la complexité des requêtes que vous souhaitez prendre en charge, vous devrez implémenter une sorte de colle connective pour gérer les liens AND et OR et NOT entre les différents critères.

Si vous en avez envie, vous pouvez créer une interface graphique simple pour charger la collection. Je considérerais le filtrage dans Excel comme un modèle, si vous n'avez rien d'autre à l'esprit.

Enfin, il devrait être simple de convertir le contenu de cette collection en SQL correspondant et de le transmettre à la base de données.

Cependant: si vous recherchez une solution simple et que vos utilisateurs comprennent SQL, vous pouvez simplement leur demander de saisir le contenu d'une clause WHERE et de créer le reste de la requête par programme. Du point de vue de la sécurité, si votre code contrôle les colonnes sélectionnées et la clause FROM et que vos autorisations de base de données sont définies correctement, et que vous effectuez une vérification de la cohérence de la chaîne provenant des utilisateurs, cette option est relativement sûre.

"implémenter un langage propre au domaine"

"Personne ne souhaite installer un serveur qui télécharge et exécute du code Python arbitraire au moment de l'exécution" "

.

Je veux un DSL mais je ne veux pas que Python soit ce DSL. D'accord. Comment allez-vous exécuter cette DSL? Quel runtime est acceptable si ce n'est pas Python?

Que se passe-t-il si un programme C intègre l’interpréteur Python? Est-ce acceptable?

Et si Python n’est pas un runtime acceptable, pourquoi at-il une balise Python?

Pourquoi ne pas créer un langage qui "compile "" génère-t-il du SQL ou le langage de requête requis par votre banque de données?

Vous créeriez essentiellement une abstraction sur votre couche de persistance.

Vous avez mentionné Python. Pourquoi ne pas utiliser Python? Si quelqu'un peut " taper & " une expression dans votre DSL, ils peuvent taper en Python.

Vous aurez besoin de règles sur la structure de l'expression, mais c'est beaucoup plus facile que d'implémenter quelque chose de nouveau.

Vous avez dit que personne ne voudrait installer un serveur qui télécharge et exécute du code arbitraire au moment de l'exécution. Cependant, c’est exactement ce que fera votre DSL (à terme), de sorte qu’il n’ya probablement pas beaucoup de différence. À moins que vous ne fassiez quelque chose de très spécifique avec les données, alors je ne pense pas qu'un DSL vous achètera autant et que cela frustrera les utilisateurs déjà familiarisés avec SQL. Ne sous-estimez pas la taille de la tâche que vous allez entreprendre.

Toutefois, pour répondre à votre question, vous devrez créer une grammaire de votre langue, analyser le texte et parcourir l’arborescence, en émettant du code ou en appelant une API que vous avez écrite (c’est pourquoi mon commentaire vous devrez toujours envoyer du code).

Il existe de nombreux textes éducatifs sur les grammaires pour les expressions mathématiques auxquelles vous pouvez vous référer sur le net, c’est assez simple. Vous pouvez avoir un outil générateur d’analyseur comme ANTLR ou Yacc que vous pouvez utiliser pour générer l’analyseur (ou utiliser un langage comme Lisp / Scheme et marier les deux). Venir avec une grammaire SQL raisonnable ne sera pas facile. Mais google 'BNF SQL' et voyez ce que vous proposez.

Bonne chance.

Cela ressemble vraiment à du SQL, mais peut-être que ça vaut la peine d'essayer d'utiliser SQLite si vous voulez garder les choses simples?

On dirait que vous voulez créer une grammaire et non un DSL. Dans ANTLR , vous pourrez créer un analyseur spécifique qui interprétera le texte et traduira des commandes spécifiques. ANTLR fournit des bibliothèques pour Python, SQL, Java, C ++, C, C #, etc.

En outre, voici un excellent exemple de moteur de calcul créé par ANTLR. en C #

Une grammaire sans contexte a généralement une structure semblable à une arborescence et les programmes fonctionnels ont également une structure similaire à une arborescence. Je ne prétends pas que ce qui suit résoudrait tous vos problèmes, mais c’est un pas dans la bonne direction si vous êtes sûr de ne pas vouloir utiliser quelque chose comme SQLite3 .

from functools import partial
def select_keys(keys, from_):
    return ({k : fun(v, row) for k, (v, fun) in keys.items()}
            for row in from_)

def select_where(from_, where):
    return (row for row in from_
            if where(row))

def default_keys_transform(keys, transform=lambda v, row: row[v]):
    return {k : (k, transform) for k in keys}

def select(keys=None, from_=None, where=None):
    """
    SELECT v1 AS k1, 2*v2 AS k2 FROM table WHERE v1 = a AND v2 >= b OR v3 = c

    translates to 

    select(dict(k1=(v1, lambda v1, r: r[v1]), k2=(v2, lambda v2, r: 2*r[v2])
        , from_=table
        , where= lambda r : r[v1] = a and r[v2] >= b or r[v3] = c)
    """
    assert from_ is not None
    idfunc = lambda k, t : t
    select_k = idfunc if keys is None  else select_keys
    if isinstance(keys, list):
        keys = default_keys_transform(keys)
    idfunc = lambda t, w : t
    select_w = idfunc if where is None else select_where
    return select_k(keys, select_w(from_, where))

Comment vous assurez-vous de ne pas donner aux utilisateurs la possibilité d’exécuter du code arbitraire? Ce cadre admet toutes les fonctions possibles. Eh bien, vous pouvez placer un emballage dessus pour des raisons de sécurité, qui exposent une liste fixe d’objets de fonction acceptables.

ALLOWED_FUNCS = [ operator.mul, operator.add, ...] # List of allowed funcs

def select_secure(keys=None, from_=None, where=None):
    if keys is not None and isinstance(keys, dict):
       for v, fun keys.values:
           assert fun in ALLOWED_FUNCS
    if where is not None:
       assert_composition_of_allowed_funcs(where, ALLOWED_FUNCS)
    return select(keys=keys, from_=from_, where=where)

Comment écrire assert_composition_of_allowed_funcs . C'est très difficile de le faire en python mais facile en lisp. Supposons qu’où se trouve une liste de fonctions à évaluer dans un format de type lèvres, par exemple où = (operator.add, (operator.getitem, rangée, v1), 2) ou où = (operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3) .

Ceci permet d'écrire une fonction apply_lisp qui s'assure que la fonction where est uniquement composée de ALLOWED_FUNCS ou de constantes telles que float, int, str.

def apply_lisp(where, rowsym, rowval, ALLOWED_FUNCS):
    assert where[0] in ALLOWED_FUNCS
    return apply(where[0],
          [ (apply_lisp(w, rowsym, rowval, ALLOWED_FUNCS)
            if isinstance(w, tuple)
            else rowval if w is rowsym
            else w if isinstance(w, (float, int, str))
            else None ) for w in where[1:] ])

De plus, vous devrez également rechercher les types exacts, car vous ne souhaitez pas que leurs types soient remplacés. Donc, n'utilisez pas isinstance , utilisez , tapez (float, int, str) . Oh, nous avons rencontré:

  

Dixième règle de programmation de Greenspun: suffisamment complexe   Programme C ou Fortran contient un fichier ad hoc spécifié de manière informelle   mise en œuvre lente de la moitié de Common Lisp, corrigée par les bogues.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top