Skip to content

Commit 90cc0a7

Browse files
authoredApr 4, 2024
Merge pull request #25 from jeff303/demo-bugfixes
Demo bugfixes
2 parents 2427566 + cf19037 commit 90cc0a7

File tree

7 files changed

+94
-24
lines changed

7 files changed

+94
-24
lines changed
 

‎README.md

+25-6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ record handling.
8383
::kcr/value-type "json" ; the format the value should be parsed in (optional)
8484
::kcr/value-parse-fn (fn [b] nil) ; a custom function to parse the value (passed as a byte array)
8585
; if both are omitted, the value won't be parsed
86+
::kcr/key->clj? true ; if true, the key will be converted to a Clojure map after parsing the bytes
87+
::kcr/val->clj? true ; if true, the value will be converted to a Clojure map after parsing the bytes
8688
::kcr/include-headers? true ; whether to include message headers in extra-metadata
8789
::kcr/include-offset? true ; whether to include the message offset in extra-metadata
8890
::kcr/include-partition? true ; whether to include the PartitionInfo in extra-metadata
@@ -159,7 +161,16 @@ can simply be ignored/not passed along from the `map-record-fn`
159161
There is an uberjar build of `kc-repl` that is capable of running basic operations using `java`. To use this mode,
160162
download (or build) the uberjar, then run:
161163

162-
`java -Dclojure.core.async.pool-size=1 -jar kc-repl-*.jar /path/to/consumer.properties`
164+
#### Without Custom Type Handlers
165+
166+
```shell
167+
java -Dclojure.core.async.pool-size=1 -jar target/kc-repl-<version>-standalone.jar /path/to/client.properties
168+
```
169+
170+
#### With Custom Type Handlers
171+
```shell
172+
rlwrap java -Dclojure.core.async.pool-size=1 -cp type-handlers/avro/target/kc-repl-type-handler-avro-<version>-avro-handler.jar:target/kc-repl-<version>-standalone.jar us.jeffevans.kc_repl.java_main /path/to/client.properties
173+
```
163174

164175
The syntax for the Java runtime is more similar to the original Kafka command line tools. You can type `help` for a
165176
more complete list.
@@ -186,11 +197,13 @@ advancing, such as `seek-while` and `seek-until`).
186197

187198
#### Extension
188199

189-
The current key and message types that can be parsed are limited (only text and JSON). There is a multimethod,
190-
`parse-type`, that provides an extension point through which parsing of new types can be supported. In the future,
191-
support for some of these will be added to this project (as submodules that can be built separately and loaded as
192-
needed). In theory, any Clojure code that defines a new method for this multimethod can take advantage of this
193-
mechanism to read additional types (provided the bytecode is available on the classpath).
200+
The base implementation supports type handling for text and json data. Additional type handlers can be added by:
201+
* extending the `us.jeffevans.kc-repl.type-handlers/type-handler` protocol
202+
* defining a `us.jeffevans.kc-repl.type-handlers/create-type-handler` multimethod implementation
203+
* adding a `kc-repl-handler-ns.txt` file to your classpath (ex: in `resources`) that indicates the namespace that
204+
should be loaded to allow for this type handler to be picked up at runtime
205+
206+
Refer to the `avro` and `protobuf` implementations under `type-handlers` for reference implementations.
194207

195208
### Building the Uberjar
196209
`clojure -J-Dclojure.core.async.pool-size=1 -T:build ci`
@@ -202,6 +215,12 @@ mechanism to read additional types (provided the bytecode is available on the cl
202215

203216
Bugs and enhancement requests will be tracked in the GitHub project (via issues).
204217

218+
## Demo Videos
219+
220+
* [Java Main demo](https://www.loom.com/share/15844e1d06454adfb43ba0652788505f?sid=8ced88db-9867-4034-ac0b-75c7191760f4)
221+
* [Clojure nREPL demo - mapping and reduction with Avro data](https://www.loom.com/share/27f35ee3788d48f08aef3fb5f69e888a?sid=7df588c1-1c15-40f7-8118-4ae6d7bee7a1)
222+
* [Clojure nREPL demo - conditional seeking with Avro data](https://www.loom.com/share/10855fbf73eb44f09333dea2a6095553?sid=532c2667-8d58-4f2e-9317-c3c48baf611b)
223+
205224
## License
206225

207226
Copyright © 2022 Jeff Evans

‎src/us/jeffevans/kc_repl.clj

+57-11
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@
4646
{}
4747
(.toArray headers))))
4848

49-
(defn- parse-type-fn-with-handlers [^String type-name type-handlers]
49+
(defn- parse-type-fn-with-handlers [^String type-name type-handlers v->clj?]
5050
(if-let [handler (get type-handlers type-name)]
51-
(partial th/parse-bytes handler)
51+
(if v->clj?
52+
(fn [topic v]
53+
(->> (th/parse-bytes handler topic v)
54+
(th/->clj handler)))
55+
(partial th/parse-bytes handler))
5256
(throw (ex-info (format "no type handler registered for %s" type-name) {::type-handler-keys (keys type-handlers)}))))
5357

5458
(defn- make-default-record-handler-fn [^String value-type type-handlers]
@@ -60,12 +64,12 @@
6064

6165
(defn- make-record-handler-fn [{:keys [::key-type ::key-parse-fn ::value-type ::value-parse-fn ::map-record-fn
6266
::include-headers? ::include-topic? ::include-partition? ::include-offset?
63-
::include-timestamp?] :as record-handling} type-handlers]
67+
::include-timestamp? ::key->clj? ::val->clj?] :as record-handling} type-handlers]
6468
(let [k-parse-fn (cond (fn? key-parse-fn)
6569
key-parse-fn
6670

6771
(some? key-type)
68-
(parse-type-fn-with-handlers key-type type-handlers)
72+
(parse-type-fn-with-handlers key-type type-handlers key->clj?)
6973

7074
;; by default, don't try to parse the key
7175
true
@@ -74,7 +78,7 @@
7478
value-parse-fn
7579

7680
(some? value-type)
77-
(parse-type-fn-with-handlers value-type type-handlers)
81+
(parse-type-fn-with-handlers value-type type-handlers val->clj?)
7882

7983
;; by default, don't try to parse the value
8084
true
@@ -247,9 +251,24 @@
247251
(print beg))))
248252
(idle-poll! consumer)))
249253

254+
(defn- remove-consumer-assignment!
255+
"Removes the given `assignment` (a TopicPartition) from the given `consumer`. If the consumer did not already
256+
have it assigned, this is a no-op."
257+
[active-assignments-atom ^KafkaConsumer consumer assignments]
258+
(doseq [topic (keys assignments)]
259+
(let [[part] (get assignments topic)
260+
tp (TopicPartition. topic part)
261+
cur-assignments (set (.assignment consumer))]
262+
(swap! active-assignments-atom disj tp)
263+
(when (contains? cur-assignments tp)
264+
(let [new-assignments (disj cur-assignments tp)]
265+
(.assign consumer new-assignments)))))
266+
(idle-poll! consumer)
267+
::ok)
268+
250269
(defn- assign-and-seek! [^KafkaConsumer consumer assignments active-assignments-atom]
251270
(doseq [topic (keys assignments)]
252-
(let [[part offset?] (get assignments topic)
271+
(let [[part ^long offset?] (get assignments topic)
253272
tp (TopicPartition. topic part)]
254273
(add-consumer-assignment! active-assignments-atom consumer tp true)
255274
(if (some? offset?)
@@ -260,6 +279,10 @@
260279
::new-assignment]}]
261280
(assign-and-seek! consumer new-assignment active-assignments-atom))
262281

282+
(defmethod handle-repl->client-message ::unassign [^KafkaConsumer consumer {:keys [::active-assignments-atom
283+
::remove-assignments]}]
284+
(remove-consumer-assignment! active-assignments-atom consumer remove-assignments))
285+
263286
(defmethod handle-repl->client-message ::seek [^KafkaConsumer consumer
264287
{:keys [::active-assignments-atom
265288
::to
@@ -428,6 +451,13 @@
428451
::active-assignments-atom active-assignments-atom
429452
::new-assignment {topic [partition offset?]}}))
430453

454+
(defn- unassign!
455+
""
456+
[to-consumer-chan from-consumer-chan active-assignments-atom topic partition & [offset?]]
457+
(consumer-roundtrip to-consumer-chan from-consumer-chan {::message-type ::unassign
458+
::active-assignments-atom active-assignments-atom
459+
::remove-assignments {topic [partition]}}))
460+
431461

432462
(s/def ::consumer-record-map-fn fn?)
433463
(s/def ::poll-xf-reducing-fn fn?)
@@ -491,11 +521,20 @@
491521
(s/def ::value-type string?)
492522
(s/def ::key-parse-fn fn?)
493523
(s/def ::value-parse-fn fn?)
524+
525+
(s/def ::key->clj? boolean?)
526+
(s/def ::val->clj? boolean?)
494527
(s/def ::map-record-fn fn?)
528+
529+
(s/def ::include-headers? boolean?)
530+
(s/def ::include-topic? boolean?)
531+
(s/def ::include-partition? boolean?)
532+
(s/def ::include-offset? boolean?)
533+
(s/def ::include-timestamp? boolean?)
495534
(s/def ::reducing-fn fn?)
496535

497536
(s/def ::record-handling-opts (s/keys :opt [::key-type ::value-type ::key-parse-fn ::value-parse-fn ::map-record-fn
498-
::reducing-fn]))
537+
::reducing-fn ::key->clj? ::val->clj?]))
499538

500539
(defn record-handling-opts->poll-xf-args
501540
"For the given record-handling-opts, and type-handlers, produce a poll-xf-args map, which can then be passed into a
@@ -563,6 +602,7 @@
563602
(read-from [this topic part offset num-msg record-handling-opts])
564603
(last-read [this record-handling-opts])
565604
(assign [this topic part offset])
605+
(unassign [this topic part])
566606
(seek [this offset])
567607
(seek+ [this offset+])
568608
(seek- [this offset-])
@@ -594,6 +634,8 @@
594634
(last-read* last-read-records-atom record-handling-opts type-handlers))
595635
(assign [_ topic part offset]
596636
(assign! to-consumer-chan from-consumer-chan active-assignments-atom topic part offset))
637+
(unassign [_ topic part]
638+
(unassign! to-consumer-chan from-consumer-chan active-assignments-atom topic part))
597639
(seek [_ offset]
598640
(seek! to-consumer-chan from-consumer-chan {::active-assignments-atom active-assignments-atom
599641
::to offset}))
@@ -804,14 +846,17 @@
804846
^long ^{:doc "The offset to start reading from", :default 0} offset
805847
^long ^{:doc "The number of messages to read", :default 10} num-msg
806848
;; TODO: figure out how to get by with something like msg-format instead of record-handling-opts in Java
807-
^{:doc "Record handling options (see documentation)"} record-handling-opts)
849+
^{:doc "Record handling options (see documentation)", ::required? true} record-handling-opts)
808850
(defop ^{:doc "Read from the current active assignments"} poll kcr-client true true
809851
^long ^{:doc "The number of messages to read", :default 10} num-msg
810852
^{:doc "Record handling options (see documentation)", ::required? true} record-handling-opts)
811853
(defop ^{:doc "Add a topic/partition to the active assignments"} assign kcr-client true true
812854
^{:doc "The topic name to read from", ::required? true} topic
813855
^long ^{:doc "The topic partition to read from", ::required? true} part
814856
^long ^{:doc "The offset to start reading from", ::required? true} offset)
857+
(defop ^{:doc "Remove a topic/partition from the active assignments"} unassign kcr-client true true
858+
^{:doc "The topic name to read from", ::required? true} topic
859+
^long ^{:doc "The topic partition to read from", ::required? true} part)
815860
(defop ^{:doc "Change the offset to poll from next for the given topic/partition"} seek kcr-client true true
816861
^long ^{:doc "The offset to seek to", ::required? true} offset)
817862
(defop ^{:doc "Increment the offset for the current assignment by the given amount"} seek+ kcr-client true true
@@ -820,16 +865,17 @@
820865
^long ^{:doc "The number of offsets to recede by", ::required? true} by)
821866
(defop ^{:doc "Seek forward in the message stream while some condition is met"} seek-while kcr-client false false
822867
^{:doc "Conditional (while) function; operates on the mapped record"} while-fn
823-
^{:doc "Record handling options (see documentation)"} record-handling-opts)
868+
^{:doc "Record handling options (see documentation)", ::required? true} record-handling-opts)
824869
(defop ^{:doc "Seek forward in the message stream while some condition is met"} seek-until kcr-client false false
825870
^{:doc "Conditional (until) function; operates on the mapped record"} while-fn
826-
^{:doc "Record handling options (see documentation)"} record-handling-opts)
871+
^{:doc "Record handling options (see documentation)", ::required? true} record-handling-opts)
827872
;; will revisit this once we have a better Java args parser
828873
(defop ^{:doc "Set a config option for a type handler arr", ::print-offsets? false} set-type-handler-config! kcr-client true true
829874
^{:doc "The type on which to set the config option", ::required? true} type-name
830875
^{:doc "The config option's key", ::required? true} k
831876
^{:doc "The config option's arguments", ::required? true, ::varargs? true} args)
832-
(defop ^{:doc "Print the last read results"} last-read kcr-client true true)
877+
(defop ^{:doc "Print the last read results"} last-read kcr-client true true
878+
^{:doc "Record handling options (see documentation)", ::required? true} record-handling-opts)
833879
(defop ^{:doc "Stop the client and disconnect the session"} stop kcr-client true false)
834880
(intern (the-ns 'user) 'help print-clj-help)
835881
(when (ifn? run-body)

‎src/us/jeffevans/kc_repl/java_main.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[us.jeffevans.kc-repl :as kcr]
66
[us.jeffevans.kc-repl.type-handler-load :as thl]
77
[instaparse.core :as insta])
8-
(:gen-class :name us.jeffevans.KcRepl))
8+
(:gen-class))
99

1010
(defn- get-input-line []
1111
(read-line))
@@ -81,7 +81,7 @@
8181
(let [continue? (atom true)]
8282
(while @continue?
8383
(let [input (get-input-line)
84-
[op & args] (if (nil? input) ["stop"] (parse-line-as-cli-args input))
84+
[op & args] (if (str/blank? input) ["stop"] (parse-line-as-cli-args input))
8585
{:keys [::kcr/invoke-fn ::kcr/opts-spec ::kcr/print-offsets?]} (get @#'kcr/java-cmds op)]
8686
(log/tracef "got input %s, op %s, args %s, opts-spec %s" input op (pr-str args) (pr-str opts-spec))
8787
(println)

‎src/us/jeffevans/kc_repl/type_handler_load.clj

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
(require (symbol type-handler-ns))))
1717

1818
(defn load-type-handlers []
19-
(log/debug "trying to load type handlers from jars")
19+
(log/debugf "trying to load type handlers from jars")
2020
(doseq [^JarFile jar-file (cp/classpath-jarfiles)]
21+
(log/debugf "checking jar: %s" (.getName jar-file))
2122
(doseq [entry (-> (.entries jar-file)
2223
enumeration-seq)]
2324
(when (= marker-file-nm (.getName entry))

‎test-common/src/us/jeffevans/kc_repl/test_containers.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
(def tc-cfg (TestcontainersConfiguration/getInstance))
1010

11-
(def ^:const cp-version "The Confluent Platform base version, to use for container images" "7.1.0")
11+
(def ^:const cp-version "The Confluent Platform base version, to use for container images" "7.2.0")
1212

1313
(def zk-network-alias "zookeeper")
1414

‎test/us/jeffevans/kc_repl_test.clj

+6-1
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,10 @@
136136
::kcr/reducing-fn reducing-fn
137137
::kcr/map-record-fn map-rec-fn})))
138138
(testing " and the new offset is correct (we should now be at offset 10 for test-topic:0)"
139-
(is (= {(TopicPartition. "test-topic" 0) [10 true]} (kcr/current-assignments tc/*kcr-client*)))))))
139+
(is (= {(TopicPartition. "test-topic" 0) [10 true]} (kcr/current-assignments tc/*kcr-client*))))
140+
(testing " and we can unassign the test-topic:0 assignment and switch to test-topic:1"
141+
;; read from partition 1 to add that to active assignments
142+
(kcr/read-from tc/*kcr-client* "test-topic" 1 0 1 "json")
143+
(kcr/unassign tc/*kcr-client* "test-topic" 0)
144+
(is (= {(TopicPartition. "test-topic" 1) [0 true]} (kcr/current-assignments tc/*kcr-client*)))))))
140145

‎type-handlers/src/us/jeffevans/kc_repl/type_handlers.clj

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
(ns us.jeffevans.kc-repl.type-handlers
2-
(:gen-class :name us.jeffevans.KcRepl))
1+
(ns us.jeffevans.kc-repl.type-handlers)
32

43
(defprotocol type-handler
54
(parse-bytes [this ^String topic ^bytes b])

0 commit comments

Comments
 (0)
Please sign in to comment.