pinot 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80d2be5dfc3b959fa6e7dce9eef3cd7600f0a6ad3f5d261230a7487e1482c2af
4
- data.tar.gz: 83e396f7d954f381fe4b88326a8217e84c827793f331e229d47af0310e5cb8fe
3
+ metadata.gz: dc12c931af8cd247929cbab2185c0ccd69a7d241e750211ba95287af996e3914
4
+ data.tar.gz: 3682d27b5db1c615f1084af77ef1d64496859a6db92a21a4bdf4d45761f8b741
5
5
  SHA512:
6
- metadata.gz: ae37bb8d8c521f537603e6bcbf1f82df414e55072bb11bf83511e1f0780656720635d597b1ce0ab1b699a21a685988e3e7fe90f611e10625741d231bae9794a5
7
- data.tar.gz: e0965c6c1a37d19e665a8545a01b35f346d5cd4955f08e6c3e51af4bac0b05a35e49213fc2e5192d76a3a4754e292de8029e76fec8a4e8ac289da6547b000284
6
+ metadata.gz: f9a4464727f662abe8c7cace2952076e45ea08802d1380a09568b9c60f4d2b58b6f8bd501f29216e59525a2d54f131bd66d19de12dbb7a5880780dc1bab06831
7
+ data.tar.gz: 890d0696eb36c2059873a9d8330f584fdd3b6ff13870567f5eee7d69a7fd286f895b9143f6ecc387a150fce460f27e3c7db4b063fb1500d9d849a7403d3c3468
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2024-05-29
4
+
5
+ - feature: support socks5
6
+ - feature: support authentication bearer token
7
+
8
+ ## [0.1.1] - 2024-05-16
9
+
10
+ - feature: Add support for controller host
11
+ - fix: handle nil column names
12
+
3
13
  ## [0.1.0] - 2024-01-16
4
14
 
5
15
  - Initial release
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)/?test_(.*)\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { "test" }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+
29
+ # Rails 4
30
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
31
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
32
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
33
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
34
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
35
+ # watch(%r{^test/.+_test\.rb$})
36
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
37
+
38
+ # Rails < 4
39
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
40
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
41
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
42
+ end
data/README.md CHANGED
@@ -1,24 +1,71 @@
1
1
  # Pinot
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pinot`. To experiment with that code, run `bin/console` for an interactive prompt.
6
-
7
3
  ## Installation
8
4
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
5
  Install the gem and add to the application's Gemfile by executing:
12
6
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
7
+ $ bundle add pinot
14
8
 
15
9
  If bundler is not being used to manage dependencies, install the gem by executing:
16
10
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
11
+ $ gem install pinot
18
12
 
19
13
  ## Usage
20
14
 
21
- TODO: Write usage instructions here
15
+ To configure our client, we must have some data about our pinot cluster, its broken and controller host, and which port/protocol both services are using. After having this information, we can setup the client to use it.
16
+
17
+ ```ruby
18
+ host = "localhost"
19
+ controller_host = "localhost"
20
+ client = Pinot::Client.new(host: host, port: 443, protocol: :https, controller_host: controller_host, controller_port: 443)
21
+ ```
22
+
23
+ After setting up your client, we can start using it to query the cluster.
24
+
25
+ ```ruby
26
+ client.execute("select * from table;")
27
+ ```
28
+
29
+ After querying, it returns a `HTTPX::ErrorResponse` in case of any HTTP error, like a timeout, or connection refused, we are returning the client's error to make easier to troubleshoot any problem and handle it accordingly.
30
+
31
+ In case of success, it returns a `Pinot::Response`, an enumerable where you can interact over the returned rows.
32
+
33
+ Each row is an array with row's values.
34
+
35
+ ```ruby
36
+ result = client.execute("select id, name, age from people;")
37
+ result.each do |row|
38
+ puts row[0] # id
39
+ puts row[1] # name
40
+ puts row[2] # age
41
+ end
42
+ ```
43
+
44
+ If the column names and types are needed, can call `columns` method on the response.
45
+
46
+ ```ruby
47
+ result = client.execute("select count(*) from people;")
48
+ resp.columns
49
+ # {"count(*)"=>"LONG"}
50
+ ```
51
+
52
+ It's not on the public API, but the instance variable `@payload` contains all information returned from the Pinot cluster in case can help with troubleshooting
53
+
54
+ ### Authentication
55
+
56
+ In case your pinot cluster is using authentication bearer token, you can specify it on the client constructor
57
+
58
+ ```ruby
59
+ client = Pinot::Client.new(bearer_token: "my-secret", **client_args)
60
+ ```
61
+
62
+ ### Socks5
63
+
64
+ Using Pinot cluster inside a VPC and need to jump through a bastion host using socks5? We got your back!
65
+
66
+ ```ruby
67
+ client = Pinot::Client.new(socks5_uri: "socks5://localhost:80", **client_args)
68
+ ```
22
69
 
23
70
  ## Development
24
71
 
@@ -28,8 +75,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
28
75
 
29
76
  ## Contributing
30
77
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/pinot. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/pinot/blob/main/CODE_OF_CONDUCT.md).
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/clickfunnels2/pinot. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/clickfunnels2/pinot/blob/main/CODE_OF_CONDUCT.md).
32
79
 
33
80
  ## Code of Conduct
34
81
 
35
- Everyone interacting in the Pinot project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/pinot/blob/main/CODE_OF_CONDUCT.md).
82
+ Everyone interacting in the Pinot project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/clickfunnels2/pinot/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,92 @@
1
+ module Pinot
2
+ class Client
3
+ attr_reader :host, :port, :controller_host, :controller_port, :protocol, :socks5_uri, :bearer_token
4
+
5
+ def initialize(host:, port:, controller_port:, controller_host: nil, protocol: :http, socks5_uri: nil, bearer_token: nil)
6
+ @host = host
7
+ @port = port
8
+ @controller_port = controller_port
9
+ @controller_host = controller_host || host
10
+ @protocol = protocol
11
+ @socks5_uri = socks5_uri
12
+ @bearer_token = bearer_token
13
+ end
14
+
15
+ def execute(sql)
16
+ response = http.post(query_sql_uri, json: {sql: sql})
17
+ return response if response.is_a?(HTTPX::ErrorResponse)
18
+ Response.new(JSON.parse(response))
19
+ end
20
+
21
+ def schema(name)
22
+ url = "#{controller_uri}/schemas/#{name}"
23
+ response = http.get(url)
24
+ JSON.parse(response)
25
+ end
26
+
27
+ def delete_segments(name, type: :offline)
28
+ type = type.to_s.upcase
29
+ url = "#{controller_uri}/segments/#{name}?type=#{type}"
30
+ response = http.delete(url)
31
+ JSON.parse(response)
32
+ end
33
+
34
+ def create_table(schema)
35
+ url = "#{controller_uri}/tables"
36
+ response = http.post(url, body: schema)
37
+ JSON.parse(response)
38
+ end
39
+
40
+ def delete_table(name, type: :offline)
41
+ type = type.to_s.downcase
42
+ url = "#{controller_uri}/tables/#{name}?type=#{type}"
43
+ response = http.delete(url)
44
+ JSON.parse(response)
45
+ end
46
+
47
+ def ingest_json(file, table:)
48
+ url = "#{controller_uri}/ingestFromFile?tableNameWithType=#{table}&batchConfigMapStr=%7B%22inputFormat%22%3A%22json%22%7D"
49
+ content_type = "multipart/form-data"
50
+ response = HTTPX.post(
51
+ url,
52
+ form: {
53
+ file: {
54
+ filename: File.basename(file.path),
55
+ content_type: content_type,
56
+ body: file.read
57
+ }
58
+ }
59
+ )
60
+ JSON.parse(response)
61
+ end
62
+
63
+ def create_schema(schema, override: true, force: false)
64
+ url = "#{controller_uri}/schemas?override=#{override}&force=#{force}"
65
+ response = http.post(url, body: schema)
66
+ JSON.parse(response)
67
+ end
68
+
69
+ def http(content_type: "application/json")
70
+ return @http if !@http.nil?
71
+ default_headers = {"Content-Type" => content_type}
72
+ default_headers["Authorization"] = "Bearer #{bearer_token}" if bearer_token
73
+ @http = HTTPX.with(headers: default_headers, timeout: { connect_timeout: 5 })
74
+ if socks5_uri
75
+ @http = @http.plugin(:proxy).with_proxy(uri: socks5_uri) if socks5_uri
76
+ end
77
+ @http
78
+ end
79
+
80
+ def uri
81
+ "#{protocol}://#{host}:#{port}"
82
+ end
83
+
84
+ def controller_uri
85
+ "#{protocol}://#{controller_host}:#{controller_port}"
86
+ end
87
+
88
+ def query_sql_uri
89
+ "#{uri}/query/sql"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,32 @@
1
+ module Pinot
2
+ class Response
3
+ include Enumerable
4
+
5
+ def initialize(payload)
6
+ @payload = payload
7
+ end
8
+
9
+ def rows
10
+ @payload.dig("resultTable", "rows")
11
+ end
12
+
13
+ def columns
14
+ names = @payload.dig("resultTable", "dataSchema", "columnNames")
15
+ types = @payload.dig("resultTable", "dataSchema", "columnDataTypes")
16
+ return {} if @payload["exceptions"].any?
17
+ ix = 0
18
+ names ||= []
19
+ names.map do |name|
20
+ ret = [name, types[ix]]
21
+ ix += 1
22
+ ret
23
+ end.to_h
24
+ end
25
+
26
+ def each(&block)
27
+ rows.each do |row|
28
+ block.call(row)
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/pinot/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pinot
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/pinot.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "httpx"
4
+
3
5
  require_relative "pinot/version"
6
+ require_relative "pinot/response"
7
+ require_relative "pinot/client"
4
8
 
5
9
  module Pinot
6
10
  class Error < StandardError; end
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Celso Fernandes
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-16 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpx
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
13
69
  description: Client for Apache Pinot
14
70
  email:
15
71
  - celso.fernandes@clickfunnels.com
@@ -20,11 +76,13 @@ files:
20
76
  - ".standard.yml"
21
77
  - CHANGELOG.md
22
78
  - CODE_OF_CONDUCT.md
79
+ - Guardfile
23
80
  - README.md
24
81
  - Rakefile
25
82
  - lib/pinot.rb
83
+ - lib/pinot/client.rb
84
+ - lib/pinot/response.rb
26
85
  - lib/pinot/version.rb
27
- - pinot.gemspec
28
86
  - sig/pinot.rbs
29
87
  homepage: https://github.com/fernandes/pinot-ruby
30
88
  licenses: []
@@ -47,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
105
  - !ruby/object:Gem::Version
48
106
  version: '0'
49
107
  requirements: []
50
- rubygems_version: 3.4.21
108
+ rubygems_version: 3.5.3
51
109
  signing_key:
52
110
  specification_version: 4
53
111
  summary: Client for Apache Pinot
data/pinot.gemspec DELETED
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/pinot/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "pinot"
7
- spec.version = Pinot::VERSION
8
- spec.authors = ["Celso Fernandes"]
9
- spec.email = ["celso.fernandes@clickfunnels.com"]
10
-
11
- spec.summary = "Client for Apache Pinot"
12
- spec.description = "Client for Apache Pinot"
13
- spec.homepage = "https://github.com/fernandes/pinot-ruby"
14
- spec.required_ruby_version = ">= 2.6.0"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/fernandes/pinot"
18
- spec.metadata["changelog_uri"] = "https://github.com/fernandes/pinot/blob/main/CHANGELOG.md"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(__dir__) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (File.expand_path(f) == __FILE__) ||
25
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
26
- end
27
- end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
31
-
32
- # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
34
-
35
- # For more information and examples about making a new gem, check out our
36
- # guide at: https://bundler.io/guides/creating_gem.html
37
- end