ksql 0.1.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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