Skip to content

Commit

Permalink
Merge pull request #47 from Intreecom/feature/timedelta
Browse files Browse the repository at this point in the history
Added duration + varint support.
  • Loading branch information
s3rius authored Mar 30, 2024
2 parents ef36c04 + e5cdceb commit 76f7c08
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 9 deletions.
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ repos:
always_run: true
args: ["python"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
rev: v1.9.0
hooks:
- id: mypy
name: python mypy
always_run: true
additional_dependencies:
- "types-python-dateutil"
pass_filenames: false
args: ["python"]
- repo: https://github.com/astral-sh/ruff-pre-commit
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,35 @@ new_query = query.with_consistency(Consistency.ALL)

All `with_` methods create new query, copying all other parameters.

Here's the list of scylla types and corresponding python types that you should use while passing parameters to queries:

| Scylla type | Python type |
| ----------- | ---------------------- |
| int | int |
| tinyint | extra_types.TinyInt |
| bigint | extra_types.BigInt |
| varint | any int type |
| float | float |
| double | extra_types.Double |
| decimal | decimal.Decimal |
| ascii | str |
| text | str |
| varchar | str |
| blob | bytes |
| boolean | bool |
| counter | extra_types.Counter |
| date | datetime.date |
| uuid | uuid.UUID |
| inet | ipaddress |
| time | datetime.time |
| timestamp | datetime.datetime |
| duration | dateutil.relativedelta |

All types from `extra_types` module are used to eliminate any possible ambiguity while passing parameters to queries. You can find more information about them in `Extra types` section.

We use relative delta from `dateutil` for duration, because it's the only way to represent it in python. Since scylla operates with months, days and nanosecond, there's no way we can represent it in python, becuase months are variable length.


## Named parameters

Also, you can provide named parameters to querties, by using name
Expand Down
12 changes: 7 additions & 5 deletions python/tests/test_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any, Callable

import pytest
from dateutil.relativedelta import relativedelta
from tests.utils import random_string

from scyllapy import Scylla
Expand Down Expand Up @@ -33,6 +34,8 @@
("INET", ipaddress.ip_address("2001:db8::8a2e:370:7334")),
("DECIMAL", Decimal("1.1")),
("DECIMAL", Decimal("1.112e10")),
("DURATION", relativedelta(months=1, days=2, microseconds=10)),
("VARINT", 1000),
],
)
async def test_bindings(
Expand All @@ -42,15 +45,14 @@ async def test_bindings(
) -> None:
table_name = random_string(4)
await scylla.execute(
f"CREATE TABLE {table_name} (id {type_name}, PRIMARY KEY (id))",
f"CREATE TABLE {table_name} (id INT, value {type_name}, PRIMARY KEY (id))",
)
insert_query = f"INSERT INTO {table_name}(id) VALUES (?)"
await scylla.execute(insert_query, [test_val])
insert_query = f"INSERT INTO {table_name}(id, value) VALUES (?, ?)"
await scylla.execute(insert_query, [1, test_val])

result = await scylla.execute(f"SELECT * FROM {table_name}")
rows = result.all()
assert len(rows) == 1
assert rows[0] == {"id": test_val}
assert rows == [{"id": 1, "value": test_val}]


@pytest.mark.anyio
Expand Down
27 changes: 26 additions & 1 deletion python/tests/test_extra_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import asdict, dataclass
from typing import Any
from typing import Any, Callable

import pytest
from tests.utils import random_string
Expand Down Expand Up @@ -147,3 +147,28 @@ async def test_autocast_positional(scylla: Scylla, typ: str, val: Any) -> None:
await scylla.execute(f"CREATE TABLE {table_name}(id INT PRIMARY KEY, val {typ})")
prepared = await scylla.prepare(f"INSERT INTO {table_name}(id, val) VALUES (?, ?)")
await scylla.execute(prepared, [1, val])


@pytest.mark.parametrize(
["cast_func", "val"],
[
(extra_types.BigInt, 1000000),
(extra_types.SmallInt, 10),
(extra_types.TinyInt, 1),
(int, 1),
],
)
@pytest.mark.anyio
async def test_varint(
scylla: Scylla,
cast_func: Callable[[Any], Any],
val: Any,
) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name}(id INT PRIMARY KEY, val VARINT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, val) VALUES (?, ?)",
(1, cast_func(val)),
)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert res.all() == [{"id": 1, "val": val}]
39 changes: 37 additions & 2 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use pyo3::{
use scylla::{
frame::{
response::result::{ColumnSpec, ColumnType, CqlValue},
value::{LegacySerializedValues, Value},
value::{CqlDuration, LegacySerializedValues, Value},
},
BufMut,
};
Expand Down Expand Up @@ -93,6 +93,11 @@ pub enum ScyllaPyCQLDTO {
Bool(bool),
Double(eq_float::F64),
Decimal(bigdecimal_04::BigDecimal),
Duration {
months: i32,
days: i32,
nanoseconds: i64,
},
Float(eq_float::F32),
Bytes(Vec<u8>),
Date(chrono::NaiveDate),
Expand Down Expand Up @@ -139,6 +144,16 @@ impl Value for ScyllaPyCQLDTO {
}
ScyllaPyCQLDTO::Decimal(decimal) => decimal.serialize(buf),
ScyllaPyCQLDTO::Unset => scylla::frame::value::Unset.serialize(buf),
ScyllaPyCQLDTO::Duration {
months,
days,
nanoseconds,
} => CqlDuration {
months: *months,
days: *days,
nanoseconds: *nanoseconds,
}
.serialize(buf),
}
}
}
Expand Down Expand Up @@ -266,6 +281,16 @@ pub fn py_to_value(
ScyllaPyError::BindingError("Cannot convert datetime to timestamp.".into()),
)?;
Ok(ScyllaPyCQLDTO::Timestamp(timestamp))
} else if item.get_type().name()? == "relativedelta" {
let months = item.getattr("months")?.extract::<i32>()?;
let days = item.getattr("days")?.extract::<i32>()?;
let nanoseconds = item.getattr("microseconds")?.extract::<i64>()? * 1_000
+ item.getattr("seconds")?.extract::<i64>()? * 1_000_000;
Ok(ScyllaPyCQLDTO::Duration {
months,
days,
nanoseconds,
})
} else if item.is_instance_of::<PyList>()
|| item.is_instance_of::<PyTuple>()
|| item.is_instance_of::<PySet>()
Expand Down Expand Up @@ -601,7 +626,17 @@ pub fn cql_to_py<'a>(
.getattr("Decimal")?
.call1((decimal.to_scientific_notation(),))?)
}
ColumnType::Custom(_) | ColumnType::Varint => Err(ScyllaPyError::ValueDowncastError(
ColumnType::Varint => {
let bigint: bigdecimal_04::num_bigint::BigInt = match unwrapped_value {
CqlValue::Varint(inner) => inner.clone().into(),
_ => return Err(ScyllaPyError::ValueDowncastError(col_name.into(), "Varint")),
};
Ok(py
.import("builtins")?
.getattr("int")?
.call1((bigint.to_string(),))?)
}
ColumnType::Custom(_) => Err(ScyllaPyError::ValueDowncastError(
col_name.into(),
"Unknown",
)),
Expand Down

0 comments on commit 76f7c08

Please sign in to comment.