Skip to content

Commit

Permalink
PostgreSQL-style placeholders
Browse files Browse the repository at this point in the history
  • Loading branch information
tlocke committed Sep 14, 2024
1 parent a8c9da9 commit 46c0002
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 7 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,21 @@ these additional parameters can be specified using the `startup_params` paramete

```

### PostgreSQL-Style Parameter Placeholders

```python
>>> import pg8000.native
>>>
>>> con = pg8000.native.Connection('postgres', password="cpsnow")
>>>
>>> con.run("SELECT $1", title='A Time Of Hope')
[['A Time Of Hope']]
>>>
>>> con.close()

```


## DB-API 2 Interactive Examples

These examples stick to the DB-API 2.0 standard.
Expand Down
35 changes: 28 additions & 7 deletions src/pg8000/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class State(Enum):
IN_PN = auto() # inside parameter name eg. :name
IN_CO = auto() # inside inline comment eg. --
IN_DQ = auto() # inside dollar-quoted string eg. $$...$$
IN_DP = auto() # inside dollar parameter eg. $1


def to_statement(query):
Expand Down Expand Up @@ -118,6 +119,9 @@ def to_statement(query):
output_query.append(c)
if prev_c == "$":
state = State.IN_DQ
elif next_c.isdigit():
state = State.IN_DP
placeholders.append("")
elif c == ":" and next_c not in ":=" and prev_c != ":":
state = State.IN_PN
placeholders.append("")
Expand Down Expand Up @@ -156,6 +160,19 @@ def to_statement(query):
except ValueError:
output_query.append(f"${len(placeholders)}")

elif state == State.IN_DP:
placeholders[-1] += c
output_query.append(c)
if next_c is None or not next_c.isdigit():
try:
placeholders[-1] = int(placeholders[-1]) - 1
except ValueError:
raise InterfaceError(
f"Expected an integer for the $ placeholder but found "
f"'{placeholders[-1]}'"
)
state = State.OUT

elif state == State.IN_CO:
output_query.append(c)
if c == "\n":
Expand All @@ -176,15 +193,19 @@ def to_statement(query):
)

def make_vals(args):
arg_list = [v for _, v in args.items()]
vals = []
for p in placeholders:
try:
vals.append(args[p])
except KeyError:
raise InterfaceError(
f"There's a placeholder '{p}' in the query, but no matching "
f"keyword argument."
)
if isinstance(p, int):
vals.append(arg_list[p])
else:
try:
vals.append(args[p])
except KeyError:
raise InterfaceError(
f"There's a placeholder '{p}' in the query, but no matching "
f"keyword argument."
)
return tuple(vals)

return "".join(output_query), make_vals
Expand Down
5 changes: 5 additions & 0 deletions test/native/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,8 @@ def test_max_parameters(con):
f"SELECT 1 WHERE 1 IN ({','.join([f':param_{i}' for i in range(SIZE)])})",
**kwargs,
)


def test_pg_placeholder_style(con):
rows = con.run("SELECT $1", title="A Time Of Hope")
assert rows[0] == ["A Time Of Hope"]

0 comments on commit 46c0002

Please sign in to comment.