Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions obp-api/src/main/scala/code/api/util/DBUtil.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,77 @@
package code.api.util

import code.api.Constant
import net.liftweb.db.{DB, DefaultConnectionIdentifier}
import net.liftweb.util.Helpers.tryo

import java.sql.{ResultSet, Types}

object DBUtil {
def dbUrl: String = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue

def isSqlServer: Boolean = dbUrl.contains("sqlserver")

/**
* SQL Server-safe alternative to Lift's DB.runQuery.
*
* Lift's DB.runQuery uses DB.asString which doesn't handle SQL Server's NVARCHAR type
* (JDBC type -9), causing MatchError. This function handles all JDBC types properly.
*
* @param query SQL query string
* @param params Query parameters (for prepared statement)
* @return Tuple of (column names, rows as List[List[String]])
*/
def runQuery(query: String, params: List[String] = Nil): (List[String], List[List[String]]) = {
DB.use(DefaultConnectionIdentifier) { conn =>
val stmt = conn.prepareStatement(query)
try {
// Set parameters
params.zipWithIndex.foreach { case (param, idx) =>
stmt.setString(idx + 1, param)
}

val rs = stmt.executeQuery()
val meta = rs.getMetaData
val colCount = meta.getColumnCount

// Get column names
val colNames = (1 to colCount).map(i => meta.getColumnName(i)).toList

// Get rows - convert all types to String safely
var rows = List[List[String]]()
while (rs.next()) {
val row = (1 to colCount).map { i =>
safeGetString(rs, i, meta.getColumnType(i))
}.toList
rows = rows :+ row
}

(colNames, rows)
} finally {
stmt.close()
}
}
}

/**
* Safely convert any JDBC type to String, including SQL Server's NVARCHAR (-9).
*/
private def safeGetString(rs: ResultSet, columnIndex: Int, jdbcType: Int): String = {
val value = jdbcType match {
case Types.NVARCHAR | Types.NCHAR | Types.LONGNVARCHAR | Types.NCLOB =>
// SQL Server NVARCHAR types that Lift doesn't handle
rs.getNString(columnIndex)
case Types.CLOB =>
val clob = rs.getClob(columnIndex)
if (clob != null) clob.getSubString(1, clob.length().toInt) else null
case Types.BLOB =>
val blob = rs.getBlob(columnIndex)
if (blob != null) new String(blob.getBytes(1, blob.length().toInt)) else null
case _ =>
rs.getString(columnIndex)
}
if (rs.wasNull()) null else value
}

def getDbConnectionParameters: (String, String, String) = {
dbUrl.contains("jdbc:h2") match {
Expand Down
35 changes: 21 additions & 14 deletions obp-api/src/main/scala/code/metrics/MappedMetrics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,8 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
AND (${trueOrFalse(excludeAppNames.isEmpty) } or appname not in ($excludeAppNamesList))
AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsList))
""".stripMargin
val (_, rows) = DB.runQuery(sqlQuery, List())
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, rows) = DBUtil.runQuery(sqlQuery)
logger.debug("code.metrics.MappedMetrics.getAllAggregateMetricsBox.sqlQuery --: " + sqlQuery)
logger.info(s"getAllAggregateMetricsBox - Query executed, returned ${rows.length} rows")
val sqlResult = rows.map(
Expand Down Expand Up @@ -504,13 +505,13 @@ object MappedMetrics extends APIMetrics with MdcLoggable{

val (dbUrl, _, _) = DBUtil.getDbConnectionParameters

val result: List[TopApi] = {
val result: Box[List[TopApi]] = tryo {
// MS SQL server has the specific syntax for limiting number of rows
val msSqlLimit = if (dbUrl.contains("sqlserver")) s"TOP ($limit)" else s""
// TODO Make it work in case of Oracle database
val otherDbLimit = if (dbUrl.contains("sqlserver")) s"" else s"LIMIT $limit"
val sqlQuery: String =
s"""SELECT ${msSqlLimit} count(*), metric.implementedbypartialfunction, metric.implementedinversion
s"""SELECT ${msSqlLimit} count(*), metric.implementedbypartialfunction, metric.implementedinversion
FROM metric
WHERE
date_c >= '${sqlTimestamp(fromDate.get)}' AND
Expand All @@ -522,29 +523,35 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
AND (${trueOrFalse(url.isEmpty)} or url = ${url.getOrElse("null")})
AND (${trueOrFalse(appName.isEmpty)} or appname = ${appName.getOrElse("null")})
AND (${trueOrFalse(verb.isEmpty)} or verb = ${verb.getOrElse("null")})
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = null)
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != null)
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(true)))} or userid = null)
AND (${falseOrTrue(anon.isDefined && anon.equals(Some(false)))} or userid != null)
AND (${trueOrFalse(httpStatusCode.isEmpty)} or httpcode = ${sqlFriendlyInt(httpStatusCode)})
AND (${trueOrFalse(excludeUrlPatterns.isEmpty)} or (url NOT LIKE ($excludeUrlPatternsQueries)))
AND (${trueOrFalse(excludeAppNames.isEmpty)} or appname not in ($excludeAppNamesNumberList))
AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty)} or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsNumberList))
GROUP BY metric.implementedbypartialfunction, metric.implementedinversion
GROUP BY metric.implementedbypartialfunction, metric.implementedinversion
ORDER BY count(*) DESC
${otherDbLimit}
""".stripMargin

val (_, rows) = DB.runQuery(sqlQuery, List())
logger.debug(s"getTopApisFuture SQL query: $sqlQuery")
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, rows) = DBUtil.runQuery(sqlQuery)
logger.debug(s"getTopApisFuture returned ${rows.length} rows")
if (rows.nonEmpty) {
logger.debug(s"getTopApisFuture first row sample: ${rows.head}")
}
val sqlResult =
rows.map { rs => // Map result to case class
TopApi(
rs(0).toInt,
tryo(rs(0).toInt).getOrElse(0), // Safe conversion with fallback
rs(1),
rs(2)
)
}
sqlResult
}
tryo(result)
result
}}
}}

Expand Down Expand Up @@ -591,11 +598,10 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
val msSqlLimit = if (dbUrl.contains("sqlserver")) s"TOP ($limit)" else s""
// TODO Make it work in case of Oracle database
val otherDbLimit: String = if (dbUrl.contains("sqlserver")) s"" else s"LIMIT $limit"

val result: List[TopConsumer] = {
val sqlQuery =
s"""SELECT ${msSqlLimit} count(*) as count, consumer.id as consumerprimaryid, metric.appname as appname,
consumer.developeremail as email, consumer.consumerid as consumerid
s"""SELECT ${msSqlLimit} count(*) as count, consumer.id as consumerprimaryid, metric.appname as appname,
consumer.developeremail as email, consumer.consumerid as consumerid
FROM metric, consumer
WHERE metric.appname = consumer.name
AND date_c >= '${sqlTimestamp(fromDate.get)}'
Expand All @@ -613,11 +619,12 @@ object MappedMetrics extends APIMetrics with MdcLoggable{
AND (${trueOrFalse(excludeUrlPatterns.isEmpty) } or (url NOT LIKE ($excludeUrlPatternsQueries)))
AND (${trueOrFalse(excludeAppNames.isEmpty) } or appname not in ($excludeAppNamesList))
AND (${trueOrFalse(excludeImplementedByPartialFunctions.isEmpty) } or implementedbypartialfunction not in ($excludeImplementedByPartialFunctionsList))
GROUP BY appname, consumer.developeremail, consumer.id, consumer.consumerid
GROUP BY appname, consumer.developeremail, consumer.id, consumer.consumerid
ORDER BY count DESC
${otherDbLimit}
""".stripMargin
val (_, rows) = DB.runQuery(sqlQuery, List())
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, rows) = DBUtil.runQuery(sqlQuery)
val sqlResult =
rows.map { rs => // Map result to case class
TopConsumer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.UUID.randomUUID

import code.api.Constant
import code.api.cache.Caching
import code.api.util.APIUtil
import code.api.util.{APIUtil, DBUtil}
import code.util.MappedUUID
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import com.tesobe.CacheKeyFromArguments
Expand Down Expand Up @@ -141,7 +141,8 @@ object ResourceUser extends ResourceUser with LongKeyedMetaMapper[ResourceUser]{
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(cacheTTL.seconds) {
val sql = "SELECT DISTINCT provider_ FROM resourceuser ORDER BY provider_"
val (_, rows) = DB.runQuery(sql, List())
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, rows) = DBUtil.runQuery(sql)
rows.flatten
}
}
Expand Down
9 changes: 6 additions & 3 deletions obp-api/src/main/scala/code/util/AttributeQueryTrait.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package code.util

import code.api.util.DBUtil
import com.openbankproject.commons.model.BankId
import net.liftweb.mapper.{BaseMappedField, BaseMetaMapper, DB}
import net.liftweb.mapper.{BaseMappedField, BaseMetaMapper}

import scala.collection.immutable.List

Expand Down Expand Up @@ -39,7 +40,8 @@ trait AttributeQueryTrait { self: BaseMetaMapper =>
def getParentIdByParams(bankId: BankId, params: Map[String, List[String]]): List[String] = {
if (params.isEmpty) {
val sql = s"SELECT DISTINCT attr.$parentIdColumn FROM $tableName attr where attr.$bankIdColumn = ? "
val (_, list) = DB.runQuery(sql, List(bankId.value))
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, list) = DBUtil.runQuery(sql, List(bankId.value))
list.flatten
} else {
val paramList = params.toList
Expand Down Expand Up @@ -67,7 +69,8 @@ trait AttributeQueryTrait { self: BaseMetaMapper =>
| AND ($sqlParametersFilter)
|""".stripMargin

val (columnNames: List[String], list: List[List[String]]) = DB.runQuery(sql, bankId.value :: parameters)
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (columnNames: List[String], list: List[List[String]]) = DBUtil.runQuery(sql, bankId.value :: parameters)
val columnNamesLowerCase = columnNames.map(_.toLowerCase)
val parentIdIndex = columnNamesLowerCase.indexOf(parentIdColumn.toLowerCase)
val nameIndex = columnNamesLowerCase.indexOf(nameColumn.toLowerCase)
Expand Down
9 changes: 6 additions & 3 deletions obp-api/src/main/scala/code/util/NewAttributeQueryTrait.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package code.util

import code.api.util.DBUtil
import com.openbankproject.commons.model.BankId
import net.liftweb.mapper.{BaseMappedField, BaseMetaMapper, DB}
import net.liftweb.mapper.{BaseMappedField, BaseMetaMapper}

import scala.collection.immutable.List

Expand Down Expand Up @@ -36,7 +37,8 @@ trait NewAttributeQueryTrait {
def getParentIdByParams(bankId: BankId, params: Map[String, List[String]]): List[String] = {
if (params.isEmpty) {
val sql = s"SELECT DISTINCT attr.$parentIdColumn FROM $tableName attr where attr.$bankIdColumn = ? "
val (_, list) = DB.runQuery(sql, List(bankId.value))
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (_, list) = DBUtil.runQuery(sql, List(bankId.value))
list.flatten
} else {
val paramList = params.toList
Expand Down Expand Up @@ -64,7 +66,8 @@ trait NewAttributeQueryTrait {
| AND ($sqlParametersFilter)
|""".stripMargin

val (columnNames: List[String], list: List[List[String]]) = DB.runQuery(sql, bankId.value :: parameters)
// Use DBUtil.runQuery which handles SQL Server NVARCHAR properly
val (columnNames: List[String], list: List[List[String]]) = DBUtil.runQuery(sql, bankId.value :: parameters)
val columnNamesLowerCase = columnNames.map(_.toLowerCase)
val parentIdIndex = columnNamesLowerCase.indexOf(parentIdColumn.toLowerCase)
val nameIndex = columnNamesLowerCase.indexOf(nameColumn.toLowerCase)
Expand Down
Loading