I have began a Play Scala project and made it have a database by uncommenting in application.conf:
default.driver = org.h2.Driver
default.url = "jdbc:h2:mem:play"
Then, I created an evolution in conf/evolutions/default/1.sql:
CREATE SEQUENCE task_id_seq;
CREATE TABLE task (
id integer NOT NULL DEFAULT nextval('task_id_seq'),
label varchar(255)
);
# --- !Downs
DROP TABLE task;
DROP SEQUENCE task_id_seq;
So, when I am accessing localhost:9000 I am expecting to see the message:
Database default needs evolution!. However, this does not appear.
I am running in development mode and I don't have the code evolutionplugin=disabled anywhere in my project.
Why is the evolution not seen?
You need to add evolutions to the list of your library dependencies, as described in the docs https://www.playframework.com/documentation/2.4.0/Evolutions.
Related
(I have multiple related questions, so I highlight them as bold)
I have a play app.
play: 2.6.19
scala: 2.12.6
h2: 1.4.197
postgresql: 42.2.5
play-slick/play-slick-evolutions: 3.0.1
slick-pg: 0.16.3
I am adding a test for DAO, and I believe it should run on an h2 in-memory database that is created when tests start, cleared when tests end.
However, my test always runs on PostgreSQL database I configure and use.
# application.conf
slick.dbs.default.profile="slick.jdbc.PostgresProfile$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://localhost:5432/postgres"
Here is my test test/dao/TodoDAOImplSpec.scala.
package dao
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.test.{Injecting, PlaySpecification, WithApplication}
class TodoDAOImplSpec extends PlaySpecification {
val conf = Map(
"slick.dbs.test.profile" -> "slick.jdbc.H2Profile$",
"slick.dbs.test.db.driver" -> "org.h2.Driver",
"slick.dbs.test.db.url" -> "jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE"
)
val fakeApp = new GuiceApplicationBuilder().configure(conf).build()
//val fakeApp = new GuiceApplicationBuilder().configure(inMemoryDatabase()).build()
//val fakeApp = new GuiceApplicationBuilder().configure(inMemoryDatabase("test")).build()
"TodoDAO" should {
"returns current state in local pgsql table" in new WithApplication(fakeApp) with Injecting {
val todoDao = inject[TodoDAOImpl]
val result = await(todoDao.index())
result.size should_== 0
}
}
}
For fakeApp, I try all three, but none of them work as expected - my test still runs on my local PostgreSQL table (in which there are 3 todo items), so the test fails.
What I have tried/found:
First, inMemoryDatabase() simply returns a Map("db.<name>.driver"->"org.h2.Driver", "db.<name>.url"->""jdbc:h2:mem:play-test-xxx"), which looks very similar to my own conf map. However, there are 2 main differeneces:
inMemoryDatabase uses db.<name>.xxx while my conf map uses slick.dbs.<name>.db.xxx. Which one should be correct?
Second, rename conf map's keys to "slick.dbs.default.profile", "slick.dbs.default.db.driver" and "slick.dbs.default.db.url" will throw error.
[error] p.a.d.e.DefaultEvolutionsApi - Unknown data type: "status_enum"; SQL statement:
ALTER TABLE todo ADD COLUMN status status_enum NOT NULL [50004-197] [ERROR:50004, SQLSTATE:HY004]
cannot create an instance for class dao.TodoDAOImplSpec
caused by #79bg46315: Database 'default' is in an inconsistent state!
The finding is interesting - is it related to my use of PostgreSQL ENUM type and slick-pg? (See slick-pg issue with h2). Does it mean this is the right configuration for running h2 in-memory tests? If so, the question becomes How to fake PostgreSQL ENUM in h2.
Third, I follow this thread, run sbt '; set javaOptions += "-Dconfig.file=conf/application-test.conf"; test' with a test configuration file conf/application-test.conf:
include "application.conf"
slick.dbs.default.profile="slick.jdbc.H2Profile$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE"
Not surprisingly, I get the same error as the 2nd trial.
It seems to me that the 2nd and 3rd trials point to the right direction (Will work on this). But why must we set name to default? Any other better approach?
In play the default database is default. You could however change that to any other database name to want, but then you need to add the database name as well. For example, I want to have a comment database that has the user table:
CREATE TABLE comment.User(
id int(250) NOT NULL AUTO_INCREMENT,
username varchar(255),
comment varchar(255),
PRIMARY KEY (id));
Then I need to have the configuration of it to connect to it (add it to the application.conf file):
db.comment.url="jdbc:mysql://localhost/comment"
db.comment.username=admin-username
db.comment.password="admin-password"
You could have the test database for your testing as mentioned above and use it within your test.
Database Tests Locally: Why not have the database, in local, as you have in production? The data is not there and running the test on local does not touch the production database; why you need an extra database?
Inconsistent State: This is when the MYSQL you wrote, changes the state of the current database within the database, that could be based on creation of a new table or when you want to delete it.
Also status_enum is not recognizable as a MySQL command obviously. Try the commands you want to use in MySQL console if you are not sure about it.
Unable to test Spring Boot & H2 with a script for creation of table using schema.sql.
So, what’s happening is that I have the following properties set:
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.initialization-mode=always
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:city;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
and, I expect the tables to be created using the schema.sql. The application works fine when I run gradle bootRun. However, when I run tests using gradle test, my tests for Repository passes, but the one for my Service fails stating that it’s trying to create the table when the table already exists:
Exception raised:
Caused by: org.h2.jdbc.JdbcSQLException: Table "CITY" already exists;
SQL statement:
CREATE TABLE city ( id BIGINT NOT NULL, country VARCHAR(255) NOT NULL, map VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, state VARCHAR(2555) NOT NULL, PRIMARY KEY (id) ) [42101-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.ddl.CreateTable.update(CreateTable.java:117)
at org.h2.command.CommandContainer.update(CommandContainer.java:101)
at org.h2.command.Command.executeUpdate(Command.java:260)
at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:192)
at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:164)
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95)
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:471)
... 105 more
The code is setup and ready to recreate the scenario. README has all the information ->
https://github.com/tekpartner/learn-spring-boot-data-jpa-h2
If the tests are run individually, they pass. I think the problem is due to schema.sql being executed twice against the same database. It fails the second time as the tables already exist.
As a workaround, you could set spring.datasource.continue-on-error=true in application.properties.
Another option is to add the #AutoConfigureTestDatabase annotation where appropriate so that a unique embedded database is used for each test.
There are 2 other possible solutions you could try:
Add a drop table if exists [tablename] in your schema.sql before you create the table.
Change the statement from CREATE TABLE to CREATE TABLE IF NOT EXISTS
I have a problem with PK generation strategy in cayenne.
I'm using PostgreSQL 9.6 with Apache Cayenne 4.0.B1.
This is my table in postgres:
create table ui_template (
id varchar(50) primary key default uuid_generate_v1(),
path varchar(300) not null,
type varchar(50) not null
);
Then I'm doing Tools -> Reengineer Database Schema in Cayenne Modeler.
I got table and enity.
And now i have to set PK Generation Strategy to Database-Generated, to let DB generate my PK's. Everything works fine when I'm inserting objects through ObjectContext. But, if I'm running gradle task cdbimport it overwrites my datamap.map.xml file with another one without Generation Strategy.
Please give me advice what I'm doing wrong.
This is my gradle task
buildscript {
// add Maven Central repository
repositories {
mavenCentral()
}
// add Cayenne Gradle Plugin
dependencies {
classpath group: 'org.apache.cayenne.plugins', name: 'cayenne-gradle-plugin', version: '4.0.B1'
classpath group: 'org.postgresql', name: 'postgresql', version: '42.1.3'
}
}
// apply plugin
apply plugin: 'org.apache.cayenne'
// set default DataMap
cayenne.defaultDataMap 'resources/datamap.map.xml'
// add Cayenne dependencies to your project
dependencies {
// this is a shortcut for 'org.apache.cayenne:cayenne-server:VERSION_OF_PLUGIN'
compile cayenne.dependency('server')
compile cayenne.dependency('java8')
}
cdbimport {
dataSource {
driver 'org.postgresql.Driver'
url 'jdbc:postgresql://localhost:5432/my_db'
username 'user'
password 'pass'
}
}
You are doing nothing wrong, it seems that you found a weak spot in cdbimport.
In your case Cayenne doesn't understand default values, for Postgres DB Cayenne marks only serial columns as db-generated.
You can either change your PK to serial (unless you have some special requirements it is always a good idea) or you can try to exclude those PKs from cdbimport task like this:
cdbimport {
//...
includeTable 'ui_template', {
excludeColumns 'id'
}
}
Be aware that excluding PKs can lead to problems with importing relationships. And moreover if you use includeTable you should explicitly list all required tables.
i deployed my very simple app in heroku by following tutorials
it works well in my localhost when i run it by sbt run
but it crashes on heroku!
here is my 1.sql:
# --- !Ups
create table contact (
id SERIAL UNIQUE,
name varchar(255),
email varchar(255),
phone varchar(255),
constraint pk_contact primary key (id)
);
create sequence contact_seq;
# --- !Downs
drop table if exists contact;
drop sequence if exists contact_seq;
heroku's log:
p.a.d.DefaultDBApi - Database [default] connected at jdbc:postgresql://...
!!! WARNING! This script contains DOWNS evolutions that are likely destructive
[warn] p.a.d.e.ApplicationEvolutions - Your production database [default] needs evolutions, including downs!
drop table if exists contact;
# --- Rev:1,Downs - a56ada6
name varchar(255),
drop sequence if exists contact_seq;
email varchar(255), phone varchar(255),
# --- Rev:1,Ups - 53110fe
create table contact (
);
id SERIAL UNIQUE,
constraint pk_contact primary key (id)
create sequence contact_seq;
[warn] p.a.d.e.ApplicationEvolutions - Run with -Dplay.evolutions.db.default.autoApply=true and -Dplay.evolutions.db.default.autoApplyDowns=true if you want to run them automatically, including downs (be careful, especially if your down evolutions drop existing data)
[info] application - ApplicationTimer demo: Starting application at 2017-04-28T08:59:05.048Z
Oops, cannot start the server.
#73o5pe90c: Database 'default' needs evolution!
and i also added
play.evolutions.db.default.autoApply=true
at end of my aplication.conf
Run with
-Dplay.evolutions.db.default.autoApply=true
and
-Dplay.evolutions.db.default.autoApplyDowns=true
If you want to run them automatically, including downs (be careful, especially if your down evolutions drop existing data).
You can set these by running:
heroku config:set JAVA_OPTS="-Dplay.evolutions.db.default.autoApply=true -Dplay.evolutions.db.default.autoApplyDowns=true"
I'm running Play with Slick integration, Evolutions and an H2 database. When starting Play in dev mode and visiting localhost:9000 (I am using https) I am told database "default" needs evolution. However the script I have in conf/evolutions/default/1.sql is not display below. Rather it only shows:
1# --- Rev:1,Ups - da39a3e
However my script reads:
# --- ! Ups
create table "USERS" ("ID" VARCHAR NOT NULL PRIMARY KEY, "ACTION" VARCHAR);
# --- ! Downs
drop table "USERS";
Naturally all transactions on this table fail. Am I missing a bit of configuration?
There must not be a space between the exclamation mark and the command:
# --- ! Ups
Wrong!
# --- !Ups
Right!