src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt

Tue, 11 May 2021 16:00:28 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 11 May 2021 16:00:28 +0200
changeset 189
f7de8158b41c
parent 188
2979436edd9e
child 190
a83f1ab56898
permissions
-rw-r--r--

fixes #137 - leaking prepared statements

universe@167 1 /*
universe@180 2 * Copyright 2021 Mike Becker. All rights reserved.
universe@167 3 *
universe@167 4 * Redistribution and use in source and binary forms, with or without
universe@167 5 * modification, are permitted provided that the following conditions are met:
universe@167 6 *
universe@167 7 * 1. Redistributions of source code must retain the above copyright
universe@167 8 * notice, this list of conditions and the following disclaimer.
universe@167 9 *
universe@167 10 * 2. Redistributions in binary form must reproduce the above copyright
universe@167 11 * notice, this list of conditions and the following disclaimer in the
universe@167 12 * documentation and/or other materials provided with the distribution.
universe@167 13 *
universe@167 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
universe@167 15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
universe@167 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
universe@167 17 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
universe@167 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
universe@167 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
universe@167 20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
universe@167 21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
universe@167 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
universe@167 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
universe@167 24 */
universe@167 25
universe@167 26 package de.uapcore.lightpit.dao
universe@167 27
universe@167 28 import de.uapcore.lightpit.entities.*
universe@167 29 import de.uapcore.lightpit.types.WebColor
universe@184 30 import de.uapcore.lightpit.util.*
universe@184 31 import de.uapcore.lightpit.viewmodel.ComponentSummary
universe@184 32 import de.uapcore.lightpit.viewmodel.IssueSummary
universe@184 33 import de.uapcore.lightpit.viewmodel.VersionSummary
universe@189 34 import org.intellij.lang.annotations.Language
universe@167 35 import java.sql.Connection
universe@167 36 import java.sql.PreparedStatement
universe@167 37 import java.sql.ResultSet
universe@167 38
universe@167 39 class PostgresDataAccessObject(private val connection: Connection) : DataAccessObject {
universe@167 40
universe@189 41 /**
universe@189 42 * Prepares the given [sql] statement and executes the [block] function with the prepared statement as receiver.
universe@189 43 * The statement is then closed properly.
universe@189 44 */
universe@189 45 private fun <R> withStatement(@Language("SQL") sql: String, block: PreparedStatement.() -> R) =
universe@189 46 connection.prepareStatement(sql).use(block)
universe@189 47
universe@189 48 /**
universe@189 49 * Prepares the given [sql] statement and executes the [block] function on that statement.
universe@189 50 * The statement is then closed properly.
universe@189 51 */
universe@189 52 private fun <R> useStatement(@Language("SQL") sql: String, block: (PreparedStatement) -> R) =
universe@189 53 connection.prepareStatement(sql).use(block)
universe@189 54
universe@189 55 /**
universe@189 56 * Executes the statement and iterates the whole result set extracting the rows with the given [extractor] function.
universe@189 57 */
universe@189 58 private fun <T> PreparedStatement.queryAll(extractor: (ResultSet) -> T): List<T> = executeQuery().use {
universe@189 59 sequence {
universe@189 60 while (it.next()) {
universe@189 61 yield(extractor(it))
universe@189 62 }
universe@189 63 }.toList()
universe@189 64 }
universe@189 65
universe@189 66 /**
universe@189 67 * Executes the statement and extracts a single row with the given [extractor] function.
universe@189 68 * If the result set is empty, null is returned.
universe@189 69 */
universe@189 70 private fun <T> PreparedStatement.querySingle(extractor: (ResultSet) -> T): T? = executeQuery().use {
universe@189 71 return if (it.next()) extractor(it) else null
universe@189 72 }
universe@189 73
universe@167 74 //<editor-fold desc="User">
universe@189 75 //language=SQL
universe@189 76 private val userQuery = "select userid, username, lastname, givenname, mail from lpit_user"
universe@189 77
universe@189 78 private fun ResultSet.extractUser() = User(getInt("userid")).apply {
universe@189 79 username = getString("username")
universe@189 80 givenname = getString("givenname")
universe@189 81 lastname = getString("lastname")
universe@189 82 mail = getString("mail")
universe@189 83 }
universe@189 84
universe@189 85 private fun ResultSet.containsUserInfo(): Boolean {
universe@189 86 getInt("userid")
universe@189 87 return !wasNull()
universe@189 88 }
universe@189 89
universe@189 90 private fun ResultSet.extractOptionalUser() = if (containsUserInfo()) extractUser() else null
universe@189 91
universe@189 92 override fun listUsers() =
universe@189 93 withStatement("$userQuery where userid > 0 order by username") {
universe@189 94 queryAll { it.extractUser() }
universe@189 95 }
universe@189 96
universe@189 97 override fun findUser(id: Int): User? =
universe@189 98 withStatement("$userQuery where userid = ?") {
universe@189 99 setInt(1, id)
universe@189 100 querySingle { it.extractUser() }
universe@189 101 }
universe@189 102
universe@189 103 override fun findUserByName(username: String): User? =
universe@189 104 withStatement("$userQuery where lower(username) = lower(?)") {
universe@189 105 setString(1, username)
universe@189 106 querySingle { it.extractUser() }
universe@189 107 }
universe@189 108
universe@189 109 override fun insertUser(user: User) {
universe@189 110 withStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)") {
universe@189 111 with(user) {
universe@189 112 setStringSafe(1, username)
universe@189 113 setStringOrNull(2, lastname)
universe@189 114 setStringOrNull(3, givenname)
universe@189 115 setStringOrNull(4, mail)
universe@167 116 }
universe@189 117 executeUpdate()
universe@167 118 }
universe@167 119 }
universe@167 120
universe@189 121 override fun updateUser(user: User) {
universe@189 122 withStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?") {
universe@189 123 with(user) {
universe@189 124 setStringOrNull(1, lastname)
universe@189 125 setStringOrNull(2, givenname)
universe@189 126 setStringOrNull(3, mail)
universe@189 127 setInt(4, id)
universe@189 128 }
universe@189 129 executeUpdate()
universe@167 130 }
universe@167 131 }
universe@167 132 //</editor-fold>
universe@167 133
universe@167 134 //<editor-fold desc="Version">
universe@167 135 //language=SQL
universe@167 136 private val versionQuery = "select versionid, project, name, node, ordinal, status from lpit_version"
universe@167 137
universe@189 138 private fun ResultSet.extractVersion() =
universe@189 139 Version(getInt("versionid"), getInt("project")).apply {
universe@189 140 name = getString("name")
universe@189 141 node = getString("node")
universe@189 142 ordinal = getInt("ordinal")
universe@189 143 status = getEnum("status")
universe@189 144 }
universe@189 145
universe@189 146 override fun listVersions(project: Project): List<Version> =
universe@189 147 withStatement("$versionQuery where project = ? order by ordinal desc, lower(name) desc") {
universe@189 148 setInt(1, project.id)
universe@189 149 queryAll { it.extractVersion() }
universe@189 150 }
universe@189 151
universe@189 152 override fun listVersionSummaries(project: Project): List<VersionSummary> =
universe@189 153 withStatement(
universe@184 154 """
universe@184 155 with version_map(issueid, versionid, isresolved) as (
universe@184 156 select issueid, versionid, 1
universe@184 157 from lpit_issue_resolved_version
universe@184 158 union
universe@184 159 select issueid, versionid, 0
universe@184 160 from lpit_issue_affected_version
universe@184 161 ),
universe@184 162 issues as (
universe@184 163 select versionid, phase, isresolved, count(issueid) as total
universe@184 164 from lpit_issue
universe@184 165 join version_map using (issueid)
universe@184 166 join lpit_issue_phases using (status)
universe@184 167 group by versionid, phase, isresolved
universe@184 168 ),
universe@184 169 summary as (
universe@184 170 select versionid, phase, isresolved, total
universe@184 171 from lpit_version v
universe@184 172 left join issues using (versionid)
universe@184 173 where v.project = ?
universe@184 174 )
universe@184 175 select versionid, project, name, node, ordinal, status, phase, isresolved, total
universe@184 176 from lpit_version
universe@184 177 join summary using (versionid)
universe@184 178 order by ordinal, name
universe@189 179 """.trimIndent()
universe@189 180 ) {
universe@189 181 setInt(1, project.id)
universe@189 182 executeQuery().use { rs ->
universe@189 183 sequence {
universe@189 184 // TODO: fix bug: this extractor is not grouping the results
universe@189 185 val versionSummary = VersionSummary(rs.extractVersion())
universe@184 186 val phase = rs.getInt("phase")
universe@184 187 val total = rs.getInt("total")
universe@184 188 val issueSummary =
universe@184 189 if (rs.getBoolean("isresolved")) versionSummary.resolvedTotal else versionSummary.reportedTotal
universe@184 190 when (phase) {
universe@184 191 0 -> issueSummary.open = total
universe@184 192 1 -> issueSummary.active = total
universe@184 193 2 -> issueSummary.done = total
universe@184 194 }
universe@184 195 yield(versionSummary)
universe@189 196 }.toList()
universe@184 197 }
universe@189 198 }
universe@184 199
universe@189 200 override fun findVersion(id: Int): Version? =
universe@189 201 withStatement("$versionQuery where versionid = ?") {
universe@189 202 setInt(1, id)
universe@189 203 querySingle { it.extractVersion() }
universe@189 204 }
universe@167 205
universe@189 206 override fun findVersionByNode(project: Project, node: String): Version? =
universe@189 207 withStatement("$versionQuery where project = ? and node = ?") {
universe@189 208 setInt(1, project.id)
universe@189 209 setString(2, node)
universe@189 210 querySingle { it.extractVersion() }
universe@189 211 }
universe@167 212
universe@167 213 override fun insertVersion(version: Version) {
universe@189 214 withStatement("insert into lpit_version (name, node, ordinal, status, project) values (?, ?, ?, ?::version_status, ?)") {
universe@189 215 with(version) {
universe@189 216 setStringSafe(1, name)
universe@189 217 setStringSafe(2, node)
universe@189 218 setInt(3, ordinal)
universe@189 219 setEnum(4, status)
universe@189 220 setInt(5, version.projectid)
universe@189 221 }
universe@189 222 executeUpdate()
universe@189 223 }
universe@189 224
universe@167 225 }
universe@167 226
universe@167 227 override fun updateVersion(version: Version) {
universe@189 228 withStatement("update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status where versionid = ?") {
universe@189 229 with(version) {
universe@189 230 setStringSafe(1, name)
universe@189 231 setStringSafe(2, node)
universe@189 232 setInt(3, ordinal)
universe@189 233 setEnum(4, status)
universe@189 234 setInt(5, id)
universe@189 235 }
universe@189 236 executeUpdate()
universe@189 237 }
universe@167 238 }
universe@167 239 //</editor-fold>
universe@167 240
universe@167 241 //<editor-fold desc="Component">
universe@167 242 //language=SQL
universe@167 243 private val componentQuery =
universe@167 244 """
universe@167 245 select id, project, name, node, color, ordinal, description,
universe@167 246 userid, username, givenname, lastname, mail
universe@167 247 from lpit_component
universe@167 248 left join lpit_user on lead = userid
universe@189 249 """.trimIndent()
universe@167 250
universe@189 251 private fun ResultSet.extractComponent(): Component =
universe@189 252 Component(getInt("id"), getInt("project")).apply {
universe@189 253 name = getString("name")
universe@189 254 node = getString("node")
universe@189 255 color = try {
universe@189 256 WebColor(getString("color"))
universe@189 257 } catch (ex: IllegalArgumentException) {
universe@189 258 WebColor("000000")
universe@189 259 }
universe@189 260 ordinal = getInt("ordinal")
universe@189 261 description = getString("description")
universe@189 262 lead = extractOptionalUser()
universe@189 263 }
universe@189 264
universe@189 265 private fun PreparedStatement.setComponent(index: Int, component: Component): Int {
universe@189 266 with(component) {
universe@189 267 var i = index
universe@189 268 setStringSafe(i++, name)
universe@189 269 setStringSafe(i++, node)
universe@189 270 setStringSafe(i++, color.hex)
universe@189 271 setInt(i++, ordinal)
universe@189 272 setStringOrNull(i++, description)
universe@189 273 setIntOrNull(i++, lead?.id)
universe@189 274 return i
universe@189 275 }
universe@167 276 }
universe@189 277
universe@189 278 override fun listComponents(project: Project): List<Component> =
universe@189 279 withStatement("$componentQuery where project = ? order by ordinal, lower(name)") {
universe@189 280 setInt(1, project.id)
universe@189 281 queryAll { it.extractComponent() }
universe@189 282 }
universe@189 283
universe@189 284 override fun listComponentSummaries(project: Project): List<ComponentSummary> =
universe@189 285 withStatement(
universe@184 286 """
universe@184 287 with issues as (
universe@184 288 select component, phase, count(issueid) as total
universe@184 289 from lpit_issue
universe@184 290 join lpit_issue_phases using (status)
universe@184 291 group by component, phase
universe@184 292 ),
universe@184 293 summary as (
universe@184 294 select c.id, phase, total
universe@184 295 from lpit_component c
universe@184 296 left join issues i on c.id = i.component
universe@184 297 where c.project = ?
universe@184 298 )
universe@184 299 select c.id, project, name, node, color, ordinal, description,
universe@184 300 userid, username, givenname, lastname, mail, phase, total
universe@184 301 from lpit_component c
universe@184 302 left join lpit_user on lead = userid
universe@184 303 join summary s on c.id = s.id
universe@184 304 order by ordinal, name
universe@189 305 """.trimIndent()
universe@189 306 ) {
universe@189 307 setInt(1, project.id)
universe@189 308 executeQuery().use { rs ->
universe@189 309 // TODO: fix bug: this extractor is not grouping the results
universe@189 310 sequence {
universe@189 311 val componentSummary = ComponentSummary(rs.extractComponent()).also {
universe@189 312 val phase = rs.getInt("phase")
universe@189 313 val total = rs.getInt("total")
universe@189 314 when (phase) {
universe@189 315 0 -> it.issueSummary.open = total
universe@189 316 1 -> it.issueSummary.active = total
universe@189 317 2 -> it.issueSummary.done = total
universe@189 318 }
universe@184 319 }
universe@184 320 yield(componentSummary)
universe@189 321 }.toList()
universe@184 322 }
universe@189 323 }
universe@184 324
universe@189 325 override fun findComponent(id: Int): Component? =
universe@189 326 withStatement("$componentQuery where id = ?") {
universe@189 327 setInt(1, id)
universe@189 328 querySingle { it.extractComponent() }
universe@189 329 }
universe@167 330
universe@189 331 override fun findComponentByNode(project: Project, node: String): Component? =
universe@189 332 withStatement("$componentQuery where project = ? and node = ?") {
universe@189 333 setInt(1, project.id)
universe@189 334 setString(2, node)
universe@189 335 querySingle { it.extractComponent() }
universe@189 336 }
universe@167 337
universe@167 338 override fun insertComponent(component: Component) {
universe@189 339 withStatement("insert into lpit_component (name, node, color, ordinal, description, lead, project) values (?, ?, ?, ?, ?, ?, ?)") {
universe@189 340 val col = setComponent(1, component)
universe@189 341 setInt(col, component.projectid)
universe@189 342 executeUpdate()
universe@189 343 }
universe@167 344 }
universe@167 345
universe@167 346 override fun updateComponent(component: Component) {
universe@189 347 withStatement("update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?") {
universe@189 348 val col = setComponent(1, component)
universe@189 349 setInt(col, component.id)
universe@189 350 executeUpdate()
universe@167 351 }
universe@167 352 }
universe@167 353
universe@189 354 //</editor-fold>
universe@189 355
universe@189 356 //<editor-fold desc="Project">
universe@167 357
universe@167 358 //language=SQL
universe@167 359 private val projectQuery =
universe@167 360 """
universe@175 361 select projectid, name, node, ordinal, description, repourl,
universe@167 362 userid, username, lastname, givenname, mail
universe@167 363 from lpit_project
universe@167 364 left join lpit_user owner on lpit_project.owner = owner.userid
universe@189 365 """.trimIndent()
universe@167 366
universe@189 367 private fun ResultSet.extractProject() =
universe@189 368 Project(getInt("projectid")).apply {
universe@189 369 name = getString("name")
universe@189 370 node = getString("node")
universe@189 371 ordinal = getInt("ordinal")
universe@189 372 description = getString("description")
universe@189 373 repoUrl = getString("repourl")
universe@189 374 owner = extractOptionalUser()
universe@189 375 }
universe@189 376
universe@189 377 private fun PreparedStatement.setProject(index: Int, project: Project): Int {
universe@189 378 var i = index
universe@189 379 with(project) {
universe@189 380 setStringSafe(i++, name)
universe@189 381 setStringSafe(i++, node)
universe@189 382 setInt(i++, ordinal)
universe@189 383 setStringOrNull(i++, description)
universe@189 384 setStringOrNull(i++, repoUrl)
universe@189 385 setIntOrNull(i++, owner?.id)
universe@189 386 }
universe@189 387 return i
universe@167 388 }
universe@189 389
universe@189 390 override fun listProjects(): List<Project> =
universe@189 391 withStatement("$projectQuery order by ordinal, lower(name)") {
universe@189 392 queryAll { it.extractProject() }
universe@189 393 }
universe@189 394
universe@189 395 override fun findProject(id: Int): Project? =
universe@189 396 withStatement("$projectQuery where projectid = ?") {
universe@189 397 setInt(1, id)
universe@189 398 querySingle { it.extractProject() }
universe@189 399 }
universe@189 400
universe@189 401 override fun findProjectByNode(node: String): Project? =
universe@189 402 withStatement("$projectQuery where node = ?") {
universe@189 403 setString(1, node)
universe@189 404 querySingle { it.extractProject() }
universe@189 405 }
universe@189 406
universe@189 407 override fun insertProject(project: Project) {
universe@189 408 withStatement("insert into lpit_project (name, node, ordinal, description, repourl, owner) values (?, ?, ?, ?, ?, ?)") {
universe@189 409 setProject(1, project)
universe@189 410 executeUpdate()
universe@189 411 }
universe@167 412 }
universe@189 413
universe@189 414 override fun updateProject(project: Project) {
universe@189 415 withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, repourl = ?, owner = ? where projectid = ?") {
universe@189 416 val col = setProject(1, project)
universe@189 417 setInt(col, project.id)
universe@189 418 executeUpdate()
universe@189 419 }
universe@167 420 }
universe@189 421
universe@189 422 override fun collectIssueSummary(project: Project): IssueSummary =
universe@189 423 withStatement(
universe@167 424 """
universe@167 425 select phase, count(*) as total
universe@167 426 from lpit_issue
universe@167 427 join lpit_issue_phases using(status)
universe@167 428 where project = ?
universe@167 429 group by phase
universe@189 430 """.trimIndent()
universe@189 431 ) {
universe@189 432 setInt(1, project.id)
universe@189 433 executeQuery().use {
universe@189 434 val summary = IssueSummary()
universe@189 435 while (it.next()) {
universe@189 436 val phase = it.getInt("phase")
universe@189 437 val total = it.getInt("total")
universe@189 438 when (phase) {
universe@189 439 0 -> summary.open = total
universe@189 440 1 -> summary.active = total
universe@189 441 2 -> summary.done = total
universe@189 442 }
universe@167 443 }
universe@189 444 summary
universe@167 445 }
universe@167 446 }
universe@167 447
universe@189 448 //</editor-fold>
universe@189 449
universe@189 450 //<editor-fold desc="Issue">
universe@167 451
universe@167 452 //language=SQL
universe@167 453 private val issueQuery =
universe@167 454 """
universe@167 455 select issueid,
universe@167 456 i.project, p.name as projectname, p.node as projectnode,
universe@167 457 component, c.name as componentname, c.node as componentnode,
universe@167 458 status, category, subject, i.description,
universe@167 459 userid, username, givenname, lastname, mail,
universe@167 460 created, updated, eta
universe@167 461 from lpit_issue i
universe@167 462 join lpit_project p on i.project = projectid
universe@167 463 left join lpit_component c on component = c.id
universe@167 464 left join lpit_user on userid = assignee
universe@189 465 """.trimIndent()
universe@167 466
universe@189 467 private fun ResultSet.extractIssue(): Issue {
universe@189 468 val proj = Project(getInt("project")).apply {
universe@189 469 name = getString("projectname")
universe@189 470 node = getString("projectnode")
universe@189 471 }
universe@189 472 val comp = getInt("component").let {
universe@189 473 if (wasNull()) null else
universe@189 474 Component(it, proj.id).apply {
universe@189 475 name = getString("componentname")
universe@189 476 node = getString("componentnode")
universe@189 477 }
universe@189 478 }
universe@189 479 val issue = Issue(getInt("issueid"), proj).apply {
universe@189 480 component = comp
universe@189 481 status = getEnum("status")
universe@189 482 category = getEnum("category")
universe@189 483 subject = getString("subject")
universe@189 484 description = getString("description")
universe@189 485 assignee = extractOptionalUser()
universe@189 486 created = getTimestamp("created")
universe@189 487 updated = getTimestamp("updated")
universe@189 488 eta = getDate("eta")
universe@189 489 }
universe@189 490
universe@189 491 fun versionQuery(table: String) =
universe@167 492 """
universe@167 493 select versionid, project, name, status, ordinal, node
universe@189 494 from lpit_version join $table using (versionid)
universe@167 495 where issueid = ?
universe@167 496 order by ordinal, name
universe@189 497 """.trimIndent()
universe@189 498
universe@189 499 issue.affectedVersions = withStatement(versionQuery("lpit_issue_affected_version")) {
universe@189 500 setInt(1, issue.id)
universe@189 501 queryAll { it.extractVersion() }
universe@189 502 }
universe@189 503 issue.resolvedVersions = withStatement(versionQuery("lpit_issue_resolved_version")) {
universe@189 504 setInt(1, issue.id)
universe@189 505 queryAll { it.extractVersion() }
universe@189 506 }
universe@189 507 return issue
universe@167 508 }
universe@167 509
universe@189 510 private fun PreparedStatement.setIssue(index: Int, issue: Issue): Int {
universe@189 511 var i = index
universe@189 512 with(issue) {
universe@189 513 setIntOrNull(i++, component?.id)
universe@189 514 setEnum(i++, status)
universe@189 515 setEnum(i++, category)
universe@189 516 setStringSafe(i++, subject)
universe@189 517 setStringOrNull(i++, description)
universe@189 518 setIntOrNull(i++, assignee?.id)
universe@189 519 setDateOrNull(i++, eta)
universe@189 520 }
universe@189 521 return i
universe@167 522 }
universe@167 523
universe@189 524 override fun listIssues(filter: IssueFilter): List<Issue> =
universe@189 525 withStatement(
universe@167 526 """
universe@167 527 with issue_version as (
universe@167 528 select issueid, versionid from lpit_issue_affected_version
universe@167 529 union select issueid, versionid from lpit_issue_resolved_version
universe@183 530 ),
universe@189 531 filtered_issues as (
universe@183 532 select distinct issueid from lpit_issue
universe@183 533 left join issue_version using (issueid)
universe@183 534 where
universe@183 535 (not ? or project = ?) and
universe@183 536 (not ? or versionid = ?) and (not ? or versionid is null) and
universe@183 537 (not ? or component = ?) and (not ? or component is null)
universe@183 538 )
universe@189 539 $issueQuery join filtered_issues using (issueid)
universe@189 540 """.trimIndent()
universe@189 541 ) {
universe@189 542 fun <T : Entity> applyFilter(filter: Filter<T>, fflag: Int, nflag: Int, idcol: Int) {
universe@189 543 when (filter) {
universe@189 544 is AllFilter -> {
universe@189 545 setBoolean(fflag, false)
universe@189 546 setBoolean(nflag, false)
universe@189 547 setInt(idcol, 0)
universe@189 548 }
universe@189 549 is NoneFilter -> {
universe@189 550 setBoolean(fflag, false)
universe@189 551 setBoolean(nflag, true)
universe@189 552 setInt(idcol, 0)
universe@189 553 }
universe@189 554 is SpecificFilter -> {
universe@189 555 setBoolean(fflag, true)
universe@189 556 setBoolean(nflag, false)
universe@189 557 setInt(idcol, filter.obj.id)
universe@189 558 }
universe@189 559 else -> {
universe@189 560 TODO("Implement range filter.")
universe@189 561 }
universe@189 562 }
universe@189 563 }
universe@189 564 when (filter.project) {
universe@189 565 is AllFilter -> {
universe@189 566 setBoolean(1, false)
universe@189 567 setInt(2, 0)
universe@189 568 }
universe@189 569 is SpecificFilter -> {
universe@189 570 setBoolean(1, true)
universe@189 571 setInt(2, filter.project.obj.id)
universe@189 572 }
universe@189 573 else -> throw IllegalArgumentException()
universe@189 574 }
universe@189 575 applyFilter(filter.version, 3, 5, 4)
universe@189 576 applyFilter(filter.component, 6, 8, 7)
universe@167 577
universe@189 578 queryAll { it.extractIssue() }
universe@189 579 }
universe@167 580
universe@189 581 override fun findIssue(id: Int): Issue? =
universe@189 582 withStatement("$issueQuery where issueid = ?") {
universe@189 583 setInt(1, id)
universe@189 584 querySingle { it.extractIssue() }
universe@189 585 }
universe@189 586
universe@189 587 private fun insertVersionInfo(id: Int, issue: Issue) {
universe@189 588 withStatement("insert into lpit_issue_affected_version (issueid, versionid) values (?,?)") {
universe@189 589 setInt(1, id)
universe@189 590 issue.affectedVersions.forEach {
universe@189 591 setInt(2, it.id)
universe@189 592 executeUpdate()
universe@167 593 }
universe@189 594 }
universe@189 595 withStatement("insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)") {
universe@189 596 setInt(1, id)
universe@189 597 issue.resolvedVersions.forEach {
universe@189 598 setInt(2, it.id)
universe@189 599 executeUpdate()
universe@167 600 }
universe@167 601 }
universe@167 602 }
universe@167 603
universe@189 604 override fun insertIssue(issue: Issue): Int {
universe@189 605 val id = withStatement(
universe@167 606 """
universe@167 607 insert into lpit_issue (component, status, category, subject, description, assignee, eta, project)
universe@167 608 values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?)
universe@167 609 returning issueid
universe@189 610 """.trimIndent()
universe@189 611 ) {
universe@189 612 val col = setIssue(1, issue)
universe@189 613 setInt(col, issue.project.id)
universe@189 614 querySingle { it.getInt(1) }!!
universe@167 615 }
universe@188 616 insertVersionInfo(id, issue)
universe@184 617 return id
universe@167 618 }
universe@167 619
universe@167 620 override fun updateIssue(issue: Issue) {
universe@189 621 withStatement(
universe@189 622 """
universe@189 623 update lpit_issue set updated = now(),
universe@189 624 component = ?, status = ?::issue_status, category = ?::issue_category, subject = ?,
universe@189 625 description = ?, assignee = ?, eta = ?
universe@189 626 where issueid = ?
universe@189 627 """.trimIndent()
universe@189 628 ) {
universe@189 629 val col = setIssue(1, issue)
universe@189 630 setInt(col, issue.id)
universe@189 631 executeUpdate()
universe@189 632 }
universe@189 633
universe@167 634 // TODO: improve by only inserting / deleting changed version information
universe@189 635 withStatement("delete from lpit_issue_affected_version where issueid = ?") {
universe@189 636 setInt(1, issue.id)
universe@189 637 executeUpdate()
universe@189 638 }
universe@189 639 withStatement("delete from lpit_issue_resolved_version where issueid = ?") {
universe@189 640 setInt(1, issue.id)
universe@189 641 executeUpdate()
universe@189 642 }
universe@188 643 insertVersionInfo(issue.id, issue)
universe@167 644 }
universe@167 645
universe@189 646 //</editor-fold>
universe@167 647
universe@189 648 //<editor-fold desc="IssueComment">
universe@167 649
universe@189 650 private fun ResultSet.extractIssueComment() =
universe@189 651 IssueComment(getInt("commentid"), getInt("issueid")).apply {
universe@189 652 created = getTimestamp("created")
universe@189 653 updated = getTimestamp("updated")
universe@189 654 updateCount = getInt("updatecount")
universe@189 655 comment = getString("comment")
universe@189 656 author = extractOptionalUser()
universe@189 657 }
universe@189 658
universe@189 659 override fun listComments(issue: Issue): List<IssueComment> =
universe@189 660 withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created") {
universe@189 661 setInt(1, issue.id)
universe@189 662 queryAll { it.extractIssueComment() }
universe@189 663 }
universe@189 664
universe@189 665 override fun insertComment(issueComment: IssueComment) {
universe@189 666 useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
universe@189 667 withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)") {
universe@189 668 with(issueComment) {
universe@189 669 updateIssueDate.setInt(1, issueid)
universe@189 670 setInt(1, issueid)
universe@189 671 setStringSafe(2, comment)
universe@189 672 setIntOrNull(3, author?.id)
universe@189 673 }
universe@189 674 executeUpdate()
universe@189 675 updateIssueDate.executeUpdate()
universe@167 676 }
universe@167 677 }
universe@167 678 }
universe@189 679 //</editor-fold>
universe@167 680 }

mercurial