influx_query 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|