diff --git a/.circleci/config.yml b/.circleci/config.yml index ca92106537..9ee02652e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,7 +51,6 @@ commands: command: | if [ ! -d node_modules ]; then npm install - npm install oracledb fi - save_cache: name: Save node_modules cache @@ -126,6 +125,9 @@ jobs: - run: name: Wait for Services to be Available command: | + DEFAULT_WAIT_TIME=60 + ORACLE_WAIT_TIME=120 + COMMANDS=$( cat ormconfig.json \ | jq -r ' @@ -139,31 +141,15 @@ jobs: ) echo "Running '$COMMANDS'" + WAIT_TIME=$([ ! -z "$(jq -r '.[] | select(.skip == false and .name == "oracle")' ormconfig.json)" ] && echo "$ORACLE_WAIT_TIME" || echo "$DEFAULT_WAIT_TIME") + if [ ! -z "$COMMANDS" ]; then docker run \ --network typeorm_default \ --tty \ ubuntu:trusty \ - timeout 60 sh -c "until ($COMMANDS); do echo \"Waiting for Services to be Available ...\"; sleep 5; done" + timeout $WAIT_TIME sh -c "until ($COMMANDS); do echo \"Waiting for Services to be Available ...\"; sleep 5; done" fi - - run: - name: "Wait for OracleDB to be Available" - command: | - COMMANDS=$( - cat ormconfig.json \ - | jq -r ' - map(select(.skip == false) - | select(.name == "oracle") - | "sleep 60" - ) - | join(" && ") - ' - ) - if [ ! -z "$COMMANDS" ]; then - echo "$COMMANDS seconds to wait for oracledb"; - $COMMANDS - fi - # Download and cache dependencies - run: name: "Run Tests with Coverage" diff --git a/DEVELOPER.md b/DEVELOPER.md index d79cf04e34..da83248ce9 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -57,10 +57,6 @@ Install all TypeORM dependencies by running this command: npm install ``` -During installation, you may have some problems with some dependencies. -For example to properly install oracle driver you need to follow all instructions from - [node-oracle documentation](https://github.com/oracle/node-oracledb). - ## ORM config To create an initial `ormconfig.json` file, run the following command: @@ -174,41 +170,3 @@ in the root of the project. Once all images are fetched and run you can run test - The docker image of mssql-server needs at least 3.25GB of RAM. - Make sure to assign enough memory to the Docker VM if you're running on Docker for Mac or Windows - -### Oracle XE - -In order to run tests on Oracle XE locally, we need to start 2 docker containers: - -- a container with Oracle XE database -- a container with typeorm and its tests - -#### 1. Booting Oracle XE database - -Execute in shell the next command: - -```shell -docker-compose up -d oracle -``` - -It will start an oracle instance only. -The instance will be run in background, -therefore, we need to stop it later on. - -#### 2. Booting typeorm for Oracle - -Execute in shell the next command: - -```shell -docker-compose -f docker-compose.oracle.yml up -``` - -it will start a nodejs instance which builds typeorm and executes unit tests. -The instance exits after the run. - -#### 3. Shutting down Oracle XE database - -Execute in shell the next command: - -```shell -docker-compose down -``` diff --git a/docker-compose.oracle.yml b/docker-compose.oracle.yml deleted file mode 100644 index f538d08cd8..0000000000 --- a/docker-compose.oracle.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3" - -services: - oracle-test: - build: - context: docker/oracle - volumes: - - .:/typeorm - - /typeorm/build - - /typeorm/node_modules - networks: - - typeorm - -networks: - typeorm: diff --git a/docker-compose.yml b/docker-compose.yml index f36474343e..bbd2fa9aac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,14 +57,18 @@ services: ports: - "26257:26257" + # oracle oracle: - image: imnotjames/oracle-xe:18 + build: + context: docker/oracle container_name: "typeorm-oracle" ports: - "1521:1521" - networks: - - default - - typeorm + #volumes: + # - oracle-data:/opt/oracle/oradata + healthcheck: + test: [ "CMD", "/opt/oracle/checkDBStatus.sh" ] + interval: 2s # google cloud spanner spanner: @@ -118,6 +122,3 @@ services: #volumes: # volume-hana-xe: # mysql8_volume: - -networks: - typeorm: diff --git a/docker/oracle/01_init.sql b/docker/oracle/01_init.sql new file mode 100644 index 0000000000..06c4357b17 --- /dev/null +++ b/docker/oracle/01_init.sql @@ -0,0 +1,22 @@ +ALTER SESSION SET CONTAINER = XEPDB1; + +CREATE TABLESPACE typeormspace32 + DATAFILE 'typeormspace32.dbf' + SIZE 100M + AUTOEXTEND ON; + +-- create users: +CREATE USER typeorm IDENTIFIED BY "oracle" DEFAULT TABLESPACE typeormspace32; + +GRANT CREATE SESSION TO typeorm; +GRANT CREATE TABLE TO typeorm; +GRANT CREATE VIEW TO typeorm; +GRANT CREATE MATERIALIZED VIEW TO typeorm; +GRANT CREATE PROCEDURE TO typeorm; +GRANT CREATE SEQUENCE TO typeorm; + +ALTER USER typeorm QUOTA UNLIMITED ON typeormspace32; + +-- set password expiry to unlimited +ALTER PROFILE DEFAULT LIMIT PASSWORD_REUSE_TIME UNLIMITED; +ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; diff --git a/docker/oracle/Dockerfile b/docker/oracle/Dockerfile index 104a739fdb..7b85195ff2 100644 --- a/docker/oracle/Dockerfile +++ b/docker/oracle/Dockerfile @@ -1,18 +1,8 @@ -FROM node:12 +FROM container-registry.oracle.com/database/express:21.3.0-xe -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get -qq -y install libaio1 && \ - apt-get -q -y autoremove && \ - rm -Rf /var/lib/apt/lists/* +ENV ORACLE_PWD=oracle +ENV ORACLE_SID=XE +COPY 01_init.sql /docker-entrypoint-initdb.d/startup/ +ENV PORT=1521 -WORKDIR /typeorm -ENTRYPOINT ["/docker-entrypoint.sh"] - -COPY . / -RUN chmod 0755 /docker-entrypoint.sh - -ENV PATH="$PATH:/typeorm/node_modules/.bin" -ENV LD_LIBRARY_PATH="/typeorm/node_modules/oracledb/instantclient_19_8/:$LD_LIBRARY_PATH" -ENV BLOB_URL="https://download.oracle.com/otn_software/linux/instantclient/19800/instantclient-basiclite-linux.x64-19.8.0.0.0dbru.zip" - -CMD ["npm", "run", "test-fast"] +EXPOSE ${PORT} diff --git a/docker/oracle/config/ormconfig.json b/docker/oracle/config/ormconfig.json deleted file mode 100644 index 95752587bc..0000000000 --- a/docker/oracle/config/ormconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "skip": false, - "name": "oracle", - "type": "oracle", - "host": "typeorm-oracle", - "username": "system", - "password": "oracle", - "port": 1521, - "sid": "XE", - "logging": false, - "extra": { - "connectString": "typeorm-oracle:1521/XE" - } - } -] diff --git a/docker/oracle/docker-entrypoint.d/030-npm-install.sh b/docker/oracle/docker-entrypoint.d/030-npm-install.sh deleted file mode 100755 index d067948857..0000000000 --- a/docker/oracle/docker-entrypoint.d/030-npm-install.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# exit when any command fails -set -e - -if [[ $INSTALL == 0 ]] && [[ ! -f ./package.json ]]; then - exit 0 -fi - -INSTALL=0 -if [[ $INSTALL == 0 ]] && [[ "$(ls ./node_modules/ | wc -l | tr -d '\n')" == '0' ]]; then - INSTALL=1 -fi -if [[ $INSTALL == 0 ]] && [[ ! -f ./node_modules/.md5 ]]; then - INSTALL=1 -fi -if [[ $INSTALL == 0 ]] && ! md5sum --check ./node_modules/.md5; then - INSTALL=1 -fi - -if [[ $INSTALL == 1 ]]; then - npm ci --no-optional --ignore-scripts - md5sum ./package-lock.json > ./node_modules/.md5 -fi diff --git a/docker/oracle/docker-entrypoint.d/040-instantclient.sh b/docker/oracle/docker-entrypoint.d/040-instantclient.sh deleted file mode 100755 index b1cdf10073..0000000000 --- a/docker/oracle/docker-entrypoint.d/040-instantclient.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -# exit when any command fails -set -e - -if [ ! -d node_modules/oracledb/instantclient_19_8 ]; then - curl -sf -o node_modules/oracledb/instantclient.zip $BLOB_URL - unzip -qqo node_modules/oracledb/instantclient.zip -d node_modules/oracledb/ - rm node_modules/oracledb/instantclient.zip - cp /lib/*/libaio.so.* node_modules/oracledb/instantclient_19_8/ -fi - diff --git a/docker/oracle/docker-entrypoint.d/050-npm-compile.sh b/docker/oracle/docker-entrypoint.d/050-npm-compile.sh deleted file mode 100755 index f9b541cf00..0000000000 --- a/docker/oracle/docker-entrypoint.d/050-npm-compile.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -# exit when any command fails -set -e - -npx rimraf build/compiled -npx tsc -cp /config/ormconfig.json build/compiled/ormconfig.json - -if [ ! -f ormconfig.json ]; then - cp ormconfig.json.dist ormconfig.json -fi diff --git a/docker/oracle/docker-entrypoint.sh b/docker/oracle/docker-entrypoint.sh deleted file mode 100755 index 942a12fa92..0000000000 --- a/docker/oracle/docker-entrypoint.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -e - -child_pid=0 -parent_pid=$$ - -catch_exits() { - echo "${0}:stopping ${child_pid}" - kill ${child_pid} & - wait - echo "${0}:stopped ${child_pid}" - - echo "${0}:exit" - exit 0 -} -trap catch_exits TERM KILL INT SIGTERM SIGINT SIGKILL - -fork() { - printf "'%s' " "${@}" | xargs -d "\n" -t sh -c -} - -if [[ ! "${ENTRYPOINT_SKIP}" ]]; then -for file in `ls -v /docker-entrypoint.d/*.sh` - do - echo "${file}:starting" - fork ${file} & - child_pid=$! - echo "${file}:pid ${child_pid}" - wait ${child_pid} - echo "${file}:stopped ${child_pid}" - if [[ "${?}" != "0" ]]; then - exit 1; - fi - done -fi - -echo "${0}:starting" -fork "${@}" & -child_pid=$! -echo "${0}:pid ${child_pid}" -wait ${child_pid} -echo "${0}:stopped ${child_pid}" diff --git a/ormconfig.circleci-common.json b/ormconfig.circleci-common.json index f459d5ca58..89f6b7645c 100644 --- a/ormconfig.circleci-common.json +++ b/ormconfig.circleci-common.json @@ -115,12 +115,12 @@ "type": "oracle", "host": "typeorm-oracle", "port": 1521, - "sid": "XE", - "username": "system", + "serviceName": "XEPDB1", + "username": "typeorm", "password": "oracle", "logging": false, "extra": { - "connectString": "typeorm-oracle:1521/XE" + "connectString": "typeorm-oracle:1521/XEPDB1" } } ] diff --git a/ormconfig.json.dist b/ormconfig.json.dist index 9f752dfa0f..41a97c6a9b 100644 --- a/ormconfig.json.dist +++ b/ormconfig.json.dist @@ -69,14 +69,14 @@ } }, { - "skip": true, + "skip": false, "name": "oracle", "type": "oracle", "host": "localhost", - "username": "system", + "username": "typeorm", "password": "oracle", "port": 1521, - "sid": "xe.oracle.docker", + "serviceName": "XEPDB1", "logging": false }, { diff --git a/package-lock.json b/package-lock.json index 616fb8c3fb..3ab1121b01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10647,6 +10647,17 @@ "node": ">= 0.8.0" } }, + "node_modules/oracledb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.3.0.tgz", + "integrity": "sha512-fr3U66QxgGXb5cs/ozLBQU50TMbZcBQEWvSaj2rJAXG8KRrsZcGOK8JTlZL1yJHeW8cSjOm6n/wTw3SJksGjDg==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=14.6" + } + }, "node_modules/ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", diff --git a/src/driver/oracle/OracleDriver.ts b/src/driver/oracle/OracleDriver.ts index 22b338fae9..0d24e68112 100644 --- a/src/driver/oracle/OracleDriver.ts +++ b/src/driver/oracle/OracleDriver.ts @@ -128,6 +128,8 @@ export class OracleDriver implements Driver { "nclob", "rowid", "urowid", + "simple-json", + "json", ] /** @@ -299,8 +301,8 @@ export class OracleDriver implements Driver { * either create a pool and create connection when needed. */ async connect(): Promise { - this.oracle.fetchAsString = [this.oracle.CLOB] - this.oracle.fetchAsBuffer = [this.oracle.BLOB] + this.oracle.fetchAsString = [this.oracle.DB_TYPE_CLOB] + this.oracle.fetchAsBuffer = [this.oracle.DB_TYPE_BLOB] if (this.options.replication) { this.slaves = await Promise.all( this.options.replication.slaves.map((slave) => { @@ -547,6 +549,8 @@ export class OracleDriver implements Driver { return DateUtils.simpleArrayToString(value) } else if (columnMetadata.type === "simple-json") { return DateUtils.simpleJsonToString(value) + } else if (columnMetadata.type === "json") { + return DateUtils.simpleJsonToString(value) } return value @@ -577,8 +581,6 @@ export class OracleDriver implements Driver { columnMetadata.type === "timestamp with local time zone" ) { value = DateUtils.normalizeHydratedDate(value) - } else if (columnMetadata.type === "json") { - value = JSON.parse(value) } else if (columnMetadata.type === "simple-array") { value = DateUtils.stringToSimpleArray(value) } else if (columnMetadata.type === "simple-json") { @@ -635,6 +637,8 @@ export class OracleDriver implements Driver { return "clob" } else if (column.type === "simple-json") { return "clob" + } else if (column.type === "json") { + return "json" } else { return (column.type as string) || "" } @@ -954,21 +958,24 @@ export class OracleDriver implements Driver { case "smallint": case "dec": case "decimal": - return this.oracle.NUMBER + return this.oracle.DB_TYPE_NUMBER case "char": case "nchar": case "nvarchar2": case "varchar2": - return this.oracle.STRING + return this.oracle.DB_TYPE_VARCHAR case "blob": - return this.oracle.BLOB + return this.oracle.DB_TYPE_BLOB + case "simple-json": case "clob": - return this.oracle.CLOB + return this.oracle.DB_TYPE_CLOB case "date": case "timestamp": case "timestamp with time zone": case "timestamp with local time zone": - return this.oracle.DATE + return this.oracle.DB_TYPE_TIMESTAMP + case "json": + return this.oracle.DB_TYPE_JSON } } diff --git a/src/driver/oracle/OracleQueryRunner.ts b/src/driver/oracle/OracleQueryRunner.ts index a11dbeb0a3..6693a9c443 100644 --- a/src/driver/oracle/OracleQueryRunner.ts +++ b/src/driver/oracle/OracleQueryRunner.ts @@ -214,7 +214,7 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { try { const executionOptions = { autoCommit: !this.isTransactionActive, - outFormat: this.driver.oracle.OBJECT, + outFormat: this.driver.oracle.OUT_FORMAT_OBJECT, } const raw = await databaseConnection.execute( @@ -323,7 +323,7 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { const executionOptions = { autoCommit: !this.isTransactionActive, - outFormat: this.driver.oracle.OBJECT, + outFormat: this.driver.oracle.OUT_FORMAT_OBJECT, } const databaseConnection = await this.connect() diff --git a/test/functional/database-schema/column-types/oracle/column-types-oracle.ts b/test/functional/database-schema/column-types/oracle/column-types-oracle.ts index 254a253bb9..68ac04b562 100644 --- a/test/functional/database-schema/column-types/oracle/column-types-oracle.ts +++ b/test/functional/database-schema/column-types/oracle/column-types-oracle.ts @@ -27,6 +27,8 @@ describe("database schema > column types > oracle", () => { const postRepository = connection.getRepository(Post) const queryRunner = connection.createQueryRunner() const table = await queryRunner.getTable("post") + const simpleJson = { id: 1, name: "simple-json" } + const json = { id: 1, name: "json" } await queryRunner.release() const post = new Post() @@ -60,12 +62,14 @@ describe("database schema > column types > oracle", () => { post.clob = "This is clob" post.nclob = "This is nclob" post.simpleArray = ["A", "B", "C"] + post.simpleJson = simpleJson + post.json = json + await postRepository.save(post) const loadedPost = (await postRepository.findOneBy({ id: 1, }))! - loadedPost.id.should.be.equal(post.id) loadedPost.name.should.be.equal(post.name) loadedPost.number.should.be.equal(post.number) loadedPost.numeric.should.be.equal(post.numeric) @@ -105,6 +109,8 @@ describe("database schema > column types > oracle", () => { loadedPost.simpleArray[1].should.be.equal(post.simpleArray[1]) loadedPost.simpleArray[2].should.be.equal(post.simpleArray[2]) + loadedPost.simpleJson.should.be.deep.equal(simpleJson) + loadedPost.json.should.be.deep.equal(json) table!.findColumnByName("id")!.type.should.be.equal("number") table! .findColumnByName("name")! @@ -158,6 +164,10 @@ describe("database schema > column types > oracle", () => { table! .findColumnByName("simpleArray")! .type.should.be.equal("clob") + table! + .findColumnByName("simpleJson")! + .type.should.be.equal("clob") + table!.findColumnByName("json")!.type.should.be.equal("json") }), )) diff --git a/test/functional/database-schema/column-types/oracle/entity/Post.ts b/test/functional/database-schema/column-types/oracle/entity/Post.ts index 0ba832a0d3..4486f6bd23 100644 --- a/test/functional/database-schema/column-types/oracle/entity/Post.ts +++ b/test/functional/database-schema/column-types/oracle/entity/Post.ts @@ -98,10 +98,15 @@ export class Post { @Column("nclob") nclob: string + @Column("json") + json: any // ------------------------------------------------------------------------- // TypeOrm Specific Type // ------------------------------------------------------------------------- @Column("simple-array") simpleArray: string[] + + @Column("simple-json") + simpleJson: any } diff --git a/test/functional/naming-strategy/legacy-oracle-naming-strategy/create-table/create-table.ts b/test/functional/naming-strategy/legacy-oracle-naming-strategy/create-table/create-table.ts deleted file mode 100644 index e939c75693..0000000000 --- a/test/functional/naming-strategy/legacy-oracle-naming-strategy/create-table/create-table.ts +++ /dev/null @@ -1,30 +0,0 @@ -import "reflect-metadata" -import { expect } from "chai" -import { - closeTestingConnections, - createTestingConnections, - reloadTestingDatabases, -} from "../../../../utils/test-utils" -import { DataSource } from "../../../../../src/data-source" - -describe("LegacyOracleNamingStrategy > create table using default naming strategy", () => { - let connections: DataSource[] - before( - async () => - (connections = await createTestingConnections({ - entities: [__dirname + "/entity/*{.js,.ts}"], - enabledDrivers: ["oracle"], - })), - ) - // without reloadTestingDatabases(connections) -> tables should be created later - after(() => closeTestingConnections(connections)) - - it("should not create the table and fail due to ORA-00972", () => - Promise.all( - connections.map(async (connection) => { - await expect( - reloadTestingDatabases([connection]), - ).to.be.rejectedWith(/ORA-00972/gi) - }), - )) -}) diff --git a/test/functional/transaction/database-specific-isolation/oracle-isolation.ts b/test/functional/transaction/database-specific-isolation/oracle-isolation.ts index 959d078355..8e10054a9f 100644 --- a/test/functional/transaction/database-specific-isolation/oracle-isolation.ts +++ b/test/functional/transaction/database-specific-isolation/oracle-isolation.ts @@ -69,6 +69,15 @@ describe("transaction > transaction with oracle connection partial isolation sup let postId: number | undefined = undefined, categoryId: number | undefined = undefined + // Initial inserts are required to prevent ORA-08177 errors in Oracle 21c when using a serializable connection + // immediately after DDL statements. This ensures proper synchronization and helps avoid conflicts. + await connection.manager + .getRepository(Post) + .save({ title: "Post #0" }) + await connection.manager + .getRepository(Category) + .save({ name: "Category #0" }) + await connection.manager.transaction( "SERIALIZABLE", async (entityManager) => { diff --git a/test/github-issues/3363/issue-3363.ts b/test/github-issues/3363/issue-3363.ts index e937b63954..3fe51d4482 100644 --- a/test/github-issues/3363/issue-3363.ts +++ b/test/github-issues/3363/issue-3363.ts @@ -76,6 +76,15 @@ describe("github issues > #3363 Isolation Level in transaction() from Connection let postId: number | undefined = undefined, categoryId: number | undefined = undefined + // Initial inserts are required to prevent ORA-08177 errors in Oracle 21c when using a serializable connection + // immediately after DDL statements. This ensures proper synchronization and helps avoid conflicts. + await connection.manager + .getRepository(Post) + .save({ title: "Post #0" }) + await connection.manager + .getRepository(Category) + .save({ name: "Category #0" }) + await connection.transaction( "SERIALIZABLE", async (entityManager) => { diff --git a/test/github-issues/9601/entity/Foo.ts b/test/github-issues/9601/entity/Foo.ts index cf9d225ad2..ce93f1f7ad 100644 --- a/test/github-issues/9601/entity/Foo.ts +++ b/test/github-issues/9601/entity/Foo.ts @@ -4,7 +4,7 @@ import { UpdateDateColumn, } from "../../../../src" -@Entity({ name: "foo", schema: "SYSTEM" }) +@Entity({ name: "foo", schema: "TYPEORM" }) export class Foo { @PrimaryGeneratedColumn({ name: "id" }) id: number diff --git a/test/github-issues/9601/entity/FooView.ts b/test/github-issues/9601/entity/FooView.ts index f016b4bf8b..06c7072ad1 100644 --- a/test/github-issues/9601/entity/FooView.ts +++ b/test/github-issues/9601/entity/FooView.ts @@ -3,7 +3,7 @@ import { Foo } from "./Foo" @ViewEntity({ name: "foo_view", - schema: "SYSTEM", + schema: "TYPEORM", expression: (connection: DataSource) => connection.createQueryBuilder(Foo, "foo").select(`foo.updatedAt`), })