Hi Damian, Thanks for your comments. I think providing to users *interface* rather than *abstract class* should be preferred (Matthias also raised this issue ), anyway I changed the corresponding parts of KIP.
Regarding with passing additional contextual information, I think it is a tradeoff, 1) first, we fix the context parameter for *init() *method in another PR and solve Rich functions afterwards 2) first, we fix the requested issues on jira ([1-3]) with providing (not-complete) Rich functions and integrate the context parameters to this afterwards (like improvement) To me, the second approach seems more incremental. However you are right, the names might confuse the users. [1] https://issues.apache.org/jira/browse/KAFKA-4218 [2] https://issues.apache.org/jira/browse/KAFKA-4726 [3] https://issues.apache.org/jira/browse/KAFKA-3745 Cheers, Jeyhun On Fri, May 19, 2017 at 10:42 AM Damian Guy <damian....@gmail.com> wrote: > Hi, > > I see you've removed the `ProcessorContext` from the RichFunction which is > good, but why is it a `RichFunction`? I'd have expected it to pass some > additional contextual information, i.e., the `RecordContext` that contains > just the topic, partition, timestamp, offset. I'm ok with it not passing > this contextual information, but is the naming incorrect? I'm not sure, > tbh. I'm wondering if we should drop `RichFunctions` until we can do it > properly with the correct context? > > Also, i don't like the abstract classes: RichValueMapper, RichValueJoiner, > RichInitializer etc. Why can't they not just be interfaces? Generally we > should provide users with Intefaces to code against, not classes. > > Thanks, > Damian > > On Fri, 19 May 2017 at 00:50 Jeyhun Karimov <je.kari...@gmail.com> wrote: > > > Hi, > > > > Thanks. I initiated the PR as well, to get a better overview. > > > > The only reason that I used abstract class instead of interface for Rich > > functions is that in future if we will have some AbstractRichFunction > > abstract classes, > > we can easily extend: > > > > public abstract class RichValueMapper<K, V, VR> implements > > ValueMapperWithKey<K, V, VR>, RichFunction *extends > AbstractRichFunction*{ > > } > > With interfaces we are only limited to interfaces for inheritance. > > > > However, I think we can easily change it (from abstract class -> > interface) > > if you think interface is a better fit. > > > > > > Cheers, > > Jeyhun > > > > > > On Fri, May 19, 2017 at 1:00 AM Matthias J. Sax <matth...@confluent.io> > > wrote: > > > > > Thanks for the update and explanations. The KIP is quite improved now > -- > > > great job! > > > > > > One more question: Why are RichValueMapper etc abstract classes and not > > > interfaces? > > > > > > > > > Overall, I like the KIP a lot! > > > > > > > > > -Matthias > > > > > > > > > On 5/16/17 7:03 AM, Jeyhun Karimov wrote: > > > > Hi, > > > > > > > > Thanks for your comments. > > > > > > > > I think supporting Lambdas for `withKey` and `AbstractRichFunction` > > > >> don't go together, as Lambdas are only supported for interfaces > AFAIK. > > > > > > > > > > > > Maybe I misunderstood your comment. > > > > *withKey* and and *withOnlyValue* are interfaces. So they don't have > > > direct > > > > relation with *AbstractRichFunction*. > > > > *withKey* and and *withOnlyValue* interfaces have only one method , > so > > > we > > > > can use lambdas. > > > > Where does the *AbstractRichFunction* comes to play? Inside Rich > > > functions. > > > > And we use Rich functions in 2 places: > > > > > > > > 1. User doesn't use rich functions. Just regular *withKey* and and > > > > *withOnlyValue* interfaces(both support lambdas) . We get those > > > interfaces > > > > and wrap into Rich function while building the topology, and send to > > > > Processor. > > > > 2. User does use rich functions (Rich functions implement *withKey* > > > > interface). As a result no lamdas here by definition. In this case, > > while > > > > building the topology we do a type check if the object type is > > > > *withKey* or *RichFunction*. > > > > > > > > So *AbstractRichFunction* is just syntactic sugar for Rich functions > > and > > > > does not affect using lambdas. > > > > > > > > Thus, if we want to support Lambdas for `withKey`, we need to have a > > > >> interface approach like this > > > >> - RichFunction -> only adding init() and close() > > > >> - ValueMapper > > > >> - ValueMapperWithKey > > > >> - RichValueMapper extends ValueMapperWithKey, RichFunction > > > > > > > > > > > > As I said above, currently we support lambdas for *withKey* > interfaces > > as > > > > well. However, I agree with your idea and I will remove the > > > > AbstractRichFunction from the design. > > > > > > > > As an alternative, we could argue, that it is sufficient to support > > > >> Lambdas for the "plain" API only, but not for any "extended API". > For > > > >> this, RichFunction could add key+init+close and AbstractRichFunction > > > >> would allow to only care about getting the key. > > > >> Not sure, which one is better. I don't like the idea of more > > overloaded > > > >> methods to get Lambdas for `withKey` interfaces too much because we > > have > > > >> already so many overlaods. On the other hand, I do see value in > > > >> supporting Lambdas for `withKey`. > > > > > > > > > > > > Just to clarify, with current design we have only one extra > overloaded > > > > method per *withOnlyValue* interface: which is *withKey* version of > > > > particular interface. > > > > We don't need extra overload for Rich function as Rich function > > > implements > > > > *withKey* interface as a result they have same type. We differentiate > > > them > > > > while building the topology. > > > > We supported lambdas for *withKey* APIs because of the comment: > > > > > > > > @Jeyhun: I did not put any thought into this, but can we have a > design > > > >> that allows for both? Also, with regard to lambdas, it might make > > sense > > > >> to allow for both `V -> newV` and `(K, V) -> newV` ? > > > > > > > > > > > > However, I don't think that this complicates the overall design > > > > significantly. > > > > > > > > > > > > Depending on what we want to support, it might make sense to > > > >> include/exclude RichFunctions from this KIP -- and thus, this also > > > >> determines if we should have a "ProcessorContext KIP" before driving > > > >> this KIP further. > > > > > > > > > > > > Based on our discussion I think we should keep Rich functions as I > > don't > > > > think that they bring extra layer of overhead to library. > > > > > > > > Any comments are appreciated. > > > > > > > > Cheers, > > > > Jeyhun > > > > > > > > > > > > On Tue, May 16, 2017 at 12:10 AM Matthias J. Sax < > > matth...@confluent.io> > > > > wrote: > > > > > > > >> Jeyhun, > > > >> > > > >> thanks for the update. > > > >> > > > >> I think supporting Lambdas for `withKey` and `AbstractRichFunction` > > > >> don't go together, as Lambdas are only supported for interfaces > AFAIK. > > > >> > > > >> Thus, if we want to support Lambdas for `withKey`, we need to have a > > > >> interface approach like this > > > >> > > > >> - RichFunction -> only adding init() and close() > > > >> > > > >> - ValueMapper > > > >> - ValueMapperWithKey > > > >> > > > >> - RichValueMapper extends ValueMapperWithKey, RichFunction > > > >> > > > >> For this approach, AbstractRichFunction does not make sense anymore, > > as > > > >> the only purpose of `RichFunction` is to allow the implementation of > > > >> init() and close() -- if you don't want those, you would implement a > > > >> different interface (ie, ValueMapperWithKey) > > > >> > > > >> As an alternative, we could argue, that it is sufficient to support > > > >> Lambdas for the "plain" API only, but not for any "extended API". > For > > > >> this, RichFunction could add key+init+close and AbstractRichFunction > > > >> would allow to only care about getting the key. > > > >> > > > >> Not sure, which one is better. I don't like the idea of more > > overloaded > > > >> methods to get Lambdas for `withKey` interfaces too much because we > > have > > > >> already so many overlaods. On the other hand, I do see value in > > > >> supporting Lambdas for `withKey`. > > > >> > > > >> Depending on what we want to support, it might make sense to > > > >> include/exclude RichFunctions from this KIP -- and thus, this also > > > >> determines if we should have a "ProcessorContext KIP" before driving > > > >> this KIP further. > > > >> > > > >> Thoughts? > > > >> > > > >> > > > >> > > > >> > > > >> -Matthias > > > >> > > > >> > > > >> On 5/15/17 11:01 AM, Jeyhun Karimov wrote: > > > >>> Hi, > > > >>> > > > >>> Sorry for super late response. Thanks for your comments. > > > >>> > > > >>> I am not an expert on Lambdas. Can you elaborate a little bit? I > > cannot > > > >>>> follow the explanation in the KIP to see what the problem is. > > > >>> > > > >>> > > > >>> - From [1] says "A functional interface is an interface that has > just > > > one > > > >>> abstract method, and thus represents a single function contract". > > > >>> So basically once we extend some interface from another (in our > case, > > > >>> ValueMapperWithKey from ValueMapper) we cannot use lambdas in the > > > >> extended > > > >>> interface. > > > >>> > > > >>> > > > >>> Further comments: > > > >>>> - The KIP get a little hard to read -- can you maybe reformat the > > > wiki > > > >>>> page a little bit? I think using `CodeBlock` would help. > > > >>> > > > >>> > > > >>> - I will work on the KIP. > > > >>> > > > >>> - What about KStream-KTable joins? You don't have overlaods added > > for > > > >>>> them. Why? (Even if I still hope that we don't need to add any new > > > >>>> overloads) > > > >>> > > > >>> > > > >>> - Actually there are more than one Processor and public APIs to be > > > >>> changed (KStream-KTable > > > >>> joins is one case). However all of them has similar structure: we > > > >> overload > > > >>> the *method* with *methodWithKey*, > > > >>> wrap it into the Rich function, send to processor and inside the > > > >> processor > > > >>> call *init* and *close* methods of the Rich function. > > > >>> As I wrote in KIP, I wanted to demonstrate the overall idea with > only > > > >>> *ValueMapper* as the same can be applied to all changes. > > > >>> Anyway I will update the KIP. > > > >>> > > > >>> - Why do we need `AbstractRichFunction`? > > > >>> > > > >>> > > > >>> Instead of overriding the *init(ProcessorContext p)* and* close()* > > > >> methods > > > >>> in every Rich function with empty body like: > > > >>> > > > >>> @Override > > > >>> void init(ProcessorContext context) {} > > > >>> > > > >>> @Override > > > >>> void close () {} > > > >>> > > > >>> I thought that we can override them once in *AbstractRichFunction* > > and > > > >>> extent new Rich functions from *AbstractRichFunction*. > > > >>> Basically this can eliminate code copy-paste and ease the > > maintenance. > > > >>> > > > >>> - What about interfaces Initializer, ForeachAction, Merger, > > Predicate, > > > >>>> Reducer? I don't want to say we should/need to add to all, but we > > > should > > > >>>> discuss all of them and add where it does make sense (e.g., > > > >>>> RichForachAction does make sense IMHO) > > > >>> > > > >>> > > > >>> Definitely agree. As I said, the same technique applies to all this > > > >>> interfaces and I didn't want to explode the KIP, just wanted to > give > > > the > > > >>> overall intuition. > > > >>> However, I will update the KIP as I said. > > > >>> > > > >>> > > > >>> Btw: I like the hierarchy `ValueXX` -- `ValueXXWithKey` -- > > > `RichValueXX` > > > >>>> in general -- but why can't we do all this with interfaces only? > > > >>> > > > >>> > > > >>> Sure we can. However the main intuition is we should not force > users > > to > > > >>> implement *init(ProcessorContext)* and *close()* functions every > time > > > >> they > > > >>> use Rich functions. > > > >>> If one needs, she can override the respective methods. However, I > am > > > open > > > >>> for discussion. > > > >>> > > > >>> > > > >>> I'd rather not see the use of `ProcessorContext` spread any > further > > > than > > > >>>> it currently is. So maybe we need another KIP that is done before > > > this? > > > >>>> Otherwise i think the scope of this KIP is becoming too large. > > > >>> > > > >>> > > > >>> That is good point. I wanted to make *init(ProcessorContext)* > method > > > >>> persistent among the library (which use ProcessorContext as an > > input), > > > >>> therefore I put *ProcessorContext* as an input. > > > >>> So the important question is that (as @dguy and @mjsax mentioned) > > > whether > > > >>> continue this KIP without providing users an access to > > > *ProcessorContext* > > > >>> (change *init (ProcessorContext)* to * init()* ) or > > > >>> initiate another KIP before this. > > > >>> > > > >>> [1] > > > >>> http://cr.openjdk.java.net/~mr/se/8/java-se-8-pfd-spec/java- > > > >> se-8-jls-pfd-diffs.pdf > > > >>> > > > >>> > > > >>> Cheers, > > > >>> Jeyhun > > > >>> > > > >>> On Mon, May 15, 2017 at 7:15 PM, Damian Guy <damian....@gmail.com> > > > >> wrote: > > > >>> > > > >>>> I'd rather not see the use of `ProcessorContext` spread any > further > > > >> than > > > >>>> it currently is. So maybe we need another KIP that is done before > > > this? > > > >>>> Otherwise i think the scope of this KIP is becoming too large. > > > >>>> > > > >>>> > > > >>>> On Mon, 15 May 2017 at 18:06 Matthias J. Sax < > matth...@confluent.io > > > > > > >>>> wrote: > > > >>>> > > > >>>>> I agree that that `ProcessorContext` interface is too broad in > > > general > > > >>>>> -- this is even true for transform/process, and it's also > reflected > > > in > > > >>>>> the API improvement list we want to do. > > > >>>>> > > > >>>>> https://cwiki.apache.org/confluence/display/KAFKA/ > > > >>>> Kafka+Streams+Discussions > > > >>>>> > > > >>>>> So I am wondering, if you question the `RichFunction` approach in > > > >>>>> general? Or if you suggest to either extend the scope of this KIP > > to > > > >>>>> include this---or maybe better, do another KIP for it and delay > > this > > > >> KIP > > > >>>>> until the other one is done? > > > >>>>> > > > >>>>> > > > >>>>> -Matthias > > > >>>>> > > > >>>>> On 5/15/17 2:35 AM, Damian Guy wrote: > > > >>>>>> Thanks for the KIP. > > > >>>>>> > > > >>>>>> I'm not convinced on the `RichFunction` approach. Do we really > > want > > > to > > > >>>>> give > > > >>>>>> every DSL method access to the `ProcessorContext` ? It has a > bunch > > > of > > > >>>>>> methods on it that seem in-appropriate for some of the DSL > > methods, > > > >>>> i.e, > > > >>>>>> `register`, `getStateStore`, `forward`, `schedule` etc. It is > far > > > too > > > >>>>>> broad. I think it would be better to have a narrower interface > > like > > > >> the > > > >>>>>> `RecordContext` - remembering it is easier to add > > > methods/interfaces > > > >>>>> later > > > >>>>>> than to remove them > > > >>>>>> > > > >>>>>> On Sat, 13 May 2017 at 22:26 Matthias J. Sax < > > matth...@confluent.io > > > > > > > >>>>> wrote: > > > >>>>>> > > > >>>>>>> Jeyhun, > > > >>>>>>> > > > >>>>>>> I am not an expert on Lambdas. Can you elaborate a little bit? > I > > > >>>> cannot > > > >>>>>>> follow the explanation in the KIP to see what the problem is. > > > >>>>>>> > > > >>>>>>> For updating the KIP title I don't know -- guess it's up to > you. > > > >>>> Maybe a > > > >>>>>>> committer can comment on this? > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> Further comments: > > > >>>>>>> > > > >>>>>>> - The KIP get a little hard to read -- can you maybe reformat > > the > > > >>>> wiki > > > >>>>>>> page a little bit? I think using `CodeBlock` would help. > > > >>>>>>> > > > >>>>>>> - What about KStream-KTable joins? You don't have overlaods > > added > > > >> for > > > >>>>>>> them. Why? (Even if I still hope that we don't need to add any > > new > > > >>>>>>> overloads) > > > >>>>>>> > > > >>>>>>> - Why do we need `AbstractRichFunction`? > > > >>>>>>> > > > >>>>>>> - What about interfaces Initializer, ForeachAction, Merger, > > > >>>> Predicate, > > > >>>>>>> Reducer? I don't want to say we should/need to add to all, but > we > > > >>>> should > > > >>>>>>> discuss all of them and add where it does make sense (e.g., > > > >>>>>>> RichForachAction does make sense IMHO) > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> Btw: I like the hierarchy `ValueXX` -- `ValueXXWithKey` -- > > > >>>> `RichValueXX` > > > >>>>>>> in general -- but why can't we do all this with interfaces > only? > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> -Matthias > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> > > > >>>>>>> On 5/11/17 7:00 AM, Jeyhun Karimov wrote: > > > >>>>>>>> Hi, > > > >>>>>>>> > > > >>>>>>>> Thanks for your comments. I think we cannot extend the two > > > >> interfaces > > > >>>>> if > > > >>>>>>> we > > > >>>>>>>> want to keep lambdas. I updated the KIP [1]. Maybe I should > > change > > > >>>> the > > > >>>>>>>> title, because now we are not limiting the KIP to only > > > ValueMapper, > > > >>>>>>>> ValueTransformer and ValueJoiner. > > > >>>>>>>> Please feel free to comment. > > > >>>>>>>> > > > >>>>>>>> [1] > > > >>>>>>>> > > > >>>>>>> > > > >>>>> https://cwiki.apache.org/confluence/display/KAFKA/KIP- > > > >>>> 149%3A+Enabling+key+access+in+ValueTransformer%2C+ > > > >>>> ValueMapper%2C+and+ValueJoiner > > > >>>>>>>> > > > >>>>>>>> > > > >>>>>>>> Cheers, > > > >>>>>>>> Jeyhun > > > >>>>>>>> > > > >>>>>>>> On Tue, May 9, 2017 at 7:36 PM Matthias J. Sax < > > > >>>> matth...@confluent.io> > > > >>>>>>>> wrote: > > > >>>>>>>> > > > >>>>>>>>> If `ValueMapperWithKey` extends `ValueMapper` we don't need > the > > > new > > > >>>>>>>>> overlaod. > > > >>>>>>>>> > > > >>>>>>>>> And yes, we need to do one check -- but this happens when > > > building > > > >>>> the > > > >>>>>>>>> topology. At runtime (I mean after KafkaStream#start() we > don't > > > >> need > > > >>>>> any > > > >>>>>>>>> check as we will always use `ValueMapperWithKey`) > > > >>>>>>>>> > > > >>>>>>>>> > > > >>>>>>>>> -Matthias > > > >>>>>>>>> > > > >>>>>>>>> > > > >>>>>>>>> On 5/9/17 2:55 AM, Jeyhun Karimov wrote: > > > >>>>>>>>>> Hi, > > > >>>>>>>>>> > > > >>>>>>>>>> Thanks for feedback. > > > >>>>>>>>>> Then we need to overload method > > > >>>>>>>>>> <VR> KStream<K, VR> mapValues(ValueMapper<? super V, ? > > extends > > > >>>> VR> > > > >>>>>>>>>> mapper); > > > >>>>>>>>>> with > > > >>>>>>>>>> <VR> KStream<K, VR> mapValues(ValueMapperWithKey<? super > V, > > ? > > > >>>>> extends > > > >>>>>>>>> VR> > > > >>>>>>>>>> mapper); > > > >>>>>>>>>> > > > >>>>>>>>>> and in runtime (inside processor) we still have to check it > is > > > >>>>>>>>> ValueMapper > > > >>>>>>>>>> or ValueMapperWithKey before wrapping it into the rich > > function. > > > >>>>>>>>>> > > > >>>>>>>>>> > > > >>>>>>>>>> Please correct me if I am wrong. > > > >>>>>>>>>> > > > >>>>>>>>>> Cheers, > > > >>>>>>>>>> Jeyhun > > > >>>>>>>>>> > > > >>>>>>>>>> > > > >>>>>>>>>> > > > >>>>>>>>>> > > > >>>>>>>>>> On Tue, May 9, 2017 at 10:56 AM Michal Borowiecki < > > > >>>>>>>>>> michal.borowie...@openbet.com> wrote: > > > >>>>>>>>>> > > > >>>>>>>>>>> +1 :) > > > >>>>>>>>>>> > > > >>>>>>>>>>> > > > >>>>>>>>>>> On 08/05/17 23:52, Matthias J. Sax wrote: > > > >>>>>>>>>>>> Hi, > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> I was reading the updated KIP and I am wondering, if we > > should > > > >> do > > > >>>>> the > > > >>>>>>>>>>>> design a little different. > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> Instead of distinguishing between a RichFunction and > > > >>>>> non-RichFunction > > > >>>>>>>>> at > > > >>>>>>>>>>>> runtime level, we would use RichFunctions all the time. > > Thus, > > > on > > > >>>>> the > > > >>>>>>>>> DSL > > > >>>>>>>>>>>> entry level, if a user provides a non-RichFunction, we > wrap > > it > > > >>>> by a > > > >>>>>>>>>>>> RichFunction that is fully implemented by Streams. This > > > >>>>> RichFunction > > > >>>>>>>>>>>> would just forward the call omitting the key: > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> Just to sketch the idea (incomplete code snippet): > > > >>>>>>>>>>>> > > > >>>>>>>>>>>>> public StreamsRichValueMapper implements > RichValueMapper() > > { > > > >>>>>>>>>>>>> private ValueMapper userProvidedMapper; // set by > > > >> constructor > > > >>>>>>>>>>>>> > > > >>>>>>>>>>>>> public VR apply(final K key, final V1 value1, final V2 > > > >>>> value2) > > > >>>>> { > > > >>>>>>>>>>>>> return userProvidedMapper(value1, value2); > > > >>>>>>>>>>>>> } > > > >>>>>>>>>>>>> } > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> From a performance point of view, I am not sure if the > > > >>>>>>>>>>>> "if(isRichFunction)" including casts etc would have more > > > >> overhead > > > >>>>>>> than > > > >>>>>>>>>>>> this approach (we would do more nested method call for > > > >>>>>>> non-RichFunction > > > >>>>>>>>>>>> which should be more common than RichFunctions). > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> This approach should not effect lambdas (or do I miss > > > >> something?) > > > >>>>> and > > > >>>>>>>>>>>> might be cleaner, as we could have one more top level > > > interface > > > >>>>>>>>>>>> `RichFunction` with methods `init()` and `close()` and > also > > > >>>>>>> interfaces > > > >>>>>>>>>>>> for `RichValueMapper` etc. (thus, no abstract classes > > > required). > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> Any thoughts? > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> -Matthias > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> > > > >>>>>>>>>>>> On 5/6/17 5:29 PM, Jeyhun Karimov wrote: > > > >>>>>>>>>>>>> Hi, > > > >>>>>>>>>>>>> > > > >>>>>>>>>>>>> Thanks for comments. I extended PR and KIP to include > rich > > > >>>>>>> functions. > > > >>>>>>>>> I > > > >>>>>>>>>>>>> will still have to evaluate the cost of deep copying of > > keys. > > > >>>>>>>>>>>>> > > > >>>>>>>>>>>>> Cheers, > > > >>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>> > > > >>>>>>>>>>>>> On Fri, May 5, 2017 at 8:02 PM Mathieu Fenniak < > > > >>>>>>>>>>> mathieu.fenn...@replicon.com> > > > >>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>> > > > >>>>>>>>>>>>>> Hey Matthias, > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>> My opinion would be that documenting the immutability of > > the > > > >>>> key > > > >>>>> is > > > >>>>>>>>> the > > > >>>>>>>>>>>>>> best approach available. Better than requiring the key > to > > > be > > > >>>>>>>>>>> serializable > > > >>>>>>>>>>>>>> (as with Jeyhun's last pass at the PR), no performance > > risk. > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>> It'd be different if Java had immutable type constraints > > of > > > >>>> some > > > >>>>>>>>> kind. > > > >>>>>>>>>>> :-) > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>> Mathieu > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>> On Fri, May 5, 2017 at 11:31 AM, Matthias J. Sax < > > > >>>>>>>>>>> matth...@confluent.io> > > > >>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> Agreed about RichFunction. If we follow this path, it > > > should > > > >>>>> cover > > > >>>>>>>>>>>>>>> all(?) DSL interfaces. > > > >>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> About guarding the key -- I am still not sure what to > do > > > >> about > > > >>>>>>> it... > > > >>>>>>>>>>>>>>> Maybe it might be enough to document it (and name the > key > > > >>>>>>> parameter > > > >>>>>>>>>>> like > > > >>>>>>>>>>>>>>> `readOnlyKey` to make is very clear). Ultimately, I > would > > > >>>> prefer > > > >>>>>>> to > > > >>>>>>>>>>>>>>> guard against any modification, but I have no good idea > > how > > > >> to > > > >>>>> do > > > >>>>>>>>>>> this. > > > >>>>>>>>>>>>>>> Not sure what others think about the risk of corrupted > > > >>>>>>> partitioning > > > >>>>>>>>>>>>>>> (what would be a user error and we could say, well, bad > > > luck, > > > >>>>> you > > > >>>>>>>>> got > > > >>>>>>>>>>> a > > > >>>>>>>>>>>>>>> bug in your code, that's not our fault), vs deep copy > > with > > > a > > > >>>>>>>>> potential > > > >>>>>>>>>>>>>>> performance hit (that we can't quantity atm without any > > > >>>>>>> performance > > > >>>>>>>>>>>>>> test). > > > >>>>>>>>>>>>>>> We do have a performance system test. Maybe it's worth > > for > > > >> you > > > >>>>> to > > > >>>>>>>>>>> apply > > > >>>>>>>>>>>>>>> the deep copy strategy and run the test. It's very > basic > > > >>>>>>> performance > > > >>>>>>>>>>>>>>> test only, but might give some insight. If you want to > do > > > >>>> this, > > > >>>>>>> look > > > >>>>>>>>>>>>>>> into folder "tests" for general test setup, and into > > > >>>>>>>>>>>>>>> "tests/kafaktests/benchmarks/streams" to find find the > > perf > > > >>>>> test. > > > >>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> -Matthias > > > >>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> On 5/5/17 8:55 AM, Jeyhun Karimov wrote: > > > >>>>>>>>>>>>>>>> Hi Matthias, > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>> I think extending KIP to include RichFunctions totally > > > >> makes > > > >>>>>>>>> sense. > > > >>>>>>>>>>>>>> So, > > > >>>>>>>>>>>>>>>> we don't want to guard the keys because it is > costly. > > > >>>>>>>>>>>>>>>> If we introduce RichFunctions I think it should not be > > > >>>> limited > > > >>>>>>> only > > > >>>>>>>>>>>>>> the 3 > > > >>>>>>>>>>>>>>>> interfaces proposed in KIP but to wide range of > > > interfaces. > > > >>>>>>>>>>>>>>>> Please correct me if I am wrong. > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>> Cheers, > > > >>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>> On Fri, May 5, 2017 at 12:04 AM Matthias J. Sax < > > > >>>>>>>>>>> matth...@confluent.io > > > >>>>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> One follow up. There was this email on the user list: > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> > > http://search-hadoop.com/m/Kafka/uyzND17KhCaBzPSZ1?subj= > > > >>>>>>>>>>>>>>> > > Shouldn+t+the+initializer+of+a+stream+aggregate+accept+the+ > > > >>>> key+ > > > >>>>>>>>>>>>>>>>> It might make sense so include Initializer, Adder, > and > > > >>>>>>> Substractor > > > >>>>>>>>>>>>>>>>> inferface, too. > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> And we should double check if there are other > interface > > > we > > > >>>>> might > > > >>>>>>>>>>> miss > > > >>>>>>>>>>>>>>> atm. > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> -Matthias > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> On 5/4/17 1:31 PM, Matthias J. Sax wrote: > > > >>>>>>>>>>>>>>>>>> Thanks for updating the KIP. > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> Deep copying the key will work for sure, but I am > > > actually > > > >>>> a > > > >>>>>>>>> little > > > >>>>>>>>>>>>>> bit > > > >>>>>>>>>>>>>>>>>> worried about performance impact... We might want to > > do > > > >>>> some > > > >>>>>>> test > > > >>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>>> quantify this impact. > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> Btw: this remind me about the idea of `RichFunction` > > > >>>>> interface > > > >>>>>>>>> that > > > >>>>>>>>>>>>>>>>>> would allow users to access record metadata (like > > > >>>> timestamp, > > > >>>>>>>>>>> offset, > > > >>>>>>>>>>>>>>>>>> partition etc) within DSL. This would be a similar > > > >> concept. > > > >>>>>>>>> Thus, I > > > >>>>>>>>>>>>>> am > > > >>>>>>>>>>>>>>>>>> wondering, if it would make sense to enlarge the > scope > > > of > > > >>>>> this > > > >>>>>>>>> KIP > > > >>>>>>>>>>> by > > > >>>>>>>>>>>>>>>>>> that? WDYT? > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> -Matthias > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>> On 5/3/17 2:08 AM, Jeyhun Karimov wrote: > > > >>>>>>>>>>>>>>>>>>> Hi Mathieu, > > > >>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>> Thanks for feedback. I followed similar approach > and > > > >>>> updated > > > >>>>>>> PR > > > >>>>>>>>>>> and > > > >>>>>>>>>>>>>>> KIP > > > >>>>>>>>>>>>>>>>>>> accordingly. I tried to guard the key in Processors > > > >>>> sending > > > >>>>> a > > > >>>>>>>>> copy > > > >>>>>>>>>>>>>> of > > > >>>>>>>>>>>>>>> an > > > >>>>>>>>>>>>>>>>>>> actual key. > > > >>>>>>>>>>>>>>>>>>> Because I am doing deep copy of an object, I think > > > memory > > > >>>>> can > > > >>>>>>> be > > > >>>>>>>>>>>>>>>>> bottleneck > > > >>>>>>>>>>>>>>>>>>> in some use-cases. > > > >>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>> Cheers, > > > >>>>>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>> On Tue, May 2, 2017 at 5:10 PM Mathieu Fenniak < > > > >>>>>>>>>>>>>>>>> mathieu.fenn...@replicon.com> > > > >>>>>>>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> Hi Jeyhun, > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> This approach would change ValueMapper (...etc) to > > be > > > >>>>>>> classes, > > > >>>>>>>>>>>>>> rather > > > >>>>>>>>>>>>>>>>> than > > > >>>>>>>>>>>>>>>>>>>> interfaces, which is also a backwards incompatible > > > >>>> change. > > > >>>>>>> An > > > >>>>>>>>>>>>>>>>> alternative > > > >>>>>>>>>>>>>>>>>>>> approach that would be backwards compatible would > be > > > to > > > >>>>>>> define > > > >>>>>>>>>>> new > > > >>>>>>>>>>>>>>>>>>>> interfaces, and provide overrides where those > > > interfaces > > > >>>>> are > > > >>>>>>>>>>> used. > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> Unfortunately, making the key parameter as "final" > > > >>>> doesn't > > > >>>>>>>>> change > > > >>>>>>>>>>>>>>> much > > > >>>>>>>>>>>>>>>>>>>> about guarding against key change. It only > prevents > > > the > > > >>>>>>>>>>> parameter > > > >>>>>>>>>>>>>>>>> variable > > > >>>>>>>>>>>>>>>>>>>> from being reassigned. If the key type is a > mutable > > > >>>> object > > > >>>>>>>>> (eg. > > > >>>>>>>>>>>>>>>>> byte[]), > > > >>>>>>>>>>>>>>>>>>>> it can still be mutated. (eg. key[0] = 0). But > I'm > > > not > > > >>>>>>> really > > > >>>>>>>>>>> sure > > > >>>>>>>>>>>>>>>>> there's > > > >>>>>>>>>>>>>>>>>>>> much that can be done about that. > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> Mathieu > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>> On Mon, May 1, 2017 at 5:39 PM, Jeyhun Karimov < > > > >>>>>>>>>>>>>> je.kari...@gmail.com > > > >>>>>>>>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> Thanks for comments. > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> The concerns makes sense. Although we can guard > for > > > >>>>>>> immutable > > > >>>>>>>>>>> keys > > > >>>>>>>>>>>>>>> in > > > >>>>>>>>>>>>>>>>>>>>> current implementation (with few changes), I > didn't > > > >>>>> consider > > > >>>>>>>>>>>>>>> backward > > > >>>>>>>>>>>>>>>>>>>>> compatibility. > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> In this case 2 solutions come to my mind. In both > > > >> cases, > > > >>>>>>> user > > > >>>>>>>>>>>>>>> accesses > > > >>>>>>>>>>>>>>>>>>>> the > > > >>>>>>>>>>>>>>>>>>>>> key in Object type, as passing extra type > parameter > > > >> will > > > >>>>>>> break > > > >>>>>>>>>>>>>>>>>>>>> backwards-compatibility. So user has to cast to > > > actual > > > >>>>> key > > > >>>>>>>>>>> type. > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> 1. Firstly, We can overload apply method with 2 > > > >> argument > > > >>>>>>> (key > > > >>>>>>>>>>> and > > > >>>>>>>>>>>>>>>>> value) > > > >>>>>>>>>>>>>>>>>>>>> and force key to be *final*. By doing this, I > > think > > > we > > > >>>>> can > > > >>>>>>>>>>>>>> address > > > >>>>>>>>>>>>>>>>> both > > > >>>>>>>>>>>>>>>>>>>>> backward-compatibility and guarding against key > > > change. > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> 2. Secondly, we can create class KeyAccess like: > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> public class KeyAccess { > > > >>>>>>>>>>>>>>>>>>>>> Object key; > > > >>>>>>>>>>>>>>>>>>>>> public void beforeApply(final Object key) { > > > >>>>>>>>>>>>>>>>>>>>> this.key = key; > > > >>>>>>>>>>>>>>>>>>>>> } > > > >>>>>>>>>>>>>>>>>>>>> public Object getKey() { > > > >>>>>>>>>>>>>>>>>>>>> return key; > > > >>>>>>>>>>>>>>>>>>>>> } > > > >>>>>>>>>>>>>>>>>>>>> } > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> We can extend *ValueMapper, ValueJoiner* and > > > >>>>>>>>> *ValueTransformer* > > > >>>>>>>>>>>>>> from > > > >>>>>>>>>>>>>>>>>>>>> *KeyAccess*. Inside processor (for example > > > >>>>>>>>>>>>>>> *KTableMapValuesProcessor*) > > > >>>>>>>>>>>>>>>>>>>>> before calling *mapper.apply(value)* we can set > the > > > >>>> *key* > > > >>>>> by > > > >>>>>>>>>>>>>>>>>>>>> *mapper.beforeApply(key)*. As a result, user can > > use > > > >>>>>>>>> *getKey()* > > > >>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>> access > > > >>>>>>>>>>>>>>>>>>>>> the key inside *apply(value)* method. > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> Cheers, > > > >>>>>>>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> On Mon, May 1, 2017 at 7:24 PM Matthias J. Sax < > > > >>>>>>>>>>>>>>> matth...@confluent.io > > > >>>>>>>>>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> Jeyhun, > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> thanks a lot for the KIP! > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> I think there are two issues we need to address: > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> (1) The KIP does not consider backward > > > compatibility. > > > >>>>> Users > > > >>>>>>>>> did > > > >>>>>>>>>>>>>>>>>>>> complain > > > >>>>>>>>>>>>>>>>>>>>>> about this in past releases already, and as the > > user > > > >>>> base > > > >>>>>>>>>>> grows, > > > >>>>>>>>>>>>>> we > > > >>>>>>>>>>>>>>>>>>>>>> should not break backward compatibility in > future > > > >>>>> releases > > > >>>>>>>>>>>>>> anymore. > > > >>>>>>>>>>>>>>>>>>>>>> Thus, we should think of a better way to allow > key > > > >>>>> access. > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> Mathieu's comment goes into the same direction > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> On the other hand, the number of compile > > failures > > > >>>> that > > > >>>>>>>>> would > > > >>>>>>>>>>>>>> need > > > >>>>>>>>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>>>>>> be > > > >>>>>>>>>>>>>>>>>>>>>>>> fixed from this change is unfortunate. :-) > > > >>>>>>>>>>>>>>>>>>>>>> (2) Another concern is, that there is no guard > to > > > >>>> prevent > > > >>>>>>>>> user > > > >>>>>>>>>>>>>> code > > > >>>>>>>>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>>>>>>> modify the key. This might corrupt partitioning > if > > > >>>> users > > > >>>>> do > > > >>>>>>>>>>> alter > > > >>>>>>>>>>>>>>> the > > > >>>>>>>>>>>>>>>>>>>>>> key (accidentally -- or users are just not aware > > > that > > > >>>>> they > > > >>>>>>>>> are > > > >>>>>>>>>>>>>> not > > > >>>>>>>>>>>>>>>>>>>>>> allowed to modify the provided key object) and > > thus > > > >>>> break > > > >>>>>>> the > > > >>>>>>>>>>>>>>>>>>>>>> application. (This was the original motivation > to > > > not > > > >>>>>>> provide > > > >>>>>>>>>>> the > > > >>>>>>>>>>>>>>> key > > > >>>>>>>>>>>>>>>>>>>> in > > > >>>>>>>>>>>>>>>>>>>>>> the first place -- it's guards against > > > modification.) > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> -Matthias > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> On 5/1/17 6:31 AM, Mathieu Fenniak wrote: > > > >>>>>>>>>>>>>>>>>>>>>>> Hi Jeyhun, > > > >>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>> I just want to add my voice that, I too, have > > > wished > > > >>>> for > > > >>>>>>>>>>> access > > > >>>>>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>>>>> the > > > >>>>>>>>>>>>>>>>>>>>>>> record key during a mapValues or similar > > operation. > > > >>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>> On the other hand, the number of compile > failures > > > >> that > > > >>>>>>> would > > > >>>>>>>>>>>>>> need > > > >>>>>>>>>>>>>>> to > > > >>>>>>>>>>>>>>>>>>>> be > > > >>>>>>>>>>>>>>>>>>>>>>> fixed from this change is unfortunate. :-) But > > at > > > >>>> least > > > >>>>>>> it > > > >>>>>>>>>>>>>> would > > > >>>>>>>>>>>>>>>>> all > > > >>>>>>>>>>>>>>>>>>>>> be > > > >>>>>>>>>>>>>>>>>>>>>> a > > > >>>>>>>>>>>>>>>>>>>>>>> pretty clear and easy change. > > > >>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>> Mathieu > > > >>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>> On Mon, May 1, 2017 at 6:55 AM, Jeyhun Karimov > < > > > >>>>>>>>>>>>>>>>> je.kari...@gmail.com > > > >>>>>>>>>>>>>>>>>>>>>> wrote: > > > >>>>>>>>>>>>>>>>>>>>>>>> Dear community, > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> I want to share KIP-149 [1] based on issues > > > >>>> KAFKA-4218 > > > >>>>>>> [2], > > > >>>>>>>>>>>>>>>>>>>> KAFKA-4726 > > > >>>>>>>>>>>>>>>>>>>>>> [3], > > > >>>>>>>>>>>>>>>>>>>>>>>> KAFKA-3745 [4]. The related PR can be found at > > > [5]. > > > >>>>>>>>>>>>>>>>>>>>>>>> I would like to get your comments. > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> [1] > > > >>>>>>>>>>>>>>>>>>>>>>>> https://cwiki.apache.org/ > > > >>>> confluence/display/KAFKA/KIP- > > > >>>>>>>>>>>>>>>>>>>>>>>> > > 149%3A+Enabling+key+access+in+ValueTransformer%2C+ > > > >>>>>>>>>>>>>>>>>>>>>>>> ValueMapper%2C+and+ValueJoiner > > > >>>>>>>>>>>>>>>>>>>>>>>> [2] https://issues.apache.org/jira > > > >> /browse/KAFKA-4218 > > > >>>>>>>>>>>>>>>>>>>>>>>> [3] https://issues.apache.org/jira > > > >> /browse/KAFKA-4726 > > > >>>>>>>>>>>>>>>>>>>>>>>> [4] https://issues.apache.org/jira > > > >> /browse/KAFKA-3745 > > > >>>>>>>>>>>>>>>>>>>>>>>> [5] https://github.com/apache/kafka/pull/2946 > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> Cheers, > > > >>>>>>>>>>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> -- > > > >>>>>>>>>>>>>>>>>>>>>>>> -Cheers > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>>> -- > > > >>>>>>>>>>>>>>>>>>>>> -Cheers > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>>> -- > > > >>>>>>>>>>>>>>>> -Cheers > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>>> Jeyhun > > > >>>>>>>>>>>>>>>> > > > >>>>>>>>>>>>>>> > > > >>>>>>>>>>> > > > >>>>>>>>>>> -- > > > >>>>>>>>>> -Cheers > > > >>>>>>>>>> > > > >>>>>>>>>> Jeyhun > > > >>>>>>>>>> > > > >>>>>>>>> > > > >>>>>>>>> -- > > > >>>>>>>> -Cheers > > > >>>>>>>> > > > >>>>>>>> Jeyhun > > > >>>>>>>> > > > >>>>>>> > > > >>>>>>> > > > >>>>>> > > > >>>>> > > > >>>>> > > > >>>> > > > >>> > > > >> > > > >> > > > > > > > > > > -- > > -Cheers > > > > Jeyhun > > > -- -Cheers Jeyhun