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