23091

Spring WebFlux: Only one connection receive subscriber allowed

Question:

I am writing a simple app with Spring 5 Webflux and Kotlin. I am trying to implement PUT endpoint in a following way:

PUT("/confs/{id}", { val id = it.pathVariable("id") ServerResponse.ok().body(service.save(it.bodyToMono(Item::class.java)), Item::class.java) })

The trick on save is that I try to read a city name from item, resolve geo coordinates, overwrite them in original item and then save to Mongo using Spring Data Mongo Reactive repo.

fun save(item: Mono<Item>): Mono<Item> { val geo = item.flatMap { val city = it.location?.city ?: "Somewhere" geoService.resolveGeoFromCity(city) } val zipped = item.zipWith(geo) .map { it.t1.location?.geo = it.t2 it.t1 } return repo.saveAll(zipped) .toMono() }

Code to resolve geo coordinates is here:

@Service class GeoService() { val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/") fun resolveGeoFromCity(city: String): Mono<Geo> { return client.get() .uri("json?address=$city&key=$API_KEY&language=en") .exchange() .flatMap { it.bodyToMono(String::class.java) } .map { parse(it) } } private fun parse(response: String): Geo { val locationMap = JsonPath.read<Map<String, Double>>(response, "$.results[0].geometry.location") return Geo(locationMap["lat"] ?: 0.0, locationMap["lng"] ?: 0.0) } }

The problem is that if make a PUT request I got following stacktrace. I have tried to stub Mono with val geo = Mono.just(Geo(0.0, 0.0)) (without using WebClient) and then it works fine.

How to fix it without sacrifying functionality?)

2018-01-01 01:41:00.595 ERROR 15120 --- [ctor-http-nio-4] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [PUT http://localhost:8097/confs/5a49675c910d123b1057207a] java.lang.IllegalStateException: Only one connection receive subscriber allowed. at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:276) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at reactor.ipc.netty.channel.FluxReceive.subscribe(FluxReceive.java:124) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.ipc.netty.ByteBufFlux.subscribe(ByteBufFlux.java:242) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at reactor.core.publisher.FluxPeek.subscribe(FluxPeek.java:83) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.ipc.netty.ByteBufFlux.subscribe(ByteBufFlux.java:242) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:97) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxDoFinallyFuseable.subscribe(FluxDoFinallyFuseable.java:48) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxMapFuseable.subscribe(FluxMapFuseable.java:63) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~ [reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.Mono.subscribe(Mono.java:3008) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:167) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:71) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE] at reactor.ipc.netty.channel.ChannelOperations.applyHandler(ChannelOperations.java:383) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at reactor.ipc.netty.http.server.HttpServerOperations.onHandlerStart(HttpServerOperations.java:359) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE] at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163) ~[netty-common-4.1.17.Final.jar:4.1.17.Final] at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) ~[netty-common-4.1.17.Final.jar:4.1.17.Final] at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403) ~[netty-common-4.1.17.Final.jar:4.1.17.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463) ~[netty-transport-4.1.17.Final.jar:4.1.17.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) ~[netty-common-4.1.17.Final.jar:4.1.17.Final] at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_112] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Assembly trace from producer [reactor.core.publisher.FluxMap] : reactor.core.publisher.Flux.map(Flux.java:4855) reactor.ipc.netty.ByteBufFlux.fromInbound(ByteBufFlux.java:68) reactor.ipc.netty.NettyInbound.receive(NettyInbound.java:90) org.springframework.http.server.reactive.ReactorServerHttpRequest.getBody(ReactorServerHttpRequest.java:148) org.springframework.http.codec.DecoderHttpMessageReader.readMono(DecoderHttpMessageReader.java:93) org.springframework.http.codec.DecoderHttpMessageReader.readMono(DecoderHttpMessageReader.java:123) org.springframework.web.reactive.function.BodyExtractors.lambda$null$0(BodyExtractors.java:101) java.util.Optional.map(Optional.java:215) org.springframework.web.reactive.function.BodyExtractors.readWithMessageReaders(BodyExtractors.java:256) org.springframework.web.reactive.function.BodyExtractors.lambda$toMono$2(BodyExtractors.java:96) org.springframework.web.reactive.function.server.DefaultServerRequest.body(DefaultServerRequest.java:126) org.springframework.web.reactive.function.server.DefaultServerRequest.body(DefaultServerRequest.java:120) org.springframework.web.reactive.function.server.DefaultServerRequest.bodyToMono(DefaultServerRequest.java:145) com.example.confs.web.ConferenceRouter$routes$1$2.invoke(ConferenceRouter.kt:31) com.example.confs.web.ConferenceRouter$routes$1$2.invoke(ConferenceRouter.kt:16) org.springframework.web.reactive.function.server.RouterFunctionDsl$PUT$1.handle(RouterFunctionDsl.kt:200) org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:61) org.springframework.web.reactive.DispatcherHandler.invokeHandler(DispatcherHandler.java:168) org.springframework.web.reactive.DispatcherHandler.lambda$handle$1(DispatcherHandler.java:160) reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:271) reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:803) reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:1649) reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1463) reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1337) reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) reactor.core.publisher.Mono.subscribe(Mono.java:3008) reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75) reactor.core.publisher.Operators.complete(Operators.java:125) reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:45) reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) reactor.core.publisher.Mono.subscribe(Mono.java:3008) reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:418) reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:210) reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:128) reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:61) reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:121) reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40) reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.Mono.subscribe(Mono.java:3008) reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:167) reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) reactor.ipc.netty.channel.ChannelOperations.applyHandler(ChannelOperations.java:383) reactor.ipc.netty.http.server.HttpServerOperations.onHandlerStart(HttpServerOperations.java:359) io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163) io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403) io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463) io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) Error has been observed by the following operator(s): |_ Flux.map(ByteBufFlux.java:68) |_ Flux.doOnNext(ByteBufFlux.java:230) |_ Flux.map(ReactorServerHttpRequest.java:148) |_ Flux.flatMap(AbstractJackson2Decoder.java:95) |_ Flux.doFinally(AbstractJackson2Decoder.java:95) |_ Flux.map(AbstractJackson2Decoder.java:117) |_ Flux.singleOrEmpty(AbstractJackson2Decoder.java:87) |_ Operators.error(FluxReceive.java:276) |_ Mono.onErrorMap(DefaultServerRequest.java:146) |_ Mono.map(ConferenceService.kt:27) |_ Mono.map(ConferenceService.kt:32) |_ Mono.zipWith(ConferenceService.kt:47) |_ Mono.map(ConferenceService.kt:48) |_ Flux.flatMap(SimpleReactiveMongoRepository.java:318) |_ MonoExtensionsKt.toMono(ConferenceService.kt:55) |_ Mono.map(ConferenceService.kt:56) |_ Flux.map(AbstractJackson2Encoder.java:99) |_ Mono.flatMap(DispatcherHandler.java:177) |_ Mono.onErrorResume(DispatcherHandler.java:177) |_ Mono.flatMap(DispatcherHandler.java:161) |_ Mono.defer(DefaultWebFilterChain.java:71) |_ Mono.doOnSuccess(MetricsWebFilter.java:59) |_ Mono.doOnError(MetricsWebFilter.java:60) |_ Mono.compose(MetricsWebFilter.java:54) |_ Mono.defer(DefaultWebFilterChain.java:71) |_ Mono.defer(DefaultWebFilterChain.java:71)

Answer1:

<h3>WebClient.exchange() result stream is unicast</h3>

The problem here is in fact that WebClient allows only one subscriber per connection. If you try to subscribe to the same <strong><em>exchanged</em></strong> connection twice - you will get java.lang.IllegalStateException: Only one connection receive subscriber allowed.

Despite the fact that I don't see where you have tried to reuse the same connection twice, I believe that you may solve that problem by using next combination of operators:

class GeoService() { val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/") fun resolveGeoFromCity(city: String): Mono<Geo> { return client.get() .uri("json?address=$city&key=$API_KEY&language=en") .exchange() .flatMap { it.bodyToMono(String::class.java) } .map { parse(it) } .share(); } ... }

in that example, flow is configured to multicasts (shares) the original source as long as there is at least one Subscriber will be subscribed. In case if you need that all subscribers receive the same date you may replace .share with .cache operator.

Also, there is an alternative to above technique. You may replace mentioned operator with a processor and get the same sharing possibility:

class GeoService() { val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/") fun resolveGeoFromCity(city: String): Mono<Geo> { return client.get() .uri("json?address=$city&key=$API_KEY&language=en") .exchange() .flatMap { it.bodyToMono(String::class.java) } .map { parse(it) } .subscribeWith(DirectProcessor.create()); } ... }

In that case, you subscribing and running consumption of source's data exactly right after calling subscribeWith, so, potentially, in that case, you may lose some part of data, etc.

<h3>Why with Mono.just(..) everything works fine?</h3>

First of all .just is a cold operator, it allows as many as possible subscribers which receive the same data at any point in time. That is why when you tried to consume the same chunk of data from the connection twice, you did not get any exceptions.

Answer2:

I had a similar issue. The fix was to specify this dependency:

org.springframework:spring-webflux:5.1.4.RELEASE

As I used spring-boot it deploys the previous version of it. Unfortunately, the link to this issue is not existing now.

So now my gradle looks like this:

compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') compile('org.springframework.boot:spring-boot-starter-webflux') // Next 2 dependencies are temporally here until the one above does not resolves next to at least 5.1.4 - where // webflux issue is resolved: // https://github.com/rstoyanchev/spr-issue-migration-test-2/issues/17323 compile('org.springframework:spring-webflux:5.1.4.RELEASE') compile('org.springframework:spring-web:5.1.4.RELEASE')

Answer3:

I've done a very similar example here:

This router will get a geolocation from name and the with another service extract the sunrise and sunset time:

I've use the <a href="https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#and-org.reactivestreams.Publisher-" rel="nofollow">and</a> method from Mono.

internal fun buildResponse(address: Mono<String>) = address.transform(geoLocationService::fromAddress).and(this::sunriseSunset, ::LocationResponse) internal fun sunriseSunset(geographicCoordinates: GeographicCoordinates) = geographicCoordinates.toMono().transform(sunriseSunsetService::fromGeographicCoordinates)

```

More details of thi <a href="https://github.com/LearningByExample/KotlinReactiveMS/blob/master/src/main/kotlin/org/learning/by/example/reactive/kotlin/microservices/KotlinReactiveMS/handlers/ApiHandler.kt" rel="nofollow">example</a>

Recommend

  • How to change tooltip colour?
  • Operations in multi index dataframe pandas
  • linux ps命令查看最消耗CPU、内存的进程
  • Ajax resets PHP session
  • keystore not found for signing config 'release'.— React native
  • What is the difference between a session store and database
  • List of Date + Hours between two dates
  • All I get is a blank page when I visit localhost:9080
  • 'JsonPropertyAttribute' is inaccessible due to its protection level [duplicate]
  • NullReferenceException in UnitTest using Verifyable on an async Method [duplicate]
  • Using Facebook Graph API 2.2 to get like and share count
  • Spring 4.3.x with OpenJDK 11
  • Django CBV: Programmatically save HTML page to PDF file on the server
  • Google Maps V3 - Resizing Map and Sporadic Map Tile Loading
  • DIH Scheduling in Solr
  • Require.js loosing url context
  • Map url text to boolean parameter with MVC attribute routing
  • React: state not updating on first click
  • Python + Tkinter Windows 7 taskbar progress
  • Is Xss protection in Spring security enabled by default?
  • Having several issues with a Python service for Windows
  • openTSDB REST API is not storing data
  • How to use MIPS load word
  • sql server back restore - login failed
  • Test an application behind a proxy server using Robot Framework and Selenium2Library
  • How to customize marker colors and shapes in scatter plot?
  • how java graphics repaint method actually works
  • How to map a native query to POJO class using jpa and hibernate
  • Trying to get lotusscript json reader
  • Semi-local Levenshtein distance
  • Ways of filling 10 places with number from [1..10] such that digit at ith place has value atmost 1 m
  • Generate a runnable jar and include libraries in it with Maven
  • How to redirect into different page by user type in php and mysql
  • time column in sqlite using gorm