Skip to content

Commit 36e8a1d

Browse files
authored
Merge pull request #16 from bmsuisse/executesql
Bug Fixes and Testing
2 parents f16fc8f + f3c4de9 commit 36e8a1d

22 files changed

+455
-101
lines changed

.github/workflows/python-test.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Python Test
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths-ignore: ["README.md", "docs", ".github"]
7+
pull_request:
8+
branches: ["main"]
9+
paths-ignore: ["README.md", "docs", ".github"]
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-22.04
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
python-version: ["3.11"]
18+
19+
steps:
20+
- uses: actions/checkout@v3
21+
- name: Set up Python ${{ matrix.python-version }}
22+
uses: actions/setup-python@v3
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
26+
- name: Setup Rust
27+
uses: actions-rust-lang/setup-rust-toolchain@v1
28+
- name: Install tooling dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
pip install maturin
32+
- name: Install Dependencies
33+
run: |
34+
pip install pytest polars pyarrow pytest-asyncio pyright python-dotenv docker pyright cffi
35+
- name: Install Project
36+
run: maturin develop
37+
- name: pytest
38+
shell: bash
39+
run: pytest
40+
- name: Pyright
41+
run: poetry run pyright .

lakeapi2sql/sql_connection.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
import lakeapi2sql._lowlevel as lvd
22
from lakeapi2sql.utils import prepare_connection_string
3+
from typing import TypedDict
4+
5+
6+
class TdsColumn(TypedDict):
7+
name: str
8+
column_type: str
9+
10+
11+
class TdsResult(TypedDict):
12+
columns: list[TdsColumn]
13+
rows: list[dict]
314

415

516
class TdsConnection:
617
def __init__(self, connection_string: str, aad_token: str | None = None) -> None:
7-
connection_string, aad_token = await prepare_connection_string(connection_string, aad_token)
818
self._connection_string = connection_string
919
self._aad_token = aad_token
1020

1121
async def __aenter__(self) -> "TdsConnection":
12-
self._connection = await lvd.connect_sql(self.connection_string, self.aad_token)
22+
connection_string, aad_token = await prepare_connection_string(self._connection_string, self._aad_token)
23+
24+
self._connection = await lvd.connect_sql(connection_string, aad_token)
1325
return self
1426

15-
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
27+
async def __aexit__(self, *args, **kwargs) -> None:
1628
pass
1729

18-
async def execute_sql(self, sql: str, arguments: list[str | int | float | bool | None]) -> list[int]:
19-
return await lvd.execute_sql(self._connection, sql, arguments)
30+
async def execute_sql(self, sql: str, arguments: list[str | int | float | bool | None] = None) -> list[int]:
31+
return await lvd.execute_sql(self._connection, sql, arguments or [])
2032

21-
async def execute_sql_with_result(self, sql: str, arguments: list[str | int | float | bool | None]) -> list[int]:
22-
return await lvd.execute_sql_with_result(self._connection, sql, arguments)
33+
async def execute_sql_with_result(
34+
self, sql: str, arguments: list[str | int | float | bool | None] = None
35+
) -> TdsResult:
36+
return await lvd.execute_sql_with_result(self._connection, sql, arguments or [])

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "maturin"
55
[project]
66
name = "lakeapi2sql"
77
requires-python = ">=3.10"
8-
version = "0.9.0"
8+
version = "0.9.1"
99
classifiers = [
1010
"Programming Language :: Rust",
1111
"Programming Language :: Python :: Implementation :: CPython",

src/lib.rs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use error::LakeApi2SqlError;
88
use futures::{StreamExt, TryStreamExt};
99
use pyo3::exceptions::{PyConnectionError, PyIOError, PyTypeError};
1010
use pyo3::prelude::*;
11-
use pyo3::types::{PyDict, PyInt, PyList, PyString};
11+
use pyo3::types::{PyDict, PyInt, PyList, PyString, PyTuple};
1212
mod arrow_convert;
1313
pub mod bulk_insert;
1414
pub mod connect;
@@ -63,7 +63,7 @@ fn into_dict_result<'a>(py: Python<'a>, meta: Option<ResultMetadata>, rows: Vec<
6363
let mut py_rows = PyList::new(
6464
py,
6565
rows.iter().map(|row| {
66-
PyList::new(
66+
PyTuple::new(
6767
py,
6868
row.cells()
6969
.map(|(c, val)| match val {
@@ -244,7 +244,7 @@ impl ToSql for ValueWrap {
244244

245245
fn to_exec_args(args: Vec<&PyAny>) -> Result<Vec<ValueWrap>, PyErr> {
246246
let mut res: Vec<ValueWrap> = Vec::new();
247-
for i in 0..args.len() - 1 {
247+
for i in 0..args.len() {
248248
let x = args[i];
249249
res.push(ValueWrap(if x.is_none() {
250250
Box::new(Option::<i64>::None) as Box<dyn ToSql>
@@ -280,27 +280,50 @@ fn execute_sql<'a>(
280280
list2
281281
});
282282
}
283+
let nr_args = args.len();
283284
let tds_args = to_exec_args(args)?;
284285

285286
let mutex = conn.0.clone();
286287
pyo3_asyncio::tokio::future_into_py(py, async move {
287-
let res = mutex
288-
.clone()
289-
.lock()
290-
.await
291-
.execute(
292-
query,
293-
tds_args
294-
.iter()
295-
.map(|x| x.0.borrow() as &dyn ToSql)
296-
.collect::<Vec<&dyn ToSql>>()
297-
.as_slice(),
298-
)
299-
.await;
288+
let res = if nr_args > 0 {
289+
mutex
290+
.clone()
291+
.lock()
292+
.await
293+
.execute(
294+
query,
295+
tds_args
296+
.iter()
297+
.map(|x| x.0.borrow() as &dyn ToSql)
298+
.collect::<Vec<&dyn ToSql>>()
299+
.as_slice(),
300+
)
301+
.await
302+
.map(|x| x.rows_affected().to_owned())
303+
} else {
304+
let arc = mutex.clone();
305+
let lock = arc.lock();
306+
let mut conn = lock.await;
307+
let res = conn.simple_query(query).await;
308+
match res {
309+
Ok(mut stream) => {
310+
let mut row_count: u64 = 0;
311+
while let Some(item) = stream.try_next().await.map_err(|er| {
312+
PyErr::new::<PyIOError, _>(format!("Error executing: {er}"))
313+
})? {
314+
if let QueryItem::Row(_) = item {
315+
row_count += 1;
316+
}
317+
}
318+
Ok(vec![row_count])
319+
}
320+
Err(a) => Err(a),
321+
}
322+
};
300323

301324
match res {
302325
Ok(re) => {
303-
return Ok(into_list(re.rows_affected()));
326+
return Ok(into_list(&re));
304327
}
305328
Err(er) => Err(PyErr::new::<PyIOError, _>(format!("Error executing: {er}"))),
306329
}

test/test_insert.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

test/test_insert_reader.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

test_server/__init__.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from pathlib import Path
2+
import docker
3+
from docker.models.containers import Container
4+
from time import sleep
5+
from typing import cast
6+
import docker.errors
7+
import os
8+
9+
10+
def _getenvs():
11+
envs = dict()
12+
with open("test_server/sql_docker.env", "r") as f:
13+
lines = f.readlines()
14+
envs = {
15+
item[0].strip(): item[1].strip()
16+
for item in [line.split("=") for line in lines if len(line.strip()) > 0 and not line.startswith("#")]
17+
}
18+
return envs
19+
20+
21+
def start_mssql_server() -> Container:
22+
client = docker.from_env() # code taken from https://github.com/fsspec/adlfs/blob/main/adlfs/tests/conftest.py#L72
23+
sql_server: Container | None = None
24+
try:
25+
m = cast(Container, client.containers.get("test4sql_lakeapi2sql"))
26+
if m.status == "running":
27+
return m
28+
else:
29+
sql_server = m
30+
except docker.errors.NotFound:
31+
pass
32+
33+
envs = _getenvs()
34+
35+
if sql_server is None:
36+
# using podman: podman run --env-file=TESTS/SQL_DOCKER.ENV --publish=1439:1433 --name=mssql1 chriseaton/adventureworks:light
37+
# podman kill mssql1
38+
sql_server = client.containers.run(
39+
"mcr.microsoft.com/mssql/server:2022-latest",
40+
environment=envs,
41+
detach=True,
42+
name="test4sql_lakeapi2sql",
43+
ports={"1433/tcp": "1444"},
44+
) # type: ignore
45+
assert sql_server is not None
46+
sql_server.start()
47+
print(sql_server.status)
48+
sleep(15)
49+
print("Successfully created sql container...")
50+
return sql_server

test_server/sql_docker.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SA_PASSWORD=MyPass@word4tests
2+
ACCEPT_EULA=Y
3+
MSSQL_PID=Express
4+
MSSQL_SA_PASSWORD=MyPass@word4tests
File renamed without changes.

0 commit comments

Comments
 (0)