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 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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ require 'influx_query'
3
+ class InfluxQuery::CLI < Thor
4
+ desc "test", "run tests"
5
+ def test
6
+ puts "No tests have been wrritten"
7
+ exit
8
+ end
9
+ end
10
+ InfluxQuery::CLI.start ARGV
@@ -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
@@ -0,0 +1,3 @@
1
+ module InfluxQuery
2
+ VERSION = '0.0.1'
3
+ end
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: []