Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit 12d99e5

Browse files
bors[bot]honzasp
andauthored
Merge #260
260: Add support for named args to the Hrana protocol r=honzasp a=honzasp Co-authored-by: Jan Špaček <[email protected]>
2 parents 950cede + e0b221c commit 12d99e5

File tree

3 files changed

+54
-14
lines changed

3 files changed

+54
-14
lines changed

docs/HRANA_SPEC.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,32 @@ The server responds with the result of the statement.
281281
```
282282
type Stmt = {
283283
"sql": string,
284-
"args": Array<Value>,
284+
"args"?: Array<Value>,
285+
"named_args"?: Array<NamedArg>,
285286
"want_rows": boolean,
286287
}
288+
289+
type NamedArg = {
290+
"name": string,
291+
"value": Value,
292+
}
287293
```
288294

289-
A statement contains the SQL text in `sql` and an array of arguments in
290-
`args`. The arguments are bound to positional parameters in the SQL statement
291-
(such as `$NNN` in Postgres or `?NNN` in SQLite). There is currently no way to
292-
bind named parameters by name (such as `:XXX` in SQLite), the protocol might be
293-
extended with this feature in the future.
295+
A statement contains the SQL text in `sql` and arguments.
296+
297+
The arguments in `args` are bound to positional parameters in the SQL statement
298+
(such as `$NNN` in Postgres or `?NNN` in SQLite). The arguments in `named_args`
299+
are bound to named arguments, such as `:AAAA`, `@AAAA` and `$AAAA` in SQLite.
300+
301+
For SQLite, the names of arguments include the prefix sign (`:`, `@` or `$`). If
302+
the name of the argument does not start with this prefix, the server will try to
303+
guess the correct prefix. If an argument is specified both as a positional
304+
argument and as a named argument, the named argument should take precedence.
305+
306+
It is an error if the request specifies an argument that is not expected by the
307+
SQL statement, or if the request does not specify and argument that is expected
308+
by the SQL statement. Some servers may not support specifying both positional
309+
and named arguments.
294310

295311
The `want_rows` field specifies whether the client is interested in the rows
296312
produced by the SQL statement. If it is set to `false`, the server should always

sqld/src/hrana/proto.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,19 @@ pub struct ExecuteResp {
6565
#[derive(Deserialize, Debug)]
6666
pub struct Stmt {
6767
pub sql: String,
68+
#[serde(default)]
6869
pub args: Vec<Value>,
70+
#[serde(default)]
71+
pub named_args: Vec<NamedArg>,
6972
pub want_rows: bool,
7073
}
7174

75+
#[derive(Deserialize, Debug)]
76+
pub struct NamedArg {
77+
pub name: String,
78+
pub value: Value,
79+
}
80+
7281
#[derive(Serialize, Debug)]
7382
pub struct StmtResult {
7483
pub cols: Vec<Col>,

sqld/src/hrana/session.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ pub enum ResponseError {
6363
#[error("SQL string contains more than one statement")]
6464
SqlManyStmts,
6565
#[error("Arguments do not match SQL parameters: {source}")]
66-
InvalidArgs { source: anyhow::Error },
66+
ArgsInvalid { source: anyhow::Error },
67+
#[error("Specifying both positional and named arguments is not supported")]
68+
ArgsBothPositionalAndNamed,
69+
6770
#[error("Transaction timed out")]
6871
TransactionTimeout,
6972
#[error("Server cannot handle additional transactions")]
@@ -206,12 +209,24 @@ fn proto_stmt_to_query(proto_stmt: proto::Stmt) -> Result<Query> {
206209
bail!(ResponseError::SqlManyStmts)
207210
}
208211

209-
let params = proto_stmt
210-
.args
211-
.into_iter()
212-
.map(proto_value_to_value)
213-
.collect();
214-
let params = Params::Positional(params);
212+
let params = if proto_stmt.named_args.is_empty() {
213+
let values = proto_stmt
214+
.args
215+
.into_iter()
216+
.map(proto_value_to_value)
217+
.collect();
218+
Params::Positional(values)
219+
} else if proto_stmt.args.is_empty() {
220+
let values = proto_stmt
221+
.named_args
222+
.into_iter()
223+
.map(|arg| (arg.name, proto_value_to_value(arg.value)))
224+
.collect();
225+
Params::Named(values)
226+
} else {
227+
bail!(ResponseError::ArgsBothPositionalAndNamed)
228+
};
229+
215230
Ok(Query { stmt, params })
216231
}
217232

@@ -258,7 +273,7 @@ fn proto_value_from_value(value: Value) -> proto::Value {
258273

259274
fn proto_response_error_from_error(error: Error) -> Result<ResponseError, Error> {
260275
Ok(match error {
261-
Error::LibSqlInvalidQueryParams(source) => ResponseError::InvalidArgs { source },
276+
Error::LibSqlInvalidQueryParams(source) => ResponseError::ArgsInvalid { source },
262277
Error::LibSqlTxTimeout(_) => ResponseError::TransactionTimeout,
263278
Error::LibSqlTxBusy => ResponseError::TransactionBusy,
264279
Error::RusqliteError(rusqlite_error) => match rusqlite_error {

0 commit comments

Comments
 (0)