pinot-client 1.25.0 → 1.27.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f76e9f295ba992d3a6c2ec7a4dc738230c8676bda0f64bafb3aa85af1530c34
4
- data.tar.gz: 4ea8dfd7e0c530f38c860928c18c1facc76d5fabb7667cf7b802864629365dd1
3
+ metadata.gz: dfd22c5e76430105c17c3a1279b002084aebb894d4c561913908a4d00038b519
4
+ data.tar.gz: e668583cdd1afde9b9b35dab28c46b2e707643911a0800cdb98950ac1de2731b
5
5
  SHA512:
6
- metadata.gz: 2a07d7e620bf3adbbcd2f809c37854a1c96da7e1bbb416cbe994d6ce7ea5d17620839aad66b2059e5f1f0de683cd5a405868d1729b5da7dbba19ff820c1ac704
7
- data.tar.gz: 2da45bfbb16a63ed13c72fdec7c2684511d27caac9a211155135a3697c88cbf36686a7ded2d569859cb751e8dac59a04b7d91aaf06b3d06136461a217166924e
6
+ metadata.gz: 1aba59c0e39a601dacccfa2f413b9405ad0835e94555272a8f7bf8177b421fd0eac861144fb44d24c69ec41f51834b67a786ed4bd29afb48ae6667671f5ce112
7
+ data.tar.gz: c81b393e62ffba2390a7c21a52fb0b0a3f0fdfd0421b3a78d9bcbcc772a35f2613e451f58a28cdcb9ecd30c6120aa114f94ae3a80c825594a0cc9baa98a521ca
@@ -0,0 +1,81 @@
1
+ require "pinot"
2
+
3
+ module Pinot
4
+ # Opt-in ActiveSupport::Notifications bridge for Rails applications.
5
+ #
6
+ # == Setup
7
+ #
8
+ # Add one line to an initializer (e.g. config/initializers/pinot.rb):
9
+ #
10
+ # require "pinot/active_support_notifications"
11
+ # Pinot::ActiveSupportNotifications.install!
12
+ #
13
+ # That's it. Every query executed via Connection#execute_sql (including those
14
+ # from execute_sql_with_params, execute_many, and PreparedStatement) will
15
+ # publish a "sql.pinot" event on the ActiveSupport::Notifications bus.
16
+ #
17
+ # == Subscribing
18
+ #
19
+ # ActiveSupport::Notifications.subscribe("sql.pinot") do |name, start, finish, id, payload|
20
+ # Rails.logger.debug "[Pinot] #{payload[:name]} — #{payload[:sql]} (#{payload[:duration].round(1)} ms)"
21
+ # end
22
+ #
23
+ # == Payload keys
24
+ #
25
+ # :sql — the SQL query string
26
+ # :name — the Pinot table name (empty string for table-less queries)
27
+ # :duration — execution time in milliseconds (Float)
28
+ # :success — true on success, false when an exception was raised
29
+ # :exception — [ExceptionClassName, message] on error, absent on success
30
+ # (follows the ActiveSupport::Notifications convention)
31
+ # :exception_object — the raw exception on error, absent on success
32
+ # (follows the ActiveSupport::Notifications convention)
33
+ #
34
+ # == Lifecycle
35
+ #
36
+ # The bridge is installed idempotently:
37
+ #
38
+ # Pinot::ActiveSupportNotifications.install! # register
39
+ # Pinot::ActiveSupportNotifications.installed? # => true
40
+ # Pinot::ActiveSupportNotifications.uninstall! # deregister (e.g. in tests)
41
+ #
42
+ # Note: this gem does NOT depend on activesupport. The bridge requires
43
+ # ActiveSupport::Notifications to already be defined at install! time (which
44
+ # is always the case in a Rails process).
45
+ module ActiveSupportNotifications
46
+ EVENT_NAME = "sql.pinot"
47
+
48
+ def self.install!
49
+ return if installed?
50
+
51
+ Pinot::Instrumentation.on_query = method(:notify)
52
+ @installed = true
53
+ end
54
+
55
+ def self.installed?
56
+ @installed || false
57
+ end
58
+
59
+ def self.uninstall!
60
+ Pinot::Instrumentation.on_query = nil
61
+ @installed = false
62
+ end
63
+
64
+ def self.notify(event)
65
+ payload = {
66
+ sql: event[:query],
67
+ name: event[:table],
68
+ duration: event[:duration_ms],
69
+ success: event[:success]
70
+ }
71
+
72
+ if (err = event[:error])
73
+ payload[:exception] = [err.class.name, err.message]
74
+ payload[:exception_object] = err
75
+ end
76
+
77
+ ::ActiveSupport::Notifications.instrument(EVENT_NAME, payload)
78
+ end
79
+ private_class_method :notify
80
+ end
81
+ end
@@ -1,8 +1,34 @@
1
1
  module Pinot
2
- # Per-broker circuit breaker. States: CLOSED (normal), OPEN (rejecting), HALF_OPEN (probing).
2
+ # Per-broker circuit breaker implementing the classic three-state machine:
3
3
  #
4
- # failure_threshold - consecutive failures before opening (default 5)
5
- # open_timeout - seconds to stay OPEN before moving to HALF_OPEN (default 30)
4
+ # CLOSED — normal operation; failures are counted
5
+ # OPEN all calls rejected immediately with BrokerCircuitOpenError
6
+ # HALF_OPEN — one probe call allowed through; success → CLOSED, failure → OPEN
7
+ #
8
+ # A breaker opens after +failure_threshold+ consecutive transport-level failures
9
+ # (BrokerUnavailableError, connection resets, timeouts). It automatically
10
+ # transitions to HALF_OPEN after +open_timeout+ seconds.
11
+ #
12
+ # Use CircuitBreakerRegistry to share breakers across Connection instances.
13
+ #
14
+ # == Configuration
15
+ #
16
+ # config = Pinot::ClientConfig.new(
17
+ # broker_list: ["broker:8099"],
18
+ # circuit_breaker_enabled: true,
19
+ # circuit_breaker_threshold: 3, # open after 3 failures (default 5)
20
+ # circuit_breaker_timeout: 10 # reopen probe after 10 s (default 30)
21
+ # )
22
+ # conn = Pinot.from_config(config)
23
+ #
24
+ # == Error class
25
+ #
26
+ # Pinot::CircuitBreaker::BrokerCircuitOpenError
27
+ # — raised when the circuit is OPEN; inherits from BrokerNotFoundError
28
+ # so callers that already rescue BrokerNotFoundError get it for free.
29
+ #
30
+ # @param failure_threshold [Integer] consecutive failures before opening (default 5)
31
+ # @param open_timeout [Integer] seconds to wait before probing again (default 30)
6
32
  class CircuitBreaker
7
33
  CLOSED = :closed
8
34
  OPEN = :open
@@ -85,7 +111,9 @@ module Pinot
85
111
  end
86
112
  end
87
113
 
88
- # Registry of per-broker CircuitBreakers, shared across transport calls.
114
+ # Thread-safe registry that lazily creates and caches one CircuitBreaker per
115
+ # broker address string. Shared by all Connection instances built from the
116
+ # same ClientConfig so that failures from parallel queries accumulate correctly.
89
117
  class CircuitBreakerRegistry
90
118
  def initialize(failure_threshold: 5, open_timeout: 30)
91
119
  @failure_threshold = failure_threshold
@@ -1,7 +1,26 @@
1
1
  require "bigdecimal"
2
2
 
3
3
  module Pinot
4
+ # Main entry point for querying Apache Pinot over HTTP.
5
+ #
6
+ # Build a Connection via the factory helpers rather than instantiating directly:
7
+ #
8
+ # # Static broker list
9
+ # conn = Pinot.from_broker_list(["broker1:8099", "broker2:8099"])
10
+ #
11
+ # # Controller-managed broker discovery
12
+ # conn = Pinot.from_controller("controller:9000")
13
+ #
14
+ # # Full configuration
15
+ # conn = Pinot.from_config(Pinot::ClientConfig.new(
16
+ # broker_list: ["broker:8099"],
17
+ # query_timeout_ms: 5_000,
18
+ # use_multistage_engine: true,
19
+ # max_retries: 2,
20
+ # circuit_breaker_enabled: true
21
+ # ))
4
22
  class Connection
23
+ # @return [Integer, nil] default query timeout in milliseconds; can be overridden per-call
5
24
  attr_accessor :query_timeout_ms
6
25
 
7
26
  def initialize(transport:, broker_selector:, use_multistage_engine: false, logger: nil,
@@ -27,6 +46,18 @@ module Pinot
27
46
  @trace = false
28
47
  end
29
48
 
49
+ # Execute a SQL query against +table+ and return a BrokerResponse.
50
+ #
51
+ # @param table [String] Pinot table name (used for broker selection)
52
+ # @param query [String] SQL query string
53
+ # @param query_timeout_ms [Integer, nil] per-call timeout override (ms); overrides
54
+ # the connection-level query_timeout_ms
55
+ # @param headers [Hash] extra HTTP headers merged into this request only
56
+ # @return [BrokerResponse]
57
+ # @raise [BrokerNotFoundError] no broker available for the table
58
+ # @raise [QueryTimeoutError] query exceeded the timeout
59
+ # @raise [BrokerUnavailableError] broker returned 503/504
60
+ # @raise [TransportError] other non-200 HTTP response
30
61
  def execute_sql(table, query, query_timeout_ms: nil, headers: {})
31
62
  Pinot::Instrumentation.instrument(table: table, query: query) do
32
63
  logger.debug "Executing SQL on table=#{table}: #{query}"
@@ -39,10 +70,24 @@ module Pinot
39
70
  end
40
71
  end
41
72
 
73
+ # Convenience wrapper around execute_sql with an explicit timeout.
74
+ # Equivalent to execute_sql(table, query, query_timeout_ms: timeout_ms).
42
75
  def execute_sql_with_timeout(table, query, timeout_ms)
43
76
  execute_sql(table, query, query_timeout_ms: timeout_ms)
44
77
  end
45
78
 
79
+ # Execute a parameterised query by substituting +params+ into +query_pattern+.
80
+ # Each +?+ placeholder in the pattern is replaced by the corresponding value
81
+ # using safe type-aware formatting (strings are quoted and escaped).
82
+ #
83
+ # @param table [String] Pinot table name
84
+ # @param query_pattern [String] SQL template, e.g. "SELECT * FROM t WHERE id = ?"
85
+ # @param params [Array] ordered values; supported types: String, Integer,
86
+ # Float, TrueClass, FalseClass, BigDecimal, Time
87
+ # @param query_timeout_ms [Integer, nil] per-call timeout override (ms)
88
+ # @param headers [Hash] extra HTTP headers for this request
89
+ # @return [BrokerResponse]
90
+ # @raise [RuntimeError] placeholder / param count mismatch or unsupported type
46
91
  def execute_sql_with_params(table, query_pattern, params, query_timeout_ms: nil, headers: {})
47
92
  query = format_query(query_pattern, params)
48
93
  execute_sql(table, query, query_timeout_ms: query_timeout_ms, headers: headers)
@@ -50,6 +95,29 @@ module Pinot
50
95
  raise e
51
96
  end
52
97
 
98
+ # Execute multiple queries in parallel and return results in the same order.
99
+ #
100
+ # Each element of +queries+ must be a Hash with keys +:table+ and +:query+
101
+ # (Strings or Symbols). An optional +:query_timeout_ms+ key overrides the
102
+ # per-query timeout.
103
+ #
104
+ # Each slot in the returned Array is a QueryResult with either a +response+ or
105
+ # an +error+ — failures are isolated so one bad query does not raise for the
106
+ # whole batch.
107
+ #
108
+ # results = conn.execute_many([
109
+ # { table: "orders", query: "SELECT count(*) FROM orders" },
110
+ # { table: "products", query: "SELECT count(*) FROM products" }
111
+ # ], max_concurrency: 4)
112
+ #
113
+ # results.each do |r|
114
+ # puts r.success? ? r.response.result_table.get_long(0, 0) : r.error.message
115
+ # end
116
+ #
117
+ # @param queries [Array<Hash>] query descriptors
118
+ # @param max_concurrency [Integer, nil] maximum simultaneous in-flight queries;
119
+ # nil means unlimited
120
+ # @return [Array<QueryResult>]
53
121
  def execute_many(queries, max_concurrency: nil)
54
122
  return [] if queries.empty?
55
123
 
@@ -79,6 +147,41 @@ module Pinot
79
147
  results
80
148
  end
81
149
 
150
+ # Return a Paginator for cursor-based iteration over large result sets.
151
+ #
152
+ # The query must include a LIMIT clause; the broker stores the result set
153
+ # and returns it in +page_size+ row slices on demand.
154
+ #
155
+ # paginator = conn.paginate("SELECT * FROM myTable LIMIT 50000", page_size: 500)
156
+ # paginator.each_row { |row| puts row.map(&:to_s).join(", ") }
157
+ #
158
+ # @param query [String] SQL query (should include LIMIT)
159
+ # @param page_size [Integer] rows per page (default Paginator::DEFAULT_PAGE_SIZE = 1000)
160
+ # @param table [String, nil] used only for broker selection; nil picks any broker
161
+ # @param extra_headers [Hash] merged into every HTTP request of this cursor session
162
+ # @return [Paginator]
163
+ def paginate(query, page_size: Paginator::DEFAULT_PAGE_SIZE, table: nil, extra_headers: {})
164
+ broker = @broker_selector.select_broker(table || "")
165
+ Paginator.new(
166
+ @transport.http_client,
167
+ broker,
168
+ query,
169
+ page_size: page_size,
170
+ extra_headers: extra_headers
171
+ )
172
+ end
173
+
174
+ # Create a PreparedStatement from a query template with +?+ placeholders.
175
+ #
176
+ # stmt = conn.prepare("myTable", "SELECT * FROM myTable WHERE id = ? AND name = ?")
177
+ # stmt.set(1, 42)
178
+ # stmt.set(2, "Alice")
179
+ # resp = stmt.execute
180
+ #
181
+ # @param table [String] Pinot table name (non-empty)
182
+ # @param query_template [String] SQL with one or more +?+ placeholders
183
+ # @return [PreparedStatementImpl]
184
+ # @raise [ArgumentError] if table or query_template is blank, or contains no placeholders
82
185
  def prepare(table, query_template)
83
186
  raise ArgumentError, "table name cannot be empty" if table.nil? || table.strip.empty?
84
187
  raise ArgumentError, "query template cannot be empty" if query_template.nil? || query_template.strip.empty?
@@ -1,9 +1,24 @@
1
1
  module Pinot
2
+ # Build a Connection from a static list of broker addresses.
3
+ #
4
+ # conn = Pinot.from_broker_list(["broker1:8099", "broker2:8099"])
5
+ #
6
+ # @param broker_list [Array<String>] broker host:port entries
7
+ # @param http_client [HttpClient, nil] optional pre-configured HTTP client
8
+ # @return [Connection]
2
9
  def self.from_broker_list(broker_list, http_client: nil)
3
10
  config = ClientConfig.new(broker_list: broker_list)
4
11
  from_config(config, http_client: http_client)
5
12
  end
6
13
 
14
+ # Build a Connection backed by a Pinot controller for automatic broker discovery.
15
+ # The controller is polled in the background to keep the broker list fresh.
16
+ #
17
+ # conn = Pinot.from_controller("controller:9000")
18
+ #
19
+ # @param controller_address [String] controller host:port (or http://host:port)
20
+ # @param http_client [HttpClient, nil] optional pre-configured HTTP client
21
+ # @return [Connection]
7
22
  def self.from_controller(controller_address, http_client: nil)
8
23
  config = ClientConfig.new(
9
24
  controller_config: ControllerConfig.new(controller_address: controller_address)
@@ -11,6 +26,25 @@ module Pinot
11
26
  from_config(config, http_client: http_client)
12
27
  end
13
28
 
29
+ # Build a Connection from a fully specified ClientConfig.
30
+ # This is the most flexible factory: it handles all transport types (HTTP,
31
+ # gRPC, ZooKeeper) and wires up the circuit breaker and retry logic from
32
+ # config flags.
33
+ #
34
+ # config = Pinot::ClientConfig.new(
35
+ # broker_list: ["broker:8099"],
36
+ # query_timeout_ms: 5_000,
37
+ # use_multistage_engine: true,
38
+ # max_retries: 2,
39
+ # retry_interval_ms: 100,
40
+ # circuit_breaker_enabled: true
41
+ # )
42
+ # conn = Pinot.from_config(config)
43
+ #
44
+ # @param config [ClientConfig] fully populated config object
45
+ # @param http_client [HttpClient, nil] optional pre-configured HTTP client
46
+ # @return [Connection]
47
+ # @raise [ConfigurationError] if no broker source is specified in config
14
48
  def self.from_config(config, http_client: nil)
15
49
  config.validate!
16
50
 
@@ -1,14 +1,23 @@
1
1
  module Pinot
2
+ # Low-level instrumentation hook that fires after every query executed via
3
+ # Connection#execute_sql. This is the extension point used by
4
+ # Pinot::ActiveSupportNotifications and any custom observability layer.
5
+ #
6
+ # Register a single callback:
7
+ #
8
+ # Pinot::Instrumentation.on_query = ->(event) do
9
+ # MyMetrics.record(event[:table], event[:duration_ms], event[:success])
10
+ # end
11
+ #
12
+ # The event Hash contains:
13
+ # :table => String — table name passed to execute_sql
14
+ # :query => String — SQL string
15
+ # :duration_ms => Float — wall-clock time in milliseconds
16
+ # :success => Boolean — false when an exception was raised
17
+ # :error => Exception or nil — the exception on failure, nil on success
18
+ #
19
+ # Only one callback can be registered at a time. Set on_query= to nil to remove it.
2
20
  module Instrumentation
3
- # Called around every query execution.
4
- # Implement by setting Pinot::Instrumentation.on_query = proc { |event| ... }
5
- # event is a Hash:
6
- # :table => String
7
- # :query => String
8
- # :duration_ms => Float
9
- # :success => Boolean
10
- # :error => Exception or nil
11
-
12
21
  def self.on_query=(callback)
13
22
  @on_query = callback
14
23
  end
@@ -0,0 +1,151 @@
1
+ module Pinot
2
+ # Cursor-based pagination using Pinot's native server-side cursor API.
3
+ #
4
+ # The broker stores the full result set and returns slices on demand.
5
+ # All fetch requests after the first are pinned to the broker that owns
6
+ # the cursor (brokerHost:brokerPort from the initial response), ensuring
7
+ # correct broker affinity.
8
+ #
9
+ # == Obtaining a Paginator
10
+ #
11
+ # paginator = conn.paginate(
12
+ # "SELECT * FROM myTable WHERE col > 0",
13
+ # page_size: 500, # rows per page (default 1000)
14
+ # table: nil, # used only for broker selection; omit for single-broker setups
15
+ # extra_headers: {} # merged into every HTTP request
16
+ # )
17
+ #
18
+ # == Iteration
19
+ #
20
+ # # Page-by-page (each page is a BrokerResponse):
21
+ # paginator.each_page { |resp| process(resp.result_table) }
22
+ #
23
+ # # Row-by-row (each row is an Array of JsonNumber/String cells):
24
+ # paginator.each_row { |row| puts row.map(&:to_s).join(", ") }
25
+ #
26
+ # # Enumerable methods work because #each is aliased to #each_row:
27
+ # rows = paginator.to_a
28
+ # paginator.select { |row| row.first.to_s.to_i > 100 }
29
+ #
30
+ # == Cursor lifecycle
31
+ #
32
+ # The cursor is deleted from the broker automatically after the last page is
33
+ # consumed. Call #delete explicitly for early cleanup (e.g. break out of loop).
34
+ # DELETE failures are swallowed — cursors expire naturally on the broker side.
35
+ #
36
+ # == Protocol
37
+ #
38
+ # 1. POST /query/sql?getCursor=true&numRows=N — submit query, get first page + requestId
39
+ # 2. GET /responseStore/{id}/results?offset=K&numRows=N — fetch subsequent pages
40
+ # 3. DELETE /responseStore/{id} — release cursor (best-effort)
41
+ class Paginator
42
+ include Enumerable
43
+
44
+ DEFAULT_PAGE_SIZE = 1000
45
+
46
+ def initialize(http_client, broker_address, query, page_size:, extra_headers: {})
47
+ raise ArgumentError, "page_size must be a positive integer" unless page_size.is_a?(Integer) && page_size > 0
48
+
49
+ @http_client = http_client
50
+ @broker_address = broker_address
51
+ @query = query
52
+ @page_size = page_size
53
+ @extra_headers = extra_headers
54
+
55
+ @request_id = nil
56
+ @cursor_base = nil # "http://host:port" — set after first response
57
+ @exhausted = false
58
+ end
59
+
60
+ # Yields each page as a BrokerResponse. Returns an Enumerator without a block.
61
+ def each_page
62
+ return enum_for(:each_page) unless block_given?
63
+
64
+ # Submit the query and get the first page + cursor metadata
65
+ first = submit_cursor
66
+ return if first.result_table.nil? || first.result_table.rows.empty?
67
+
68
+ yield first
69
+
70
+ fetched = first.num_rows || first.result_table.rows.size
71
+ total = first.num_rows_result_set || 0
72
+
73
+ while fetched < total
74
+ page = fetch_page(fetched)
75
+ rows = page.result_table&.rows || []
76
+ break if rows.empty?
77
+
78
+ yield page
79
+
80
+ fetched += rows.size
81
+ break if rows.size < @page_size
82
+ end
83
+
84
+ delete
85
+ end
86
+
87
+ # Yields each row Array across all pages. Returns an Enumerator without a block.
88
+ # Aliased as #each so Enumerable methods (.map, .select, .to_a, etc.) work.
89
+ def each(&block)
90
+ return enum_for(:each) unless block_given?
91
+
92
+ each_page do |response|
93
+ response.result_table.rows.each(&block)
94
+ end
95
+ end
96
+
97
+ alias each_row each
98
+
99
+ # Delete the cursor from the broker early (also called automatically after exhaustion).
100
+ def delete
101
+ return unless @request_id && @cursor_base
102
+
103
+ url = "#{@cursor_base}/responseStore/#{@request_id}"
104
+ @http_client.delete(url, headers: json_headers)
105
+ @request_id = nil
106
+ rescue StandardError
107
+ # best-effort; cursor will expire naturally
108
+ end
109
+
110
+ private
111
+
112
+ def submit_cursor
113
+ base = broker_base(@broker_address)
114
+ url = "#{base}/query/sql?getCursor=true&numRows=#{@page_size}"
115
+ body = JSON.generate("sql" => @query)
116
+ resp = @http_client.post(url, body: body, headers: json_headers)
117
+
118
+ raise TransportError, "cursor submit returned HTTP #{resp.code}" unless resp.code.to_i == 200
119
+
120
+ parsed = BrokerResponse.from_json(resp.body)
121
+
122
+ @request_id = parsed.request_id
123
+ @cursor_base = broker_base_from_response(parsed) || base
124
+
125
+ parsed
126
+ end
127
+
128
+ def fetch_page(offset)
129
+ url = "#{@cursor_base}/responseStore/#{@request_id}/results?offset=#{offset}&numRows=#{@page_size}"
130
+ resp = @http_client.get(url, headers: json_headers)
131
+
132
+ raise TransportError, "cursor fetch returned HTTP #{resp.code}" unless resp.code.to_i == 200
133
+
134
+ BrokerResponse.from_json(resp.body)
135
+ end
136
+
137
+ def broker_base(address)
138
+ return address if address.start_with?("http://", "https://")
139
+ "http://#{address}"
140
+ end
141
+
142
+ def broker_base_from_response(resp)
143
+ return nil unless resp.broker_host && resp.broker_port
144
+ "http://#{resp.broker_host}:#{resp.broker_port}"
145
+ end
146
+
147
+ def json_headers
148
+ { "Content-Type" => "application/json; charset=utf-8" }.merge(@extra_headers)
149
+ end
150
+ end
151
+ end
@@ -188,7 +188,11 @@ module Pinot
188
188
  :num_docs_scanned, :num_entries_scanned_in_filter,
189
189
  :num_entries_scanned_post_filter, :total_docs,
190
190
  :time_used_ms, :min_consuming_freshness_time_ms,
191
- :num_groups_limit_reached
191
+ :num_groups_limit_reached,
192
+ # cursor fields — only present when getCursor=true
193
+ :request_id, :num_rows_result_set, :offset, :num_rows,
194
+ :broker_host, :broker_port,
195
+ :submission_time_ms, :expiration_time_ms
192
196
 
193
197
  def self.from_json(json_str)
194
198
  hash = JSON.parse(json_str)
@@ -215,6 +219,19 @@ module Pinot
215
219
  @total_docs = hash["totalDocs"] || 0
216
220
  @time_used_ms = hash["timeUsedMs"] || 0
217
221
  @min_consuming_freshness_time_ms = hash["minConsumingFreshnessTimeMs"] || 0
222
+
223
+ @request_id = hash["requestId"]
224
+ @num_rows_result_set = hash["numRowsResultSet"]
225
+ @offset = hash["offset"]
226
+ @num_rows = hash["numRows"]
227
+ @broker_host = hash["brokerHost"]
228
+ @broker_port = hash["brokerPort"]
229
+ @submission_time_ms = hash["submissionTimeMs"]
230
+ @expiration_time_ms = hash["expirationTimeMs"]
231
+ end
232
+
233
+ def cursor?
234
+ !@request_id.nil?
218
235
  end
219
236
  end
220
237
  end
@@ -40,6 +40,15 @@ module Pinot
40
40
  end
41
41
  end
42
42
 
43
+ def delete(url, headers: {})
44
+ uri = URI.parse(url)
45
+ with_connection(url) do |http|
46
+ req = Net::HTTP::Delete.new(uri.request_uri)
47
+ headers.each { |k, v| req[k] = v }
48
+ http.request(req)
49
+ end
50
+ end
51
+
43
52
  def close
44
53
  @reaper.kill rescue nil
45
54
  @pool_mutex.synchronize do
@@ -183,6 +192,8 @@ module Pinot
183
192
  # 250 = ExecutionTimeoutError (server-side), 400 = BrokerTimeoutError.
184
193
  TIMEOUT_ERROR_CODES = [250, 400].freeze
185
194
 
195
+ attr_reader :http_client
196
+
186
197
  def initialize(http_client:, extra_headers: {}, timeout_ms: nil, logger: nil,
187
198
  max_retries: 0, retry_interval_ms: 200)
188
199
  @http_client = http_client
data/lib/pinot/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pinot
2
- VERSION = "1.25.0"
2
+ VERSION = "1.27.0"
3
3
  end
data/lib/pinot.rb CHANGED
@@ -20,6 +20,7 @@ require_relative "pinot/controller_based_broker_selector"
20
20
  require_relative "pinot/transport"
21
21
  require_relative "pinot/circuit_breaker"
22
22
  require_relative "pinot/query_result"
23
+ require_relative "pinot/paginator"
23
24
  require_relative "pinot/connection"
24
25
  require_relative "pinot/prepared_statement"
25
26
  require_relative "pinot/connection_factory"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinot-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.25.0
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xiang Fu
@@ -103,6 +103,7 @@ files:
103
103
  - LICENSE
104
104
  - README.md
105
105
  - lib/pinot.rb
106
+ - lib/pinot/active_support_notifications.rb
106
107
  - lib/pinot/broker_selector.rb
107
108
  - lib/pinot/circuit_breaker.rb
108
109
  - lib/pinot/config.rb
@@ -115,6 +116,7 @@ files:
115
116
  - lib/pinot/grpc_transport.rb
116
117
  - lib/pinot/instrumentation.rb
117
118
  - lib/pinot/logger.rb
119
+ - lib/pinot/paginator.rb
118
120
  - lib/pinot/prepared_statement.rb
119
121
  - lib/pinot/proto/broker_service.proto
120
122
  - lib/pinot/proto/broker_service_pb.rb