Skip to content

Conversation

ia-beklund
Copy link

Added a QuestDB Viewer tool to Kinding which takes a .zip export of a historian folder, and opens the export similar to the .idb viewer DB tool. Also includes refactor to move common components used in the .idb and quest tools to core.

Copy link
Collaborator

@paul-griffith paul-griffith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Standing up an entire QuestDB instance is very heavy by comparison to SQLite. I suspect there's a lot of optimization we can do to that startup, since this will only ever be a read only copy. In particular, an entire webserver complete with its own configuration GUI, plus many worker threads, seems like a crazy hit. I'd like us to start very small with how much QuestDB we're surfacing via Kindling.

Image 2. We need to either reflow the 'main' panel to account for this extra tool (3 - 4 - 3? something else?), or put QuestDB into the same 'tool' as SQLite and figure out a way to layer them on top of each other we don't hate down the road. This basic configuration view works great for now, but in the future we may want more bespoke views for history (and auditing, and alarm journal) data that's all in Quest, much like there are different 'overlays' for SQLite IDB files.
  1. You've also got some (minor) style faults


val httpPort = ServerSocket(0).use { it.localPort } // web console port
val config = """
pg.net.bind.to=0.0.0.0:$pgPort
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be localhost, not 0.0.0.0, most likely, so we're not inadvertently exposing a questdb instance to the entire network


@Suppress("SqlResolve")
val tables: List<Table> = connection
.executeQuery("""SELECT table_name FROM tables();""")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Doesn't need to be a triple quoted string

)
}
val size: Long
connection.executeQuery("""SELECT diskSize FROM table_storage() WHERE tableName = '$tableName'""").use { rs ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Doesn't need to be a triple quoted string
Or, if it is, might as well format the SQL query nicer

val size: Long
connection.executeQuery("""SELECT diskSize FROM table_storage() WHERE tableName = '$tableName'""").use { rs ->
// Move the cursor to the first row. If there's no row, default to 0.
size = if (rs.next()) rs.getLong("diskSize") else 0L
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could collapse this to a single expression - val size = connection.executeQuery().use { rs.getLong() else 0L}

size = size,
)
} catch (e: Exception) {
println("Warning: Could not process table '$tableName'. Error: ${e.message}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a logger

val value = resultSet.getObject(i + 1)
when {
types[i] == Boolean::class.javaObjectType -> value == 1
types[i] == Date::class.java && value is Number -> Date(value.toLong())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these sqlite-based datatype hacks still actually needed for quest?

resultSet.metaData.getColumnName(i + 1)
}
val types = List(columnCount) { i ->
val isTimestamp = names[i].containsInOrder("tsmp", true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this isTimestamp stuff - makes sense for generic sqlite view, probably not needed for quest

@Joshlha
Copy link
Collaborator

Joshlha commented Sep 26, 2025

@paul-griffith Ready for review again.
Main Changes:

  • No more postgresql jdbc driver usage
  • Using the QuestDB CairoEngine low-level api for direct access to the files
  • Had to write some kotlin stuff to make working with the API a little nicer
  • I introduced context paramaters, partly for fun and learning and partly because (I think) they're useful here.

@paul-griffith paul-griffith merged commit 63627fb into inductiveautomation:main Oct 1, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants