I have a NestJS project where I generate the migration with typeorm:generate-migration. Because of uuid_generate_v4() the script is not cross compatible between PostgreSQL (dev/prod) and SQLite (tests). How to make it work?
User Entity :
#Entity()
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
}
Migration :
export class initUser1667668869578 implements MigrationInterface {
name = 'initUser1667668869578'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
The error (caused because SQLite doesn't have uuid_generate_v4()) :
Migration "initUser1667668869578" failed, error: SQLITE_ERROR: near "(": syntax error
Natively there's nothing you can replace that function call with. Alternatives:
Drop NOT NULL DEFAULT uuid_generate_v4() part and use a trigger after insert instead. Example.
Define your own uuid_generate_v4() - you will need an extension for that.
If dev and prod are on PostgreSQL, it would make sense to run your tests on PostgreSQL as well, unless you're just testing whether it'd be possible to migrate.
Drop the db-side default value part entirely and generate it in the app.
Related
I am writing a junit test using Spring Boot JPA.
My entity has an attribute of type UUID (BaseEntity defines a Long id which is a sequence in database). My mapping is in a orm.xml file:
public class User extends BaseEntity {
private String username;
private UUID uuid;
...
}
I have defined a UserRepoImpl class that search for a User using a given UUID (jpaRepo being an interface extending JpaRepository<User,Long>:
public Optional<User> getUserByUuid(UUID aUuid) {
return jpaRepo.findByUuid(aUuid);
}
I have written a junit to test this method against a H2 database and I use a sql file to insert data before the test :
#ActiveProfiles("tu")
#ExtendWith(SpringExtension.class)
#DataJpaTest(includeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
#Sql("/sql/infra/repo/user-repository.sql")
public class UserRepositoryImplTest {
#Autowired
private UserRepositoryImpl cut;
#Test
void should_ReturnUser_WhenUUIDExist() {
UUID uuid = UUID.fromString("9fc1cd41-9d28-463f-94b9-542836572802");
Optional<User> user = cut.getUserByUuid(uuid);
Assertions.assertTrue(user.isPresent());
Assertions.assertEquals(1L, user.get().getId());
Assertions.assertEquals(uuid, user.get().getUuid());
}
}
The SQL file inserts a user with a UUID that I have converted :
INSERT INTO USERS (id, USERNAME, UUID)
VALUES (3, 'user3', X'9FC1CD419D28463F94B9542836572802');
The test fails because getUserByUuid() doesn't return any user.
What I don't understand is why the UUID column is generated in H2 with a binary type when there is a UUID type in H2 :
Hibernate: create table users (id bigint not null, username varchar(255), uuid binary(255), account_id bigint, primary key (id))
I tried to use varchar for storing the UUID and that works but I don't want to use varchar to store my UUID.
I use https://www.piiatomi.org/uuid_converter.html to make conversion between UUID and hex.
Any idea ?
Thank you!
binary(255) is a fixed-length binary string (byte array in Java) with exactly 255 bytes. This is a wrong data type for UUID values, but some versions of Hibernate ORM incorrectly choose it for UUID properties.
You can override this default with some correct type:
#Column(columnDefinition="UUID")
private UUID uuid;
As part of learning r2DBC i have come across an issue facing with Enum conversion. I am using PostgreSQL here.
When reading data for Film where rating is PG-13 and NC-17( anything with dash ) i am facing issues.
Below is my schema for table
create table film
(
film_id integer default nextval('film_film_id_seq'::regclass) not null
constraint film_pkey
primary key,
title varchar(255) not null,
description text,
release_year year,
language_id smallint not null
constraint film_language_id_fkey
references language
on update cascade on delete restrict,
rental_duration smallint default 3 not null,
rental_rate numeric(4, 2) default 4.99 not null,
length smallint,
replacement_cost numeric(5, 2) default 19.99 not null,
rating mpaa_rating default 'G'::mpaa_rating,
last_update timestamp default now() not null,
special_features text[]
);
And the mpaa_rating is defined as
create type mpaa_rating as enum ('G', 'PG', 'PG-13', 'R', 'NC-17');
This is my code which registers the converters in my Configuration
#Configuration
#EnableTransactionManagement
#EnableR2dbcRepositories
#EnableR2dbcAuditing
public class DVDRentalDBConfiguration extends AbstractR2dbcConfiguration {
#Bean
public ConnectionFactory connectionFactory() {
System.out.println("Initializing postgreSQL connection factory");
return new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost")
.database("dvdrental")
.username("postgres")
.password("postgres")
.codecRegistrar(EnumCodec.builder().withEnum("mpaa_rating", Rating.class).build())
.build()
);
}
#Override
protected List<Object> getCustomConverters() {
return Collections.singletonList(new RatingWritingConverter());
}
#Bean
ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
System.out.println("Initializing postgreSQL connection factory");
return new R2dbcTransactionManager(connectionFactory);
}
}
My code for retrieving is pretty simple
private Mono<FilmModel> getFilmById(Long id) {
return filmRepository.findById(id).switchIfEmpty(Mono.error(DataFormatException::new));
}
Adding the exception which is thrown https://gist.github.com/harryalto/bd51bbcdd081868c5064c808d08205e4
I tried researching stack overflow but couldn't figure out the issue. Any help is greatly appreciated.
If you are using Spring Boot/Spring Data R2dbc to map table to POJO, you can skip the enum definition in Postgres, by default Spring Data R2dbc will handle the enum as varchar/char in db side, and use Enum in java POJO, check my example, and schema sql script and mapped entity class. Spring Boot registered mapping converter to convert them automatically.
If you would like to handle the Enum type yourself, check this example.
I am using knex.js with postgresql
So I have a table with a nullable foreign key.
shortened version:
exports.up = function (knex, Promise) {
return knex.schema.createTable('Note', function (table) {
table.string('id').primary()
table
.string('sourceId')
.references('id')
.inTable('Source')
.onDelete('SET NULL')
.index()
})
}
exports.down = function (knex, Promise) {
return knex.schema.dropTable('Note')
}
I am able to create a Note with or without a sourceId. However, if I create a Note with a sourceId and then update it to set the sourceId to NULL, the update does not work. I do not get an error message, but the foreign key is not removed.
For example if I create a Note with:
{
id: '123',
sourceId: '456'
}
and then try to update it:
const result = await Note.query().updateAndFetchById(id, {
id: '123',
sourceId: null
})
The result I get is :
Note {
id: '123',
sourceId: '456'
}
I have no problem if I try to update other nullable values to null (as long as they are not foreign keys) and I can update the sourceId to a different source's id.
If I try to update a not nullable foreign key to null, I get an error. But in the above case, I get no error. It just doesn't update.
Any idea what might be going on here?
I am trying to implement a little state machine with go and store my states in a postgres db.
i created my database like this:
CREATE TABLE state_machines
(
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
initial_state TEXT NOT NULL,
"name" TEXT NOT NULL
);
CREATE TABLE state_machine_states
(
state_machine_id uuid NOT NULL REFERENCES state_machines(id) ON DELETE CASCADE,
"name" TEXT NOT NULL,
PRIMARY KEY(state_machine_id, "name")
);
// StateMachine is the DB schema
type StateMachine struct {
ID *uuid.UUID `pg:"id,pk,type:uuid,default:uuid_generate_v4()"`
Name string `pg:"name"`
InitialState string `pg:"initial_state"`
States []*StateMachineState `pg:"fk:state_machine_id"`
}
// StateMachineState is the DB schema
type StateMachineState struct {
StateMachineID uuid.UUID `pg:"state_machine_id,fk"`
Name string `pg:"name"`
}
I am using go-pg 9.2 and i am trying to load a state machine and a list of its states from the "States" relation.
My function to load the state machines looks like this:
func (cep *repository) GetStateMachines() ([]*StateMachine, error) {
stateMachines := []*StateMachine{}
err := cep.db.Model(&stateMachines).
Relation("States").
Relation("Transitions").
Select()
return stateMachines, err
}
If I execute it, I always get the error message Error reading state machine: model=StateMachine does not have relation="States"
I have done similar relations before and they worked and now, I cannot get it to work again :(
Try upgrading to v10 which fully supports relations: https://pg.uptrace.dev/orm/has-many-relation/
See if there is .Debug() function in go-pg for a query.
I use https://gorm.io/ , and there is a Debug funcion, that returns all of the SQL queries.
When i see what queries are send, i log in postgresql and try them manually to see a more detailed error.
I had an existing PostgreSQL database with a table created like this:
CREATE TABLE product (id SERIAL PRIMARY KEY, name VARCHAR(100) DEFAULT NULL)
This table is described in a YML Doctrine2 file within a Symfony2 project:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
fields:
id:
id: true
type: integer
nullable: false
generator:
strategy: SEQUENCE
name:
type: string
length: 100
nullable: true
When I run for the first time the Doctrine Migrations diff task, I should get a versioning file with no data in the up and down methods. But what I get instead is this :
// ...
class Version20120807125808 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("ALTER TABLE product ALTER id DROP DEFAULT");
}
public function down(Schema $schema)
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("CREATE SEQUENCE product_id_seq");
$this->addSql("SELECT setval('product_id_seq', (SELECT MAX(id) FROM product))");
$this->addSql("ALTER TABLE product ALTER id SET DEFAULT nextval('product_id_seq')");
}
}
Why are differences detected? How can I avoid this? I tried several sequence strategies with no success.
A little update on this question.
Using Doctrine 2.4, the solution is to use the IDENTITY generator strategy :
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
name:
type: string
length: 100
nullable: true
To avoid DROP DEFAULT on fields that have a default value in the database, the default option on the field is the way to go. Of course this can be done with lifecycle callbacks, but it's necessary to keep the default value in the database if this database is used by other apps.
For a "DEFAULT NOW()" like default value, the solution is the following one:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
creation_date:
type: datetime
nullable: false
options:
default: CURRENT_TIMESTAMP
Doctrine 2.0 does not support the SQL DEFAULT keyword, and will always try to drop a postgres default value.
I have found no solution to this problem, I just let doctrine handle the sequences itself.
This is a opened bug registered here :
http://www.doctrine-project.org/jira/browse/DBAL-903