Skip to content

Commit

Permalink
Merge pull request #47 from nackjicholson/select-value-op
Browse files Browse the repository at this point in the history
add select_value operation
  • Loading branch information
nackjicholson committed Sep 27, 2020
2 parents 3b883d5 + b9ec461 commit b6f9b5b
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 0 deletions.
6 changes: 6 additions & 0 deletions aiosql/adapters/aiosqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ async def select_one(conn, _query_name, sql, parameters, record_class=None):
result = record_class(**dict(zip(column_names, result)))
return result

@staticmethod
async def select_value(conn, _query_name, sql, parameters):
async with conn.execute(sql, parameters) as cur:
result = await cur.fetchone()
return result[0] if result else None

@staticmethod
@aiocontextmanager
async def select_cursor(conn, _query_name, sql, parameters):
Expand Down
5 changes: 5 additions & 0 deletions aiosql/adapters/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ async def select_one(self, conn, query_name, sql, parameters, record_class=None)
result = record_class(**dict(result))
return result

async def select_value(self, conn, query_name, sql, parameters):
parameters = self.maybe_order_params(query_name, parameters)
async with MaybeAcquire(conn) as connection:
return await connection.fetchval(sql, *parameters)

@aiocontextmanager
async def select_cursor(self, conn, query_name, sql, parameters):
parameters = self.maybe_order_params(query_name, parameters)
Expand Down
7 changes: 7 additions & 0 deletions aiosql/adapters/psycopg2.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ def select_one(conn, _query_name, sql, parameters, record_class=None):
result = record_class(**dict(zip(column_names, result)))
return result

@staticmethod
def select_value(conn, _query_name, sql, parameters):
with conn.cursor() as cur:
cur.execute(sql, parameters)
result = cur.fetchone()
return result[0] if result else None

@staticmethod
@contextmanager
def select_cursor(conn, _query_name, sql, parameters):
Expand Down
8 changes: 8 additions & 0 deletions aiosql/adapters/sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ def select_one(conn, _query_name, sql, parameters, record_class=None):
cur.close()
return result

@staticmethod
def select_value(conn, _query_name, sql, parameters):
cur = conn.cursor()
cur.execute(sql, parameters)
result = cur.fetchone()
cur.close()
return result[0] if result else None

@staticmethod
@contextmanager
def select_cursor(conn, _query_name, sql, parameters):
Expand Down
5 changes: 5 additions & 0 deletions aiosql/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def fn(self: Queries, conn, *args, **kwargs):
conn, query_name, sql, _params(args, kwargs), record_class
)

elif operation_type == SQLOperationType.SELECT_VALUE:

def fn(self: Queries, conn, *args, **kwargs):
return self.driver_adapter.select_value(conn, query_name, sql, _params(args, kwargs))

else:
raise ValueError(f"Unknown operation_type: {operation_type}")

Expand Down
3 changes: 3 additions & 0 deletions aiosql/query_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def _make_query_datum(self, query_str: str):
elif query_name.endswith("^"):
operation_type = SQLOperationType.SELECT_ONE
query_name = query_name[:-1]
elif query_name.endswith("$"):
operation_type = SQLOperationType.SELECT_VALUE
query_name = query_name[:-1]
else:
operation_type = SQLOperationType.SELECT

Expand Down
11 changes: 11 additions & 0 deletions aiosql/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class SQLOperationType(Enum):
SCRIPT = 3
SELECT = 4
SELECT_ONE = 5
SELECT_VALUE = 6


class QueryDatum(NamedTuple):
Expand Down Expand Up @@ -63,6 +64,11 @@ def select_one(
) -> Optional[Any]:
...

def select_value(
self, conn: Any, query_name: str, sql: str, parameters: Union[List, Dict],
) -> Optional[Any]:
...

def select_cursor(
self, conn: Any, query_name: str, sql: str, parameters: Union[List, Dict]
) -> ContextManager[Any]:
Expand Down Expand Up @@ -113,6 +119,11 @@ async def select_one(
) -> Optional[Any]:
...

async def select_value(
self, conn: Any, query_name: str, sql: str, parameters: Union[List, Dict],
) -> Optional[Any]:
...

async def select_cursor(
self, conn: Any, query_name: str, sql: str, parameters: Union[List, Dict]
) -> AsyncContextManager[Any]:
Expand Down
16 changes: 16 additions & 0 deletions docs/defining-sql-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ queries.get_user_by_username(conn, username="willvaughn")
# => (1, "willvaughn", "William Vaughn") or None
```

### `$` Select Value

The `$` operator will execute the query, and only return the first value of the first row of a result set. If there are no rows in the result set it returns `None`. This is implemented by returing the first element of the tuple returned by `cur.fetchone()` of the underlying driver. This is mostly useful for queries returning IDs, COUNTs or other aggregates.

```sql
-- name: get-count$
select count(*) from users
```

When used from Python:

```python
queries.get_count(conn)
# => 3
```

### `!` Insert/Update/Delete

The `!` operator executes SQL without returning any results. It is meant for statements that use `insert`, `update`, and `delete` to make modifications to database rows without a necessary return value.
Expand Down
6 changes: 6 additions & 0 deletions tests/blogdb/sql/users/users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ select userid,
where lastname = :lastname
order by username asc;


-- name: get-all-sorted
-- Get all user records sorted by username
select * from users order by username asc;


-- name: get-count$
-- Get number of users
select count(*) from users;
9 changes: 9 additions & 0 deletions tests/test_aiosqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ async def test_select_one(sqlite3_db_path, queries):
assert actual == expected


@pytest.mark.asyncio
async def test_select_value(sqlite3_db_path, queries):
async with aiosqlite.connect(sqlite3_db_path) as conn:
actual = await queries.users.get_count(conn)

expected = 3
assert actual == expected


@pytest.mark.asyncio
async def test_insert_returning(sqlite3_db_path, queries):
async with aiosqlite.connect(sqlite3_db_path) as conn:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ async def test_select_one(pg_dsn, queries):
assert actual == expected


@pytest.mark.asyncio
async def test_select_value(pg_dsn, queries):
conn = await asyncpg.connect(pg_dsn)
actual = await queries.users.get_count(conn)
expected = 3
assert actual == expected


@pytest.mark.asyncio
async def test_insert_returning(pg_dsn, queries):
async with asyncpg.create_pool(pg_dsn) as pool:
Expand Down
6 changes: 6 additions & 0 deletions tests/test_psycopg2.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def test_select_one(pg_conn, queries):
assert actual == expected


def test_select_value(pg_conn, queries):
actual = queries.users.get_count(pg_conn)
expected = 3
assert actual == expected


def test_insert_returning(pg_conn, queries):
with pg_conn:
blogid, title = queries.blogs.pg_publish_blog(
Expand Down
6 changes: 6 additions & 0 deletions tests/test_sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ def test_select_one(sqlite3_conn, queries):
assert actual == expected


def test_select_value(sqlite3_conn, queries):
actual = queries.users.get_count(sqlite3_conn)
expected = 3
assert actual == expected


def test_insert_returning(sqlite3_conn, queries):
with sqlite3_conn:
blogid = queries.blogs.publish_blog(
Expand Down

0 comments on commit b6f9b5b

Please sign in to comment.