From 9826a39955d4c37683afbeb9684bfeb700107a62 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sat, 22 Oct 2022 16:21:50 +0200
Subject: [PATCH 01/19] feat: Cockroach DB support

---
 .github/workflows/test.yml                    |  3 +-
 backend/app/build.gradle                      | 21 ++++-
 .../test/resources/application-cockroach.yaml |  7 ++
 .../configuration/LiquibaseConfiguration.kt   |  2 +-
 .../activity/ActivityRevisionRepository.kt    |  2 +-
 .../main/resources/db/changelog/schema.xml    | 89 +++++++++++++++++++
 .../kotlin/io/tolgee/CleanDbTestListener.kt   | 12 ++-
 .../testing/AbstractTransactionalTest.kt      |  2 -
 8 files changed, 131 insertions(+), 7 deletions(-)
 create mode 100644 backend/app/src/test/resources/application-cockroach.yaml

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fb0c8e98cf..078cfdb8d2 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -58,6 +58,7 @@ jobs:
       fail-fast: false
       matrix:
         command: [ "server-app:runContextRecreatingTests", "server-app:runStandardTests", "data:test" ]
+        profile: [ "default, cockroach" ]
     steps:
       - uses: actions/checkout@v2
 
@@ -91,7 +92,7 @@ jobs:
           tar -xzf ~/backend-testing.tgz ./backend/testing/build
 
       - name: Run backend tests
-        run: ./gradlew ${{ matrix.command }}
+        run: SPRING_PROFILES_ACTIVE=${{ matrix.profile }} ./gradlew ${{ matrix.command }}
         env:
           SKIP_SERVER_BUILD: true
 
diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index b5ffe3b626..5ce9c1fbc6 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -167,19 +167,38 @@ dependencies {
 test {
     useJUnitPlatform()
     maxHeapSize = "2048m"
-    //maxParallelForks = (int) (Runtime.runtime.availableProcessors() / 2 + 1)
+}
+
+task testCockroach(type: Test, group: 'verification') {
+    useJUnitPlatform()
+    maxHeapSize = "2048m"
+    systemProperty 'SPRING_PROFILES_ACTIVE', 'cockroach'
 }
 
 task runContextRecreatingTests(type: Test, group: 'verification') {
+    dependsOn "startCockroachDb"
     useJUnitPlatform {
         includeTags "contextRecreating"
     }
+    finalizedBy "stopCockroachDb"
 }
 
 task runStandardTests(type: Test, group: 'verification') {
+    dependsOn "startCockroachDb"
     useJUnitPlatform {
         excludeTags "contextRecreating"
     }
+    finalizedBy "stopCockroachDb"
+}
+
+task startCockroachDb(type: Exec){
+    onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:latest start-single-node --insecure"
+}
+
+task stopCockroachDb(type: Exec){
+    onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
+    commandLine "bash", "-c", "docker stop roach"
 }
 
 springBoot {
diff --git a/backend/app/src/test/resources/application-cockroach.yaml b/backend/app/src/test/resources/application-cockroach.yaml
new file mode 100644
index 0000000000..6f7e8b7d60
--- /dev/null
+++ b/backend/app/src/test/resources/application-cockroach.yaml
@@ -0,0 +1,7 @@
+spring:
+  datasource:
+    url: jdbc:postgresql://localhost:26257/postgres
+    username: root
+tolgee:
+  postgres-autostart:
+    enabled: false
diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
index 2bf3bcdf82..e40dc9d813 100644
--- a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
@@ -16,7 +16,7 @@ class LiquibaseConfiguration {
     liquibase.dataSource = dataSource
     liquibase.changeLog = "classpath:db/changelog/schema.xml"
     liquibase.defaultSchema = "public"
-
+    liquibase.setChangeLogParameters(mapOf("isCockroach" to "true"))
     return liquibase
   }
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/activity/ActivityRevisionRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/activity/ActivityRevisionRepository.kt
index 4669f24598..c45349b145 100644
--- a/backend/data/src/main/kotlin/io/tolgee/repository/activity/ActivityRevisionRepository.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/repository/activity/ActivityRevisionRepository.kt
@@ -61,7 +61,7 @@ interface ActivityRevisionRepository : JpaRepository<ActivityRevision, Long> {
 
   @Query(
     """
-      select count(ar.id) as count, function('to_char', ar.timestamp, 'yyyy-MM-dd') as date
+      select count(ar.id) as count, cast(cast(ar.timestamp as date) as text) as date
       from ActivityRevision ar
       where ar.projectId = :projectId
       group by date
diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml
index 1914365bf3..aa7fdeb8a9 100644
--- a/backend/data/src/main/resources/db/changelog/schema.xml
+++ b/backend/data/src/main/resources/db/changelog/schema.xml
@@ -82,6 +82,44 @@
             <column name="created_by_id" type="BIGINT"/>
         </createTable>
     </changeSet>
+    <changeSet author="jenik (generated)" id="1585252533730-6">
+        <preConditions onFail="CONTINUE">
+            <not>
+                <changeLogPropertyDefined property="isCockroach" value="true"/>
+            </not>
+        </preConditions>
+        <createTable tableName="source">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints primaryKey="true" primaryKeyName="sourcePK"/>
+            </column>
+            <column name="created_at" type="TIMESTAMP WITHOUT TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="updated_at" type="TIMESTAMP WITHOUT TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="name" type="VARCHAR(255)"/>
+            <column name="repository_id" type="BIGINT"/>
+        </createTable>
+    </changeSet>
+    <changeSet author="jenik (generated)" id="1585252533730-6crdb">
+        <preConditions onFail="CONTINUE">
+            <changeLogPropertyDefined property="isCockroach" value="true"/>
+        </preConditions>
+        <createTable tableName="source">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints primaryKey="true" primaryKeyName="sourcePK"/>
+            </column>
+            <column name="created_at" type="TIMESTAMP WITHOUT TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="updated_at" type="TIMESTAMP WITHOUT TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="name" type="VARCHAR(2000)"/>
+            <column name="repository_id" type="BIGINT"/>
+        </createTable>
+    </changeSet>
     <changeSet author="jenik (generated)" id="1585252533730-6">
         <createTable tableName="source">
             <column autoIncrement="true" name="id" type="BIGINT">
@@ -444,6 +482,14 @@
     <changeSet author="jenik (generated)" id="1621606909199-2">
         <createSequence incrementBy="100" sequenceName="hibernate_sequence" startValue="1000000000"/>
     </changeSet>
+    <changeSet author="jenik (generated)" id="1621606909199-2crdb">
+        <preConditions onFail="CONTINUE">
+            <changeLogPropertyDefined property="isCockroach" value="true"/>
+        </preConditions>
+        <sql>
+           select setval('hibernate_sequence', 1000000000)
+        </sql>
+    </changeSet>
     <changeSet author="jenik (generated)" id="1621606909199-3">
         <createTable tableName="import">
             <column name="id" type="BIGINT">
@@ -1088,6 +1134,11 @@
         </createTable>
     </changeSet>
     <changeSet author="jenik (generated)" id="1624975131291-13">
+        <preConditions onFail="CONTINUE">
+            <not>
+                <changeLogPropertyDefined property="isCockroach" value="true"/>
+            </not>
+        </preConditions>
         <createTable tableName="revision">
             <column autoIncrement="true" name="id" type="INT">
                 <constraints nullable="false" primaryKey="true" primaryKeyName="revisionPK"/>
@@ -1098,6 +1149,20 @@
             <column name="author_id" type="BIGINT"/>
         </createTable>
     </changeSet>
+    <changeSet author="jenik (generated)" id="1624975131291-13crdb">
+        <preConditions onFail="CONTINUE">
+            <changeLogPropertyDefined property="isCockroach" value="true"/>
+        </preConditions>
+        <createTable tableName="revision">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true" primaryKeyName="revisionPK"/>
+            </column>
+            <column name="timestamp" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="author_id" type="BIGINT"/>
+        </createTable>
+    </changeSet>
     <changeSet author="jenik (generated)" id="1624975131291-14">
         <createTable tableName="screenshot_aud">
             <column name="id" type="BIGINT">
@@ -1356,7 +1421,21 @@
     <changeSet author="jenik (generated)" id="1626957302292-4">
         <addNotNullConstraint columnDataType="bigint" columnName="key_id" tableName="translation" validate="true"/>
     </changeSet>
+    <changeSet author="jenik" id="1626957302295-0">
+        <preConditions onFail="CONTINUE">
+            <changeLogPropertyDefined property="isCockroach" value="true"/>
+        </preConditions>
+        <sql>
+            set
+            enable_experimental_alter_column_type_general = true
+        </sql>
+    </changeSet>
     <changeSet author="jenik (generated)" id="1626957302295-1">
+        <preConditions onFail="CONTINUE">
+            <not>
+                <changeLogPropertyDefined property="isCockroach" value="true"/>
+            </not>
+        </preConditions>
         <modifyDataType tableName="revision" columnName="id" newDataType="BIGINT"/>
     </changeSet>
     <changeSet author="jenik (generated)" id="1633085681853-3">
@@ -1399,6 +1478,11 @@
                              constraintName="organization_third_party_billing_id_unique" tableName="organization"/>
     </changeSet>
     <changeSet author="jenik" id="1637177424380-0">
+        <preConditions onFail="CONTINUE">
+            <not>
+                <changeLogPropertyDefined property="isCockroach" value="true"/>
+            </not>
+        </preConditions>
         <modifyDataType
                 columnName="name"
                 newDataType="varchar(2000)"
@@ -2101,6 +2185,11 @@
         <addUniqueConstraint columnNames="key_hash" constraintName="api_key_hash_unique" tableName="api_key"/>
     </changeSet>
     <changeSet author="jenik (generated)" id="1661361670848-3">
+        <preConditions onFail="CONTINUE">
+            <not>
+                <changeLogPropertyDefined property="isCockroach" value="true"/>
+            </not>
+        </preConditions>
         <sqlFile path="classpath:db/changelog/hideApiKeyMigration.sql" splitStatements="false"/>
     </changeSet>
     <changeSet author="jenik (generated)" id="1661420385794-1">
diff --git a/backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt b/backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt
index d25bdc0ad6..eb6e4e5213 100644
--- a/backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt
+++ b/backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory
 import org.springframework.context.ApplicationContext
 import org.springframework.test.context.TestContext
 import org.springframework.test.context.TestExecutionListener
+import java.sql.Connection
 import java.sql.ResultSet
 import javax.sql.DataSource
 import kotlin.system.measureTimeMillis
@@ -47,7 +48,7 @@ class CleanDbTestListener : TestExecutionListener {
     val ds: DataSource = appContext.getBean(DataSource::class.java)
     ds.connection.use { conn ->
       val stmt = conn.createStatement()
-      val databaseName: Any = "postgres"
+      val databaseName: Any = getDatabaseName(conn)
       val ignoredTablesString = ignoredTables.joinToString(", ") { "'$it'" }
       val rs: ResultSet = stmt.executeQuery(
         String.format(
@@ -71,6 +72,15 @@ class CleanDbTestListener : TestExecutionListener {
     }
   }
 
+  private fun getDatabaseName(conn: Connection): String {
+    val rs = conn.getMetaData().catalogs
+    val data = mutableListOf<String>()
+    while (rs.next()) {
+      data.add(rs.getString(1))
+    }
+    return data.single()
+  }
+
   @Throws(Exception::class)
   override fun afterTestMethod(testContext: TestContext) {
   }
diff --git a/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt b/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt
index 75cd912aa6..829223fee5 100644
--- a/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt
+++ b/backend/testing/src/main/kotlin/io/tolgee/testing/AbstractTransactionalTest.kt
@@ -2,7 +2,6 @@ package io.tolgee.testing
 
 import io.tolgee.CleanDbTestListener
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.TestExecutionListeners
 import org.springframework.test.context.support.DependencyInjectionTestExecutionListener
 import org.springframework.test.context.transaction.TestTransaction
@@ -16,7 +15,6 @@ import javax.persistence.EntityManager
     CleanDbTestListener::class
   ]
 )
-@ActiveProfiles(profiles = ["local"])
 abstract class AbstractTransactionalTest {
   @Autowired
   protected lateinit var entityManager: EntityManager

From 7d6eba2c262de8926954d3c09ea13ba0bccbbe9b Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sat, 22 Oct 2022 16:31:57 +0200
Subject: [PATCH 02/19] feat: Cockroach DB support > fix workflow

---
 .github/workflows/test.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 078cfdb8d2..5ec8a34449 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -58,7 +58,7 @@ jobs:
       fail-fast: false
       matrix:
         command: [ "server-app:runContextRecreatingTests", "server-app:runStandardTests", "data:test" ]
-        profile: [ "default, cockroach" ]
+        profile: [ "default", "cockroach" ]
     steps:
       - uses: actions/checkout@v2
 

From 2d5f9b5b161965053acd71cdf47d2b5dbb29e76c Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sat, 22 Oct 2022 16:57:51 +0200
Subject: [PATCH 03/19] feat: Cockroach DB support > cockroach switch

---
 backend/app/build.gradle                             |  2 +-
 .../src/test/resources/application-cockroach.yaml    |  2 ++
 .../tolgee/configuration/LiquibaseConfiguration.kt   |  8 ++++++--
 .../configuration/tolgee/DatabaseProperties.kt       | 12 ++++++++++++
 .../tolgee/configuration/tolgee/TolgeeProperties.kt  |  3 ++-
 .../data/src/main/resources/db/changelog/schema.xml  |  9 ---------
 6 files changed, 23 insertions(+), 13 deletions(-)
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/DatabaseProperties.kt

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index 5ce9c1fbc6..d19a769b7b 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -193,7 +193,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:latest start-single-node --insecure"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:22.1 start-single-node --insecure"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/backend/app/src/test/resources/application-cockroach.yaml b/backend/app/src/test/resources/application-cockroach.yaml
index 6f7e8b7d60..cd41838663 100644
--- a/backend/app/src/test/resources/application-cockroach.yaml
+++ b/backend/app/src/test/resources/application-cockroach.yaml
@@ -3,5 +3,7 @@ spring:
     url: jdbc:postgresql://localhost:26257/postgres
     username: root
 tolgee:
+  database:
+    type: COCKROACH
   postgres-autostart:
     enabled: false
diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
index e40dc9d813..6835e58e4f 100644
--- a/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/configuration/LiquibaseConfiguration.kt
@@ -1,5 +1,7 @@
 package io.tolgee.configuration
 
+import io.tolgee.configuration.tolgee.DatabaseProperties
+import io.tolgee.configuration.tolgee.TolgeeProperties
 import liquibase.integration.spring.SpringLiquibase
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
@@ -10,13 +12,15 @@ import javax.sql.DataSource
 class LiquibaseConfiguration {
   @Bean
   @Primary
-  fun liquibase(dataSource: DataSource): SpringLiquibase {
+  fun liquibase(dataSource: DataSource, properties: TolgeeProperties): SpringLiquibase {
     val liquibase = SpringLiquibase()
 
     liquibase.dataSource = dataSource
     liquibase.changeLog = "classpath:db/changelog/schema.xml"
     liquibase.defaultSchema = "public"
-    liquibase.setChangeLogParameters(mapOf("isCockroach" to "true"))
+    if (properties.database.type == DatabaseProperties.DatabaseType.COCKROACH) {
+      liquibase.setChangeLogParameters(mapOf("isCockroach" to "true"))
+    }
     return liquibase
   }
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/DatabaseProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/DatabaseProperties.kt
new file mode 100644
index 0000000000..87347e1ce7
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/DatabaseProperties.kt
@@ -0,0 +1,12 @@
+package io.tolgee.configuration.tolgee
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties(prefix = "tolgee.database")
+class DatabaseProperties {
+  var type: DatabaseType = DatabaseType.POSTGRES
+
+  enum class DatabaseType {
+    COCKROACH, POSTGRES
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt
index 8828f21240..2491ce9ae3 100644
--- a/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/TolgeeProperties.kt
@@ -31,5 +31,6 @@ open class TolgeeProperties(
   var postgresAutostart: PostgresAutostartProperties = PostgresAutostartProperties(),
   var sendInBlueProperties: SendInBlueProperties = SendInBlueProperties(),
   open var import: ImportProperties = ImportProperties(),
-  var rateLimitProperties: RateLimitProperties = RateLimitProperties()
+  var rateLimitProperties: RateLimitProperties = RateLimitProperties(),
+  var database: DatabaseProperties = DatabaseProperties()
 )
diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml
index aa7fdeb8a9..336ce696fb 100644
--- a/backend/data/src/main/resources/db/changelog/schema.xml
+++ b/backend/data/src/main/resources/db/changelog/schema.xml
@@ -1421,15 +1421,6 @@
     <changeSet author="jenik (generated)" id="1626957302292-4">
         <addNotNullConstraint columnDataType="bigint" columnName="key_id" tableName="translation" validate="true"/>
     </changeSet>
-    <changeSet author="jenik" id="1626957302295-0">
-        <preConditions onFail="CONTINUE">
-            <changeLogPropertyDefined property="isCockroach" value="true"/>
-        </preConditions>
-        <sql>
-            set
-            enable_experimental_alter_column_type_general = true
-        </sql>
-    </changeSet>
     <changeSet author="jenik (generated)" id="1626957302295-1">
         <preConditions onFail="CONTINUE">
             <not>

From 728f12f98ab7c004f66607bf2e71da4db05d28c5 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sat, 22 Oct 2022 17:06:11 +0200
Subject: [PATCH 04/19] feat: Cockroach DB support > image vers

---
 backend/app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index d19a769b7b..87549ba7b4 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -193,7 +193,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:22.1 start-single-node --insecure"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:v22.1.9 start-single-node --insecure"
 }
 
 task stopCockroachDb(type: Exec){

From 5f638712a95b8dc32f383f5b51417fe519146cd9 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sat, 22 Oct 2022 18:44:28 +0200
Subject: [PATCH 05/19] feat: Cockroach DB support > e2e with cockroach

---
 .github/workflows/test.yml                       |  2 ++
 backend/app/build.gradle                         |  2 +-
 .../main/resources/application-cockroach.yaml    |  9 +++++++++
 .../testDataBuilder/data/TagsTestData.kt         |  6 ++++--
 .../query_builders/TranslationsViewBuilder.kt    | 16 ++++++++++++----
 e2e/docker-compose.yml                           | 10 +++++++++-
 gradle/e2e.gradle                                | 15 ++++++++++++---
 7 files changed, 49 insertions(+), 11 deletions(-)
 create mode 100644 backend/app/src/main/resources/application-cockroach.yaml

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5ec8a34449..c12a189dde 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -118,6 +118,7 @@ jobs:
       matrix:
         total_jobs: [ 10 ]
         job_index: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+        profile: ["cockroach", "default"]
     steps:
       - uses: actions/checkout@v2
 
@@ -188,6 +189,7 @@ jobs:
           SKIP_INSTALL_E2E_DEPS: true
           E2E_TOTAL_JOBS: ${{matrix.total_jobs}}
           E2E_JOB_INDEX: ${{matrix.job_index}}
+          SPRING_PROFILES_ACTIVE: ${{matrix.profile}}
 
       - uses: actions/upload-artifact@v2
         if: failure()
diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index 87549ba7b4..7228642489 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -193,7 +193,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 8086:8080 -p 26257:26257 cockroachdb/cockroach:v22.1.9 start-single-node --insecure"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.1.9 start-single-node --insecure"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/backend/app/src/main/resources/application-cockroach.yaml b/backend/app/src/main/resources/application-cockroach.yaml
new file mode 100644
index 0000000000..84f1e6ba8f
--- /dev/null
+++ b/backend/app/src/main/resources/application-cockroach.yaml
@@ -0,0 +1,9 @@
+spring:
+  datasource:
+    url: jdbc:postgresql://cockroachdb:26257/postgres
+    username: root
+tolgee:
+  database:
+    type: COCKROACH
+  postgres-autostart:
+    enabled: false
diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
index 28b6d83e3f..7768ee523e 100644
--- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
@@ -55,16 +55,18 @@ class TagsTestData : BaseTestData("tagsTestUser", "tagsTestProject") {
         }
       }
       (1..20).forEach { keyNum ->
+        val keyNumString = keyNum.toString().padEnd(2, '0')
         addKey {
-          name = "test key $keyNum"
+          name = "test key $keyNumString"
         }.build {
           addMeta {
             self {
               (1..20).forEach { tagNum ->
+                val tagNumString = tagNum.toString().padEnd(2, '0')
                 tags.add(
                   Tag().apply {
                     project = projectBuilder.self
-                    name = "tag $keyNum $tagNum"
+                    name = "tag $keyNumString $tagNumString"
                   }
                 )
               }
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/TranslationsViewBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/TranslationsViewBuilder.kt
index 5ce8b7c292..1b6bd36e6e 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/query_builders/TranslationsViewBuilder.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/query_builders/TranslationsViewBuilder.kt
@@ -1,5 +1,7 @@
 package io.tolgee.service.query_builders
 
+import io.tolgee.configuration.tolgee.DatabaseProperties
+import io.tolgee.configuration.tolgee.TolgeeProperties
 import io.tolgee.dtos.request.translation.TranslationFilterByState
 import io.tolgee.dtos.request.translation.TranslationFilters
 import io.tolgee.dtos.response.CursorValue
@@ -338,9 +340,13 @@ class TranslationsViewBuilder(
     ): Page<KeyWithTranslationsView> {
       val em = applicationContext.getBean(EntityManager::class.java)
       val tagService = applicationContext.getBean(TagService::class.java)
+      val properties = applicationContext.getBean(TolgeeProperties::class.java)
+      val isCockroachDb = properties.database.type == DatabaseProperties.DatabaseType.COCKROACH
 
-      // otherwise it takes forever for postgres to plan the execution
-      em.createNativeQuery("SET join_collapse_limit TO 1").executeUpdate()
+      if (!isCockroachDb) {
+        // otherwise it takes forever for postgres to plan the execution
+        em.createNativeQuery("SET join_collapse_limit TO 1").executeUpdate()
+      }
 
       var translationsViewBuilder = TranslationsViewBuilder(
         cb = em.criteriaBuilder,
@@ -366,8 +372,10 @@ class TranslationsViewBuilder(
       }
       val views = query.resultList.map { KeyWithTranslationsView.of(it, languages.toList()) }
 
-      // reset the value
-      em.createNativeQuery("SET join_collapse_limit TO DEFAULT").executeUpdate()
+      if (!isCockroachDb) {
+        // reset the value
+        em.createNativeQuery("SET join_collapse_limit TO DEFAULT").executeUpdate()
+      }
 
       val keyIds = views.map { it.keyId }
       tagService.getTagsForKeyIds(keyIds).let { tagMap ->
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index 702d316db8..b9c25911f9 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -7,7 +7,7 @@ services:
       - 8201:8201
       - 8091:8091
     environment:
-      - spring.profiles.active=docker,e2e
+      - spring.profiles.active=docker,e2e,${SPRING_PROFILES_ACTIVE:-default}
       - tolgee.smtp.host=fakesmtp
       - tolgee.smtp.port=1025
       - tolgee.frontend-url=http://localhost:8201
@@ -18,5 +18,13 @@ services:
     ports:
       - "21025:1025"
       - "21080:1080"
+  cockroachdb:
+    container_name: tolgee_cockroach_e2e
+    image: cockroachdb/cockroach:v22.1.9
+    ports:
+      - 26257:26257
+    command:
+      - start-single-node
+      - --insecure
 volumes:
   e2e-db-data:
diff --git a/gradle/e2e.gradle b/gradle/e2e.gradle
index fd40094d9f..20bfc8f7a4 100644
--- a/gradle/e2e.gradle
+++ b/gradle/e2e.gradle
@@ -6,6 +6,7 @@ ext {
     E2E_DIR = "${project.projectDir}/e2e"
     BILLING_E2E_DIR = "${project.projectDir}/../billing/e2e"
     WEBAPP_DIR = "${project.projectDir}/webapp"
+    IS_COCKROACH = System.getenv("SPRING_PROFILES_ACTIVE")?.contains("cockroach")
 }
 
 
@@ -80,7 +81,7 @@ task runE2e(type: Exec, group: "e2e") {
     finalizedBy "saveServerLogs", "stopDockerE2e", "cleanupDockerE2e"
 }
 
-task saveServerLogs(type: Exec, group: "e2E"){
+task saveServerLogs(type: Exec, group: "e2E") {
     commandLine "bash", "-c", "docker-compose logs > server.log"
     workingDir E2E_DIR
 }
@@ -122,13 +123,21 @@ task runWebAppNpmStartE2eDev(type: Exec, group: "e2e") {
 
 task runDockerE2e(type: Exec, group: "e2e") {
     dependsOn "tagDockerLocal"
-    commandLine "docker-compose", "up", "-d"
+    if (IS_COCKROACH) {
+        commandLine "docker-compose", "up", "-d", "fakesmtp", "cockroachdb", "app"
+    } else {
+        commandLine "docker-compose", "up", "-d", "fakesmtp", "app"
+    }
     workingDir E2E_DIR
     finalizedBy "waitForRunningContainer"
 }
 
 task runDockerE2eDev(type: Exec, group: "e2e") {
-    commandLine "docker-compose", "up", "-d", "fakesmtp"
+    if (IS_COCKROACH) {
+        commandLine "docker-compose", "up", "-d", "fakesmtp", "cockroachdb"
+    } else {
+        commandLine "docker-compose", "up", "-d", "fakesmtp"
+    }
     workingDir E2E_DIR
 }
 

From d5d4d0cc1bb136e76eb717a26858a4ab485e8768 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sun, 23 Oct 2022 18:51:19 +0200
Subject: [PATCH 06/19] feat: Cockroach DB support > test with cockroach
 22.2-beta

---
 backend/app/build.gradle | 4 +++-
 e2e/docker-compose.yml   | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index 7228642489..d85c94ad1e 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -170,9 +170,11 @@ test {
 }
 
 task testCockroach(type: Test, group: 'verification') {
+    dependsOn "startCockroachDb"
     useJUnitPlatform()
     maxHeapSize = "2048m"
     systemProperty 'SPRING_PROFILES_ACTIVE', 'cockroach'
+    finalizedBy "stopCockroachDb"
 }
 
 task runContextRecreatingTests(type: Test, group: 'verification') {
@@ -193,7 +195,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.1.9 start-single-node --insecure"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-beta.4 start-single-node --insecure"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index b9c25911f9..5ce09480db 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -20,7 +20,7 @@ services:
       - "21080:1080"
   cockroachdb:
     container_name: tolgee_cockroach_e2e
-    image: cockroachdb/cockroach:v22.1.9
+    image: cockroachdb/cockroach-unstable:v22.2.0-beta.4
     ports:
       - 26257:26257
     command:

From b1f8145ac1b31e8d79e59e692a5b8a7c0bf08068 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sun, 23 Oct 2022 21:05:51 +0200
Subject: [PATCH 07/19] feat: Cockroach DB support > fix tag tests

---
 .github/workflows/test.yml                                  | 2 +-
 .../io/tolgee/api/v2/controllers/TagsControllerTest.kt      | 6 +++---
 .../tolgee/development/testDataBuilder/data/TagsTestData.kt | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c12a189dde..a89b30828d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -105,7 +105,7 @@ jobs:
       - uses: actions/upload-artifact@v2
         if: always()
         with:
-          name: backend_test_reports_${{ steps.version.outputs.reportName }}
+          name: backend_test_reports_${{ matrix.profile }}
           path: |
             ./**/build/reports/**/*
 
diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/TagsControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/TagsControllerTest.kt
index a4eb4b26cc..fbea144c46 100644
--- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/TagsControllerTest.kt
+++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/TagsControllerTest.kt
@@ -48,8 +48,8 @@ class TagsControllerTest : ProjectAuthControllerTest("/v2/projects/") {
     performProjectAuthGet("tags?page=5").andAssertThatJson {
       node("_embedded.tags") {
         isArray.hasSize(20)
-        node("[0].name").isEqualTo("tag 14 12")
-        node("[19].name").isEqualTo("tag 15 10")
+        node("[0].name").isEqualTo("tag 05 19")
+        node("[19].name").isEqualTo("tag 06 18")
       }
     }
   }
@@ -61,7 +61,7 @@ class TagsControllerTest : ProjectAuthControllerTest("/v2/projects/") {
       node("_embedded.tags") {
         isArray.hasSize(20)
         node("[0].id").isValidId
-        node("[0].name").isEqualTo("tag 11 3")
+        node("[0].name").isEqualTo("tag 02 19")
       }
     }
   }
diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
index 7768ee523e..6258141509 100644
--- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/TagsTestData.kt
@@ -55,14 +55,14 @@ class TagsTestData : BaseTestData("tagsTestUser", "tagsTestProject") {
         }
       }
       (1..20).forEach { keyNum ->
-        val keyNumString = keyNum.toString().padEnd(2, '0')
+        val keyNumString = keyNum.toString().padStart(2, '0')
         addKey {
           name = "test key $keyNumString"
         }.build {
           addMeta {
             self {
               (1..20).forEach { tagNum ->
-                val tagNumString = tagNum.toString().padEnd(2, '0')
+                val tagNumString = tagNum.toString().padStart(2, '0')
                 tags.add(
                   Tag().apply {
                     project = projectBuilder.self

From 5405fc35f0d6410b76e2949e546255d2892ce083 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Sun, 23 Oct 2022 21:09:36 +0200
Subject: [PATCH 08/19] feat: Cockroach DB support > run cockroach in memory
 for testing

---
 backend/app/build.gradle | 2 +-
 e2e/docker-compose.yml   | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index d85c94ad1e..38d0afc7e0 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -195,7 +195,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-beta.4 start-single-node --insecure"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-beta.4 start-single-node --insecure --store=type=mem,size=0.25"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index 5ce09480db..9b52318d6d 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -26,5 +26,6 @@ services:
     command:
       - start-single-node
       - --insecure
+      - --store=type=mem,size=0.25
 volumes:
   e2e-db-data:

From ab73ffe63bc235be7948433921ef4828e4dfa2f2 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Thu, 10 Nov 2022 14:14:57 +0100
Subject: [PATCH 09/19] fix: Update CockroachDB versions

---
 backend/app/build.gradle | 2 +-
 e2e/docker-compose.yml   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index 38d0afc7e0..de893e1fdc 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -195,7 +195,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-beta.4 start-single-node --insecure --store=type=mem,size=0.25"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-rc.1 start-single-node --insecure --store=type=mem,size=0.25"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index 9b52318d6d..90055b613d 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -20,7 +20,7 @@ services:
       - "21080:1080"
   cockroachdb:
     container_name: tolgee_cockroach_e2e
-    image: cockroachdb/cockroach-unstable:v22.2.0-beta.4
+    image: cockroachdb/cockroach-unstable:v22.2.0-rc.1
     ports:
       - 26257:26257
     command:

From 171b4c5c98740094984e1b12277e66cbc910236c Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Wed, 14 Dec 2022 14:35:14 +0100
Subject: [PATCH 10/19] fix: Update cockroach db to 22.0 stable version

---
 backend/app/build.gradle | 2 +-
 e2e/docker-compose.yml   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index de893e1fdc..ebdf44e5cf 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -195,7 +195,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0-rc.1 start-single-node --insecure --store=type=mem,size=0.25"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0 start-single-node --insecure --store=type=mem,size=0.25"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index 90055b613d..adf0083630 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -20,7 +20,7 @@ services:
       - "21080:1080"
   cockroachdb:
     container_name: tolgee_cockroach_e2e
-    image: cockroachdb/cockroach-unstable:v22.2.0-rc.1
+    image: cockroachdb/cockroach-unstable:v22.2.0
     ports:
       - 26257:26257
     command:

From 9d9a4834ee42080429b07b57c0124d18860d8d2f Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Wed, 14 Dec 2022 18:16:02 +0100
Subject: [PATCH 11/19] fix: Update cockroach db to 22.0 stable version

---
 backend/app/build.gradle | 2 +-
 e2e/docker-compose.yml   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index ebdf44e5cf..64733befdf 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -195,7 +195,7 @@ task runStandardTests(type: Test, group: 'verification') {
 
 task startCockroachDb(type: Exec){
     onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach-unstable:v22.2.0 start-single-node --insecure --store=type=mem,size=0.25"
+    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.2.0 start-single-node --insecure --store=type=mem,size=0.25"
 }
 
 task stopCockroachDb(type: Exec){
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index adf0083630..f1eac1c677 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -20,7 +20,7 @@ services:
       - "21080:1080"
   cockroachdb:
     container_name: tolgee_cockroach_e2e
-    image: cockroachdb/cockroach-unstable:v22.2.0
+    image: cockroachdb/cockroach:v22.2.0
     ports:
       - 26257:26257
     command:

From 644d584cd4ed098d64b64c7797b00e845a6e5e99 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Tue, 3 Jan 2023 11:23:05 +0100
Subject: [PATCH 12/19] fix: Billing and Cockroach

---
 backend/app/build.gradle | 18 ------------------
 build.gradle             | 10 ++++++++++
 2 files changed, 10 insertions(+), 18 deletions(-)

diff --git a/backend/app/build.gradle b/backend/app/build.gradle
index 64733befdf..441e4c0922 100644
--- a/backend/app/build.gradle
+++ b/backend/app/build.gradle
@@ -169,14 +169,6 @@ test {
     maxHeapSize = "2048m"
 }
 
-task testCockroach(type: Test, group: 'verification') {
-    dependsOn "startCockroachDb"
-    useJUnitPlatform()
-    maxHeapSize = "2048m"
-    systemProperty 'SPRING_PROFILES_ACTIVE', 'cockroach'
-    finalizedBy "stopCockroachDb"
-}
-
 task runContextRecreatingTests(type: Test, group: 'verification') {
     dependsOn "startCockroachDb"
     useJUnitPlatform {
@@ -193,16 +185,6 @@ task runStandardTests(type: Test, group: 'verification') {
     finalizedBy "stopCockroachDb"
 }
 
-task startCockroachDb(type: Exec){
-    onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.2.0 start-single-node --insecure --store=type=mem,size=0.25"
-}
-
-task stopCockroachDb(type: Exec){
-    onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-    commandLine "bash", "-c", "docker stop roach"
-}
-
 springBoot {
     buildInfo {
         properties {
diff --git a/build.gradle b/build.gradle
index a27640244c..c61102ce32 100644
--- a/build.gradle
+++ b/build.gradle
@@ -147,4 +147,14 @@ task ktlintFormat(type: JavaExec, group: "formatting") {
 
 subprojects {
     task allDeps(type: DependencyReportTask) {}
+
+    task startCockroachDb(type: Exec){
+        onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
+        commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.2.1 start-single-node --insecure --store=type=mem,size=0.25"
+    }
+
+    task stopCockroachDb(type: Exec){
+        onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
+        commandLine "bash", "-c", "docker stop roach"
+    }
 }

From 04cdb60687a9d5dbe0959d7db27636db152616f4 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Tue, 3 Jan 2023 15:54:59 +0100
Subject: [PATCH 13/19] fix: Billing and Cockroach > Fix deadlock

---
 .../src/main/kotlin/io/tolgee/Application.kt  |  2 ++
 .../api/v2/controllers/V2ImportController.kt  |  2 +-
 .../resources/application-devcockroach.yaml   |  3 ++
 .../testDataBuilder/TestDataService.kt        |  2 ++
 .../io/tolgee/service/LanguageService.kt      |  3 ++
 .../kotlin/io/tolgee/util/transactionUtil.kt  |  1 +
 .../io/tolgee/util/withTimeoutAndRetry.kt     | 28 +++++++++++++++++++
 build.gradle                                  |  2 +-
 8 files changed, 41 insertions(+), 2 deletions(-)
 create mode 100644 backend/app/src/main/resources/application-devcockroach.yaml
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/util/withTimeoutAndRetry.kt

diff --git a/backend/app/src/main/kotlin/io/tolgee/Application.kt b/backend/app/src/main/kotlin/io/tolgee/Application.kt
index 53f90bbeb3..a8e8cd3013 100644
--- a/backend/app/src/main/kotlin/io/tolgee/Application.kt
+++ b/backend/app/src/main/kotlin/io/tolgee/Application.kt
@@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration
 import org.springframework.boot.context.properties.ConfigurationPropertiesScan
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories
+import org.springframework.retry.annotation.EnableRetry
 
 @SpringBootApplication(
   exclude = [
@@ -43,6 +44,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories
 @EntityScan("io.tolgee.model")
 @ConfigurationPropertiesScan
 @EnableJpaRepositories("io.tolgee.repository")
+@EnableRetry
 class Application {
   companion object {
     @JvmStatic
diff --git a/backend/app/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt b/backend/app/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt
index e0b8165893..50eddaeade 100644
--- a/backend/app/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt
+++ b/backend/app/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt
@@ -350,7 +350,7 @@ class V2ImportController(
   }
 
   private fun checkLanguageFromProject(languageId: Long): Language {
-    val existingLanguage = languageService.findById(languageId).orElse(null) ?: throw NotFoundException()
+    val existingLanguage = languageService.find(languageId) ?: throw NotFoundException()
     if (existingLanguage.project.id != projectHolder.project.id) {
       throw BadRequestException(io.tolgee.constants.Message.IMPORT_LANGUAGE_NOT_FROM_PROJECT)
     }
diff --git a/backend/app/src/main/resources/application-devcockroach.yaml b/backend/app/src/main/resources/application-devcockroach.yaml
new file mode 100644
index 0000000000..0ca0de1d63
--- /dev/null
+++ b/backend/app/src/main/resources/application-devcockroach.yaml
@@ -0,0 +1,3 @@
+spring:
+  datasource:
+    url: jdbc:postgresql://localhost:26257/postgres
diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
index 98e844cf66..a530b752c1 100644
--- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
@@ -256,6 +256,8 @@ class TestDataService(
     projectBuilders.forEach { projectBuilder ->
       executeInNewTransaction(transactionManager) {
         projectService.save(projectBuilder.self)
+      }
+      executeInNewTransaction(transactionManager) {
         saveAllProjectDependants(projectBuilder)
       }
     }
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt b/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt
index cabe60ca0b..ecdaf174d2 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt
@@ -16,6 +16,7 @@ import org.springframework.data.domain.Page
 import org.springframework.data.domain.PageRequest
 import org.springframework.data.domain.Pageable
 import org.springframework.data.domain.Sort
+import org.springframework.retry.annotation.Retryable
 import org.springframework.stereotype.Service
 import org.springframework.transaction.annotation.Transactional
 import java.util.*
@@ -78,6 +79,8 @@ class LanguageService(
     return find(id) ?: throw NotFoundException(Message.LANGUAGE_NOT_FOUND)
   }
 
+  @Transactional
+  @Retryable()
   fun find(id: Long): Language? {
     return languageRepository.findById(id).orElse(null)
   }
diff --git a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
index a581438d07..007c8aea94 100644
--- a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
@@ -7,6 +7,7 @@ import org.springframework.transaction.support.TransactionTemplate
 fun <T> executeInNewTransaction(transactionManager: PlatformTransactionManager, fn: () -> T): T {
   val tt = TransactionTemplate(transactionManager)
   tt.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
+  tt.isolationLevel = TransactionDefinition.ISOLATION_SERIALIZABLE
 
   return tt.execute {
     fn()
diff --git a/backend/data/src/main/kotlin/io/tolgee/util/withTimeoutAndRetry.kt b/backend/data/src/main/kotlin/io/tolgee/util/withTimeoutAndRetry.kt
new file mode 100644
index 0000000000..6219636842
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/util/withTimeoutAndRetry.kt
@@ -0,0 +1,28 @@
+package io.tolgee.util
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.async
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+
+fun <T> withTimeoutAndRetry(timeoutInMs: Long = 4000, fn: () -> T): T {
+  return runBlocking(Dispatchers.IO) {
+    val retries = 3
+    var exp: TimeoutCancellationException? = null
+    (0..retries).forEach {
+      val task = async { fn() }
+      try {
+        return@runBlocking withTimeout(timeoutInMs) {
+          task.await()
+        }
+      } catch (e: TimeoutCancellationException) {
+        exp = e
+      }
+    }
+    exp?.let {
+      throw it
+    }
+    throw IllegalStateException("No exception caught!")
+  }
+}
diff --git a/build.gradle b/build.gradle
index c61102ce32..a6c776cc54 100644
--- a/build.gradle
+++ b/build.gradle
@@ -150,7 +150,7 @@ subprojects {
 
     task startCockroachDb(type: Exec){
         onlyIf { System.getenv("SPRING_PROFILES_ACTIVE") == "cockroach" }
-        commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 cockroachdb/cockroach:v22.2.1 start-single-node --insecure --store=type=mem,size=0.25"
+        commandLine "bash", "-c", "docker run --rm -d --name=roach -p 26257:26257 -p 8089:8080 cockroachdb/cockroach:v22.2.1 start-single-node --insecure --store=type=mem,size=0.25"
     }
 
     task stopCockroachDb(type: Exec){

From 429e487028e1bfb7e83dd8c9728862cd42b3d8fa Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Wed, 4 Jan 2023 10:48:42 +0100
Subject: [PATCH 14/19] fix: Billing and Cockroach > transactionUtil

---
 backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
index 007c8aea94..a581438d07 100644
--- a/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/util/transactionUtil.kt
@@ -7,7 +7,6 @@ import org.springframework.transaction.support.TransactionTemplate
 fun <T> executeInNewTransaction(transactionManager: PlatformTransactionManager, fn: () -> T): T {
   val tt = TransactionTemplate(transactionManager)
   tt.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
-  tt.isolationLevel = TransactionDefinition.ISOLATION_SERIALIZABLE
 
   return tt.execute {
     fn()

From 866f2af5619d6ccbd86d0a4317db9b74b4886912 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Wed, 4 Jan 2023 14:22:30 +0100
Subject: [PATCH 15/19] fix: Cockroach > Fix Auto translation test

---
 .../io/tolgee/development/testDataBuilder/TestDataService.kt    | 2 --
 1 file changed, 2 deletions(-)

diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
index a530b752c1..98e844cf66 100644
--- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt
@@ -256,8 +256,6 @@ class TestDataService(
     projectBuilders.forEach { projectBuilder ->
       executeInNewTransaction(transactionManager) {
         projectService.save(projectBuilder.self)
-      }
-      executeInNewTransaction(transactionManager) {
         saveAllProjectDependants(projectBuilder)
       }
     }

From e669e058556ba0e2a4c3a843ef3461c7a40976cc Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Wed, 4 Jan 2023 18:01:27 +0100
Subject: [PATCH 16/19] fix: Cockroach > Add entity listening using
 interceptors, no new transaction in listener

---
 .../iterceptor/ActivityInterceptor.kt         |  6 +++++
 .../iterceptor/PreCommitEventPublisher.kt     | 22 ++++++++++++++++
 .../io/tolgee/events/OnEntityPreDelete.kt     |  9 +++++++
 .../io/tolgee/events/OnEntityPrePersist.kt    |  9 +++++++
 .../io/tolgee/events/OnEntityPreUpdate.kt     |  9 +++++++
 .../service/OrganizationStatsService.kt       | 26 ++++++++++++++-----
 .../io/tolgee/service/ScreenshotService.kt    | 26 ++++++++++++++-----
 .../tolgee/service/project/ProjectService.kt  |  1 +
 e2e/docker-compose.yml                        |  1 +
 9 files changed, 95 insertions(+), 14 deletions(-)
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreDelete.kt
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/events/OnEntityPrePersist.kt
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt

diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityInterceptor.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityInterceptor.kt
index 5cdf98d831..cc412b6813 100644
--- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityInterceptor.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityInterceptor.kt
@@ -26,6 +26,7 @@ class ActivityInterceptor : EmptyInterceptor() {
     propertyNames: Array<out String>?,
     types: Array<out Type>?
   ): Boolean {
+    preCommitEventsPublisher.onPersist(entity)
     interceptedEventsManager.onFieldModificationsActivity(
       entity, state, null, propertyNames, RevisionType.ADD
     )
@@ -39,6 +40,7 @@ class ActivityInterceptor : EmptyInterceptor() {
     propertyNames: Array<out String>?,
     types: Array<out Type>?
   ) {
+    preCommitEventsPublisher.onDelete(entity)
     interceptedEventsManager.onFieldModificationsActivity(
       entity, null, state, propertyNames, RevisionType.DEL
     )
@@ -52,6 +54,7 @@ class ActivityInterceptor : EmptyInterceptor() {
     propertyNames: Array<out String>?,
     types: Array<out Type>?
   ): Boolean {
+    preCommitEventsPublisher.onUpdate(entity)
     interceptedEventsManager.onFieldModificationsActivity(
       entity,
       currentState,
@@ -76,4 +79,7 @@ class ActivityInterceptor : EmptyInterceptor() {
 
   val interceptedEventsManager: InterceptedEventsManager
     get() = applicationContext.getBean(InterceptedEventsManager::class.java)
+
+  val preCommitEventsPublisher: PreCommitEventPublisher
+    get() = applicationContext.getBean(PreCommitEventPublisher::class.java)
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
new file mode 100644
index 0000000000..80319fd533
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
@@ -0,0 +1,22 @@
+package io.tolgee.activity.iterceptor
+
+import io.tolgee.events.OnEntityPreDelete
+import io.tolgee.events.OnEntityPreUpdate
+import org.springframework.context.ApplicationContext
+import org.springframework.stereotype.Component
+
+@Component
+class PreCommitEventPublisher(private val applicationContext: ApplicationContext) {
+
+  fun onPersist(entity: Any?) {
+    applicationContext.publishEvent(OnEntityPreDelete(this, entity))
+  }
+
+  fun onUpdate(entity: Any?) {
+    applicationContext.publishEvent(OnEntityPreUpdate(this, entity))
+  }
+
+  fun onDelete(entity: Any?) {
+    applicationContext.publishEvent(OnEntityPreDelete(this, entity))
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreDelete.kt b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreDelete.kt
new file mode 100644
index 0000000000..3a6186476e
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreDelete.kt
@@ -0,0 +1,9 @@
+package io.tolgee.events
+
+import io.tolgee.activity.iterceptor.PreCommitEventPublisher
+import org.springframework.context.ApplicationEvent
+
+class OnEntityPreDelete(
+  val source: PreCommitEventPublisher,
+  val entity: Any?
+) : ApplicationEvent(source)
diff --git a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPrePersist.kt b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPrePersist.kt
new file mode 100644
index 0000000000..bece6b98a1
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPrePersist.kt
@@ -0,0 +1,9 @@
+package io.tolgee.events
+
+import io.tolgee.activity.iterceptor.PreCommitEventPublisher
+import org.springframework.context.ApplicationEvent
+
+class OnEntityPrePersist(
+  val source: PreCommitEventPublisher,
+  val entity: Any?
+) : ApplicationEvent(source)
diff --git a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt
new file mode 100644
index 0000000000..3632df6bf7
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt
@@ -0,0 +1,9 @@
+package io.tolgee.events
+
+import io.tolgee.activity.iterceptor.PreCommitEventPublisher
+import org.springframework.context.ApplicationEvent
+
+class OnEntityPreUpdate(
+  val source: PreCommitEventPublisher,
+  val entity: Any?
+) : ApplicationEvent(source)
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/OrganizationStatsService.kt b/backend/data/src/main/kotlin/io/tolgee/service/OrganizationStatsService.kt
index fba08a8857..47456dcc7d 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/OrganizationStatsService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/OrganizationStatsService.kt
@@ -1,12 +1,15 @@
 package io.tolgee.service
 
+import org.hibernate.FlushMode
+import org.hibernate.Session
 import org.springframework.stereotype.Service
 import java.math.BigDecimal
 import javax.persistence.EntityManager
+import javax.persistence.FlushModeType
 
 @Service
 class OrganizationStatsService(
-  private val entityManager: EntityManager
+  private val entityManager: EntityManager,
 ) {
   fun getProjectLanguageCount(projectId: Long): Long {
     return entityManager
@@ -33,9 +36,13 @@ class OrganizationStatsService(
   }
 
   fun getCurrentTranslationCount(organizationId: Long): Long {
-    val result = entityManager.createNativeQuery(
-      """
-      select 
+    val session = entityManager.unwrap(Session::class.java)
+    return try {
+      session.hibernateFlushMode = FlushMode.MANUAL
+      session.flushMode = FlushModeType.COMMIT
+      val query = session.createNativeQuery(
+        """
+      select
          (select sum(keyCount * languageCount) as translationCount
           from (select p.id as projectId, count(l.id) as languageCount
                 from project as p
@@ -47,8 +54,13 @@ class OrganizationStatsService(
                                   join key as k on k.project_id = p.id
                          where p.organization_owner_id = :organizationId
                          group by p.id) as keyCounts on keyCounts.projectId = languageCounts.projectId)
-      """.trimIndent()
-    ).setParameter("organizationId", organizationId).singleResult as BigDecimal? ?: 0
-    return result.toLong()
+        """.trimIndent()
+      ).setParameter("organizationId", organizationId)
+      val result = query.singleResult as BigDecimal? ?: 0
+      result.toLong()
+    } finally {
+      session.hibernateFlushMode = FlushMode.AUTO
+      session.flushMode = FlushModeType.AUTO
+    }
   }
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt b/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
index c362074e59..a0b039aab7 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
@@ -15,8 +15,11 @@ import io.tolgee.repository.ScreenshotRepository
 import io.tolgee.security.AuthenticationFacade
 import io.tolgee.service.ImageUploadService.Companion.UPLOADED_IMAGES_STORAGE_FOLDER_NAME
 import io.tolgee.util.ImageConverter
+import io.tolgee.util.executeInNewTransaction
 import org.springframework.core.io.InputStreamSource
+import org.springframework.retry.annotation.Retryable
 import org.springframework.stereotype.Service
+import org.springframework.transaction.PlatformTransactionManager
 import org.springframework.transaction.annotation.Transactional
 
 @Service
@@ -25,13 +28,13 @@ class ScreenshotService(
   private val fileStorage: FileStorage,
   private val tolgeeProperties: TolgeeProperties,
   private val imageUploadService: ImageUploadService,
-  private val authenticationFacade: AuthenticationFacade
+  private val authenticationFacade: AuthenticationFacade,
+  private val transactionManager: PlatformTransactionManager
 ) {
   companion object {
     const val SCREENSHOTS_STORAGE_FOLDER_NAME = "screenshots"
   }
 
-  @Transactional
   fun store(screenshotImage: InputStreamSource, key: Key): Screenshot {
     if (getScreenshotsCountForKey(key) >= tolgeeProperties.maxScreenshotsPerKey) {
       throw BadRequestException(
@@ -45,16 +48,25 @@ class ScreenshotService(
     return storeProcessed(image.toByteArray(), thumbnail.toByteArray(), key)
   }
 
+  @Retryable
   fun storeProcessed(image: ByteArray, thumbnail: ByteArray, key: Key): Screenshot {
     val screenshotEntity = Screenshot().also {
       it.key = key
       it.extension = "png"
     }
-    key.screenshots.add(screenshotEntity)
-    screenshotRepository.save(screenshotEntity)
-    fileStorage.storeFile(screenshotEntity.getThumbnailPath(), thumbnail)
-    fileStorage.storeFile(screenshotEntity.getFilePath(), image)
-    return screenshotEntity
+    try {
+      return executeInNewTransaction(transactionManager) {
+        key.screenshots.add(screenshotEntity)
+        screenshotRepository.save(screenshotEntity)
+        fileStorage.storeFile(screenshotEntity.getThumbnailPath(), thumbnail)
+        fileStorage.storeFile(screenshotEntity.getFilePath(), image)
+        screenshotEntity
+      }
+    } catch (e: Exception) {
+      fileStorage.deleteFile(screenshotEntity.getThumbnailPath())
+      fileStorage.deleteFile(screenshotEntity.getFilePath())
+      throw e
+    }
   }
 
   @Transactional
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt
index 62ec8779ae..69fb6a1885 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt
@@ -311,6 +311,7 @@ class ProjectService constructor(
     projectRepository.saveAll(projects)
 
   @CacheEvict(cacheNames = [Caches.PROJECTS], key = "#result.id")
+  @Transactional
   fun save(project: Project): Project {
     val isCreating = project.id == 0L
     projectRepository.save(project)
diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml
index f1eac1c677..8e7466cd03 100644
--- a/e2e/docker-compose.yml
+++ b/e2e/docker-compose.yml
@@ -23,6 +23,7 @@ services:
     image: cockroachdb/cockroach:v22.2.0
     ports:
       - 26257:26257
+      - 8088:8080
     command:
       - start-single-node
       - --insecure

From a2ba36d2e1df5e8d7b4617248070d18ed783855e Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Thu, 5 Jan 2023 09:18:54 +0100
Subject: [PATCH 17/19] fix: Cockroach > TestListener

---
 .../service/CockroachStatsTestListener.kt     | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt

diff --git a/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt b/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt
new file mode 100644
index 0000000000..3175a9054d
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt
@@ -0,0 +1,19 @@
+package io.tolgee.service
+
+import io.tolgee.events.OnEntityPrePersist
+import io.tolgee.model.Language
+import org.springframework.context.event.EventListener
+import org.springframework.stereotype.Service
+
+@Service
+class CockroachStatsTestListener(
+  private val organizationStatsService: OrganizationStatsService
+) {
+
+  @EventListener
+  fun onLanguageAdd(event: OnEntityPrePersist) {
+    (event.entity as? Language)?.let {
+      organizationStatsService.getCurrentTranslationCount(event.entity.project.organizationOwner.id)
+    }
+  }
+}

From 03fd50cddd31d03256c8c7f74989cdf9ed5fc536 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Thu, 5 Jan 2023 09:18:54 +0100
Subject: [PATCH 18/19] fix: Cockroach > TestListener

---
 .../io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt   | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
index 80319fd533..5d36d5e5d5 100644
--- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt
@@ -1,6 +1,7 @@
 package io.tolgee.activity.iterceptor
 
 import io.tolgee.events.OnEntityPreDelete
+import io.tolgee.events.OnEntityPrePersist
 import io.tolgee.events.OnEntityPreUpdate
 import org.springframework.context.ApplicationContext
 import org.springframework.stereotype.Component
@@ -9,7 +10,7 @@ import org.springframework.stereotype.Component
 class PreCommitEventPublisher(private val applicationContext: ApplicationContext) {
 
   fun onPersist(entity: Any?) {
-    applicationContext.publishEvent(OnEntityPreDelete(this, entity))
+    applicationContext.publishEvent(OnEntityPrePersist(this, entity))
   }
 
   fun onUpdate(entity: Any?) {

From adbb2cf1884b4cc1b7b05410a1a2574a890f1f82 Mon Sep 17 00:00:00 2001
From: Jan Cizmar <cizmar@chlupac.com>
Date: Thu, 5 Jan 2023 11:02:49 +0100
Subject: [PATCH 19/19] fix: Cockroach > Fix tests

---
 .../service/CockroachStatsTestListener.kt     | 19 ----------
 .../io/tolgee/service/ScreenshotService.kt    | 38 +++++++++++--------
 2 files changed, 23 insertions(+), 34 deletions(-)
 delete mode 100644 backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt

diff --git a/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt b/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt
deleted file mode 100644
index 3175a9054d..0000000000
--- a/backend/data/src/main/kotlin/io/tolgee/service/CockroachStatsTestListener.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.tolgee.service
-
-import io.tolgee.events.OnEntityPrePersist
-import io.tolgee.model.Language
-import org.springframework.context.event.EventListener
-import org.springframework.stereotype.Service
-
-@Service
-class CockroachStatsTestListener(
-  private val organizationStatsService: OrganizationStatsService
-) {
-
-  @EventListener
-  fun onLanguageAdd(event: OnEntityPrePersist) {
-    (event.entity as? Language)?.let {
-      organizationStatsService.getCurrentTranslationCount(event.entity.project.organizationOwner.id)
-    }
-  }
-}
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt b/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
index a0b039aab7..c11ff65f61 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/ScreenshotService.kt
@@ -35,6 +35,11 @@ class ScreenshotService(
     const val SCREENSHOTS_STORAGE_FOLDER_NAME = "screenshots"
   }
 
+  /**
+   * CockroachDB has issues with uploading multiple screenshots in the same time, so we
+   * need to make it retryable and executed in new transaction
+   */
+  @Retryable
   fun store(screenshotImage: InputStreamSource, key: Key): Screenshot {
     if (getScreenshotsCountForKey(key) >= tolgeeProperties.maxScreenshotsPerKey) {
       throw BadRequestException(
@@ -45,28 +50,31 @@ class ScreenshotService(
     val converter = ImageConverter(screenshotImage.inputStream)
     val image = converter.getImage()
     val thumbnail = converter.getThumbNail()
-    return storeProcessed(image.toByteArray(), thumbnail.toByteArray(), key)
+    var screenshotEntity: Screenshot? = null
+    try {
+      return executeInNewTransaction(transactionManager) {
+        screenshotEntity = storeProcessed(image.toByteArray(), thumbnail.toByteArray(), key)
+        screenshotEntity!!
+      }
+    } catch (e: Exception) {
+      screenshotEntity?.id?.let {
+        fileStorage.deleteFile(screenshotEntity!!.getThumbnailPath())
+        fileStorage.deleteFile(screenshotEntity!!.getFilePath())
+      }
+      throw e
+    }
   }
 
-  @Retryable
   fun storeProcessed(image: ByteArray, thumbnail: ByteArray, key: Key): Screenshot {
     val screenshotEntity = Screenshot().also {
       it.key = key
       it.extension = "png"
     }
-    try {
-      return executeInNewTransaction(transactionManager) {
-        key.screenshots.add(screenshotEntity)
-        screenshotRepository.save(screenshotEntity)
-        fileStorage.storeFile(screenshotEntity.getThumbnailPath(), thumbnail)
-        fileStorage.storeFile(screenshotEntity.getFilePath(), image)
-        screenshotEntity
-      }
-    } catch (e: Exception) {
-      fileStorage.deleteFile(screenshotEntity.getThumbnailPath())
-      fileStorage.deleteFile(screenshotEntity.getFilePath())
-      throw e
-    }
+    key.screenshots.add(screenshotEntity)
+    screenshotRepository.save(screenshotEntity)
+    fileStorage.storeFile(screenshotEntity.getThumbnailPath(), thumbnail)
+    fileStorage.storeFile(screenshotEntity.getFilePath(), image)
+    return screenshotEntity
   }
 
   @Transactional