I am trying to write a custom data type binding for PGInterval and Duration to use jOOQ together with TimescaleDB. Sadly jOOQ does not use it when generating functions for the database routines.
Here's my binding class:
import org.jooq.*
import org.jooq.conf.ParamType
import org.jooq.impl.DSL
import org.postgresql.util.PGInterval
import java.sql.SQLFeatureNotSupportedException
import java.sql.Types
import java.time.Duration
import java.util.*
class PostgresIntervalDurationBinding: Binding<Any, Duration> {
override fun converter(): Converter<Any, Duration> {
return object : Converter<Any, Duration> {
override fun from(t: Any?): Duration {
return if (t == null) Duration.ZERO else Duration.ofSeconds(pgIntervalToSeconds(t as PGInterval))
}
override fun to(u: Duration?): Any? {
return if (u == null || u === Duration.ZERO) null else PGInterval().seconds = u.seconds.toDouble()
}
override fun fromType(): Class<Any> {
return Any::class.java
}
override fun toType(): Class<Duration> {
return Duration::class.java
}
}
}
override fun sql(ctx: BindingSQLContext<Duration>?) {
if (ctx?.render()?.paramType() == ParamType.INLINED)
ctx.render()?.visit(DSL.inline(ctx.convert(converter()).value()))?.sql("::interval")
else
ctx?.render()?.sql("?::interval")
}
override fun register(ctx: BindingRegisterContext<Duration>?) {
ctx?.statement()?.registerOutParameter(ctx.index(), Types.VARCHAR)
}
override fun set(ctx: BindingSetStatementContext<Duration>?) {
ctx?.statement()?.setString(ctx.index(), Objects.toString(ctx.convert(converter()).value(), null))
}
override fun get(ctx: BindingGetResultSetContext<Duration>?) {
ctx?.convert(converter())?.value(ctx.resultSet().getString(ctx.index()))
}
override fun get(ctx: BindingGetStatementContext<Duration>?) {
ctx?.convert(converter())?.value(ctx.statement().getString(ctx.index()))
}
override fun set(ctx: BindingSetSQLOutputContext<Duration>?) {
throw SQLFeatureNotSupportedException()
}
override fun get(ctx: BindingGetSQLInputContext<Duration>?) {
throw SQLFeatureNotSupportedException()
}
companion object {
fun pgIntervalToSeconds(t: PGInterval): Long {
var seconds = 0L
with(t){
seconds += Duration.ofSeconds(this.seconds.toLong()).seconds
seconds += Duration.ofMinutes(this.minutes.toLong()).seconds
seconds += Duration.ofHours(this.hours.toLong()).seconds
seconds += Duration.ofDays(this.days.toLong()).seconds
if (months > 0 || years > 0) throw SQLFeatureNotSupportedException()
}
return seconds
}
}
}
This is my configuration in the pom:
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<includes>.*</includes>
<inputSchema>public</inputSchema>
<excludes>set_adaptive_chunking
| flyway_schema_history
</excludes>
<forcedTypes>
<forcedType>
<userType>java.time.Duration</userType>
<binding>de.ninjaneers.dmc.jooq.databindings.PostgresIntervalDurationBinding
</binding>
<expression>.*interval.*</expression>
<types>.*</types>
</forcedType>
</forcedTypes>
</database>
For example I expect jOOQ to generate the routine
time_bucket(bucket_with interval, ts timestamp with time zone)
as
timeBucket(Field<Duration> bucketWidth, Field<Timestamp> ts)
but I get
timeBucket(Field<Object> bucketWidth, Field<Timestamp> ts)
You are confusing the two configuration flags:
<expression>.*interval.*</expression>
<types>.*</types>
<expression> is a regex matching the identifier of the column/attribute/parameter whose type you would like to replace. <types> matches the data types. This is what you want:
<types>.*interval.*</types>
<expression>.*</expression>
Related
How can a Snackbar be shown above a Dialog or AlertDialog in Jetpack Compose? Everything I have tried has resulted in the snack bar being below the scrim of the dialog not to mention the dialog itself.
According to Can I display material design Snackbar in dialog? it is possible in non-Compose Android by using a custom or special (like getDialog().getWindow().getDecorView()) view, but that isn't accessible from Compose I believe (at least not without a lot of effort).
I came up with a solution that mostly works. It uses the built-in Snackbar() composable for the rendering but handles the role of SnackbarHost() with a new function SnackbarInDialogContainer().
Usage example:
var error by remember { mutableStateOf<String?>(null) }
AlertDialog(
...
text = {
...
if (error !== null) {
SnackbarInDialogContainer(error, dismiss = { error = null }) {
Snackbar(it, Modifier.padding(WindowInsets.ime.asPaddingValues()))
}
}
}
...
)
It has the following limitations:
Has to be used in place within the dialog instead of at the top level
There is no host to queue messages, instead that has to be handled elsewhere if desired
Dismissal is done with a callback (i.e. { error = null} above) instead of automatically
Actions currently do nothing at all, but that could be fixed (I had no use for them, the code do include everything necessary to render the actions I believe, but none of the interaction).
This has built-in support for avoiding the IME (software keyboard), but you may still need to follow https://stackoverflow.com/a/73889690/582298 to make it fully work.
Code for the Composable:
#Composable
fun SnackbarInDialogContainer(
text: String,
actionLabel: String? = null,
duration: SnackbarDuration =
if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite,
dismiss: () -> Unit,
content: #Composable (SnackbarData) -> Unit
) {
val snackbarData = remember {
SnackbarDataImpl(
SnackbarVisualsImpl(text, actionLabel, true, duration),
dismiss
)
}
val dur = getDuration(duration, actionLabel)
if (dur != Long.MAX_VALUE) {
LaunchedEffect(snackbarData) {
delay(dur)
snackbarData.dismiss()
}
}
val popupPosProvider by imeMonitor()
Popup(
popupPositionProvider = popupPosProvider,
properties = PopupProperties(clippingEnabled = false),
) {
content(snackbarData)
}
}
#Composable
private fun getDuration(duration: SnackbarDuration, actionLabel: String?): Long {
val accessibilityManager = LocalAccessibilityManager.current
return remember(duration, actionLabel, accessibilityManager) {
val orig = when (duration) {
SnackbarDuration.Short -> 4000L
SnackbarDuration.Long -> 10000L
SnackbarDuration.Indefinite -> Long.MAX_VALUE
}
accessibilityManager?.calculateRecommendedTimeoutMillis(
orig, containsIcons = true, containsText = true, containsControls = actionLabel != null
) ?: orig
}
}
/**
* Monitors the size of the IME (software keyboard) and provides an updating
* PopupPositionProvider.
*/
#Composable
private fun imeMonitor(): State<PopupPositionProvider> {
val provider = remember { mutableStateOf(ImePopupPositionProvider(0)) }
val context = LocalContext.current
val decorView = remember(context) { context.getActivity()?.window?.decorView }
if (decorView != null) {
val ime = remember { WindowInsetsCompat.Type.ime() }
val bottom = remember { MutableStateFlow(0) }
LaunchedEffect(Unit) {
while (true) {
bottom.value = ViewCompat.getRootWindowInsets(decorView)?.getInsets(ime)?.bottom ?: 0
delay(33)
}
}
LaunchedEffect(Unit) {
bottom.collect { provider.value = ImePopupPositionProvider(it) }
}
}
return provider
}
/**
* Places the popup at the bottom of the screen but above the keyboard.
* This assumes that the anchor for the popup is in the middle of the screen.
*/
private data class ImePopupPositionProvider(val imeSize: Int): PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect, windowSize: IntSize,
layoutDirection: LayoutDirection, popupContentSize: IntSize
) = IntOffset(
anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2, // centered on screen
anchorBounds.top + (anchorBounds.height - popupContentSize.height) / 2 + // centered on screen
(windowSize.height - imeSize) / 2 // move to the bottom of the screen
)
}
private fun Context.getActivity(): Activity? {
var currentContext = this
while (currentContext is ContextWrapper) {
if (currentContext is Activity) {
return currentContext
}
currentContext = currentContext.baseContext
}
return null
}
private data class SnackbarDataImpl(
override val visuals: SnackbarVisuals,
val onDismiss: () -> Unit,
) : SnackbarData {
override fun performAction() { /* TODO() */ }
override fun dismiss() { onDismiss() }
}
private data class SnackbarVisualsImpl(
override val message: String,
override val actionLabel: String?,
override val withDismissAction: Boolean,
override val duration: SnackbarDuration
) : SnackbarVisuals
i wonder is there any way to import dynamic from node-modules?
for example import(ace-builds/src-noconflict/mode-${file} for Ace-editor
With webpack there are two possibilities. First is to use import() by overwriting ace.config.loadModule
import ace from "ace-builds"
ace.config.setDefaultValue("session", "useWorker", false)
ace.config.loadModule = function(moduleName, onLoad) {
var module, moduleType;
if (Array.isArray(moduleName)) {
moduleType = moduleName[0];
moduleName = moduleName[1];
}
var done = m=>{
console.log(moduleName, "loaded")
onLoad && onLoad(m)
};
console.log(moduleName)
var parts = moduleName.split("/");
if (parts[1] == "ext") {
import(`ace-builds/src-noconflict/ext-${parts[2]}`).then(done);
} else if (parts[1] == "theme") {
import(`ace-builds/src-noconflict/theme-${parts[2]}`).then(done);
} else if (parts[1] == "mode") {
import(`ace-builds/src-noconflict/mode-${parts[2]}`).then(done);
} else if (parts[1] == "keyboard") {
import(`ace-builds/src-noconflict/keybinding-${parts[2]}`).then(done);
} else if (parts[1] == "snippets") {
import(`ace-builds/src-noconflict/snippets/${parts[2]}`).then(done);
} else {
console.error(moduleName, "not implemented")
}
}
the second is to use file-loader by adding
import "ace-builds"
import "ace-builds/webpack-resolver"
both work with your example when added to https://github.com/saba-bg/Ace-editor-dynamic-mode-import/blob/016928b521ebc5fd5b39c52352dafddd99d9df6b/src/editor/Ace.js#L7, instead of languages.map part
I am trying to add a column of type array to my Postgres table using exposed.The goal is to have a statement like:
UPDATE posts
SET like_user_id = like_user_id || $1, likes = likes + 1
WHERE NOT (like_user_id #> $1)
AND pid = ($2)
posts table:
CREATE TABLE posts (
pid SERIAL PRIMARY KEY,
title VARCHAR(255),
body VARCHAR,
user_id INT REFERENCES users(uid),
author VARCHAR REFERENCES users(username),
date_created TIMESTAMP
like_user_id INT[] DEFAULT ARRAY[]::INT[],
likes INT DEFAULT 0
);
Kotlin Exposed framework does not have support for array of column type natively, you need to implement it yourself. Here's a generic version I found while trying to do the same thing https://github.com/LorittaBot/Loritta/blob/db577852a76266d207361b7d8257d24b4ee0b947/platforms/discord/legacy/src/main/java/com/mrpowergamerbr/loritta/utils/exposed/array.kt
fun <T> Table.array(name: String, columnType: ColumnType): Column<Array<T>> = registerColumn(name, ArrayColumnType(columnType))
class ArrayColumnType(private val type: ColumnType) : ColumnType() {
private fun supportsArrays() = !loritta.config.database.type.startsWith("SQLite")
override fun sqlType(): String = buildString {
if (!supportsArrays()) {
append("TEXT")
} else {
append(type.sqlType())
append(" ARRAY")
}
}
override fun valueToDB(value: Any?): Any? {
if (!supportsArrays())
return "'NOT SUPPORTED'"
if (value is Array<*>) {
val columnType = type.sqlType().split("(")[0]
val jdbcConnection = (TransactionManager.current().connection as JdbcConnectionImpl).connection
return jdbcConnection.createArrayOf(columnType, value)
} else {
return super.valueToDB(value)
}
}
override fun valueFromDB(value: Any): Any {
if (!supportsArrays()) {
val clazz = type::class
val clazzName = clazz.simpleName
if (clazzName == "LongColumnType")
return arrayOf<Long>()
if (clazzName == "TextColumnType")
return arrayOf<String>()
error("Unsupported Column Type")
}
if (value is java.sql.Array) {
return value.array
}
if (value is Array<*>) {
return value
}
error("Array does not support for this database")
}
override fun notNullValueToDB(value: Any): Any {
if (!supportsArrays())
return "'NOT SUPPORTED'"
if (value is Array<*>) {
if (value.isEmpty())
return "'{}'"
val columnType = type.sqlType().split("(")[0]
val jdbcConnection = (TransactionManager.current().connection as JdbcConnectionImpl).connection
return jdbcConnection.createArrayOf(columnType, value) ?: error("Can't create non null array for $value")
} else {
return super.notNullValueToDB(value)
}
}
}
When using a PublishSubject in conjunction with blockingGet(), there seems to be a race condition where a subscriber does not receive the events.
I attached a basic JUnit test in Kotlin, which has 2 methods.
rxTestBroken() shows the broken behavior with a PublishSubject.
rxTestOk() shows that everything works fine with a BehaviorSubject, because the latter replays the last event in case the subscriber is not subscribed in time.
Where does this race condition come from and is using a BehaviorSubject the correct fix?
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.concurrent.TimeUnit
class StateMachine(val stateSubject: Subject<Int>) {
companion object {
val STATE_IDLE = 1
val STATE_READY = 2
}
val output = 10L
var currentState = STATE_IDLE
fun scheduleNextState(nextState: Int) {
Thread(Runnable {
currentState = nextState
stateSubject.onNext(currentState)
}).start()
}
fun start() = scheduleNextState(STATE_READY)
fun stop() = scheduleNextState(STATE_IDLE)
}
class RxTest {
fun stateOutput(stateSubject: Subject<Int>): Single<Int> {
val stateMachine = StateMachine(stateSubject)
val waitForIdle = stateSubject
.startWith(stateMachine.currentState)
.doOnNext {
if (it != StateMachine.STATE_IDLE) { stateMachine.stop() }
}
.filter { it == StateMachine.STATE_IDLE }
.firstOrError()
val scanFile = stateSubject
.doOnSubscribe { stateMachine.start() }
.filter {
when (it) {
StateMachine.STATE_READY -> true
StateMachine.STATE_IDLE -> false
else -> throw RuntimeException("Wrong state $it")
}
}
.firstOrError()
.map { stateMachine.output.toInt() }
.doFinally { stateMachine.stop() }
return waitForIdle.flatMap { scanFile }.timeout(1, TimeUnit.SECONDS).onErrorReturnItem(-1)
}
#Test
fun rxTestBroken() {
for (i in 1..10000) {
assertThat(stateOutput(PublishSubject.create<Int>()).blockingGet())
.withFailMessage("worked $i times")
.isEqualTo(10)
}
}
#Test
fun rxTestOk() {
for (i in 1..10000) {
assertThat(stateOutput(BehaviorSubject.createDefault(StateMachine.STATE_IDLE)).blockingGet())
.withFailMessage("worked $i times")
.isEqualTo(10)
}
}
}
I have this interface :
interface IPoint {
getDist(): string;
getDist(x: number): any;
}
and I need a class to implement it but I can't get the right syntax to implement the getDist() method
in the class..
class Point implements IPoint {
// Constructor
constructor (public x: number, public y: number) { }
pointMethod() { }
getDist() {
Math.sqrt(this.x * this.x + this.y * this.y);
}
// Static member
static origin = new Point(0, 0);
}
it says:
Class 'Point' declares interface 'IPoint' but does not implement it:
Types of property 'getDist' of types 'Point' and 'IPoint' are
incompatible:Call signatures of types '() => void' and '{ (): string;
(x: number): any; }' are incompatible
What's the proper way to do this?
Thanks
When you declare the function in the class you need to decorate it with the overloads:
getDist(): string;
getDist(x: number): any;
getDist(x?: number): any {
// your code
}
This answer describes how to implement method overloading in TypeScript, and it's not pretty:
interface IPoint {
getDist(): string;
getDist(x: number): any;
}
class Point implements IPoint {
// Constructor
constructor (public x: number, public y: number) { }
pointMethod() { }
getDist(x?: number) {
if (x && typeof x == "number") {
return 'foo';
} else {
return 'bar';
}
}
}
N.B. with the particular combination of declared return types in the interface, you are limited to returning strings from getDist.
also you can use the default value
interface Foo{
next()
next(steps: number)
prev()
prev(steps: number)
}
next(steps: number = 1) {
// ...
}
prev(steps: number = 1) {
// ...
}
The following is a variation on some of the above
class Position {
x: number;
y: number;
constructor(x : number = 0, y : number = 0) {
this.x = x;
this.y = y;
}
}
class Pen {
colour: string;
constructor(colour: string) {
this.colour = colour;
}
}
class PlottingHead {
isPenUp: boolean;
pen: Pen;
position: Position;
constructor() {
this.penUp();
}
move(p: Position): void;
move(x: number, y: number): void;
move(x: number | Position, y?: number): void {
if (typeof x === "number")
{
x = new Position(x, y);
}
this.penUp();
this.position = x;
}
draw(x: number | Position, y?: number): void {
if (typeof x === "number")
{
x = new Position(x, y);
}
this.penDown();
this.position = x;
}
penDown(): void {
this.isPenUp = false;
}
penUp(): void {
this.isPenUp = true;
}
onChangePen(newPen: Pen) {
this.penUp();
this.pen = newPen;
}
}
The move function takes either a single position object or a pair of numeric values. This can obviously be extended as required.