diff --git a/README.md b/README.md index c762f79..9270b31 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/pg8000/native.py b/src/pg8000/native.py index 76d7370..75967b3 100644 --- a/src/pg8000/native.py +++ b/src/pg8000/native.py @@ -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): @@ -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("") @@ -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": @@ -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 diff --git a/test/native/test_query.py b/test/native/test_query.py index afe56c8..84e2560 100644 --- a/test/native/test_query.py +++ b/test/native/test_query.py @@ -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"]