DB insert/update rows from file, query dates from months

This commit is contained in:
Guntis Smaukstelis
2023-10-23 16:20:45 +03:00
parent aa4fa57adb
commit 47be3bf385
3 changed files with 89 additions and 23 deletions
+14 -2
View File
@@ -2,15 +2,27 @@ package app
import cats.effect._ import cats.effect._
import cats.implicits.catsSyntaxTuple2Parallel import cats.implicits.catsSyntaxTuple2Parallel
import db.{FileService, DataService} import db.Main.transactor
import db.{DataService, FileService, PostgresService}
import doobie.Transactor
import fetch.{FetchService, FileFetchScheduler} import fetch.{FetchService, FileFetchScheduler}
import server.Server import server.Server
object Main extends IOApp { object Main extends IOApp {
def transactor[F[_] : Async]: Transactor[F] = Transactor.fromDriverManager[F](
"org.postgresql.Driver",
"jdbc:postgresql://localhost:5432/weather-tool",
"postgres",
"mysecretpassword"
)
def run(args: List[String]): IO[ExitCode] = { def run(args: List[String]): IO[ExitCode] = {
val xa = transactor[IO]
for { for {
postgresService <- PostgresService.of(xa)
fileService <- FileService.of fileService <- FileService.of
dataService <- DataService.of(fileService) dataService <- DataService.of(fileService, postgresService)
fetch <- FetchService.of fetch <- FetchService.of
fileFetchScheduler <- FileFetchScheduler.of(dataService, fetch) fileFetchScheduler <- FileFetchScheduler.of(dataService, fetch)
+14 -3
View File
@@ -1,5 +1,6 @@
package db package db
import cats.effect.unsafe.implicits.global
import cats.effect.{Clock, IO, Ref} import cats.effect.{Clock, IO, Ref}
import cats.implicits.toTraverseOps import cats.implicits.toTraverseOps
import fetch.FileNameService import fetch.FileNameService
@@ -14,10 +15,12 @@ trait DataServiceTrait {
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]] def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]]
def getDates: IO[List[LocalDate]] def getDates: IO[List[LocalDate]]
def getDateFileNames(date: LocalDate): IO[List[String]] def getDateFileNames(date: LocalDate): IO[List[String]]
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]]
} }
object DataService { object DataService {
def of(fileService: FileService): IO[DataService] = { def of(fileService: FileService, postgresService: PostgresService): IO[DataService] = {
for { for {
log <- Slf4jLogger.create[IO] log <- Slf4jLogger.create[IO]
fileNameService = new FileNameService() fileNameService = new FileNameService()
@@ -26,11 +29,12 @@ object DataService {
.map(content => (fileName, content))) .map(content => (fileName, content)))
state = contents.toMap state = contents.toMap
stateRef <- Ref.of[IO, Map[String, List[String]]](state) stateRef <- Ref.of[IO, Map[String, List[String]]](state)
} yield new DataService(fileService, new FileNameService(), log, stateRef) } yield new DataService(fileService, postgresService, new FileNameService(), log, stateRef)
} }
} }
class DataService private( class DataService private(
fileService: FileService, fileService: FileService,
postgresService: PostgresService,
fileNameService: FileNameService, fileNameService: FileNameService,
log: Logger[IO], log: Logger[IO],
private val state: Ref[IO, Map[String, List[String]]] private val state: Ref[IO, Map[String, List[String]]]
@@ -47,6 +51,10 @@ class DataService private(
} }
def save(fileName: String, content: String): IO[String] = { def save(fileName: String, content: String): IO[String] = {
// TODO remove unsafeRunSync
postgresService.save(fileName, content).unsafeRunSync()
// TODO delete this
fileService.save(fileName, content).redeemWith( fileService.save(fileName, content).redeemWith(
error => IO.raiseError(error), error => IO.raiseError(error),
savedFileName => { savedFileName => {
@@ -63,7 +71,10 @@ class DataService private(
def getDates: IO[List[LocalDate]] = fileService.getDates def getDates: IO[List[LocalDate]] = fileService.getDates
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]] = fileService.getDatesByMonths(monthList) def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]] = {
// fileService.getDatesByMonths(monthList)
postgresService.getDatesByMonths(monthList)
}
def getDateFileNames(date: LocalDate): IO[List[String]] = fileService.getDateFileNames(date) def getDateFileNames(date: LocalDate): IO[List[String]] = fileService.getDateFileNames(date)
+56 -13
View File
@@ -2,14 +2,16 @@ package db
import cats.data.NonEmptyList import cats.data.NonEmptyList
import cats.effect._ import cats.effect._
import cats.implicits.{catsSyntaxParallelTraverse1, toFoldableOps}
import doobie._ import doobie._
import doobie.implicits._ import doobie.implicits._
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.{LocalDateTime, ZoneId, ZonedDateTime} import java.time.{LocalDate, LocalDateTime, ZoneId, ZonedDateTime}
import doobie.postgres.implicits._ import doobie.postgres.implicits._
import org.typelevel.log4cats.Logger import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger import org.typelevel.log4cats.slf4j.Slf4jLogger
import parse.{Parser, WeatherStationData}
object PostgresService { object PostgresService {
def of(transactor: Transactor[IO]): IO[PostgresService] = { def of(transactor: Transactor[IO]): IO[PostgresService] = {
@@ -17,7 +19,52 @@ object PostgresService {
} }
} }
class PostgresService(transactor: Transactor[IO], log: Logger[IO]) { class PostgresService(transactor: Transactor[IO], log: Logger[IO]) extends DataServiceTrait {
private val rigaZone = ZoneId.of("Europe/Riga")
// in pgAdmin run: ```SET TIMEZONE = 'Europe/Riga';```
def save(fileName: String, content: String): IO[String] = {
val strLines = content.split(System.lineSeparator()).toList
val weatherStationData = strLines.flatMap(Parser.parseLine)
insertInWeatherTable(weatherStationData)
.attempt
.flatMap {
case Left(error) => log.error(s"Write db '$fileName' failed with error: ${error.getMessage}") *> IO.raiseError(error)
case Right(rowCount) => log.info(s"write rows: $rowCount file: $fileName").as(fileName)
}
}
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]] = {
val monthYearPairs = monthList.map { localDate =>
val month = localDate.atStartOfDay.atZone(rigaZone).getMonthValue
val year = localDate.atStartOfDay.atZone(rigaZone).getYear
(month, year)
}
val whereClauses = monthYearPairs.map {
case (month, year) => fr"(EXTRACT(MONTH FROM dateTime) = $month AND EXTRACT(YEAR FROM dateTime) = $year)"
}
val combinedWhereClause = whereClauses.intercalate(fr" OR ")
(
fr"""
SELECT DISTINCT DATE(dateTime)
FROM weather
WHERE """ ++ combinedWhereClause
)
.query[LocalDate]
.to[List]
.transact(transactor)
}
def readFile(fileName: String): IO[List[String]] = ???
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]] = ???
def getDates: IO[List[LocalDate]] = ???
def getDateFileNames(date: LocalDate): IO[List[String]] = ???
def getResourceContent(path: String): IO[String] = { def getResourceContent(path: String): IO[String] = {
val streamResource = Resource.make(IO(getClass.getResourceAsStream(path))) { stream => val streamResource = Resource.make(IO(getClass.getResourceAsStream(path))) { stream =>
IO(stream.close()).handleErrorWith(_ => IO.unit) IO(stream.close()).handleErrorWith(_ => IO.unit)
@@ -37,26 +84,22 @@ class PostgresService(transactor: Transactor[IO], log: Logger[IO]) {
implicit val doubleOptionMeta: Meta[Option[Double]] = Meta[Double].imap(Option(_))(_.getOrElse(Double.NaN)) implicit val doubleOptionMeta: Meta[Option[Double]] = Meta[Double].imap(Option(_))(_.getOrElse(Double.NaN))
def insertInWeatherTable(): IO[Int] = { def insertInWeatherTable(data: List[WeatherStationData]): IO[Int] = {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") data.parTraverse(line => {
val dateTime = LocalDateTime.parse("20230516_1500", formatter) val zonedTime: ZonedDateTime = line.timestamp.atZone(rigaZone)
val w = line.weather
val rigaZone = ZoneId.of("Europe/Riga")
val zonedTime: ZonedDateTime = dateTime.atZone(rigaZone)
// in pgAdmin run: ```SET TIMEZONE = 'Europe/Riga';```
for { for {
insertTableSql <- getResourceContent("/db/insert_weather_table.sql") insertTableSql <- getResourceContent("/db/insert_weather_table.sql")
result <- Update[(ZonedDateTime, String, Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], List[String])]( result <- Update[(ZonedDateTime, String, Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], Option[Double], List[String])](
insertTableSql insertTableSql
).run((zonedTime, "Rīga", Some(20.2), Some(8.8), Some(15.6), None, None, None, None, None, None, None, None, None, None, List("hail", "rain"))).transact(transactor) ).run((zonedTime, line.city, w.tempMax, w.tempMin, w.tempAvg, w.precipitation, w.windAvg, w.windMax, w.visibilityMin, w.visibilityAvg, w.snowAvg, w.atmPressure, w.dewPoint, w.humidity, w.sunDuration, w.phenomena))
.transact(transactor)
} yield result } yield result
}).map(_.sum)
} }
def selectWeatherTable(): IO[List[(String, Option[Double])]] = { def selectWeatherTable(): IO[List[(String, Option[Double])]] = {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
val rigaZone = ZoneId.of("Europe/Riga")
val from = LocalDateTime.parse("20230516_0100", formatter).atZone(rigaZone) val from = LocalDateTime.parse("20230516_0100", formatter).atZone(rigaZone)
val to = LocalDateTime.parse("20230516_1500", formatter).atZone(rigaZone) val to = LocalDateTime.parse("20230516_1500", formatter).atZone(rigaZone)
val citiesNel = NonEmptyList.of("Rīga", "Rēzekne") val citiesNel = NonEmptyList.of("Rīga", "Rēzekne")