Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Potential bug with references #122

@JoonasC

Description

@JoonasC

I think I found a bug with entity references/foreign keys.
So, I have the following database schema (specified with DBML https://www.dbml.org/home/):
image

Table users {
  id integer [pk, unique, increment, not null]
  username varchar(10) [not null, unique]
  full_name varchar(20) [not null, unique]
  email varchar(20) [not null, unique]
  password_hash varchar(60) [not null]
}

Table projects {
  id integer [pk, unique, increment, not null]
  name varchar(30) [not null, unique]
  description text [not null]
  project_owner_id integer [ref: - users.id, not null]
}

Table project_membership {
  id integer [pk, unique, increment, not null]
  user_id integer [not null]
  project_id integer [not null]
}

Ref: users.id - project_membership.user_id [delete: cascade]
Ref: projects.id - project_membership.project_id [delete: cascade]

Table variables {
  id integer [pk, unique, increment, not null]
  name varchar(20) [not null]
  type variable_type [not null]
  unit varchar(5) [not null]
  dimension text [not null]
  objective objective [not null]
  exogenous boolean [not null]
  system_descriptive boolean [not null]
  project_id integer [not null]
}

enum variable_type {
  EFFORT
  FLOW
  CONNECTING
  DISPLACEMENT
  MOMENTUM
  TYPELESS
}

Ref: projects.id - variables.project_id [delete: cascade]

Table component_specifications {
  id integer [pk, unique, increment, not null]
  name varchar(20) [unique, not null]
  type component_type [not null]
  project_id integer [not null]
}

enum component_type {
  RESISTOR
  CAPACITOR
  INERTIA
  SOURCE_OF_FLOW
  SOURCE_OF_EFFORT
  TRANSFORMER
  GYRATOR
  FLOW_JUNCTION
  EFFORT_JUNCTION
  SINK
}

Ref: projects.id - component_specifications.project_id [delete: cascade]

Table component_specification_internal_variables {
  id integer [pk, unique, increment, not null]
  component_id integer [not null]
  variable_id integer [not null]
}

Ref: component_specifications.id - component_specification_internal_variables.component_id [delete: cascade]
Ref: variables.id - component_specification_internal_variables.variable_id [delete: cascade]

Table component_specification_additional_internal_variables {
  id integer [pk, unique, increment, not null]
  component_id integer [not null]
  variable_id integer [not null]
}

Ref: component_specifications.id - component_specification_additional_internal_variables.component_id [delete: cascade]
Ref: variables.id - component_specification_additional_internal_variables.variable_id [delete: cascade]

The component_specification_internal_variables and component_specification_additional_internal_variables tables are mapping tables for many-to-many references between a component specification's additional internal and internal variables.
Both tables are virtually identical, with only the name being different, so I would expect them to behave identically in Ktorm.

Here is the corresponding Ktorm schema:

interface User : Entity<User> {
    companion object : Entity.Factory<User>()

    val id: Int
    var username: String
    var fullName: String
    var email: String
    var passwordHash: String
}

object Users : Table<User>("users") {
    val id by int("id").primaryKey().bindTo { it.id }
    val username by varchar("username").bindTo { it.username }
    val fullName by varchar("full_name").bindTo { it.fullName }
    val email by varchar("email").bindTo { it.email }
    val passwordHash by varchar("password_hash").bindTo { it.passwordHash }
}

interface Project : Entity<Project> {
    companion object : Entity.Factory<Project>()

    val id: Int
    var name: String
    var description: String
    var owner: User
}

object Projects : Table<Project>("projects") {
    val id by int("id").primaryKey().bindTo { it.id }
    val name by varchar("name").bindTo { it.name }
    val description by text("description").bindTo { it.description }
    val projectOwnerId by int("project_owner_id").references(Users) { it.owner }
}

interface ProjectMembershipMapping : Entity<ProjectMembershipMapping> {
    companion object : Entity.Factory<ProjectMembershipMapping>()

    val id: Int
    var user: User
    var project: Project
}

object ProjectMembership : Table<ProjectMembershipMapping>("project_membership") {
    val id by int("id").primaryKey().bindTo { it.id }
    val userId by int("user_id").references(Users) { it.user }
    val projectId by int("project_id").references(Projects) { it.project }
}

interface ComponentSpecificationEntity : Entity<ComponentSpecificationEntity> {
    companion object : Entity.Factory<ComponentSpecificationEntity>()

    val id: Int
    var name: String
    var type: ComponentType
    var project: Project
}

object ComponentSpecifications : Table<ComponentSpecificationEntity>("component_specifications") {
    val id by int("id").primaryKey().bindTo { it.id }
    val name by varchar("name").bindTo { it.name }
    val type by componentType("type").bindTo { it.type }
    val projectId by int("project_id").references(Projects) { it.project }
}

interface VariableEntity : Entity<VariableEntity> {
    companion object : Entity.Factory<VariableEntity>()

    val id: Int
    var name: String
    var type: VariableType
    var unit: String
    var dimension: String
    var objective: Objective
    var exogenous: Boolean
    var systemDescriptive: Boolean
    var project: Project
}

object Variables : Table<VariableEntity>("variables") {
    val id by int("id").primaryKey().bindTo { it.id }
    val name by varchar("name").bindTo { it.name }
    val type by variableType("type").bindTo { it.type }
    val unit by varchar("unit").bindTo { it.unit }
    val dimension by varchar("dimension").bindTo { it.dimension }
    val objective by objective("objective").bindTo { it.objective }
    val exogenous by boolean("exogenous").bindTo { it.exogenous }
    val systemDescriptive by boolean("system_descriptive").bindTo { it.systemDescriptive }
    val projectId by int("project_id").references(Projects) { it.project }
}

interface ComponentSpecificationInternalVariableMapping : Entity<ComponentSpecificationInternalVariableMapping> {
    companion object : Entity.Factory<ComponentSpecificationInternalVariableMapping>()

    val id: Int
    var component: ComponentSpecificationEntity
    var variable: VariableEntity
}

object ComponentSpecificationInternalVariables :
    Table<ComponentSpecificationInternalVariableMapping>("component_specification_internal_variables") {
    val id by int("id").primaryKey().bindTo { it.id }
    val componentId by int("component_id").references(ComponentSpecifications) { it.component }
    val variableId by int("variable_id").references(Variables) { it.variable }
}

interface ComponentSpecificationAdditionalInternalVariableMapping :
    Entity<ComponentSpecificationAdditionalInternalVariableMapping> {
    companion object : Entity.Factory<ComponentSpecificationAdditionalInternalVariableMapping>()

    val id: Int
    var component: ComponentSpecificationEntity
    var variable: VariableEntity
}

object ComponentSpecificationAdditionalInternalVariables :
    Table<ComponentSpecificationAdditionalInternalVariableMapping>("component_specification_additional_internal_variables") {
    val id by int("id").primaryKey().bindTo { it.id }
    val componentId by int("component_id").references(ComponentSpecifications) { it.component }
    val variableId by int("variable_id").references(Variables) { it.variable }
}

So, for the following test code:

database.useTransaction {
  database.sequenceOf(Users).add(
    User {
      username = "testuser"
      fullName = "Test Tester"
      email = "[email protected]"
      passwordHash = passwordHashForTestUsers
    }
  )

  val ownerUser: User =
    database.sequenceOf(Users).find { it.username eq "testuser" }
      ?: fail("Couldn't find recently created user")

  database.sequenceOf(Projects).add(
    Project {
      name = "Test Project"
      description = "Some description"
      owner = ownerUser
    }
  )

  val project: Project =
    database.sequenceOf(Projects).find { it.name eq "Test Project" }
      ?: fail("Couldn't find recently created project")

  database.sequenceOf(Variables).add(
    VariableEntity {
      name = "T1"
      type = VariableType.EFFORT
      unit = "T^2"
      dimension = "T.T"
      objective = Objective.NONE
      exogenous = false
      systemDescriptive = false
      this.project = project
    }
  )

  val variable: VariableEntity =
    database.sequenceOf(Variables).find { it.name eq "T1" }
      ?: fail("Couldn't find recently created variable")

  database.sequenceOf(ComponentSpecifications).add(
    ComponentSpecificationEntity {
      name = "SOE"
      type = ComponentType.SOURCE_OF_EFFORT
      this.project = project
    }
  )
  database.sequenceOf(ComponentSpecifications).add(
    ComponentSpecificationEntity {
      name = "SNK"
      type = ComponentType.SINK
      this.project = project
    }
  )

  val sourceOfEffortComponent: ComponentSpecificationEntity =
    database.sequenceOf(ComponentSpecifications).find { it.name eq "SOE" }
      ?: fail("Couldn't find recently created component specification")
  val sinkComponent: ComponentSpecificationEntity =
    database.sequenceOf(ComponentSpecifications).find { it.name eq "SNK" }
      ?: fail("Couldn't find recently created component specification")

  database.sequenceOf(ComponentSpecificationInternalVariables).add(
    ComponentSpecificationInternalVariableMapping {
      component = sourceOfEffortComponent
      this.variable = variable
    }
  )
  database.sequenceOf(ComponentSpecificationAdditionalInternalVariables).add(
    ComponentSpecificationAdditionalInternalVariableMapping {
      component = sinkComponent
      this.variable = variable
    }
  )

  val internalVariableMapping: ComponentSpecificationInternalVariableMapping =
    database
      .sequenceOf(ComponentSpecificationInternalVariables)
      .find { it.componentId eq sourceOfEffortComponent.id }
      ?: fail("Couldn't find recently created internal variable mapping")
  val additionalInternalVariableMapping: ComponentSpecificationAdditionalInternalVariableMapping =
    database
      .sequenceOf(ComponentSpecificationAdditionalInternalVariables)
      .find { it.componentId eq sinkComponent.id }
      ?: fail("Couldn't find recently created additional internal variable mapping")

  println("Internal variable name: ${internalVariableMapping.variable.name}")
  println("Additional internal variable name: ${additionalInternalVariableMapping.variable.name}")
}

I get the following results:
Internal variable name: T1
Additional internal variable name:

As you can see, the variable loses it's name information, when referenced through additional internal variables, even though both tables and the Ktorm schema for them is virtually identical.

Here are the SQL queries generated by ktorm:
For internal variables:
select component_specification_internal_variables.id as component_specification_internal_variables_id, component_specification_internal_variables.component_id as component_specification_internal_variables_component_id, component_specification_internal_variables.variable_id as component_specification_internal_variables_variable_id, _ref0.id as _ref0_id, _ref0.name as _ref0_name, _ref0.type as _ref0_type, _ref0.project_id as _ref0_project_id, _ref1.id as _ref1_id, _ref1.name as _ref1_name, _ref1.description as _ref1_description, _ref1.project_owner_id as _ref1_project_owner_id, _ref2.id as _ref2_id, _ref2.username as _ref2_username, _ref2.full_name as _ref2_full_name, _ref2.email as _ref2_email, _ref2.password_hash as _ref2_password_hash, _ref3.id as _ref3_id, _ref3.name as _ref3_name, _ref3.type as _ref3_type, _ref3.unit as _ref3_unit, _ref3.dimension as _ref3_dimension, _ref3.objective as _ref3_objective, _ref3.exogenous as _ref3_exogenous, _ref3.system_descriptive as _ref3_system_descriptive, _ref3.project_id as _ref3_project_id, _ref4.id as _ref4_id, _ref4.name as _ref4_name, _ref4.description as _ref4_description, _ref4.project_owner_id as _ref4_project_owner_id, _ref5.id as _ref5_id, _ref5.username as _ref5_username, _ref5.full_name as _ref5_full_name, _ref5.email as _ref5_email, _ref5.password_hash as _ref5_password_hash from component_specification_internal_variables left join component_specifications _ref0 on component_specification_internal_variables.component_id = _ref0.id left join projects _ref1 on _ref0.project_id = _ref1.id left join users _ref2 on _ref1.project_owner_id = _ref2.id left join variables _ref3 on component_specification_internal_variables.variable_id = _ref3.id left join projects _ref4 on _ref3.project_id = _ref4.id left join users _ref5 on _ref4.project_owner_id = _ref5.id where component_specification_internal_variables.component_id = ? limit ?

For additional internal variables:
select component_specification_additional_internal_variables.id as component_specification_additional_internal_variables_id, component_specification_additional_internal_variables.component_id as component_specification_additional_internal_variables_component_id, component_specification_additional_internal_variables.variable_id as component_specification_additional_internal_variables_variable_id, _ref0.id as _ref0_id, _ref0.name as _ref0_name, _ref0.type as _ref0_type, _ref0.project_id as _ref0_project_id, _ref1.id as _ref1_id, _ref1.name as _ref1_name, _ref1.description as _ref1_description, _ref1.project_owner_id as _ref1_project_owner_id, _ref2.id as _ref2_id, _ref2.username as _ref2_username, _ref2.full_name as _ref2_full_name, _ref2.email as _ref2_email, _ref2.password_hash as _ref2_password_hash, _ref3.id as _ref3_id, _ref3.name as _ref3_name, _ref3.type as _ref3_type, _ref3.unit as _ref3_unit, _ref3.dimension as _ref3_dimension, _ref3.objective as _ref3_objective, _ref3.exogenous as _ref3_exogenous, _ref3.system_descriptive as _ref3_system_descriptive, _ref3.project_id as _ref3_project_id, _ref4.id as _ref4_id, _ref4.name as _ref4_name, _ref4.description as _ref4_description, _ref4.project_owner_id as _ref4_project_owner_id, _ref5.id as _ref5_id, _ref5.username as _ref5_username, _ref5.full_name as _ref5_full_name, _ref5.email as _ref5_email, _ref5.password_hash as _ref5_password_hash from component_specification_additional_internal_variables left join component_specifications _ref0 on component_specification_additional_internal_variables.component_id = _ref0.id left join projects _ref1 on _ref0.project_id = _ref1.id left join users _ref2 on _ref1.project_owner_id = _ref2.id left join variables _ref3 on component_specification_additional_internal_variables.variable_id = _ref3.id left join projects _ref4 on _ref3.project_id = _ref4.id left join users _ref5 on _ref4.project_owner_id = _ref5.id where component_specification_additional_internal_variables.component_id = ? limit ?

Sorry for this issue being so long, but I don't really know how else to convey the information about the potential bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions