Pergunta

Eu estou escrevendo um servidor que eu esperava para ser executado por várias pessoas diferentes, dos quais nem todos I terão contato direto com. Os servidores irão se comunicar uns com os outros em um cluster. Parte da funcionalidade do servidor envolve a seleção de um pequeno subconjunto de linhas de uma potencialmente muito grande mesa. A escolha exata do que as linhas são selecionadas vai precisar de algum ajuste, e é importante que é possível para a pessoa que executa o cluster (por exemplo, eu mesmo) para atualizar os critérios de selecção sem ficar cada administrador do servidor para implantar uma nova versão do servidor .

Basta escrever a função em Python não é realmente uma opção, já que ninguém vai querer instalar um servidor que baixa e executa arbitrário código Python em tempo de execução.

O que eu preciso são sugestões sobre a maneira mais simples de implementar uma Domain Specific Language para alcançar este objetivo. As necessidades linguísticas para ser capaz de avaliação simples expressão, bem como consultando os índices da tabela e iterar as linhas retornadas. Facilidade de leitura e escrita da língua é secundário à facilidade de implementação. Eu também prefiro não ter que escrever todo um otimizador de consulta, de modo algo que explicitamente especifica que índices para consulta seria o ideal.

A interface que isso terá para compilar contra será semelhante em capacidades para o que o App Engine do armazenamento de dados exportações: Você pode consultar para as faixas sequenciais em qualquer índice na tabela (por exemplo, menos do que, maior que, alcance e igualdade consultas), em seguida, filtrar a linha retornada por qualquer expressão booleana. Você também pode concatenar vários conjuntos de resultados independentes juntos.

Sei que esta pergunta soa muito parecido com o que estou pedindo SQL. No entanto, eu não quero exigem que o armazenamento de dados apoiando esses dados ser um banco de dados relacional, e eu não quero que a sobrecarga de tentar reimplementar SQL mim. Eu também estou lidando com apenas uma única tabela com um esquema conhecido. Finalmente, não se junta será necessária. Algo muito mais simples seria de longe preferível.

Edit:. Descrição expandida para esclarecer alguns equívocos

Foi útil?

Solução

A construção de uma DSL para ser interpretado por Python.

Passo 1. Construir as classes em tempo de execução e objetos. Essas classes terá todos os loops de cursor e instruções SQL e todos os que o processamento algorítmico escondido em seus métodos. Você vai fazer uso pesado do Comando e padrões de projeto Estratégia para construir essas classes. A maioria das coisas são um comando, opções e escolhas são plug-in estratégias. Olhe para o projeto para Task API do Apache Ant - é um exemplo bom.

Passo 2. Validar que este sistema de objetos realmente funciona. Certifique-se de que o design é simples e completa. Você está testes irão construir os objetos Command e estratégia, e em seguida, executar o objeto de comando de nível superior. Os objetos comando irá fazer o trabalho.

Neste ponto, você está em grande parte feito. O seu tempo de execução é apenas uma configuração de objetos criados a partir do domínio acima. [Isto não é tão fácil como parece. Ele requer alguns cuidados para definir um conjunto de classes que pode ser instanciado e, em seguida, "falar entre si" para fazer o trabalho de sua aplicação.]

Note que o que você terá vai exigir nada mais do que declarações. O que há de errado com processual? Um você começar a escrever uma DSL com elementos processuais, você achar que precisa de mais e mais recursos até que você tenha escrito Python com sintaxe diferente. Não é bom.

Além disso, intérpretes de língua processuais são simplesmente difícil de escrever. Estado de execução e abrangência de referências são simplesmente difícil de gerir.

Você pode usar Python nativa - e parar de se preocupar com a "sair da caixa de areia". Na verdade, é assim que você teste de unidade tudo, usando um script Python curto para criar seus objetos. Python será o DSL.

[ "Mas espere", você diz: "Se eu simplesmente usar Python como o povo DSL pode executar coisas arbitrárias." Depende do que está no PYTHONPATH e sys.path. Olhada na local módulo maneiras de controlar o que está disponível.]

A DSL declarativa é mais simples. É inteiramente um exercício de representação. Um bloco de Python que apenas define os valores de algumas variáveis ??é bom. Isso é o que os usos Django.

Você pode usar o ConfigParser como uma linguagem para representar o seu run- configuração do tempo de objetos.

Você pode usar JSON ou YAML como uma linguagem para representar a configuração de tempo de execução de objetos. Pronto-a-analisadores são totalmente disponível.

Você pode usar XML, também. É mais difícil de design e análise, mas ele funciona bem. As pessoas adoram. Isso é como Ant e Maven (e um monte de outras ferramentas) usam sintaxe declarativa para descrever os procedimentos. Eu não recomendo, porque é uma dor prolixo no pescoço. Eu recomendo simplesmente usando Python.

Ou, você pode ir para fora da profunda-end e inventar a sua própria sintaxe e escrever seu próprio analisador.

Outras dicas

Eu acho que nós vamos precisar de um pouco mais informações aqui. Deixe-me saber se qualquer um dos seguintes se baseia em suposições incorretas.

Em primeiro lugar, como você apontou-se, já existe uma DSL para selecionar linhas de arbitrária tables-- ele é chamado de "SQL". Desde que você não quer reinventar SQL, eu estou supondo que você só precisa de consulta de uma única tabela com um formato fixo.

Se este for o caso, você provavelmente não precisa implementar uma DSL (apesar de que é certamente um caminho a percorrer); pode ser mais fácil, se você está acostumado a Orientação a Objetos, para criar um objeto de filtro.

Mais especificamente, uma coleção "Filter" que iria realizar um ou mais objetos SelectionCriterion. Você pode implementar estes para herdar de uma ou mais classes de base que representam tipos de seleções (Range, LessThan, ExactMatch, como, etc.) Uma vez que estas classes de base estão no lugar, você pode criar versões específicas colunas herdadas que são apropriados para essa coluna . Finalmente, dependendo da complexidade das consultas que você deseja apoio, você vai querer implementar algum tipo de cola conjuntivo a alça AND e OR e NOT ligações entre os vários critérios.

Se você sentir como ele, você pode criar uma interface gráfica simples para carregar a coleção; Eu olhava para a filtragem no Excel como um modelo, se você não tem outra coisa em mente.

Finalmente, deve ser trivial para converter o conteúdo desta coleção para o SQL correspondente e passar isso para o banco de dados.

No entanto: se o que você está depois é a simplicidade, e seus usuários compreender SQL, você pode simplesmente pedir-lhes para digitar o conteúdo de uma cláusula WHERE, e programaticamente construir o resto da consulta. A partir de uma perspectiva de segurança, se o seu código tem o controle sobre as colunas selecionadas e cláusula FROM, e suas permissões de banco de dados são definidas corretamente, e você fazer alguns testes de sanidade na corda vindo os usuários, isso seria uma opção relativamente segura.

"implementar uma Domain Specific Language"

"ninguém vai querer instalar um servidor que baixa e executa arbitrário código Python em tempo de execução"

Eu quero um DSL, mas eu não quero Python ser que DSL. OK. Como você vai executar este DSL? O tempo de execução é aceitável se não Python?

E se eu tiver um programa C que passa a incorporar o interpretador Python? É aceitável?

E - se o Python não é um tempo de execução aceitável - por que é que isto tem a tag Python

Por que não criar uma linguagem que quando se "compila" que gera SQL ou qualquer linguagem de consulta seu armazenamento de dados requer?

Você estaria criando basicamente uma abstração sobre sua camada de persistência.

Você mencionou Python. Por que não usar Python? Se alguém pode "tipo em" uma expressão em seu DSL, eles podem digitar em Python.

Você vai precisar de algumas regras sobre a estrutura da expressão, mas isso é muito mais fácil do que implementar algo novo.

Você disse que ninguém vai querer instalar um servidor que baixa e executa arbitrário código em tempo de execução. No entanto, isso é exatamente o que seu DSL vai fazer (eventualmente) para que haja provavelmente não é que muita diferença. A menos que você está fazendo algo muito específico com os dados, então eu não acho que um DSL vai comprar-lhe muito e vai frustrar os usuários que já são versados ??em SQL. Não subestime o tamanho da tarefa que você estará assumindo.

Para responder a sua pergunta no entanto, você vai precisar para chegar a uma gramática para o seu idioma, algo para analisar o texto e percorrer a árvore, emitindo código ou chamar uma API que você escreveu (que é por isso que o meu comentário que você ainda vai ter que enviar algum código).

Há uma abundância de textos educativos sobre gramáticas para expressões matemáticas você pode consultar na net, que é bastante para a frente. Você pode ter uma ferramenta de gerador de analisador como ANTLR ou Yacc você pode usar para ajudar a gerar o analisador (ou use uma linguagem como Lisp / Scheme e casar os dois para cima). Chegando-se com uma gramática SQL razoável não será fácil. Mas o Google 'BNF SQL' e ver o que você venha com.

O melhor de sorte.

É realmente soa como SQL, mas talvez vale a pena tentar usar SQLite se você quiser mantê-lo simples?

Parece que você deseja criar uma gramática não uma DSL. Eu olhar para ANTLR que permitirá que você para criar um analisador específico que irá interpretar o texto e traduzir para comandos específicos. ANTLR fornece bibliotecas para Python, SQL, Java, C ++, C, C #, etc.

Além disso, aqui é um bom exemplo de um motor ANTLR cálculo criado em C #

Uma gramática livre de contexto geralmente tem uma árvore como a estrutura e programas funcionais têm uma árvore como estrutura também. Não tenho a pretensão da seguinte iria resolver todos os seus problemas, mas é um bom passo na direção se você tem certeza que você não quer usar algo como 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))

Como você se certificar de que você não está dando aos usuários a capacidade de executar código arbitrário. Este quadro admite todas as funções possíveis. Bem, você pode direito um invólucro sobre ele para a segurança que expõem uma lista fixa de objetos de função que são aceitáveis.

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)

Como escrever assert_composition_of_allowed_funcs. É muito difícil de fazer que em python, mas fácil em Lisp. Vamos supor que, onde está uma lista de funções a serem avaliados em um lábios como formato ou seja where=(operator.add, (operator.getitem, row, v1), 2) ou where=(operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3).

Isto torna possível escrever uma função apply_lisp que garante que o onde a função é feita apenas por ALLOWED_FUNCS ou constantes como 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:] ])

Além, você também precisa verificar se há tipos exatos, porque você não quer que seus tipos para ser substituído. Portanto, não use isinstance, uso type in (float, int, str). Oh nós ter executado em:

regra décimo de Greenspun de Programação: qualquer suficientemente complicado programa C ou Fortran contém um ad hoc, informalmente especificada implementação bug-ridden lento de metade do Lisp Comum.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top