influx_query 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +152 -0
- data/bin/influx_query +10 -0
- data/lib/influx_query.rb +124 -0
- data/lib/version.rb +3 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d265802226b7cd24ecfa9d979cf6545b2199ac1796d0c8c196857c4733cc48cf
|
4
|
+
data.tar.gz: 8756e0f15784bd7d90fcd11c0cfb53f80361e5db1c28e4a770f314efafb6ede3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b027c73985ec0c54c8b579344a65f85385d9ca7653728270715cf1183ba73d7cbd29bbbe54fc0b9fb123e6583ed7763c7dc38415590efdb4d898979fed89c2f1
|
7
|
+
data.tar.gz: b52f4ca54d4f823d069155bbcddaf857e69f5797b5f6a5d094251dd9c913d1ba4dfac9e93a91e2e2eb971d2f1315bd59e3ca8c554a1565961fdd8d901cd0800c
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
### influx_query
|
2
|
+
|
3
|
+
This is a gem that provides a minimal chainable DSL for InfluxDB.
|
4
|
+
|
5
|
+
### Installation
|
6
|
+
|
7
|
+
As usual, either
|
8
|
+
|
9
|
+
```
|
10
|
+
# in the gemfile
|
11
|
+
gem 'influx_query'
|
12
|
+
```
|
13
|
+
|
14
|
+
or
|
15
|
+
|
16
|
+
```
|
17
|
+
# on the command line
|
18
|
+
gem install influx_query
|
19
|
+
```
|
20
|
+
|
21
|
+
### Background
|
22
|
+
|
23
|
+
However before going into the API it's important to understand the
|
24
|
+
core concept behind how a query is built.
|
25
|
+
|
26
|
+
The `InfluxDB::Client` gem has a method `query` that accepts a string
|
27
|
+
as its first argument. Queries can of course be written as regular strings and passed to this, but if user-generated values are to be used in the queries, then it is unsafe to directly interpolate them.
|
28
|
+
|
29
|
+
Thankfully, the `query` method can do parameterized queries using
|
30
|
+
the `params` keyword. It looks like this:
|
31
|
+
|
32
|
+
```
|
33
|
+
influxdb_client.query(
|
34
|
+
"select * from things where foo=%{val} ;",
|
35
|
+
params: { val: "bar" }
|
36
|
+
)
|
37
|
+
```
|
38
|
+
|
39
|
+
This gem abstracts this away and also makes a chainable dsl.
|
40
|
+
|
41
|
+
### Usage
|
42
|
+
|
43
|
+
This gem depends on [`influxdb-ruby`](https://github.com/influxdata/influxdb-ruby)
|
44
|
+
but doesn't come with it included. You should require that gem separately and build
|
45
|
+
an `InfluxDB::Client` instance using your credentials, as described in their README.
|
46
|
+
|
47
|
+
Once you have such a client, you can initialize `InfluxQuery` -
|
48
|
+
each instance handles only a single query.
|
49
|
+
|
50
|
+
```
|
51
|
+
client = InfluxDB::Client.new(host: "my_host.com")
|
52
|
+
query = InfluxQuery.new(client: client, source: "things")
|
53
|
+
|
54
|
+
# At any point, can call this to see the query
|
55
|
+
puts query.finalize
|
56
|
+
|
57
|
+
# This fires off "SELECT * FROM things":
|
58
|
+
result = query.resolve
|
59
|
+
|
60
|
+
```
|
61
|
+
|
62
|
+
`#initialize` accepts some keyword opts in addition to `client` and also makes `attr_reader`s for them. However it is not necessary to manually read/write these since they all have default values set and
|
63
|
+
are controlled by the chaining dsl.
|
64
|
+
|
65
|
+
- `conditions`: an array of strings, such as `"foo = '%{val}'"`.
|
66
|
+
These are joined using `AND` when the query is evaluated.
|
67
|
+
- `params`: the values which will be interpolated into the final query.
|
68
|
+
- `select_columns`: an array of strings.
|
69
|
+
Defaults to `["*"]` and will stay that way unless manually altered.
|
70
|
+
- `source`: string, the measurement to fetch data from, e.g. "things"s
|
71
|
+
|
72
|
+
In between `#initialize` and `#resolve`, other methods can be called:
|
73
|
+
|
74
|
+
**filter!**
|
75
|
+
|
76
|
+
Args:
|
77
|
+
|
78
|
+
1. is a key (used internally by `#params`).
|
79
|
+
2. is a tag/field name.
|
80
|
+
3. comparison operator
|
81
|
+
4. value
|
82
|
+
|
83
|
+
```
|
84
|
+
query
|
85
|
+
.filter!(:start, "time", "<", "now() - 30d")
|
86
|
+
.filter!(:start, "time", ">", "now() - 15d")
|
87
|
+
```
|
88
|
+
|
89
|
+
This previous example has its functionality served by a helper method
|
90
|
+
as well:
|
91
|
+
|
92
|
+
**add_time_filters!**
|
93
|
+
|
94
|
+
Args are just `start_time` and `end_time` keywords.
|
95
|
+
|
96
|
+
```
|
97
|
+
query
|
98
|
+
.add_time_filters!(
|
99
|
+
start_time: "now() - 30d",
|
100
|
+
end_time: "now() - 15d"
|
101
|
+
)
|
102
|
+
```
|
103
|
+
|
104
|
+
Influx's SQL-like query language lacks a proper WHERE IN type clause, so
|
105
|
+
we have to emulate it using something like `WHERE foo=1 OR foo=2 OR foo=3`.
|
106
|
+
There's a method to help with this:
|
107
|
+
|
108
|
+
**add_where_in_filter!**
|
109
|
+
|
110
|
+
```
|
111
|
+
query
|
112
|
+
.add_where_in_filter!(:foo, "foo", [1,2,3])
|
113
|
+
```
|
114
|
+
|
115
|
+
Moving on, these should be self explanatory
|
116
|
+
|
117
|
+
**limit!**
|
118
|
+
|
119
|
+
```
|
120
|
+
query
|
121
|
+
.limit!(500)
|
122
|
+
```
|
123
|
+
|
124
|
+
**offset!**
|
125
|
+
|
126
|
+
```
|
127
|
+
query
|
128
|
+
.offset!(250)
|
129
|
+
```
|
130
|
+
|
131
|
+
**group by**
|
132
|
+
|
133
|
+
```
|
134
|
+
query
|
135
|
+
.group_by("foo")
|
136
|
+
```
|
137
|
+
|
138
|
+
### Advanced usage
|
139
|
+
|
140
|
+
It is possible to use subqueries in conditions, if you manually push those condition strings into `#conditions`.
|
141
|
+
|
142
|
+
It is possible to build subqueries using the chaining dsl, if you call
|
143
|
+
`#finalize_subquery` instead of `#finalize`. This method is actually used by
|
144
|
+
`#finalize` under the hood. Literally the only the difference is it doesn't
|
145
|
+
insert a semicolon at the end. It's up to you to add parenthesis around the
|
146
|
+
subquery as needed.
|
147
|
+
|
148
|
+
If you have a subquery you want to include in the main FROM clause, manually set the `source` to point to it.
|
149
|
+
|
150
|
+
Please feel free to send an email for help with this.
|
151
|
+
maxpleaner@gmail.com
|
152
|
+
|
data/bin/influx_query
ADDED
data/lib/influx_query.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
class InfluxQuery
|
2
|
+
|
3
|
+
attr_reader :params, :conditions, :select_columns, :source, :client
|
4
|
+
|
5
|
+
def initialize(
|
6
|
+
source:, client:, conditions: nil, params: nil, select_columns: nil
|
7
|
+
)
|
8
|
+
@conditions = conditions || []
|
9
|
+
@params = params || {}
|
10
|
+
@select_columns = select_columns || ["*"]
|
11
|
+
@source = source
|
12
|
+
@client = client
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve
|
16
|
+
client.query finalize, params: params
|
17
|
+
end
|
18
|
+
|
19
|
+
def finalize
|
20
|
+
"#{finalize_subquery} ;"
|
21
|
+
end
|
22
|
+
|
23
|
+
def finalize_subquery
|
24
|
+
(
|
25
|
+
"select #{@select_columns.join(",")} " +
|
26
|
+
"from #{@source}" +
|
27
|
+
"#{finalize_conditions}"
|
28
|
+
).tap do |str|
|
29
|
+
str << " GROUP BY %{group_by}" if @params[:group_by]
|
30
|
+
str << " LIMIT %{limit}" if @params[:limit]
|
31
|
+
str << " OFFSET %{offset}" if @params[:offset]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def finalize_conditions
|
36
|
+
return "" unless @conditions.any?
|
37
|
+
" where #{@conditions.join(" AND ")}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def limit!(limit)
|
41
|
+
return self unless limit
|
42
|
+
limit = limit.to_i
|
43
|
+
raise(ArgumentError, "Cannot add a limit of 0") if limit.zero?
|
44
|
+
@params.merge! limit: limit
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def offset!(offset)
|
49
|
+
return self unless offset
|
50
|
+
offset = offset.to_i
|
51
|
+
@params.merge! offset: offset
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def group_by!(group_by)
|
56
|
+
return self unless group_by
|
57
|
+
@params.merge! group_by: group_by
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_conditions!(properties, operator: "AND", wrap_clause: false)
|
62
|
+
clause = build_query_constraints(
|
63
|
+
properties: properties,
|
64
|
+
operator: operator
|
65
|
+
)
|
66
|
+
clause = "(#{clause})" if wrap_clause
|
67
|
+
@conditions.push clause
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_where_in_filter!(param_key_base, col_name, vals)
|
72
|
+
return self unless vals.any?
|
73
|
+
properties = vals.map.with_index do |val, idx|
|
74
|
+
param_key = :"#{param_key_base}_#{idx}"
|
75
|
+
@params.merge! param_key => val
|
76
|
+
{
|
77
|
+
key: col_name,
|
78
|
+
operator: "=",
|
79
|
+
value: "%{#{param_key}}"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
add_conditions!(properties, operator: "OR", wrap_clause: true)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Most of the filters have the same structure.
|
86
|
+
def filter!(param_name, col_name, operator, val)
|
87
|
+
return self unless val
|
88
|
+
@params.merge!(param_name => val)
|
89
|
+
add_conditions! [{
|
90
|
+
key: col_name,
|
91
|
+
operator: operator,
|
92
|
+
value: "%{#{param_name}}"
|
93
|
+
}]
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_time_filters!(start_date: nil, end_date: nil, **_opts)
|
97
|
+
start_date ||= (Time.now.utc - 7.days).beginning_of_day.to_i
|
98
|
+
end_date ||= Time.now.utc.end_of_day.to_i
|
99
|
+
@params.merge! start_date: start_date, end_date: end_date
|
100
|
+
add_conditions! [
|
101
|
+
{ key: 'time', operator: ">=", value: "%{start_date}s" },
|
102
|
+
{ key: 'time', operator: "<=", value: "%{end_date}s" }
|
103
|
+
], operator: "AND"
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_query_constraints(properties: [], operator: 'AND')
|
107
|
+
conditions = []
|
108
|
+
properties.each do |property|
|
109
|
+
# Influx requires different quotation types for different situations.
|
110
|
+
# no quotes are used here:
|
111
|
+
# where time > 123123123s (s -> seconds)
|
112
|
+
# but single quotes are used here
|
113
|
+
# where controller_action = 'sessions_controller#create'
|
114
|
+
value = if property[:value].is_a?(String) && property[:key] != 'time'
|
115
|
+
"'#{property[:value]}'"
|
116
|
+
else
|
117
|
+
property[:value]
|
118
|
+
end
|
119
|
+
conditions << "#{property[:key]} #{property[:operator]} #{value}"
|
120
|
+
end
|
121
|
+
conditions.join(" #{operator} ")
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: influx_query
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- max pleaner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: ''
|
14
|
+
email: maxpleaner@gmail.com
|
15
|
+
executables:
|
16
|
+
- influx_query
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- bin/influx_query
|
22
|
+
- lib/influx_query.rb
|
23
|
+
- lib/version.rb
|
24
|
+
homepage: http://github.com/maxp-edcast/influx_query
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - "~>"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '2.3'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 2.7.3
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.7.3
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: query DSL for Influx
|
48
|
+
test_files: []
|