Leggera estensione per la sintassi delle istruzioni preparate SQL. Ho bisogno di un consiglio

6

Nella libreria di astrazione del database sto estendendo la sintassi delle istruzioni preparate SQL per suggerire un parser con tipo letterale previsto. Lo considero un miglioramento molto essenziale, il mio ragionamento è possibile leggere qui . Se vuoi mettere in discussione l'idea stessa, per favore fammelo sapere nei commenti: vorrei iniziare un'altra domanda invece di mescolarla qui.

Il consiglio che voglio è sulla sintassi. Sono abbastanza ignorante di tutte le lingue e dialetti accanto a php e mysql, e potrebbe esserci già una soluzione per scopi generali di cui non sono a conoscenza. In caso contrario, desidero conoscere la tua opinione personale o il tuo suggerimento.

Al momento, solo i segnaposto posizionali supportati. Tuttavia mi è stato chiesto più volte di aggiungere il supporto per i nomi. Tuttavia, non posso decidere quale sintassi scegliere per quello.

Al momento contrassegno un punto interrogativo convenzionale con una singola lettera, cioè ?s - stringa, ?i - int, ?n - nome (identificatore) e così via.

Tuttavia per il segnaposto nominato (stile :name ) l'unica opzione ragionevole per me è

i:name

o, se decido di aggiungere altri modificatori dettagliati, anche

int:name

quindi, la query sarà simile a

SELECT * FROM ident:table WHERE id IN (int:id)

La domanda è: vedi qualche inconveniente per una tale sintassi? Hai un suggerimento per qualsiasi altro stile? Pensi che sia leggibile?

Un'altra domanda è sul ripetere le cose. Al momento ho due segnaposto per gli array - ?a per la lista di stringhe separate da virgole e ?u per le coppie ?n=?s . Mi infastidisce già da molto tempo, che una tale sintassi è superflua, ma insufficiente. Cosa succede se ho bisogno di un set di identificatori separati da virgola? O anche una dichiarazione complessa come l'intera clausola VALUES? Con una sorta di operatore di ripetizione, posso rendere la query SQL estremamente migliorata. Ecco alcuni esempi di ciò a cui sto pensando:

SELECT * FROM table WHERE id IN([?i])
SELECT [?n] FROM table
INSERT INTO table VALUES [(?i,?s,?s,?s)]
UPDATE table SET [?n=?s]

Come puoi vedere, utilizzo parentesi quadre per designare una ripetizione. Tuttavia, non sono ancora sicuro se dovrei usarli. La prima obiezione è che si scontreranno con la sintassi dell'identificatore del server SQL. Il prossimo è piuttosto filosofico: è un altro elemento di sintassi e voglio introdurre meno modifiche possibili alla sintassi.

Inoltre, questa sintassi richiederà una logica di analisi molto più complicata, che sto cercando di evitare anche io. Quindi - un'altra opzione è solo per estendere il set di tipi, sia con segnaposti distinti come ?k per l'elenco di numeri, ?o per l'elenco di identificatori, che con l'aggiunta di un singolo = modificatore di lettera al tipo esistente come ?as (o ?astr o astr:name ) è un array di stringhe, ?an e simili.

C'è un'altra opzione - per rilevare automaticamente le ripetizioni. Se abbiamo uno scalare, formattalo come un singolo valore. Se arriva la matrice, formatta come un elenco separato da virgole. Tuttavia, molte persone ritengono questo approccio inaccettabile, in quanto può portare a risultati imprevedibili e query errate (comunque comunque sicure). Sono ancora in dubbio se posso scegliere in questo modo.

    
posta Your Common Sense 18.03.2014 - 13:39
fonte

2 risposte

3

La sintassi deve coprire tre aspetti:

  1. Un modo per specificare il tipo di segnaposto
  2. Un modo per specificare l'origine segnaposto
  3. Un modo per gestire gli array

Hai già specificato le specifiche del tipo con la sintassi ?type - manterrei questo come è e combinalo con gli elementi per gestire gli altri due.

Anche la gestione dell'array utilizzando [...] (con ... come un'altra espressione segnaposto) sembra ragionevole.

La parte fondamentale è il modo in cui si specifica l'origine segnaposto. Potrebbe non essere immediatamente ovvio osservare gli esempi di codice, ma in realtà ci sono un numero piuttosto elevato di fonti di segnaposto diverse. Passiamo attraverso alcuni esempi:

  • SELECT xyz FROM table WHERE name = ?s AND age > ?i

    Supponendo che $params sia la serie di parametri di livello superiore, il ?s segnaposto prende $params[0] e il ?i segnaposto prende $params[1] . Di conseguenza, il comportamento predefinito che si desidera è che la sorgente del parametro sia una chiave a incremento zero .

  • INSERT INTO table VALUES [(?i,?s,?s,?s)]

    Qui la matrice per [(?i, ?s, ?s, ?s)] è presumibilmente in $params[0] e se iterata via foreach ($params[0] as $key => $value) , i segnaposti interni saranno popolati da $value[0] a $value[3] . Questo è coerente con l'esempio precedente.

  • SELECT * FROM table WHERE id IN([?i])

    Qui abbiamo un problema: presumibilmente l'array $params[0] dovrebbe essere del formato [$id1, $id2, $id3, ...] piuttosto che [[$id1], [$id2], [$id3], ...] . Quindi in questo caso vogliamo che ?i sia $value stesso, piuttosto che $value[0] (come implica l'approccio di auto-incremento)

  • UPDATE table SET [?n=?s]

    Qui questo problema diventa ancora più chiaro. In questo caso è probabile che il formato dell'array sia [$name1 => $value1, $name2 => $value2, ...] anziché [[$name1, $value1], [$name2, $value2], ...] . Pertanto, il segnaposto ?n deve eseguire il binding a $key e ?s a $value .

  • INSERT INTO table VALUES [(:id, :name)]

    Ovviamente ora puoi fare la stessa cosa usando le chiavi con nome. Qui :id lega $value['id'] e :name lega $value['name'] .

  • SELECT :cols FROM table WHERE id IN(:ids)

    E gli array potrebbero anche essere presi da parametri con nome. Anche questo dovrà essere gestito in qualche modo.

Questa è una panoramica approssimativa di ciò che è necessario essere in grado di gestire per supportare pienamente gli array.

Ecco una possibile sintassi che copre tutte le possibilità. Di seguito userò $key e $value per denominare la coppia chiave / valore corrente. Per il% di primo livello,$value conterrà i segnaposti di primo livello e $key non è definito.

?type      -- source is implicitly $value[$i] with $i being an auto-incrementing,
           -- zero-based integer. I.e. $value[0], $value[1], $value[2], ...
:name?type -- source is $value['name']
v?type     -- source is $value itself
k?type     -- source is $key itself

[...]      -- source for array is $value[$i], again with auto-incrementing $i
:name[...] -- source is $value['name']
v[...]     -- source is $value itself
k[....]    -- source is $key itself

Ecco come sarebbero gli esempi usando questa sintassi:

  • SELECT xyz FROM table WHERE name = ?s AND age > ?i (lo stesso)
  • INSERT INTO table VALUES [(?i,?s,?s,?s)] (lo stesso)
  • SELECT * FROM table WHERE id IN([v?i]) (è necessario specificare che vogliamo il valore)
  • UPDATE table SET [k?n = v?s] (è necessario specificare che vogliamo la chiave / il valore)
  • INSERT INTO table VALUES [(:id?i, :name?s)] (tipi aggiunti)
  • SELECT :cols[v?n] FROM table WHERE id IN(:ids[v?i]) (aggiunta specifica dell'array)

Una cosa che non è coperta da questi esempi è la sintassi v[...] . Un possibile caso d'uso è quello di indirizzare tutti i valori segnaposto di livello superiore come una matrice:

query('INSERT INTO table VALUES (v[?s])', 'v1', 'v2', 'v3');
// vs
query('INSERT INTO table VALUES ([?s])', array('v1', 'v2', 'v3'));

La sintassi k[...] è inclusa solo per coerenza - non ha alcun uso pratico e non è nemmeno possibile per gli array (solo PHP e gt; = 5.5 gli iteratori possono avere array come chiavi).

Ora questa sintassi ti fornisce tutti gli elementi necessari per gestire completamente gli array: i dettagli (come caratteri e ordine usati) possono essere migliorati.

A questo punto si può cominciare a chiedersi se la completa gestione dell'array sia davvero necessaria - per questo sono necessari molti elementi sintattici. Forse un semplice ?ai per le clausole IN lo farà?

Penso che abbia senso includere questo tipo di sintassi, nonostante la complessità aggiunta. Non aggiunge overhead sintattico quando non viene utilizzato (è necessaria solo la parte ?type ) e si ha la funzionalità quando ne hai bisogno. Nella mia esperienza, le vulnerabilità di SQL injection (nonostante il numero totale di noob) sono di solito introdotte durante la generazione di query complesse, poiché la generazione di query ti pone al di fuori della consueta cornice segnaposto. Il modo migliore per evitare ciò è assicurarsi che sia possibile anche creare query più complesse senza lasciare il framework.

    
risposta data 25.03.2014 - 23:06
fonte
2

Suggerisco di iniziare tutti i parametri con ? . I vantaggi di questo sono:

  • Estende un paradigma familiare già esistente.
  • Non ci saranno conflitti di sintassi o ambiguità.
  • C'è un chiaro indicatore visivo di cosa sono i segnaposto.

Quindi gli identificativi dei nomi sarebbero:

SELECT * FROM ?ident:table WHERE id IN (?int:id)

Se desideri implementare segnaposti più complessi, ti suggerisco di iniziare definendo un costrutto generale come ?{...} . Quindi puoi utilizzare tutto ciò che è più semplice e chiaro al suo interno, senza temere il contrasto con SQL. E, ancora, c'è un indicatore visivo molto chiaro nella query su cosa sia SQL e cosa sia un parametro.

insert into table values ?{array:(?i,?s,?s,?s)}
update table set ?{array:(foo=?i,bar=?s)}

Il trucco in questo intero progetto sta implementando il giusto livello di complessità, in modo tale da aiutare piuttosto che rendere le cose più complicate. Penso che sia un'idea con potenziale, però.

    
risposta data 25.03.2014 - 10:30
fonte

Leggi altre domande sui tag