# Async rust client for Apache Druid
Fully asynchronous, future-enabled [Apache Druid](http://druid.io/) client library for rust programming language.
The library provides staticly typed API for [Native Queries](https://druid.apache.org/docs/latest/querying/querying.html)
## Installation
The library is hosted on [crates.io](https://crates.io/crates/druid-io/).
```toml
[dependencies]
druid-io = "*"
```
## Supported Native Queries
* Timeseries
* TopN
* GroupBy
* Scan
* Search
* TimeBoundary
* SegmentMetadata
* DataSourceMetadata
## Usage
### Client
Connect to a druid cluster throughly staticly provided list of brokers:
```rust
let druid_client = DruidClient::new(vec!["localhost:8082".to_string()]);
```
Connector to Druid cluster through Zookeeper - supports autodiscovery of new brokers and load balancing:
```rust
TODO:
```
### Querying
#### Timeseries
See [Timeseries query documentation](https://druid.apache.org/docs/latest/querying/timeseriesquery.html)
```rust
#[derive(Serialize, Deserialize, Debug)]
pub struct TimeAggr {
count: usize,
count_fraction: f32,
user: String,
}
let timeseries = Timeseries {
data_source: DataSource::table("wikipedia"),
limit: Some(10),
descending: false,
granularity: Granularity::All,
filter: Some(Filter::selector("user", "Taffe316")),
aggregations: vec![
Aggregation::count("count"),
Aggregation::StringFirst {
name: "user".into(),
field_name: "user".into(),
max_string_bytes: 1024,
},
],
post_aggregations: vec![PostAggregation::Arithmetic {
name: "count_fraction".into(),
function: "/".into(),
fields: vec![
PostAggregator::field_access("count_percent", "count"),
PostAggregator::constant("hundred", 100.into()),
],
ordering: None,
}],
intervals: vec!["-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z".into()],
context: context,
};
let result = tokio_test::block_on(druid_client.timeseries::(×eries));
```
#### TopN
See [Apache Druid TopN query documentation](https://druid.apache.org/docs/latest/querying/topnquery.html)
```rust
#[derive(Serialize, Deserialize, Debug)]
struct WikiPage {
page: String,
user: Option,
count: usize,
}
let top_n = TopN {
data_source: DataSource::table("wikipedia"),
dimension: Dimension::default("page"),
threshold: 10,
metric: "count".into(),
aggregations: vec![
Aggregation::count("count"),
Aggregation::StringFirst {
name: "user".into(),
field_name: "user".into(),
max_string_bytes: 1024,
},
],
intervals: vec!["-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z".into()],
granularity: Granularity::All,
context: Default::default(),
};
let druid_client = DruidClient::new(vec!["localhost:8082".to_string()]);
let result = tokio_test::block_on(druid_client.top_n::(&top_n));
```
#### GroupBy
See [Apache Druid GroupBy query documentation](https://druid.apache.org/docs/latest/querying/groupbyquery.html)
```rust
let group_by = GroupBy {
data_source: DataSource::table("wikipedia"),
dimensions: vec![Dimension::Default {
dimension: "page".into(),
output_name: "page".into(),
output_type: OutputType::STRING,
}],
limit_spec: Some(LimitSpec {
limit: 10,
columns: vec![OrderByColumnSpec::new(
"page",
Ordering::Descending,
SortingOrder::Alphanumeric,
)],
}),
granularity: Granularity::All,
filter: Some(Filter::selector("user", "Taffe316")),
aggregations: vec![
Aggregation::count("count"),
Aggregation::StringFirst {
name: "user".into(),
field_name: "user".into(),
max_string_bytes: 1024,
},
],
post_aggregations: vec![PostAggregation::Arithmetic {
name: "count_fraction".into(),
function: "/".into(),
fields: vec![
PostAggregator::field_access("count_percent", "count"),
PostAggregator::constant("hundred", 100.into()),
],
ordering: None,
}],
having: Some(HavingSpec::greater_than("count_fraction", 0.01.into())),
intervals: vec!["-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z".into()],
subtotal_spec: Default::default(),
context: Default::default(),
};
let result = tokio_test::block_on(druid_client.group_by::(&group_by));
```
#### Scan (with inner join)
See [Apache Druid TimeBoundary query documentation](https://druid.apache.org/docs/latest/querying/scan-query.html)
Let's try something more complex: inner join
```rust
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ScanEvent {
#[serde(rename(deserialize = "__time"))]
time: usize,
city_name: Option,
comment: Option,
namespace: Option,
page: Option,
region_iso_code: Option,
user: String,
#[serde(rename(deserialize = "c.languages"))]
languages: Option,
}
let scan = Scan {
data_source: DataSource::join(JoinType::Inner)
.left(DataSource::table("wikipedia"))
.right(
DataSource::query(
Scan {
data_source: DataSource::table("countries"),
batch_size: 10,
intervals: vec![
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z"
.into(),
],
result_format: ResultFormat::List,
columns: vec!["Name".into(), "languages".into()],
limit: None,
filter: None,
ordering: Some(Ordering::None),
context: std::collections::HashMap::new(),
}
.into(),
),
"c.",
)
.condition("countryName == \"c.Name\"")
.build()
.unwrap(),
batch_size: 10,
intervals: vec!["-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z".into()],
result_format: ResultFormat::List,
columns: vec![],
limit: Some(10),
filter: None,
ordering: Some(Ordering::None),
context: Default::default(),
};
let result = tokio_test::block_on(druid_client.scan::(&scan));
```