Skip to content

Commit

Permalink
Merge branch 'release/1.0.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
s3rius committed Aug 23, 2023
2 parents efedb97 + eae594a commit b0198a4
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scyllapy"
version = "1.0.6"
version = "1.0.7"
edition = "2021"

[lib]
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ async def insert(scylla: Scylla):
)
```

Important note: All variables should be in snake_case.
Otherwise the error may be raised or parameter may not be placed in query correctly.
This happens, because scylla makes all parameters in query lowercase.

The scyllapy makes all parameters lowercase, but you may run into problems,
if you use multiple parameters that differ only in cases of some letters.


## Preparing queries

Also, queries can be prepared. You can either prepare raw strings, or `Query` objects.
Expand Down
13 changes: 13 additions & 0 deletions python/scyllapy/_internal/extra_types.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ class Double:

class Counter:
def __init__(self, val: int) -> None: ...

class Unset:
"""
Class for unsetting the variable.
If you want to set NULL to a column,
when performing INSERT statements,
it's better to use Unset instead of setting
NULL, because it may result in better performance.
https://rust-driver.docs.scylladb.com/stable/queries/values.html#unset-values
"""
def __init__(self) -> None: ...
24 changes: 22 additions & 2 deletions python/tests/test_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def test_collections(


@pytest.mark.anyio
async def test_named_parameters(scylla: Scylla):
async def test_named_parameters(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(
f"CREATE TABLE {table_name} (id INT, name TEXT, age INT, PRIMARY KEY (id))"
Expand All @@ -100,6 +100,7 @@ async def test_named_parameters(scylla: Scylla):
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert res.first() == to_insert


@pytest.mark.anyio
async def test_timestamps(scylla: Scylla) -> None:
table_name = random_string(4)
Expand All @@ -114,4 +115,23 @@ async def test_timestamps(scylla: Scylla) -> None:
await scylla.execute(insert_query, [1, now])

res = await scylla.execute(f"SELECT time FROM {table_name}")
assert res.scalar() == now
assert res.scalar() == now


@pytest.mark.anyio
async def test_none_vals(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, None])
results = await scylla.execute(f"SELECT * FROM {table_name}")
assert results.first() == {"id": 1, "name": None}


@pytest.mark.anyio
async def test_cases(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (:Id, :NaMe)",
{"Id": 1, "NaMe": 2},
)
10 changes: 10 additions & 0 deletions python/tests/test_extra_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,13 @@ async def test_counter(scylla: Scylla) -> None:
rows = res.all()
assert len(rows) == 1
assert rows[0] == {"id": 1, "count": 1}


@pytest.mark.anyio
async def test_unset(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")

await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, extra_types.Unset()]
)
28 changes: 28 additions & 0 deletions python/tests/test_queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from dataclasses import dataclass
import pytest
from scyllapy import Scylla
from tests.utils import random_string


@pytest.mark.anyio
async def test_empty_scalars(scylla: Scylla):
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY)")
res = await scylla.execute(f"SELECT id FROM {table_name}")

assert res.all() == []
assert res.scalars() == []


@pytest.mark.anyio
async def test_as_class(scylla: Scylla):
@dataclass
class TestDTO:
id: int

table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY)")
await scylla.execute(f"INSERT INTO {table_name}(id) VALUES (?)", [42])
res = await scylla.execute(f"SELECT id FROM {table_name}")

assert res.all(as_class=TestDTO) == [TestDTO(id=42)]
14 changes: 14 additions & 0 deletions src/extra_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ simple_wrapper!(BigInt, i64);
simple_wrapper!(Double, f64);
simple_wrapper!(Counter, i64);

#[pyclass(name = "Unset")]
#[derive(Clone, Copy)]
pub struct ScyllaPyUnset {}

#[pymethods]
impl ScyllaPyUnset {
#[new]
#[must_use]
pub fn py_new() -> Self {
Self {}
}
}

/// Create new module for extra types.
///
/// # Errors
Expand All @@ -50,5 +63,6 @@ pub fn add_module<'a>(py: Python<'a>, name: &'static str) -> PyResult<&'a PyModu
module.add_class::<BigInt>()?;
module.add_class::<Double>()?;
module.add_class::<Counter>()?;
module.add_class::<ScyllaPyUnset>()?;
Ok(module)
}
2 changes: 1 addition & 1 deletion src/query_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl ScyllaPyQueryResult {
return Err(anyhow::anyhow!("The query doesn't have returns ."));
};
if rows.is_empty() {
return Ok(None);
return Ok(Some(rows.to_object(py)));
}
let Some(col_name) = self.inner.col_specs.first() else{
return Err(anyhow::anyhow!("Cannot find any columns"));
Expand Down
19 changes: 14 additions & 5 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use scylla::frame::{

use std::net::IpAddr;

use crate::extra_types::{BigInt, Counter, Double, SmallInt, TinyInt};
use crate::extra_types::{BigInt, Counter, Double, ScyllaPyUnset, SmallInt, TinyInt};

/// Small function to integrate anyhow result
/// and `pyo3_asyncio`.
Expand Down Expand Up @@ -40,8 +40,10 @@ where
/// This enum implements Value interface,
/// and any of it's variants can
/// be bound to query.
#[derive(Clone, Hash, PartialEq, Eq)]
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub enum ScyllaPyCQLDTO {
Null,
Unset,
String(String),
BigInt(i64),
Int(i32),
Expand Down Expand Up @@ -87,6 +89,8 @@ impl Value for ScyllaPyCQLDTO {
ScyllaPyCQLDTO::Timestamp(timestamp) => {
scylla::frame::value::Timestamp(*timestamp).serialize(buf)
}
ScyllaPyCQLDTO::Null => Option::<i16>::None.serialize(buf),
ScyllaPyCQLDTO::Unset => scylla::frame::value::Unset.serialize(buf),
}
}
}
Expand All @@ -102,8 +106,12 @@ impl Value for ScyllaPyCQLDTO {
/// May raise an error, if
/// value cannot be converted or unnown type was passed.
pub fn py_to_value(item: &PyAny) -> anyhow::Result<ScyllaPyCQLDTO> {
if item.is_instance_of::<PyString>() {
if item.is_none() {
Ok(ScyllaPyCQLDTO::Null)
} else if item.is_instance_of::<PyString>() {
Ok(ScyllaPyCQLDTO::String(item.extract::<String>()?))
} else if item.is_instance_of::<ScyllaPyUnset>() {
Ok(ScyllaPyCQLDTO::Unset)
} else if item.is_instance_of::<PyBool>() {
Ok(ScyllaPyCQLDTO::Bool(item.extract::<bool>()?))
} else if item.is_instance_of::<PyInt>() {
Expand Down Expand Up @@ -424,14 +432,15 @@ pub fn parse_python_query_params(
if params.is_instance_of::<PyList>() || params.is_instance_of::<PyTuple>() {
let params = params.extract::<Vec<&PyAny>>()?;
for param in params {
values.add_value(&py_to_value(param)?)?;
let py_dto = py_to_value(param)?;
values.add_value(&py_dto)?;
}
return Ok(values);
} else if params.is_instance_of::<PyDict>() {
if allow_dicts {
let dict = params.extract::<HashMap<&str, &PyAny>>()?;
for (name, value) in dict {
values.add_named_value(name, &py_to_value(value)?)?;
values.add_named_value(name.to_lowercase().as_str(), &py_to_value(value)?)?;
}
return Ok(values);
}
Expand Down

0 comments on commit b0198a4

Please sign in to comment.