Nothing
###############################################################################@
#' Create a RelTableModel object
#'
#' @param l DEPRECATED. A named list with the function parameters.
#' If `NULL` (default) the function parameters are used. If not `NULL`, the
#' function parameters are ignored and taken from l.
#' @param tableName a character vector of length one
#' @param fields a tibble with the following columns:
#' - *name*: character
#' - *type*: character
#' - *nullable*: logical (optional, defaults to TRUE)
#' - *unique*: logical (optional, defaults = FALSE)
#' - *comment*: character (optional, defaults to NA_character_)
#' @param primaryKey a character vector of any length. All
#' values should be in fields$name
#' @param foreignKeys a list of foreign keys. Each foreign key is defined
#' as a list with the following elements:
#' - *refTable*: a character vector of length one (the referenced table)
#' - *key*: a tibble with a "from" and a "to" columns
#' - (*cardinality*): an optional integer vector with 4 values:
#' + fmin: from minimum cardinality
#' + fmax: from maximum cardinality
#' + tmin: to minimum cardinality
#' + tmax: to maximum cardinality
#' @param indexes a list of indexes. Each index is defined by
#' 3 columns:
#' - *field*: character (all in fields$name)
#' - *order*: character
#' - *unique*: logical
#' @param display a list gathering:
#' - *x*: single numeric value for the x position of the table
#' - *y*: single numeric value for the y position of the table
#' - *color*: single character value corresponding to the color of the table
#' - *comment*: single character value with some description of the table
#'
#' @details When defining a matrix, 3 and only 3 fields must be defined:
#' 2 of types 'row' and 'column' and the 3rd of your choice. In this case
#' primaryKey is defined automatically as the combination of row and column.
#'
#' @return A RelTableModel object.
#'
#' @export
#'
RelTableModel <- function(
l = NULL,
tableName,
fields,
primaryKey = NULL,
foreignKeys = NULL,
indexes = NULL,
display = list(
x = as.numeric(NA),
y = as.numeric(NA),
color = as.character(NA),
comment = as.character(NA)
)
) {
############################################################################@
## Management of params ----
if (!is.null(l)) {
warning("l param is deprecated and will probably removed in the future")
} else {
l <- list(
tableName = tableName,
fields = fields,
primaryKey = primaryKey,
foreignKeys = foreignKeys,
indexes = indexes,
display = display
)
}
############################################################################@
## Checks ----
### Simple checks ----
tableInfo <- c(
"tableName",
"fields",
"primaryKey",
"foreignKeys",
"indexes",
"display"
)
mandatoryFieldInfo <- c("name", "type")
fieldInfo <- c("name", "type", "nullable", "unique", "comment")
stopifnot(
is.data.frame(l$fields),
all(mandatoryFieldInfo %in% colnames(l$fields))
)
if (!('nullable' %in% colnames(l$fields))) {
l$fields$nullable <- TRUE
}
if (!('unique' %in% colnames(l$fields))) {
l$fields$unique <- FALSE
}
if (!('comment' %in% colnames(l$fields))) {
l$fields$comment <- NA_character_
}
stopifnot(
all(tableInfo %in% names(l)),
all(names(l) %in% tableInfo),
is.character(l$tableName),
length(l$tableName) == 1,
!is.na(l$tableName),
all(colnames(l$fields) %in% fieldInfo),
# nrow(l$fields) > 0,
is.character(l$fields$name),
all(!is.na(l$fields$name)),
!any(duplicated(l$fields$name)),
is.character(l$fields$type),
all(!is.na(l$fields$type)),
is.logical(l$fields$nullable),
all(!is.na(l$fields$nullable)),
is.logical(l$fields$unique),
all(!is.na(l$fields$unique)),
is.character(l$fields$comment),
is.null(l$primaryKey) || is.character(l$primaryKey),
all(!is.na(l$primaryKey)),
all(l$primaryKey %in% l$fields$name)
)
for (att in setdiff(
names(attributes(l$fields)),
c("row.names", "names", "class")
)) {
attr(l$fields, att) <- NULL
}
l$fields <- dplyr::as_tibble(l$fields) |>
dplyr::select(c("name", "type", "nullable", "unique", "comment")) |>
dplyr::mutate(
"comment" = as.character(
ifelse(is.na(.data$comment), "", .data$comment)
)
)
### Primary key ----
l$primaryKey <- sort(l$primaryKey)
if (length(l$primaryKey) == 1) {
l$fields[
which(l$fields$name == l$primaryKey),
"unique"
] <- TRUE
}
### Matrix and Field types ----
if (any(l$fields$type %in% c("row", "column"))) {
if (!all(c("row", "column") %in% l$fields$type)) {
stop("Both row and column should be provided when modelling a matrix")
}
if (nrow(l$fields) != 3) {
stop("A matrix model should have 3 and only 3 fields")
}
if (length(unique(l$fields$type)) != 3) {
stop(paste(
"A matrix model should have 3 fields":"2 of types 'row' and 'column' and the 3rd of your choice"
))
}
rcfields <- l$fields |> dplyr::filter(.data$type %in% c("row", "column"))
if (any(rcfields$nullable)) {
stop("Matrix row and column cannot be nullable")
}
if (any(rcfields$unique)) {
stop(paste(
"The combination of row and column is unique (primary key)",
" but not row names and column names independently"
))
}
l$primaryKey <- rcfields$name
if (setdiff(l$fields$type, c("row", "column")) == "base64") {
stop("A matrix cannot store base64 documents")
}
check_types(setdiff(l$fields$type, c("row", "column")))
} else {
check_types(l$fields$type)
}
### Foreign keys ----
if (!is.null(l$foreignKeys)) {
stopifnot(is.list(l$foreignKeys))
fkn <- c("refTable", "key")
l$foreignKeys <- lapply(
l$foreignKeys,
function(fko) {
fk <- fko[fkn]
stopifnot(
all(names(fk) %in% fkn),
all(fkn %in% names(fk)),
!is.na(fk$refTable),
is.character(fk$refTable),
length(fk$refTable) == 1,
!is.na(fk$refTable),
is.data.frame(fk$key),
all(c("from", "to") %in% names(fk$key)),
all(names(fk$key) %in% c("from", "to")),
is.character(fk$key$from),
all(!is.na(fk$key$from)),
is.character(fk$key$to),
all(!is.na(fk$key$to)),
all(fk$key$from %in% l$fields$name)
)
for (att in setdiff(
names(attributes(fk$key)),
c("row.names", "names", "class")
)) {
attr(fk$key, att) <- NULL
}
fk$key <- dplyr::as_tibble(fk$key)
cnames <- c("fmin", "fmax", "tmin", "tmax")
if ("cardinality" %in% names(fko)) {
stopifnot(
is.integer(fko$cardinality),
length(fko$cardinality) == 4,
all(
cnames %in%
names(fko$cardinality)
),
all(!is.na(fko$cardinality)),
all(fko$cardinality >= -1),
fko$cardinality["fmin"] > -1,
fko$cardinality["tmin"] > -1,
fko$cardinality["fmax"] == -1 ||
(fko$cardinality["fmax"] > 0 &&
fko$cardinality["fmax"] >= fko$cardinality["fmin"]),
fko$cardinality["tmax"] == -1 ||
(fko$cardinality["tmax"] > 0 &&
fko$cardinality["tmax"] >= fko$cardinality["tmin"])
)
fk$cardinality <- fko$cardinality[cnames]
} else {
fk$cardinality <- c(0, -1, 1, 1)
names(fk$cardinality) <- cnames
}
return(fk)
}
)
names(l$foreignKeys) <- NULL
}
### Indexes ----
if (!is.null(l$indexes)) {
stopifnot(is.list(l$indexes))
# idn <- c("field", "unique")
idn <- c("fields", "unique")
l$indexes <- lapply(
l$indexes,
function(ind) {
stopifnot(
is.list(ind),
all(names(ind) %in% idn),
all(idn %in% names(ind)),
is.character(ind$fields),
all(!is.na(ind$fields)),
is.logical(ind$unique),
all(!is.na(ind$unique)),
length(ind$unique) == 1,
all(ind$fields %in% l$fields$name)
)
ind$fields <- unique(ind$fields)
return(ind)
}
)
}
### Display ----
if (is.null(l$display)) {
l$display <- list(
x = as.numeric(NA),
y = as.numeric(NA),
color = as.character(NA),
comment = as.character(NA)
)
}
stopifnot(is.list(l$display))
dn <- c("x", "y", "color", "comment")
stopifnot(
all(names(l$display) %in% dn),
all(dn %in% names(l$display)),
is.numeric(l$display$x),
is.numeric(l$display$y),
is.character(l$display$color),
is.character(l$display$comment),
all(unlist(lapply(l$display, length)) == 1)
)
############################################################################@
## Creating the object ----
toRet <- l
class(toRet) <- c("RelTableModel", class(toRet))
toRet <- correct_constraints(toRet)
return(toRet)
}
###############################################################################@
#' Check if the object is a [RelTableModel] object
#'
#' @param x any object
#'
#' @return A single logical: TRUE if x is a [RelTableModel] object
#'
#' @export
#'
is.RelTableModel <- function(x) {
inherits(x, "RelTableModel")
}
###############################################################################@
#' Check if the object is a [RelTableModel] matrix object
#'
#' A matrix model is a special [RelTableModel] object with 3 and only 3 fields:
#' 2 of types 'row' and 'column' and the 3rd of your choice.
#'
#' @param x any object
#'
#' @return A single logical: TRUE if x is a [RelTableModel] matrix object
#'
#' @export
#'
is.MatrixModel <- function(x) {
inherits(x, "RelTableModel") &&
"row" %in% x$fields$type
}
###############################################################################@
#' Format a [RelTableModel] object for printing
#'
#' @param x a [RelTableModel] object
#' @param ... for generics compatibility (not used)
#'
#' @return A single character
#'
#' @export
#'
format.RelTableModel <- function(x, ...) {
f <- x$fields
pk <- x$primaryKey
it <- index_table(x)
ind <- NULL
# uq <- NULL
if (!is.null(it)) {
it <- it |> dplyr::filter(.data$index != 0)
ind <- unique(it$field)
# uq <- unique(it$field[which(it$unique)])
}
f$i <- unlist(lapply(
f$name,
function(n) {
paste(sort(it$index[which(it$field == n)]), collapse = ",")
}
))
toRet <- paste0(
sprintf(
"Table name: %s%s",
x$tableName,
ifelse(is.MatrixModel(x), " (matrix)", "")
),
"\n",
paste(
apply(
f,
1,
function(y) {
toRet <- " "
toRet <- paste0(
toRet,
ifelse(y[1] %in% pk, "*", " "),
# ifelse(y[1] %in% uq, "+ ", " "),
ifelse(y[4] == "TRUE" & !y[1] %in% pk, "+ ", " "),
y[1]
)
toRet <- paste0(
toRet,
" (",
y[2],
ifelse(
y[1] %in% ind,
# paste0(", idx.", y[5]),
paste0(", idx.", y[6]),
""
),
ifelse(y[3] == "FALSE", ", not", ","),
" nullable",
")"
)
return(toRet)
}
),
collapse = "\n"
)
)
if (length(x$foreignKeys) > 0) {
toRet <- paste0(
toRet,
"\n",
"Referenced tables:\n",
paste(
unlist(lapply(
x$foreignKeys,
function(y) {
y$refTable
}
)),
collapse = "\n"
)
)
}
return(toRet)
}
###############################################################################@
#' @export
#'
print.RelTableModel <- function(x, ...) {
cat(format(x, ...), "\n")
}
###############################################################################@
#' @export
#'
length.RelTableModel <- function(x) {
nrow(x$fields)
}
###############################################################################@
#' @export
#'
lengths.RelTableModel <- function(x, use.names = TRUE) {
lengths(unclass(x), use.names = use.names)
}
###############################################################################@
#' List indexes of a [RelTableModel] object
#'
#' @param x a [RelTableModel] object
#'
#' @return A tibble with the following columns:
#' - **index**: an integer corresponding to the index number
#' - **field**: a character corresponding to field belonging to the index
#' - **unique**: a logical indicating the uniqueness of the field
#'
#' @export
#'
index_table <- function(x) {
stopifnot(is.RelTableModel(x))
pk <- x$primaryKey
ind <- x$indexes
toRet <- NULL
i <- 0
if (length(pk) > 0) {
toRet <- dplyr::tibble(
index = i,
field = pk,
# unique=length(pk)==1
uniqueIndex = TRUE
)
}
for (ci in ind) {
i <- i + 1
toRet <- dplyr::bind_rows(
toRet,
dplyr::tibble(
index = i,
field = ci$fields,
uniqueIndex = ci$unique
)
)
}
return(toRet)
}
###############################################################################@
#' Get the types of the columns of a [RelTableModel] object
#'
#' @param x a [RelTableModel] object
#'
#' @return A col_spec object with the type of each column
#'
#' @export
#'
col_types <- function(x) {
stopifnot(is.RelTableModel(x))
do.call(
readr::cols,
structure(
lapply(
x$fields$type,
function(y) {
switch(
y,
"row" = readr::col_character(),
"column" = readr::col_character(),
"integer" = readr::col_integer(),
"numeric" = readr::col_double(),
"logical" = readr::col_logical(),
"character" = readr::col_character(),
"Date" = readr::col_date(),
"POSIXct" = readr::col_datetime(),
"base64" = readr::col_character()
)
}
),
.Names = x$fields$name
)
)
}
###############################################################################@
#' Correct the constraints of a table to make them consistent
#'
#' @param x a [RelTableModel] object
#' @param createPKIndex should an index be create for the primary key
#' (default: FALSE)
#'
#' @export
#'
correct_constraints <- function(x, createPKIndex = FALSE) {
stopifnot(is.RelTableModel(x))
## Primary key uniqueness
if (length(x$primaryKey) == 1) {
x$fields[which(x$fields$name == x$primaryKey), "unique"] <- TRUE
x$fields[which(x$fields$name == x$primaryKey), "nullable"] <- FALSE
}
if (length(x$primaryKey) > 0) {
ei <- lapply(
x$indexes,
function(y) {
identical(sort(y$fields), sort(x$primaryKey))
}
) |>
unlist() |>
as.logical() |>
which()
if (length(ei) > 1) {
stop("Check this part of code")
}
if (length(ei) == 0) {
if (createPKIndex) {
x$indexes <- unique(c(
x$indexes,
list(
list(
fields = sort(x$primaryKey),
unique = TRUE
)
)
))
}
} else {
x$indexes[[ei]]$unique <- TRUE
}
}
## Index uniqueness
if (length(x$indexes) > 0) {
for (i in 1:length(x$indexes)) {
if (x$indexes[[i]]$unique && length(x$indexes[[i]]$fields) == 1) {
if (
x$fields[
which(x$fields$name == x$indexes[[i]]$fields),
"type"
] %in%
c("row", "column")
) {
stop("Matrix row and column cannot be unique individually")
}
x$fields[
which(x$fields$name == x$indexes[[i]]$fields),
"unique"
] <- TRUE
}
}
}
## Field uniqueness
uniqueFields <- x$fields |>
dplyr::filter(.data$unique) |>
dplyr::pull("name")
if (length(x$indexes) > 0 && length(uniqueFields) > 0) {
for (i in 1:length(x$indexes)) {
if (any(x$indexes[[i]]$fields %in% uniqueFields)) {
x$indexes[[i]]$unique <- TRUE
}
}
}
##
return(x)
}
###############################################################################@
#' Confront a [RelTableModel] to actual data
#'
#' @param x a [RelTableModel]
#' @param d a data frame or a matrix for matrix model
#' @param checks a character vector with the name of optional checks to be done
#' (Default: all of them c("unique", "not nullable"))
#'
#' @return A report as a list
#'
#' @export
#'
confront_table_data <- function(
x,
d,
checks = c("unique", "not nullable")
) {
## Optional checks ----
if (length(checks) > 0) {
checks <- match.arg(
checks,
c("unique", "not nullable"),
several.ok = TRUE
)
}
stopifnot(is.RelTableModel(x))
## Matrix model ----
if (is.MatrixModel(x)) {
if (!inherits(d, c("matrix", "Matrix"))) {
stop(x$tableName, " is neither a matrix nor a Matrix")
}
vf <- x$fields |> dplyr::filter(!.data$type %in% c("row", "column"))
toRet <- list(
missingFields = character(0),
suppFields = character(0),
availableFields = vf$name,
fields = list(),
success = TRUE
)
toRet$fields[[vf$name]] <- list(success = TRUE, message = NULL)
if (!inherits(d[1], vf$type)) {
toRet$fields[[vf$name]]$success <- FALSE
toRet$success <- FALSE
toRet$fields[[vf$name]]$message <- paste(
c(
toRet$fields[[vf$name]]$message,
sprintf(
'Unexpected "%s"',
paste(class(d[1]), collapse = ", ")
)
),
collapse = " "
)
}
if ("not nullable" %in% checks) {
mis <- sum(is.na(d))
if (mis != 0) {
toRet$fields[[vf$name]]$message <- paste(
c(
toRet$fields[[vf$name]]$message,
sprintf(
'Missing values %s/%s = %s%s',
mis,
length(d),
round(mis * 100 / length(d)),
"%"
)
),
collapse = " "
)
if (!vf$nullable) {
toRet$fields[[vf$name]]$success <- FALSE
toRet$success <- FALSE
}
}
}
if ("unique" %in% checks && vf$unique && any(duplicated(d))) {
toRet$fields[[vf$name]]$success <- FALSE
toRet$success <- FALSE
toRet$fields[[vf$name]]$message <- paste(
c(
toRet$fields[[vf$name]]$message,
"Some values are duplicated"
),
collapse = " "
)
}
return(toRet)
}
if (!inherits(d, "data.frame")) {
stop(x$tableName, " is not a data.frame")
}
## Available fields ----
missingFields <- setdiff(x$fields$name, colnames(d))
suppFields <- setdiff(colnames(d), x$fields$name)
availableFields <- intersect(colnames(d), x$fields$name)
toRet <- list(
missingFields = missingFields,
suppFields = suppFields,
availableFields = availableFields,
fields = list(),
success = length(missingFields) == 0 && length(suppFields) == 0
)
## Fields ----
for (fn in availableFields) {
ft <- x$fields$type[which(x$fields$name == fn)]
ft <- ifelse(ft %in% c("row", "column"), "character", ft)
fe <- x$fields$nullable[which(x$fields$name == fn)]
fu <- x$fields$unique[which(x$fields$name == fn)]
toRet$fields[[fn]] <- list(success = TRUE, message = NULL)
if (!inherits(dplyr::pull(d, !!fn), ft)) {
if (ft != "base64" || !inherits(dplyr::pull(d, !!fn), "character")) {
toRet$fields[[fn]]$success <- FALSE
toRet$success <- FALSE
toRet$fields[[fn]]$message <- paste(
c(
toRet$fields[[fn]]$message,
sprintf(
'Unexpected "%s"',
paste(class(dplyr::pull(d, !!fn)), collapse = ", ")
)
),
collapse = " "
)
}
}
if ("not nullable" %in% checks) {
mis <- sum(is.na(dplyr::pull(d, !!fn)))
if (mis != 0) {
toRet$fields[[fn]]$message <- paste(
c(
toRet$fields[[fn]]$message,
sprintf(
'Missing values %s/%s = %s%s',
mis,
nrow(d),
round(mis * 100 / nrow(d)),
"%"
)
),
collapse = " "
)
if (!fe) {
toRet$fields[[fn]]$success <- FALSE
toRet$success <- FALSE
}
}
}
toTest <- dplyr::pull(d, !!fn)
toTest <- toTest[which(!is.na(toTest))]
if ("unique" %in% checks && fu && any(duplicated(toTest))) {
toRet$fields[[fn]]$success <- FALSE
toRet$success <- FALSE
toRet$fields[[fn]]$message <- paste(
c(
toRet$fields[[fn]]$message,
"Some values are duplicated"
),
collapse = " "
)
}
}
## Indexes ----
if ("unique" %in% checks && length(x$indexes) > 0) {
toRet$indexes <- list()
for (i in 1:length(x$indexes)) {
toRet$indexes[[i]] <- list()
idx <- x$indexes[[i]]
if (any(idx$fields %in% missingFields)) {
toRet$indexes[[i]]$success <- FALSE
toRet$indexes[[i]]$message <- paste(
c(
toRet$indexes[[i]]$message,
"Missing field"
),
collapse = " "
)
toRet$success <- FALSE
} else {
if (!idx$unique) {
toRet$indexes[[i]]$success <- TRUE
} else {
toTest <- dplyr::filter(
d[, idx$fields],
stats::complete.cases(d[, idx$fields])
)
if (nrow(dplyr::distinct(toTest)) != nrow(toTest)) {
toRet$indexes[[i]]$success <- FALSE
toRet$indexes[[i]]$message <- paste(
c(
toRet$indexes[[i]]$message,
"Some values are duplicated"
),
collapse = " "
)
toRet$success <- FALSE
} else {
toRet$indexes[[i]]$success <- TRUE
}
}
}
}
}
if ("not nullable" %in% checks && is.MatrixModel(x)) {
if (!"indexes" %in% names(toRet)) {
toRet$indexes <- list()
}
i <- length(toRet$indexes) + 1
rf <- x$fields |>
dplyr::filter(.data$type == "row") |>
dplyr::pull("name")
cf <- x$fields |>
dplyr::filter(.data$type == "column") |>
dplyr::pull("name")
fe <- x$fields |>
dplyr::filter(!.data$type %in% c("row", "column")) |>
dplyr::pull("nullable")
ncells <- length(unique(d[, rf])) * length(unique(d[, cf]))
mis <- ncells - nrow(d)
if (mis > 0) {
toRet$indexes[[i]]$message <- paste(
c(
toRet$indexes[[i]]$message,
sprintf(
'Missing cells %s/%s = %s%s',
mis,
ncells,
round(mis * 100 / ncells),
"%"
)
),
collapse = " "
)
if (!fe) {
toRet$indexes[[i]]$success <- FALSE
toRet$success <- FALSE
}
}
}
## Return the results ----
return(toRet)
}
###############################################################################@
#' Check if two [RelTableModel] are identical
#'
#' @param x a [RelTableModel]
#' @param y a [RelTableModel]
#' @param includeDisplay a single logical (default: TRUE) indicating if
#' the display should be included in the comparison
#'
#' @return A logical: TRUE if the 2 models are identical
#'
#' @export
#'
identical_RelTableModel <- function(x, y, includeDisplay = TRUE) {
stopifnot(is.RelTableModel(x), is.RelTableModel(y))
## Name ----
toRet <- x$tableName == y$tableName
## Fields ----
toRet <- toRet &&
identical(
x$fields |> dplyr::arrange(.data$name),
y$fields |> dplyr::arrange(.data$name)
)
## Primary key ----
toRet <- toRet &&
length(x$primaryKey) == length(y$primaryKey) &&
all(x$primaryKey == y$primaryKey)
## Indexes ----
toRet <- toRet && length(x$indexes) == length(y$indexes)
if (toRet && length(x$indexes) > 0) {
# xidx <- lapply(
# x$indexes,
# function(z) {
# z$fields <- sort(z$fields)
# return(z)
# }
# )
xidx <- x$indexes
xidx <- xidx[order(unlist(lapply(
xidx,
function(z) paste(z$fields, collapse = ", ")
)))]
# yidx <- lapply(
# y$indexes,
# function(z) {
# z$fields <- sort(z$fields)
# return(z)
# }
# )
yidx <- y$indexes
yidx <- yidx[order(unlist(lapply(
yidx,
function(z) paste(z$fields, collapse = ", ")
)))]
toRet <- identical(xidx, yidx)
}
## Foreign keys ----
toRet <- toRet && length(x$foreignKeys) == length(y$foreignKeys)
if (toRet && length(x$foreignKeys) > 0) {
xfk <- lapply(
x$foreignKeys,
function(z) {
z$key <- z$key |> dplyr::arrange(.data$from, .data$to)
return(z)
}
)
xfk <- xfk[order(unlist(lapply(
xfk,
function(z) {
paste0(
z$refTable,
" [",
paste(
paste(z$key$from, z$key$to, sep = "->"),
collapse = ", "
),
"]"
)
}
)))]
yfk <- lapply(
y$foreignKeys,
function(z) {
z$key <- z$key |> dplyr::arrange(.data$from, .data$to)
return(z)
}
)
yfk <- yfk[order(unlist(lapply(
yfk,
function(z) {
paste0(
z$refTable,
" [",
paste(
paste(z$key$from, z$key$to, sep = "->"),
collapse = ", "
),
"]"
)
}
)))]
toRet <- identical(xfk, yfk)
}
## Display ----
if (includeDisplay) {
toRet <- toRet &&
length(x$display) == length(y$display) &&
(length(x$display) == 0 ||
identical(
x$display[order(names(x$display))],
y$display[order(names(y$display))]
))
}
return(toRet)
}
###############################################################################@
#' Get foreign keys from [RelTableModel]
#'
#' @param x a [RelTableModel]
#'
#' @return A tibble with the following fields:
#' - from: the origin of the key
#' - ff: the key fields in from
#' - to: the target of the key
#' - tf: the key fields in to
#' - fmin: minimum cardinality of from
#' - fmax: maximum cardinality of from
#' - tmin: minimum cardinality of to
#' - tmax: maximum cardinality of to
#'
#' @export
#'
get_foreign_keys.RelTableModel <- function(x) {
tn <- x$tableName
fk <- x$foreignKeys
if (length(fk) == 0) {
return(NULL)
}
toRet <- do.call(
rbind,
lapply(
fk,
function(k) {
to <- k$refTable
kt <- k$key |> dplyr::arrange(.data$from, .data$to)
dplyr::tibble(
to = to,
ff = list(kt$from),
tf = list(kt$to)
) |>
dplyr::bind_cols(dplyr::as_tibble(t(k$cardinality)))
}
)
)
toRet <- toRet |>
dplyr::mutate(
from = tn
) |>
dplyr::select(
"from",
"ff",
"to",
"tf",
"fmin",
"fmax",
"tmin",
"tmax"
)
return(toRet)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.