ksql 0.1.0.beta
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/.rspec +3 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +88 -0
- data/LICENSE.txt +21 -0
- data/README.md +319 -0
- data/Rakefile +12 -0
- data/examples/quickstart.rb +58 -0
- data/lib/generators/ksql.rb +19 -0
- data/lib/generators/templates/initializer.rb +6 -0
- data/lib/ksql/api/close_query.rb +26 -0
- data/lib/ksql/api/ksql.rb +34 -0
- data/lib/ksql/api/query.rb +32 -0
- data/lib/ksql/api/stream.rb +11 -0
- data/lib/ksql/client.rb +70 -0
- data/lib/ksql/collection.rb +48 -0
- data/lib/ksql/configuration.rb +3 -0
- data/lib/ksql/connection/client.rb +47 -0
- data/lib/ksql/connection/request.rb +27 -0
- data/lib/ksql/connection/response.rb +25 -0
- data/lib/ksql/error.rb +7 -0
- data/lib/ksql/handlers/collection.rb +22 -0
- data/lib/ksql/handlers/raw.rb +20 -0
- data/lib/ksql/handlers/stream.rb +19 -0
- data/lib/ksql/handlers/typed_row.rb +24 -0
- data/lib/ksql/stream.rb +89 -0
- data/lib/ksql/version.rb +5 -0
- data/lib/ksql.rb +38 -0
- data/sig/ksql.rbs +4 -0
- metadata +118 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ksql'
|
4
|
+
|
5
|
+
# The following example is from https://ksqldb.io/quickstart.html
|
6
|
+
|
7
|
+
# * Create a stream
|
8
|
+
|
9
|
+
Ksql::Client.ksql("CREATE STREAM riderLocations (profileId VARCHAR, latitude DOUBLE, longitude DOUBLE)
|
10
|
+
WITH (kafka_topic='locations', value_format='json', partitions=1);")
|
11
|
+
|
12
|
+
# * Create materialized views
|
13
|
+
|
14
|
+
Ksql::Client.ksql("CREATE TABLE currentLocation AS
|
15
|
+
SELECT profileId,
|
16
|
+
LATEST_BY_OFFSET(latitude) AS la,
|
17
|
+
LATEST_BY_OFFSET(longitude) AS lo
|
18
|
+
FROM riderlocations
|
19
|
+
GROUP BY profileId
|
20
|
+
EMIT CHANGES;")
|
21
|
+
|
22
|
+
Ksql::Client.ksql("CREATE TABLE ridersNearMountainView AS
|
23
|
+
SELECT ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1) AS distanceInMiles,
|
24
|
+
COLLECT_LIST(profileId) AS riders,
|
25
|
+
COUNT(*) AS count
|
26
|
+
FROM currentLocation
|
27
|
+
GROUP BY ROUND(GEO_DISTANCE(la, lo, 37.4133, -122.1162), -1);")
|
28
|
+
|
29
|
+
# * Run a push query over the stream
|
30
|
+
|
31
|
+
stream = Ksql::Client.stream('SELECT * FROM riderLocations WHERE GEO_DISTANCE(latitude, longitude, 37.4133, -122.1162) <= 5 EMIT CHANGES;')
|
32
|
+
|
33
|
+
stream.start do |location|
|
34
|
+
File.open('output.log', 'a') do |f|
|
35
|
+
f.write("Latitude: #{location.latitude}, Longitude: #{location.longitude}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# * Populate the stream with events
|
40
|
+
|
41
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('c2309eec', 37.7877, -122.4205);")
|
42
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('18f4ea86', 37.3903, -122.0643);")
|
43
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('4ab5cbad', 37.3952, -122.0813);")
|
44
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('8b6eae59', 37.3944, -122.0813);")
|
45
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('4a7c7b41', 37.4049, -122.0822);")
|
46
|
+
Ksql::Client.ksql("INSERT INTO riderLocations (profileId, latitude, longitude) VALUES ('4ddad000', 37.7857, -122.4011);")
|
47
|
+
|
48
|
+
# * Run a Pull query against the materialized view
|
49
|
+
|
50
|
+
Ksql::Client.query('SELECT * from ridersNearMountainView WHERE distanceInMiles <= 10;')
|
51
|
+
|
52
|
+
# * Clean up ksqlDB
|
53
|
+
|
54
|
+
stream.close
|
55
|
+
|
56
|
+
Ksql::Client.ksql('DROP TABLE IF EXISTS ridersNearMountainView;')
|
57
|
+
Ksql::Client.ksql('DROP TABLE IF EXISTS currentLocation;')
|
58
|
+
Ksql::Client.ksql('DROP STREAM IF EXISTS riderLocations;')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
# Creates the Ksql initializer file for Rails apps.
|
6
|
+
#
|
7
|
+
# @example Invokation from terminal
|
8
|
+
# rails generate ksql
|
9
|
+
#
|
10
|
+
class KsqlGenerator < Rails::Generators::Base
|
11
|
+
desc "Description:\n This creates a Rails initializer for Ksql"
|
12
|
+
|
13
|
+
source_root File.expand_path('templates', __dir__)
|
14
|
+
|
15
|
+
desc 'Configures Ksql to connect to ksqlDB'
|
16
|
+
def generate_layout
|
17
|
+
template 'initializer.rb', 'config/initializers/ksql.rb'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Api
|
5
|
+
class CloseQuery
|
6
|
+
Headers = { 'Accept' => 'application/json' }.freeze
|
7
|
+
|
8
|
+
#
|
9
|
+
# Build the ksqlDB /close-query request
|
10
|
+
#
|
11
|
+
# @param [String] id Query ID
|
12
|
+
# @param [Hash] headers Request headers
|
13
|
+
#
|
14
|
+
# @return [Ksql::Connection::Request] Request instance
|
15
|
+
#
|
16
|
+
def self.build(id, headers:)
|
17
|
+
::Ksql::Connection::Request.new(
|
18
|
+
{ queryId: id },
|
19
|
+
'/close-query',
|
20
|
+
Headers.merge(headers),
|
21
|
+
:post
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Api
|
5
|
+
class Ksql
|
6
|
+
Headers = { 'Accept' => 'application/json' }.freeze
|
7
|
+
|
8
|
+
#
|
9
|
+
# Build the ksqlDB /ksql request
|
10
|
+
#
|
11
|
+
# @param [String] ksql SQL Statement
|
12
|
+
# @param [Integer] command_sequence_number The statements will not be run until all existing commands have completed.
|
13
|
+
# @param [Hash] headers Request headers
|
14
|
+
# @param [Hash] session_variables Variable substitution values
|
15
|
+
# @param [Hash] streams_properties Property overrides to run the statements with
|
16
|
+
#
|
17
|
+
# @return [Ksql::Connection::Request] Request instance
|
18
|
+
#
|
19
|
+
def self.build(ksql, command_sequence_number:, headers:, session_variables:, streams_properties:)
|
20
|
+
::Ksql::Connection::Request.new(
|
21
|
+
{
|
22
|
+
ksql: ksql,
|
23
|
+
commandSequenceNumber: command_sequence_number,
|
24
|
+
sessionVariables: session_variables,
|
25
|
+
streamsProperties: streams_properties,
|
26
|
+
}.compact,
|
27
|
+
'/ksql',
|
28
|
+
Headers.merge(headers),
|
29
|
+
:post
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Api
|
5
|
+
class Query
|
6
|
+
Headers = { 'Accept' => 'application/json' }.freeze
|
7
|
+
|
8
|
+
#
|
9
|
+
# Build the ksqlDB /query-stream request
|
10
|
+
#
|
11
|
+
# @param [String] sql SQL Statement
|
12
|
+
# @param [Hash] headers Request headers
|
13
|
+
# @param [Hash] properties Optional properties for the query
|
14
|
+
# @param [Hash] session_variables Variable substitution values
|
15
|
+
#
|
16
|
+
# @return [Ksql::Connection::Request] Request instance
|
17
|
+
#
|
18
|
+
def self.build(sql, headers:, properties:, session_variables:)
|
19
|
+
::Ksql::Connection::Request.new(
|
20
|
+
{
|
21
|
+
sql: sql,
|
22
|
+
properties: properties,
|
23
|
+
sessionVariables: session_variables
|
24
|
+
}.compact,
|
25
|
+
'/query-stream',
|
26
|
+
self::Headers.merge(headers),
|
27
|
+
:post
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/ksql/client.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
class Client
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Request /close-query endpoint
|
8
|
+
#
|
9
|
+
# @param [String] id Query ID
|
10
|
+
# @param [Hash] headers Request headers
|
11
|
+
#
|
12
|
+
# @return [Array/Hash/String/Integer] Request result
|
13
|
+
#
|
14
|
+
def close_query(id, headers: {})
|
15
|
+
request = Api::CloseQuery.build(id, headers: headers)
|
16
|
+
result = Connection::Client.call_sync(request)
|
17
|
+
Handlers::Raw.handle(result)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Request /ksql endpoint
|
22
|
+
#
|
23
|
+
# @param [String] ksql SQL Statement
|
24
|
+
# @param [Integer] command_sequence_number The statements will not be run until all existing commands have completed.
|
25
|
+
# @param [Hash] headers Request headers
|
26
|
+
# @param [Hash] session_variables Variable substitution values
|
27
|
+
# @param [Hash] streams_properties Property overrides to run the statements with
|
28
|
+
#
|
29
|
+
# @return [Ksql::@type] Request result
|
30
|
+
#
|
31
|
+
def ksql(ksql, command_sequence_number: nil, headers: {}, session_variables: {}, streams_properties: {})
|
32
|
+
request = Api::Ksql.build(ksql, command_sequence_number: command_sequence_number, headers: headers, session_variables: session_variables, streams_properties: streams_properties)
|
33
|
+
result = Connection::Client.call_sync(request)
|
34
|
+
Handlers::TypedRow.handle(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Request /query-stream endpoint synchronously
|
39
|
+
#
|
40
|
+
# @param [String] sql SQL Statement
|
41
|
+
# @param [Hash] headers Request headers
|
42
|
+
# @param [Hash] properties Optional properties for the query
|
43
|
+
# @param [Hash] session_variables Variable substitution values
|
44
|
+
#
|
45
|
+
# @return [Ksql::Connection::Request] Request result
|
46
|
+
#
|
47
|
+
def query(sql, headers: {}, properties: {}, session_variables: {})
|
48
|
+
request = Api::Query.build(sql, headers: headers, properties: properties, session_variables: session_variables)
|
49
|
+
result = Connection::Client.call_sync(request)
|
50
|
+
Handlers::Collection.handle(result)
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Request /query-stream endpoint asynchronously
|
55
|
+
#
|
56
|
+
# @param [String] sql SQL Statement
|
57
|
+
# @param [Hash] headers Request headers
|
58
|
+
# @param [Hash] properties Optional properties for the query
|
59
|
+
# @param [Hash] session_variables Variable substitution values
|
60
|
+
#
|
61
|
+
# @return [Ksql::Stream] Stream instance
|
62
|
+
#
|
63
|
+
def stream(sql, headers: {}, properties: {}, session_variables: {})
|
64
|
+
request = Api::Stream.build(sql, headers: headers, properties: properties, session_variables: session_variables)
|
65
|
+
client, prepared_request = Connection::Client.call_async(request)
|
66
|
+
Handlers::Stream.handle(client, prepared_request)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Ksql
|
6
|
+
class Collection < Struct.new(:rows)
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
#
|
10
|
+
# * Generate a class to fit ksqlDB query returned data
|
11
|
+
# * Populate the collection
|
12
|
+
#
|
13
|
+
# @param [Hash] struct_schema Struct Schema definition
|
14
|
+
# @param [Array] items Collection rows
|
15
|
+
#
|
16
|
+
def initialize(struct_schema, items)
|
17
|
+
struct = Ksql.const_set(id_to_struct(struct_schema['queryId']), Class.new(Struct.new(*struct_schema['columnNames'].map { |n| n.downcase.to_sym })))
|
18
|
+
self.rows = items.map { |i| struct.new(*i) }
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Allow iterations block on Rows
|
23
|
+
#
|
24
|
+
# @param [Block] &block Block to execute on each row
|
25
|
+
#
|
26
|
+
# @return [Array] Rows enumerable
|
27
|
+
#
|
28
|
+
def each(&block)
|
29
|
+
rows.each do |r|
|
30
|
+
yield(r)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
#
|
37
|
+
# Dynamically generate a name based on the ksqlDB Query ID
|
38
|
+
#
|
39
|
+
# @param [String] id ksqlDB Query ID
|
40
|
+
#
|
41
|
+
# @return [String] Collection element Struct name
|
42
|
+
#
|
43
|
+
def id_to_struct(id)
|
44
|
+
struct_id = id.present? ? id.strip.gsub('-', '_') : SecureRandom.hex
|
45
|
+
"Query#{struct_id}Row"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net-http2'
|
4
|
+
|
5
|
+
module Ksql
|
6
|
+
module Connection
|
7
|
+
class Client
|
8
|
+
class << self
|
9
|
+
#
|
10
|
+
# Execute the HTTP2 Sync Request
|
11
|
+
#
|
12
|
+
# @param [Ksql::Connection::Request] request Built request
|
13
|
+
#
|
14
|
+
# @return [Ksql::Connection::Response] HTTP2 Request response
|
15
|
+
#
|
16
|
+
def call_sync(request)
|
17
|
+
response = client.call(*request.to_params)
|
18
|
+
::Ksql::Connection::Response.new(body: response.body, headers: response.headers)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Prepare the HTTP2 Async Request based on the built input request
|
23
|
+
#
|
24
|
+
# @param [Ksql::Connection::Request] request Built request
|
25
|
+
#
|
26
|
+
# @return [Array] Client, Built Async Request
|
27
|
+
#
|
28
|
+
def call_async(request)
|
29
|
+
@@client = client
|
30
|
+
prepared_request = @@client.prepare_request(*request.to_params)
|
31
|
+
return @@client, prepared_request
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
#
|
37
|
+
# Return HTTP2 Client instance
|
38
|
+
#
|
39
|
+
# @return [NetHttp2::Client] HTTP2 Client
|
40
|
+
#
|
41
|
+
def client
|
42
|
+
NetHttp2::Client.new(::Ksql.config.host)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Connection
|
5
|
+
class Request < Struct.new(:body, :endpoint, :headers, :method)
|
6
|
+
#
|
7
|
+
# Returns the request params
|
8
|
+
#
|
9
|
+
# @return [Array] Request params
|
10
|
+
#
|
11
|
+
def to_params
|
12
|
+
return method, endpoint, { body: body.to_json, headers: headers.merge(auth_headers) }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
#
|
18
|
+
# Prepares Authorization headers if Auth is configured
|
19
|
+
#
|
20
|
+
# @return [Hash] Authorization headers
|
21
|
+
#
|
22
|
+
def auth_headers
|
23
|
+
::Ksql.config.auth.present? ? { 'Authorization' => "Basic #{::Ksql.config.auth}" } : {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Connection
|
5
|
+
class Response
|
6
|
+
attr_reader :body, :headers
|
7
|
+
|
8
|
+
STATUS_KEY = ':status'.freeze
|
9
|
+
|
10
|
+
def initialize(body:, headers:)
|
11
|
+
@body = JSON.parse(body)
|
12
|
+
@headers = headers
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Check whether or not a Request has returned an Error
|
17
|
+
#
|
18
|
+
# @return [Boolean] True if error
|
19
|
+
#
|
20
|
+
def error?
|
21
|
+
!headers[STATUS_KEY].to_s.match?(/20[01]/)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/ksql/error.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Handlers
|
5
|
+
class Collection
|
6
|
+
#
|
7
|
+
# Handle the response to generate an Enumerable collection
|
8
|
+
#
|
9
|
+
# @param [Ksql::Connection::Response] response HTTP2 Request response
|
10
|
+
#
|
11
|
+
# @return [Ksql::Collection] Collection result
|
12
|
+
#
|
13
|
+
def self.handle(response)
|
14
|
+
return Ksql::Error.new(response.body) if response.error?
|
15
|
+
|
16
|
+
body_dup = response.body
|
17
|
+
headers = body_dup.shift
|
18
|
+
Ksql::Collection.new(headers, body_dup)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Handlers
|
5
|
+
class Raw
|
6
|
+
#
|
7
|
+
# Return the Response raw parsed body
|
8
|
+
#
|
9
|
+
# @param [Ksql::Connection::Response] response HTTP2 Request response
|
10
|
+
#
|
11
|
+
# @return [Array/Hash/String/Integer] Response parsed body
|
12
|
+
#
|
13
|
+
def self.handle(response)
|
14
|
+
return Ksql::Error.new(response.body) if response.error?
|
15
|
+
|
16
|
+
response.body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Handlers
|
5
|
+
class Stream
|
6
|
+
#
|
7
|
+
# Instanciate a Ksql::Stream class to handle the streamed connection
|
8
|
+
#
|
9
|
+
# @param [Ksql::Connection::Client] client Client instance
|
10
|
+
# @param [Ksql::Connection::Request] request Built request
|
11
|
+
#
|
12
|
+
# @return [Ksql::Stream] Stream instance
|
13
|
+
#
|
14
|
+
def self.handle(client, request)
|
15
|
+
::Ksql::Stream.new(client, request)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
module Handlers
|
5
|
+
class TypedRow
|
6
|
+
#
|
7
|
+
# Define and instanciate an OpenStruct to fit the typed request response
|
8
|
+
#
|
9
|
+
# @param [Ksql::Connection::Response] response HTTP2 Request response
|
10
|
+
#
|
11
|
+
# @return [Ksql::@type] OpenStruct instance
|
12
|
+
#
|
13
|
+
def self.handle(response)
|
14
|
+
return Ksql::Error.new(response.body) if response.error?
|
15
|
+
return response.body unless response.body.present?
|
16
|
+
|
17
|
+
parsed_body = response.body.first
|
18
|
+
row_type = parsed_body.delete('@type').camelize
|
19
|
+
row_class = Ksql.const_defined?(row_type) ? "Ksql::#{row_type}".constantize : Ksql.const_set(row_type, Class.new(OpenStruct))
|
20
|
+
row_class.new(parsed_body)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ksql/stream.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ksql
|
4
|
+
class StreamError < StandardError; end
|
5
|
+
|
6
|
+
class Stream
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
def initialize(client, request)
|
10
|
+
@client = client
|
11
|
+
@request = request
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Close the streaming connection
|
16
|
+
#
|
17
|
+
def close
|
18
|
+
raise StreamError.new('The stream hasn\'t stared!') unless @id.present?
|
19
|
+
|
20
|
+
@client.close
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Specify the action to take when the Streaming connection gets closed.
|
25
|
+
#
|
26
|
+
# @param [Block] &block Code to execute on connection closure
|
27
|
+
#
|
28
|
+
def on_close(&block)
|
29
|
+
@client.on(:close) { yield }
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Specify the action to take when the Streaming connection raises an error.
|
34
|
+
#
|
35
|
+
# @param [Block] &block Code to execute when connection errors occur
|
36
|
+
#
|
37
|
+
def on_error(&block)
|
38
|
+
@client.on(:error) { |e| yield(e) }
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Streaming connection handler
|
43
|
+
#
|
44
|
+
# * Start the stream
|
45
|
+
# * Wrap the stream events into OpenStruct instances
|
46
|
+
# * Execute the passed block
|
47
|
+
#
|
48
|
+
# @param [Block] &block Code to execute each time an event arrives
|
49
|
+
#
|
50
|
+
def start(&block)
|
51
|
+
@headers = {}
|
52
|
+
|
53
|
+
@request.on(:headers) { |headers| @headers = headers }
|
54
|
+
|
55
|
+
@request.on(:body_chunk) do |body|
|
56
|
+
next unless body.present?
|
57
|
+
|
58
|
+
response = Ksql::Connection::Response.new(body: body, headers: @headers)
|
59
|
+
raise Ksql::StreamError.new(response.body['message']) if response.error?
|
60
|
+
|
61
|
+
if response.body.is_a? Hash
|
62
|
+
@event_class = build_event_class(response.body)
|
63
|
+
next
|
64
|
+
else
|
65
|
+
event = @event_class.new(*response.body)
|
66
|
+
end
|
67
|
+
|
68
|
+
yield(event)
|
69
|
+
end
|
70
|
+
|
71
|
+
@client.call_async(@request)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
#
|
77
|
+
# Define a Struct class to fit the streaming data into.
|
78
|
+
#
|
79
|
+
# @param [Hash] schema Query schema
|
80
|
+
#
|
81
|
+
# @return [Ksql::Row] Stream event class
|
82
|
+
#
|
83
|
+
def build_event_class(schema)
|
84
|
+
@id = schema['queryId']
|
85
|
+
row_const = "Stream#{schema['queryId'].gsub('-', '_')}Row"
|
86
|
+
Ksql.const_defined?(row_const) ? "Ksql::#{row_const}".constantize : Ksql.const_set(row_const, Class.new(Struct.new(*schema['columnNames'].map { |c| c.downcase.to_sym })))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/ksql/version.rb
ADDED
data/lib/ksql.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
require 'json'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
require_relative 'ksql/api/close_query'
|
9
|
+
require_relative 'ksql/api/ksql'
|
10
|
+
require_relative 'ksql/api/query'
|
11
|
+
require_relative 'ksql/api/stream'
|
12
|
+
|
13
|
+
require_relative 'ksql/handlers/collection'
|
14
|
+
require_relative 'ksql/handlers/raw'
|
15
|
+
require_relative 'ksql/handlers/stream'
|
16
|
+
require_relative 'ksql/handlers/typed_row'
|
17
|
+
|
18
|
+
require_relative 'ksql/connection/client'
|
19
|
+
require_relative 'ksql/connection/request'
|
20
|
+
require_relative 'ksql/connection/response'
|
21
|
+
|
22
|
+
require_relative 'ksql/client'
|
23
|
+
require_relative 'ksql/collection'
|
24
|
+
require_relative 'ksql/configuration'
|
25
|
+
require_relative 'ksql/error'
|
26
|
+
require_relative 'ksql/stream'
|
27
|
+
|
28
|
+
require_relative 'ksql/version'
|
29
|
+
|
30
|
+
module Ksql
|
31
|
+
def self.config
|
32
|
+
@config ||= Ksql::Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
yield(config)
|
37
|
+
end
|
38
|
+
end
|
data/sig/ksql.rbs
ADDED