Edgedb back reference one to many links to limit choices on queries - one-to-many

I have a schema looking like this where I abstracted out the contact information:
type Address {
required property location -> str{
constraint max_len_value(100);
};
}
type Phone{
required property number -> int32{
constraint min_value(0);
constraint max_value(9999999999);
};
}
abstract type Contact {
multi link address -> Address{
constraint exclusive;
on target delete delete source;
};
multi link phone -> Phone{
constraint exclusive;
on target delete delete source;
};
}
type User extending Contact{
required property name -> str{
constraint max_len_value(40);
constraint exclusive;
};
required property rank -> str{
constraint max_len_value(40);
};
}
type Worker extending Contact{
required property name -> str{
constraint max_len_value(40);
};
required property job -> str{
constraint max_len_value(40);
};
}
If I want to limit the choices when doing a reverse query for the Address and Phone to inputs only related to User for User and only Worker for Worker (So queries are restricted by the previous inputs for the object [eg. you cant query the address for User in to be added to Worker]). The relationship is a many to one relationship.
There are two ways I can do this but what is the best practice for efficiency for Edgedb.
Would I use the backlink like this:
type Address {
required property location -> str{
constraint max_len_value(100);
};
link in_user := .<address[is User]
link in_worker := .<address[is Worker]
}
type Phone{
required property number -> int32{
constraint min_value(0);
constraint max_value(9999999999);
};
link in_user := .<phone[is User]
link in_worker := .<phone[is Worker]
}
Or should I use a foreign key link (like in SQL) like this:
type Address {
required property location -> str{
constraint max_len_value(100);
};
link in_user -> User{
on target delete delete source;
};
link in_worker -> Worker{
on target delete delete source;
};
}
type Phone{
required property number -> int32{
constraint min_value(0);
constraint max_value(9999999999);
};
link in_user -> User{
on target delete delete source;
};
link in_worker -> Worker{
on target delete delete source;
};
}

Related

Defining foreign key relationships in Go using GORM [duplicate]

I have those two models:
User model:
type User struct {
DBBase
Email string `gorm:"column:email" json:"email"`
Password string `gorm:"column:password" json:"-"`
}
func (User) TableName() string {
return "t_user"
}
User info model:
type UserInfo struct {
User User `gorm:"foreignkey:u_id;association_foreignkey:id"`
UID uint `gorm:"column:u_id" json:"-"`
FirstName string `gorm:"column:first_name" json:"first_name"`
LastName string `gorm:"column:last_name" json:"last_name"`
Phone string `gorm:"column:phone" json:"phone"`
Address string `gorm:"column:address" json:"address"`
}
func (UserInfo) TableName() string {
return "t_user_info"
}
and I want to make UID related to the id of the user table.
this is the function that creates the user:
func (dao *AuthDAO) Register(rs app.RequestScope, user *models.User, userInfo *models.UserInfo) (userErr error, userInfoErr error) {
createUser := rs.Db().Create(&user)
userInfo.UID = user.ID
createUserInfo := rs.Db().Create(&userInfo)
return createUser.Error, createUserInfo.Error
}
I did try what gorm wrote on the documentation, but without success:
http://doc.gorm.io/associations.html
Note!
from gorm 2.0 this is no longer necessary, read more here:
gorm.io/docs/belongs_to.html#FOREIGN-KEY-Constraints
The solution is to add this line when migrating the database:
db.Model(&models.UserInfo{}).AddForeignKey("u_id", "t_user(id)", "RESTRICT", "RESTRICT")
migration (gorm documentation)
I found the following code correctly created the foreign key without having to do any custom migration code; just the usual AutoMigrate.
type Account struct {
ID uint `gorm:"primary_key"`
}
type Transaction struct {
ID uint `gorm:"primary_key"`
AccountID uint
Account Account `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
I am using "Gorm 2.0" which is a dependency of gorm.io/gorm v1.23.3.
Read about the belongs to relationship in https://gorm.io/docs/belongs_to.html
Also, there's a good example here: https://medium.com/#the.hasham.ali/how-to-use-uuid-key-type-with-gorm-cc00d4ec7100
// User is the model for the user table.
type User struct {
Base
SomeFlag bool `gorm:"column:some_flag;not null;default:true"`
Profile Profile
}// Profile is the model for the profile table.
type Profile struct {
Base
Name string `gorm:"column:name;size:128;not null;"`
UserID uuid.UUID `gorm:"type:uuid;column:user_foreign_key;not null;"`
}
We can add foreign key constraints in the latest version using CreateConstraint.
Example:
Suppose we have two entity
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
Now create database foreign key for user & credit_cards
db.Migrator().CreateConstraint(&User{}, "CreditCards")
db.Migrator().CreateConstraint(&User{}, "fk_users_credit_cards")
which translates to the following SQL code for Postgres:
ALTER TABLE `credit_cards` ADD CONSTRAINT `fk_users_credit_cards` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
Referrence:
Gorm official doc for Constraint Creation
Changes in the latest version
For recent release of Gorm i.e. 1.21.xIf you have a one to one mapping of fields and not have parent to child mapping, here is what you will do
type BaseConfig struct {
ID uuid.UUID `gorm:"primary_key" json:"id"`
}
type Ledger struct {
BaseConfigId uuid.UUID `json:"base_config_id"`
BaseConfig BaseConfig `gorm:"column:base_config_id;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
In Migration script you will need to follow as per docs
WriteClient.Migrator().CreateConstraint(&models.BaseConfig{}, "Ledgers")
WriteClient.Migrator().CreateConstraint(&models.BaseConfig{}, "fk_base_configs_id")
I feel the tripping point is the missing documentation for how to associate. Some juggling around could be there to figure it out, so hope my answer saves some time.

Creating related Models on Laravel/Eloquent relations with primary key type UUID

Laravel: 7; PHP: 7.4/8.0
In my project I have two related models: User and TimeAccount. Both use UUID for their primary key, the key name is still id.
Migrations:
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
// snip...
});
}
// snip...
}
class CreateTimeAccountsTable extends Migration
{
public function up()
{
Schema::create('time_accounts', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignUuid('user_id')->nullable()->constrained();
// snip...
});
}
// snip...
}
Models:
class User
{
use IsIdentifiedByUuid;
protected $keyType = 'string';
public function timeAccount()
{
return $this->hasOne(TimeAccount::class);
}
// snip...
}
class TimeAccount
{
use IsIdentifiedByUuid;
protected $keyType = 'string';
public function user()
{
return $this->belongsTo(User::class);
}
// snip...
}
IsIdentifiedByUuid Trait:
trait IsIdentifiedByUuid
{
protected static function bootIsIdentifiedByUuid()
{
static::creating(fn ($model) => static::isIdentifiedByUuidCreatingHandler($model));
static::saving(fn ($model) => static::isIdentifiedByUuidSavingHandler($model));
}
public function initializeIsIdentifiedByUuid()
{
$this->keyType = 'string';
}
protected function getUuidColumn(): string
{
return property_exists($this, 'uuid_column') ? $this->uuid_column : 'id';
}
protected static function getNewUuid(): UuidInterface
{
$columnName = app(static::class)->getUuidColumn();
$tableName = app(static::class)->getTable();
$columnDescriptor = "$tableName.$columnName";
$uuid = "";
$query = \DB::table($tableName)
->select($columnDescriptor)
->where($columnDescriptor, $uuid);
$attempts = 0;
do {
if ($attempts >= \App\Constants\Uuid::MAX_ATTEMPTS) {
throw new UuidMaxGeneratorAttemptsExceededException();
}
$uuid = Str::uuid();
$attempts++;
} while ($query->setBindings([ $uuid->toString() ])->count() > 0);
return $uuid;
}
/**
* Handles the creation of a model.
* - Generates new UUID if the UUID column is empty and auto-increment is enabled
*
* #param Model $model
* #throws \App\Exceptions\UuidMaxGeneratorAttemptsExceededException
*/
protected static function isIdentifiedByUuidCreatingHandler(Model $model)
{
$columnName = $model->getUuidColumn();
if ($model->getIncrementing() && !$model->{$columnName}) {
$uuid = static::getNewUuid();
\Log::debug(
"IsIdentifiedByUuid [CREATING]:" .
" Generating new UUID for `" . get_class($model) . ": $uuid`"
);
$model->{$columnName} = $uuid->toString();
} else {
\Log::debug(
"IsIdentifiedByUuid [CREATING]:" .
" Using existing UUID for `" . get_class($model) . ": $model->{$columnName}`"
);
}
}
/**
* Handles the saving of a Model.
* - Prevents changes to the UUID column
* - Rolls back changed value to old value
*
* #param Model $model
*/
protected static function isIdentifiedByUuidSavingHandler(Model $model)
{
$columnName = $model->getUuidColumn();
$originalUuid = $model->getOriginal($columnName);
if (!is_null($originalUuid) &&
$originalUuid !== $model->{$columnName}
) {
\Log::debug(
"IsIdentifiedByUuid [SAVING]:" .
" Prevented change of UUID for `" . get_class($model) . ":$originalUuid`"
);
$model->{$columnName} = $originalUuid;
}
}
}
Now the Problem:
Within an user-observer on the created hook I'm doing this:
$userTimeAccount = $user->timeAccount()->create([
// snip...
]);
So far - before switching to UUID instead of integer keys - everything worked fine! As far as I know creating the related model before the user-model has been saved (and obviously before auto-inc was triggered) is something that is explicitly allowed by eloquent/Laravel (though I can not find the section of the docs).
After switching to UUID this is not working anymore and I'm getting an SQL error:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (laravel_torus.time_accounts, CONSTRAINT time_accounts_user_id_foreign FOREIGN KEY (user_id) REFERENCES users (id)) (SQL: insert into time_accounts (time_account_type_id, balance, borrow, user_id, id, updated_at, created_at) values (3, 0, 0, 0, a995807b-b7e1-4af3-96de-7c48187f943d, 2021-07-09 12:54:25, 2021-07-09 12:54:25))
SQL beautified:
insert into `time_accounts`
(`time_account_type_id`, `balance`, `borrow`, `user_id`, `id`, `updated_at`, `created_at`)
values
(3, 0, 0, 0, 'bad86e70-7496-4b42-b2a1-5cc3c5a4d06c', '2021-07-09 11:19:17', '2021-07-09 11:19:17');
Somehow Laravel tries to insert zero (integer) for the Foreign key which will obviously not work. I even tried setting keyType to 'string' manually without effect.
Where am I wrong?
EDIT: It seems like there is a race condition. The created hook of the Observer will be triggered before the created hook of the User model. This leads to the missing ID - the ID is present when using integer and auto increment, just as it should.
TL;DR
I was not able to solve this with MySQL and switched to Postgres SQL.
DONT USE UUID WITH MYSQL unless you are, absolutely sure that: 1. you need to and 2. the performance impact will be negligible!
I really can not explain how this error is happening at all. The ID in question already exists (looking with the debugger into the $attribnutes array of the model), still Laravel will return integer 0 when calling $user->id.
While investigating the problem I noticed that Laravel is creating String columns for the UUID instead of binary (wich is what I would have expected). Since string comparism is quite cost intensive compared to integer/binary comparism I decided to abandon MySQL. UUID's are mandatory for this project and a shift to another DBMS, away from MySQL, in future was already intended.
Postgres SQL supports a special Column type uuid wich will be used by Laravel if a UUID column is defined. This does not only store and compare the ID as binary but also checks for format and unique values, meaning you can not enter an invalid UUID by accident like in MySQL regular sting columns wich seem to have no checks at all apart from not null and unique.

Gorm add multiple slices in inserting in a many to many

I'm new using go and gorm. I'm trying to insert many values in one SQL query.
I wrote this query to add multiple conversations to a user:
relationUserConversation := make([][]uint, len(users))
for i, v := range users {
relationUserConversation[i] = []uint{conversation.ID, v}
}
result = r.db.Debug().Exec(
"INSERT INTO `user_has_conversations` (`user_has_conversations`.`conversation_id`, `user_has_conversations`.`user_id`) VALUES ?",
relationUserConversation, // If i do this it works relationUserConversation[0], relationUserConversation[1]
// The issue is because the query has this value "VALUES ((35,1),(35,2))", but should be to work (35,1),(35,2)
)
I also tried to add it directly with the conversation that would be what I would like to do, but I'm having issue trying to add the relation with the many to many because instead of creating the relation between the user and the conversation it tries to add the user.
My conversation model:
type Conversation struct {
ID uint `gorm:"primarykey"`
Users []*User `gorm:"many2many:user_has_conversations;"`
Messages []ConversationMessage
}
Would be great if i could create a new conversation with the related users in one query instead of creating first the conversation and after the relation to the users.
Below is a minimum working example using the Gorm Appends method (see documentation here) to create a many to many association between two (or more) models. Hopefully you can adapt this to your use case.
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Conversations []Conversation `gorm:"many2many:user_conversations;"`
}
type Conversation struct {
gorm.Model
Name string
Users []*User `gorm:"many2many:user_conversations;"`
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
err = db.AutoMigrate(&User{}, &Conversation{})
if err != nil {
fmt.Print(err)
}
userOne := User{
Name: "User One",
}
userTwo := User{
Name: "User Two",
}
// Create users
db.Create(&userOne)
db.Create(&userTwo)
conversation := Conversation{
Name: "Conversation One",
}
// Create conversation
db.Create(&conversation)
// Append users
err = db.Model(&conversation).Association("Users").Append([]User{userOne, userTwo})
if err != nil {
fmt.Print(err)
}
for _, convUser := range conversation.Users {
fmt.Println("Hello I am in the conversation: " + convUser.Name)
}
// Clean up database
db.Delete(&userOne)
db.Delete(&userTwo)
db.Delete(&conversation)
}
Number of queries
If you enable Debug() on Gorm:
err = db.Debug().Model(&conversation).Association("Users").Append([]User{userOne, userTwo})
It shows this:
[0.144ms] [rows:2] INSERT INTO `user_conversations`
(`conversation_id`,`user_id`) VALUES (8,15),(8,16) ON CONFLICT DO NOTHING
The Values part is correct (what you were trying to do manually) and achieved using the ORM.

Optaplanner: Dynamically choose between hard/soft constraint?

I know that you can insert a weight for each constraint dynamically, but is it possible to make the user be in charge of if a rule is adding points to hardConstraintMatch or softConstraintMatch?
Yes, its possible:
rule foo
when
MyParametrization($fooIsHard : fooIsHard)
... // actual pattern
then
if ($fooIsHard) {
scoreHolder.addHard...(...);
} else {
scoreHolder.addSoft...(...);
}
end

DbUnit does not update postgresql sequences on insert

I am using DbUnit to run some test on a postgreSql database. In order to be able to run my test, I bring the database into a well known state by repopulating the database tables before each test, running a clean insert. Therefore I use the FlatXmlDataSet definition below (compare with the attached SQL schema).
However, if I run the testCreateAvatar() test case, I get an exception because of a status code mismatch, which is caused by a failed sql insert, because of an already existing primary key (id field). A look into my database shows me, that the insert of the test datasets does not update the corresponding *avatars_id_seq* and *users_id_seq* sequence tables, which are used to generate the id fields (mechanism of postgresql to generate auto-increment values).
That means, that the auto-increment value is not updated, if I define static IDs in the FlatXmlDataSet definitions. So my question is how I could change this behavior or set the auto-increment value on my own (using DbUnit).
Avatar creation test case
#Test
public void testCreateAvatar() throws Exception {
// Set up the request url.
final HttpPost request = new HttpPost(
"http://localhost:9095/rest/avatars");
// Setup the JSON blob, ...
JSONObject jsonAvatar = new JSONObject();
jsonAvatar.put("imageUrl", "images/dussel.jpg");
// ... add it to the post request ...
StringEntity input = new StringEntity(jsonAvatar.toString());
input.setContentType("application/json");
request.setEntity(input);
// ... and execute the request.
final HttpResponse response = HttpClientBuilder.create().build()
.execute(request);
// Verify the result.
assertThat(response.getStatusLine().getStatusCode(),
equalTo(HttpStatus.SC_CREATED));
// Fetch dussel duck from the database ...
Avatar dussel = getServiceObjDao().queryForFirst(
getServiceObjDao().queryBuilder().where()
.eq("image_url", "images/dussel.jpg")
.prepare());
// ... and verify that the object was created correctly.
assertThat(dussel, notNullValue());
assertThat("images/dussel.jpg", equalTo(dussel.getImageUrl()));
}
The DbUnit dataset
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<!-- Avatars -->
<avatars
id="1"
image_url="images/donald.jpg" />
<avatars
id="2"
image_url="images/daisy.jpg" />
<!-- Users -->
<users
id = "1"
name = "Donald Duck"
email = "donald.duck#entenhausen.de"
password = "quack" />
<users
id = "2"
name = "Daisy Duck"
email = "daisy.duck#entenhausen.de"
password = "flower" />
</dataset>
The users and avatars table schema
CREATE TABLE avatars (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
image_url VARCHAR(200),
UNIQUE (image_url)
);
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
name VARCHAR(160) NOT NULL,
email VARCHAR (355) UNIQUE NOT NULL,
password VARCHAR(30) NOT NULL,
avatar_id BIGINT,
UNIQUE (name),
CONSTRAINT user_avatar_id FOREIGN KEY (avatar_id)
REFERENCES avatars (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
The function below finds all sequences in a database, extracts the name of the corresponding table from the sequence name and finally updates the current value of the sequences based on the maximum id value in the corresponding table. As there has been no better solution yet, this seems to be the way to go. Hope, this helps someone.
Simple solution based on harmic's suggestion
#Before
public void resetSequence() {
Connection conn = null;
try {
// Establish a database connection.
conn = DriverManager.getConnection(
this.props.getProperty("database.jdbc.connectionURL"),
this.props.getProperty("database.jdbc.username"),
this.props.getProperty("database.jdbc.password"));
// Select all sequence names ...
Statement seqStmt = conn.createStatement();
ResultSet rs = seqStmt.executeQuery("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';");
// ... and update the sequence to match max(id)+1.
while (rs.next()) {
String sequence = rs.getString("relname");
String table = sequence.substring(0, sequence.length()-7);
Statement updStmt = conn.createStatement();
updStmt.executeQuery("SELECT SETVAL('" + sequence + "', (SELECT MAX(id)+1 FROM '" + table + "'));");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
}
}
}
You can set the value of a sequence using setval, for example
SELECT SETVAL('sequence_name', 1000);
Where sequence_name is the name of the sequence, visible in psql using /dt on the table, and 1000 is the value you want to set it to. You would probably want to set it to the Max value of Id in the table.
What I don't really know is how to get DbUnit to emit this SQL.