create_affinity_test_table <- function(con, affinity) {
  dbExecute(con, paste0("CREATE TABLE a (a ", affinity, ")"))
  dbWriteTable(con, "a", data.frame(a = NA_integer_), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 2L), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 3), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 4.5), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 5L), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = "6"), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 7.5), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 8L), append = TRUE)
  dbWriteTable(con, "a", list_df(a = list(as.raw(9))), append = TRUE)
  dbWriteTable(con, "a", data.frame(a = 10L), append = TRUE)
}

check_affinity_get <- function(affinity, type,
                               real_type = "numeric", integer_type = type,
                               blob_integer_type = integer_type) {
  con <- memory_db()
  on.exit(dbDisconnect(con))

  create_affinity_test_table(con, affinity)

  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 0")$a), type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 1")$a), type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 2")$a), integer_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 3")$a), blob_integer_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 4")$a), real_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 5")$a), real_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 6")$a), real_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 7")$a), real_type)
  expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 8")$a), real_type)
  expect_warning(
    expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 9")$a), real_type),
    "coercing"
  )
  expect_warning(
    expect_equal(class(dbGetQuery(con, "SELECT * FROM a LIMIT 10")$a), real_type),
    "coercing"
  )
}

check_affinity_fetch <- function(affinity, type,
                                 real_type = "numeric", integer_type = type,
                                 blob_type = real_type) {
  con <- memory_db()
  on.exit(dbDisconnect(con))

  create_affinity_test_table(con, affinity)

  rs <- dbSendQuery(con, "SELECT * FROM a")
  expect_equal(class(dbFetch(rs, 0)$a), type)
  expect_equal(class(dbFetch(rs, 1)$a), type)
  expect_equal(class(dbFetch(rs, 1)$a), type)
  expect_equal(class(dbFetch(rs, 1)$a), type)
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  expect_warning(
    expect_equal(class(dbFetch(rs, 1)$a), real_type),
    if (identical(type, class(blob()))) NA else "coercing"
  )
  expect_equal(class(dbFetch(rs, 1)$a), real_type)
  dbClearResult(rs)
}

test_that("affinity checks for dbGetQuery()", {
  `%>%` <- magrittr::`%>%`

  check_affinity_get("INTEGER", "integer")
  check_affinity_get("TEXT", "character", "character")
  check_affinity_get("REAL", "numeric")
  check_affinity_get("INT", "integer")
  check_affinity_get("CHAR", "character", "character")
  check_affinity_get("CLOB", "character", "character")
  check_affinity_get("FLOA", "numeric")
  check_affinity_get("DOUB", "numeric")
  check_affinity_get("NUMERIC", "numeric", "numeric", "integer")
  check_affinity_get("BLOB", class(blob()), "numeric", "integer", "numeric") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing")
})

test_that("affinity checks for dbFetch()", {
  `%>%` <- magrittr::`%>%`

  check_affinity_fetch("INTEGER", "integer")
  check_affinity_fetch("TEXT", "character", "character")
  check_affinity_fetch("REAL", "numeric")
  check_affinity_fetch("INT", "integer")
  check_affinity_fetch("CHAR", "character", "character")
  check_affinity_fetch("CLOB", "character", "character")
  check_affinity_fetch("FLOA", "numeric")
  check_affinity_fetch("DOUB", "numeric")
  check_affinity_fetch("NUMERIC", "numeric", "numeric", "integer")
  check_affinity_fetch("BLOB", class(blob()), class(blob())) %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing") %>%
    expect_warning("coercing")
})

test_that("affinity checks for inline queries", {
  skip("NYI")
})

test_that("affinity of untyped NULL with repeated fetch", {
  conn <- dbConnect(SQLite(), ":memory:")
  res <- dbSendQuery(conn, "SELECT NULL UNION ALL SELECT NULL")
  expect_identical(dbFetch(res, 1)[[1]], NA)
  expect_identical(dbFetch(res, 1)[[1]], NA)
  dbClearResult(res)
  dbDisconnect(conn)
})
