working on tests;

bugfixes;
This commit is contained in:
Zotov Dmitry 2019-02-13 23:23:04 +05:00
parent 4032cdb431
commit 3bbd65cd00
74 changed files with 386 additions and 304 deletions

View File

@ -15,9 +15,7 @@ export class Post {
@Column()
text: string;
@Column("int", {
nullable: false
})
@Column({ nullable: false })
likesCount: number;
}
}

View File

@ -171,15 +171,15 @@ export class CockroachDriver implements Driver {
createDateDefault: "now()",
updateDate: "timestamptz",
updateDateDefault: "now()",
version: "int",
treeLevel: "int",
migrationId: "int",
version: Number,
treeLevel: Number,
migrationId: Number,
migrationName: "varchar",
migrationTimestamp: "int8",
cacheId: "int",
cacheId: Number,
cacheIdentifier: "varchar",
cacheTime: "int8",
cacheDuration: "int",
cacheDuration: Number,
cacheQuery: "string",
cacheResult: "string",
};
@ -322,7 +322,7 @@ export class CockroachDriver implements Driver {
return value;
// unique_rowid() generates bigint value and should not be converted to number
if (columnMetadata.type === Number && !columnMetadata.isArray && !columnMetadata.isGenerated) {
if ((columnMetadata.type === Number && !columnMetadata.isArray) || columnMetadata.generationStrategy === "increment") {
value = parseInt(value);
} else if (columnMetadata.type === Boolean) {
@ -411,7 +411,7 @@ export class CockroachDriver implements Driver {
/**
* Creates a database type from a given column metadata.
*/
normalizeType(column: { type?: ColumnType, length?: number | string, precision?: number|null, scale?: number, isArray?: boolean, isGenerated?: boolean, generationStrategy?: "increment"|"uuid" }): string {
normalizeType(column: { type?: ColumnType, length?: number | string, precision?: number|null, scale?: number, isArray?: boolean, isGenerated?: boolean, generationStrategy?: "increment"|"uuid"|"rowid" }): string {
if (column.type === Number || column.type === "integer" || column.type === "int4") {
return "int";
@ -568,7 +568,7 @@ export class CockroachDriver implements Driver {
return Object.keys(insertResult).reduce((map, key) => {
const column = metadata.findColumnWithDatabaseName(key);
if (column) {
OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column)));
}
return map;
}, {} as ObjectLiteral);

View File

@ -314,6 +314,13 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
const upQueries: string[] = [];
const downQueries: string[] = [];
table.columns
.filter(column => column.isGenerated && column.generationStrategy === "increment")
.forEach(column => {
upQueries.push(`CREATE SEQUENCE ${this.buildSequenceName(table, column)}`);
downQueries.push(`DROP SEQUENCE ${this.buildSequenceName(table, column)}`);
});
upQueries.push(this.createTableSql(table, createForeignKeys));
downQueries.push(this.dropTableSql(table));
@ -369,6 +376,13 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
upQueries.push(this.dropTableSql(table));
downQueries.push(this.createTableSql(table, createForeignKeys));
table.columns
.filter(column => column.isGenerated && column.generationStrategy === "increment")
.forEach(column => {
upQueries.push(`DROP SEQUENCE ${this.buildSequenceName(table, column)}`);
downQueries.push(`CREATE SEQUENCE ${this.buildSequenceName(table, column)}`);
});
await this.executeQueries(upQueries, downQueries);
}
@ -452,6 +466,10 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
const upQueries: string[] = [];
const downQueries: string[] = [];
if (column.generationStrategy === "increment") {
throw new Error(`Adding sequential generated columns into existing table is not supported`);
}
upQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ADD ${this.buildCreateColumnSql(table, column)}`);
downQueries.push(`ALTER TABLE ${this.escapeTableName(table)} DROP COLUMN "${column.name}"`);
@ -724,8 +742,13 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
if (oldColumn.isGenerated !== newColumn.isGenerated && newColumn.generationStrategy !== "uuid") {
if (newColumn.isGenerated) {
upQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT unique_rowid()`);
downQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`);
if (newColumn.generationStrategy === "increment") {
throw new Error(`Adding sequential generated columns into existing table is not supported`);
} else if (newColumn.generationStrategy === "rowid") {
upQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT unique_rowid()`);
downQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`);
}
} else {
upQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`);
@ -823,6 +846,11 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
upQueries.push(`ALTER TABLE ${this.escapeTableName(table)} DROP COLUMN "${column.name}"`);
downQueries.push(`ALTER TABLE ${this.escapeTableName(table)} ADD ${this.buildCreateColumnSql(table, column)}`);
if (column.generationStrategy === "increment") {
upQueries.push(`DROP SEQUENCE ${this.buildSequenceName(table, column)}`);
downQueries.push(`CREATE SEQUENCE ${this.buildSequenceName(table, column)}`);
}
await this.executeQueries(upQueries, downQueries);
clonedTable.removeColumn(column);
@ -1084,7 +1112,7 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
// CockroachDB stores unique indices and UNIQUE constraints
if (index.isUnique) {
const unique = new TableUnique({
name: this.connection.namingStrategy.uniqueConstraintName(table.name, index.columnNames),
name: index.name,
columnNames: index.columnNames
});
const up = this.createUniqueConstraintSql(table, unique);
@ -1158,12 +1186,14 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
await this.startTransaction();
try {
// ignore spatial_ref_sys; it's a special table supporting PostGIS
// TODO generalize this as this.driver.ignoreTables
const selectDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || schemaname || '"."' || tablename || '" CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (${schemaNamesString}) AND tablename NOT IN ('spatial_ref_sys')`;
const selectDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || table_schema || '"."' || table_name || '" CASCADE;' as "query" FROM "information_schema"."tables" WHERE "table_schema" IN (${schemaNamesString})`;
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
await Promise.all(dropQueries.map(q => this.query(q["query"])));
const selectSequenceDropsQuery = `SELECT 'DROP SEQUENCE "' || sequence_schema || '"."' || sequence_name || '";' as "query" FROM "information_schema"."sequences" WHERE "sequence_schema" IN (${schemaNamesString})`;
const sequenceDropQueries: ObjectLiteral[] = await this.query(selectSequenceDropsQuery);
await Promise.all(sequenceDropQueries.map(q => this.query(q["query"])));
await this.commitTransaction();
} catch (error) {
@ -1334,6 +1364,10 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
if (dbColumn["column_default"] !== null && dbColumn["column_default"] !== undefined) {
if (dbColumn["column_default"] === "unique_rowid()") {
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "rowid";
} else if (dbColumn["column_default"].indexOf("nextval") !== -1) {
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "increment";
@ -1624,24 +1658,9 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
/**
* Builds sequence name from given table and column.
*/
protected buildSequenceName(table: Table, columnOrName: TableColumn|string, currentSchema?: string, disableEscape?: true, skipSchema?: boolean): string {
protected buildSequenceName(table: Table, columnOrName: TableColumn|string, disableEscape?: true): string {
const columnName = columnOrName instanceof TableColumn ? columnOrName.name : columnOrName;
let schema: string|undefined = undefined;
let tableName: string|undefined = undefined;
if (table.name.indexOf(".") === -1) {
tableName = table.name;
} else {
schema = table.name.split(".")[0];
tableName = table.name.split(".")[1];
}
if (schema && schema !== currentSchema && !skipSchema) {
return disableEscape ? `${schema}.${tableName}_${columnName}_seq` : `"${schema}"."${tableName}_${columnName}_seq"`;
} else {
return disableEscape ? `${tableName}_${columnName}_seq` : `"${tableName}_${columnName}_seq"`;
}
return disableEscape ? `${table.name}_${columnName}_seq` : `"${table.name}_${columnName}_seq"`;
}
/**
@ -1680,9 +1699,18 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
protected buildCreateColumnSql(table: Table, column: TableColumn) {
let c = "\"" + column.name + "\"";
if (column.isGenerated && column.generationStrategy !== "uuid")
c += " INT DEFAULT unique_rowid()";
if (!column.isGenerated || column.type === "uuid")
if (column.isGenerated) {
if (column.generationStrategy === "increment") {
c += ` INT DEFAULT nextval('${this.buildSequenceName(table, column)}')`;
} else if (column.generationStrategy === "rowid") {
c += " INT DEFAULT unique_rowid()";
} else if (column.generationStrategy === "uuid") {
c += " UUID DEFAULT gen_random_uuid()";
}
}
if (!column.isGenerated)
c += " " + this.connection.driver.createFullType(column);
if (column.charset)
c += " CHARACTER SET \"" + column.charset + "\"";
@ -1690,10 +1718,8 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
c += " COLLATE \"" + column.collation + "\"";
if (!column.isNullable)
c += " NOT NULL";
if (column.default !== undefined && column.default !== null)
if (!column.isGenerated && column.default !== undefined && column.default !== null)
c += " DEFAULT " + column.default;
if (column.isGenerated && column.generationStrategy === "uuid" && !column.default)
c += " DEFAULT gen_random_uuid()";
return c;
}

View File

@ -556,7 +556,7 @@ export class OracleDriver implements Driver {
return Object.keys(insertResult).reduce((map, key) => {
const column = metadata.findColumnWithDatabaseName(key);
if (column) {
OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column)));
}
return map;
}, {} as ObjectLiteral);

View File

@ -725,7 +725,7 @@ export class PostgresDriver implements Driver {
return Object.keys(insertResult).reduce((map, key) => {
const column = metadata.findColumnWithDatabaseName(key);
if (column) {
OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column)));
}
return map;
}, {} as ObjectLiteral);

View File

@ -548,7 +548,7 @@ export class SqlServerDriver implements Driver {
return Object.keys(insertResult).reduce((map, key) => {
const column = metadata.findColumnWithDatabaseName(key);
if (column) {
OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column)));
}
return map;
}, {} as ObjectLiteral);

View File

@ -473,6 +473,7 @@ export class EntityMetadataBuilder {
entityMetadata: entityMetadata,
args: {
target: args.target,
name: args.name,
columns: args.columns,
}
});

View File

@ -103,7 +103,7 @@ export class ColumnMetadata {
/**
* Specifies generation strategy if this column will use auto increment.
*/
generationStrategy?: "uuid"|"increment";
generationStrategy?: "uuid"|"increment"|"rowid";
/**
* Column comment.
@ -443,7 +443,7 @@ export class ColumnMetadata {
}
// this is bugfix for #720 when increment number is bigint we need to make sure its a string
if (this.generationStrategy === "increment" && this.type === "bigint")
if ((this.generationStrategy === "increment" || this.generationStrategy === "rowid") && this.type === "bigint")
value = String(value);
map[useDatabaseName ? this.databaseName : this.propertyName] = value;
@ -454,7 +454,7 @@ export class ColumnMetadata {
} else { // no embeds - no problems. Simply return column property name and its value of the entity
// this is bugfix for #720 when increment number is bigint we need to make sure its a string
if (this.generationStrategy === "increment" && this.type === "bigint")
if ((this.generationStrategy === "increment" || this.generationStrategy === "rowid") && this.type === "bigint")
value = String(value);
return { [useDatabaseName ? this.databaseName : this.propertyName]: value };

View File

@ -241,4 +241,4 @@ export class ManyToManySubjectBuilder {
return identifier;
}
}
}

View File

@ -475,31 +475,6 @@ export abstract class QueryBuilder<Entity> {
* schema name, otherwise returns escaped table name.
*/
protected getTableName(tablePath: string): string {
// let tablePath = tableName;
// const driver = this.connection.driver;
// const schema = (driver.options as SqlServerConnectionOptions|PostgresConnectionOptions).schema;
// const metadata = this.connection.hasMetadata(tableName) ? this.connection.getMetadata(tableName) : undefined;
/*if (driver instanceof SqlServerDriver || driver instanceof PostgresDriver || driver instanceof MysqlDriver) {
if (metadata) {
if (metadata.schema) {
tablePath = `${metadata.schema}.${tableName}`;
} else if (schema) {
tablePath = `${schema}.${tableName}`;
}
if (metadata.database && !(driver instanceof PostgresDriver)) {
if (!schema && !metadata.schema && driver instanceof SqlServerDriver) {
tablePath = `${metadata.database}..${tablePath}`;
} else {
tablePath = `${metadata.database}.${tablePath}`;
}
}
} else if (schema) {
tablePath = `${schema!}.${tableName}`;
}
}*/
return tablePath.split(".")
.map(i => {
// this condition need because in SQL Server driver when custom database name was specified and schema name was not, we got `dbName..tableName` string, and doesn't need to escape middle empty string

View File

@ -21,6 +21,10 @@ export class RelationCountLoader {
async load(rawEntities: any[]): Promise<RelationCountLoadResult[]> {
const onlyUnique = (value: any, index: number, self: any) => {
return self.indexOf(value) === index;
};
const promises = this.relationCountAttributes.map(async relationCountAttr => {
if (relationCountAttr.relation.isOneToMany) {
@ -37,9 +41,10 @@ export class RelationCountLoader {
const inverseSideTableAlias = relationCountAttr.alias || inverseSideTableName; // if condition (custom query builder factory) is set then relationIdAttr.alias defined
const inverseSidePropertyName = inverseRelation.propertyName; // "category" from "post.category"
const referenceColumnValues = rawEntities
let referenceColumnValues = rawEntities
.map(rawEntity => rawEntity[relationCountAttr.parentAlias + "_" + referenceColumnName])
.filter(value => !!value);
referenceColumnValues = referenceColumnValues.filter(onlyUnique);
// ensure we won't perform redundant queries for joined data which was not found in selection
// example: if post.category was not found in db then no need to execute query for category.imageIds
@ -89,9 +94,10 @@ export class RelationCountLoader {
secondJunctionColumn = relationCountAttr.relation.junctionEntityMetadata!.columns[0];
}
const referenceColumnValues = rawEntities
let referenceColumnValues = rawEntities
.map(rawEntity => rawEntity[relationCountAttr.parentAlias + "_" + joinTableColumnName])
.filter(value => value);
.filter(value => !!value);
referenceColumnValues = referenceColumnValues.filter(onlyUnique);
// ensure we won't perform redundant queries for joined data which was not found in selection
// example: if post.category was not found in db then no need to execute query for category.imageIds

View File

@ -36,11 +36,11 @@ export class RelationIdLoader {
const results = rawEntities.map(rawEntity => {
const result: ObjectLiteral = {};
relationIdAttr.relation.joinColumns.forEach(joinColumn => {
result[joinColumn.databaseName] = rawEntity[this.buildColumnAlias(relationIdAttr.parentAlias, joinColumn.databaseName)];
result[joinColumn.databaseName] = this.connection.driver.prepareHydratedValue(rawEntity[this.buildColumnAlias(relationIdAttr.parentAlias, joinColumn.databaseName)], joinColumn.referencedColumn!);
});
relationIdAttr.relation.entityMetadata.primaryColumns.forEach(primaryColumn => {
result[primaryColumn.databaseName] = rawEntity[this.buildColumnAlias(relationIdAttr.parentAlias, primaryColumn.databaseName)];
result[primaryColumn.databaseName] = this.connection.driver.prepareHydratedValue(rawEntity[this.buildColumnAlias(relationIdAttr.parentAlias, primaryColumn.databaseName)], primaryColumn);
});
return result;
});
@ -96,9 +96,19 @@ export class RelationIdLoader {
if (relationIdAttr.queryBuilderFactory)
relationIdAttr.queryBuilderFactory(qb);
const results = await qb.getRawMany();
results.forEach(result => {
joinColumns.forEach(column => {
result[column.databaseName] = this.connection.driver.prepareHydratedValue(result[column.databaseName], column.referencedColumn!);
});
relation.inverseRelation!.entityMetadata.primaryColumns.forEach(column => {
result[column.databaseName] = this.connection.driver.prepareHydratedValue(result[column.databaseName], column);
});
});
return {
relationIdAttribute: relationIdAttr,
results: await qb.getRawMany()
results
};
} else {
@ -166,9 +176,16 @@ export class RelationIdLoader {
if (relationIdAttr.queryBuilderFactory)
relationIdAttr.queryBuilderFactory(qb);
const results = await qb.getRawMany();
results.forEach(result => {
[...joinColumns, ...inverseJoinColumns].forEach(column => {
result[column.databaseName] = this.connection.driver.prepareHydratedValue(result[column.databaseName], column.referencedColumn!);
});
});
return {
relationIdAttribute: relationIdAttr,
results: await qb.getRawMany()
results
};
}
});

View File

@ -332,9 +332,9 @@ export class RawSqlResultsToEntityTransformer {
return columns.reduce((valueMap, column) => {
rawSqlResults.forEach(rawSqlResult => {
if (relation.isManyToOne || relation.isOneToOneOwner) {
valueMap[column.databaseName] = rawSqlResult[this.buildColumnAlias(parentAlias, column.databaseName)];
valueMap[column.databaseName] = this.driver.prepareHydratedValue(rawSqlResult[this.buildColumnAlias(parentAlias, column.databaseName)], column);
} else {
valueMap[column.databaseName] = rawSqlResult[this.buildColumnAlias(parentAlias, column.referencedColumn!.databaseName)];
valueMap[column.databaseName] = this.driver.prepareHydratedValue(rawSqlResult[this.buildColumnAlias(parentAlias, column.referencedColumn!.databaseName)], column);
}
});
return valueMap;
@ -369,4 +369,4 @@ export class RawSqlResultsToEntityTransformer {
virtualColumns.forEach(virtualColumn => delete entity[virtualColumn]);
}*/
}
}

View File

@ -163,9 +163,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
// find foreign keys that exist in the schemas but does not exist in the entity metadata
const tableForeignKeysToDrop = table.foreignKeys.filter(tableForeignKey => {
const metadataFK = metadata.foreignKeys.find(metadataForeignKey => metadataForeignKey.name === tableForeignKey.name);
return !metadataFK
|| metadataFK.onDelete && metadataFK.onDelete !== tableForeignKey.onDelete
|| metadataFK.onUpdate && metadataFK.onUpdate !== tableForeignKey.onUpdate; // TODO: bug, fix later (messer)
return !metadataFK || metadataFK.onDelete !== tableForeignKey.onDelete || metadataFK.onUpdate !== tableForeignKey.onUpdate;
});
if (tableForeignKeysToDrop.length === 0)
return;

View File

@ -41,7 +41,7 @@ export interface TableColumnOptions {
/**
* Specifies generation strategy if this column will use auto increment.
*/
generationStrategy?: "uuid"|"increment";
generationStrategy?: "uuid"|"increment"|"rowid";
/**
* Indicates if column is a primary key.

View File

@ -41,8 +41,9 @@ export class TableColumn {
/**
* Specifies generation strategy if this column will use auto increment.
* `rowid` option supported only in CockroachDB.
*/
generationStrategy?: "uuid"|"increment";
generationStrategy?: "uuid"|"increment"|"rowid";
/**
* Indicates if column is a primary key.
@ -203,4 +204,4 @@ export class TableColumn {
});
}
}
}

View File

@ -1,4 +1,5 @@
import "reflect-metadata";
import {CockroachDriver} from "../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {expect} from "chai";
@ -12,7 +13,6 @@ describe("database schema > indices > reading index from entity and updating dat
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["mysql"]
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
@ -43,11 +43,19 @@ describe("database schema > indices > reading index from entity and updating dat
const table = await queryRunner.getTable("person");
await queryRunner.release();
expect(table!.indices.length).to.be.equal(1);
expect(table!.indices[0].name).to.be.equal("IDX_TEST");
expect(table!.indices[0].isUnique).to.be.true;
expect(table!.indices[0].columnNames.length).to.be.equal(2);
expect(table!.indices[0].columnNames).to.include.members(["firstname", "lastname"]);
// CockroachDB stores unique indices as UNIQUE constraints
if (connection.driver instanceof CockroachDriver) {
expect(table!.uniques.length).to.be.equal(1);
expect(table!.uniques[0].name).to.be.equal("IDX_TEST");
expect(table!.uniques[0].columnNames.length).to.be.equal(2);
expect(table!.uniques[0].columnNames).to.include.members(["firstname", "firstname"]);
} else {
expect(table!.indices.length).to.be.equal(1);
expect(table!.indices[0].name).to.be.equal("IDX_TEST");
expect(table!.indices[0].isUnique).to.be.true;
expect(table!.indices[0].columnNames.length).to.be.equal(2);
expect(table!.indices[0].columnNames).to.include.members(["firstname", "firstname"]);
}
})));

View File

@ -251,6 +251,8 @@ describe("query builder > relation-count-decorator-many-to-many > many-to-many",
.addOrderBy("post.id, categories.id")
.getMany();
// console.log(loadedPosts);
expect(loadedPosts![0].categoryCount).to.be.equal(3);
expect(loadedPosts![0].categories[0].postCount).to.be.equal(2);
expect(loadedPosts![0].categories[1].postCount).to.be.equal(1);

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../../src/decorator/columns/Column";
import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn";
@ -9,12 +9,9 @@ import {RelationId} from "../../../../../../src/decorator/relations/RelationId";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
code: number;
@Column()
title: string;

View File

@ -12,14 +12,13 @@ describe("embedded > embedded-many-to-many-case1", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["cockroachdb"]
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
describe("owner side", () => {
it.only("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation", () => Promise.all(connections.map(async connection => {
it("should insert, load, update and remove entities with embeddeds when embedded entity having ManyToMany relation", () => Promise.all(connections.map(async connection => {
const user1 = new User();
user1.id = 1;

View File

@ -36,6 +36,7 @@ describe("entity-model", () => {
Category.useConnection(connection);
const category = Category.create();
category.id = 1;
category.name = "Persistence";
await category.save();

View File

@ -1,15 +1,15 @@
import {PrimaryColumn} from "../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../src/decorator/entity/Entity";
import {BaseEntity} from "../../../../src/repository/BaseEntity";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
@Entity()
export class Category extends BaseEntity {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
name: string;
}
}

View File

@ -1,4 +1,5 @@
import "reflect-metadata";
import {CockroachDriver} from "../../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
import {Connection} from "../../../../../src/connection/Connection";
import {EntityMetadata} from "../../../../../src/metadata/EntityMetadata";
@ -41,11 +42,19 @@ describe("entity-schema > indices > basic", () => {
const table = await queryRunner.getTable("person");
await queryRunner.release();
expect(table!.indices.length).to.be.equal(1);
expect(table!.indices[0].name).to.be.equal("IDX_TEST");
expect(table!.indices[0].isUnique).to.be.true;
expect(table!.indices[0].columnNames.length).to.be.equal(2);
expect(table!.indices[0].columnNames).to.include.members(["FirstName", "LastName"]);
// CockroachDB stores unique indices as UNIQUE constraints
if (connection.driver instanceof CockroachDriver) {
expect(table!.uniques.length).to.be.equal(1);
expect(table!.uniques[0].name).to.be.equal("IDX_TEST");
expect(table!.uniques[0].columnNames.length).to.be.equal(2);
expect(table!.uniques[0].columnNames).to.include.members(["FirstName", "LastName"]);
} else {
expect(table!.indices.length).to.be.equal(1);
expect(table!.indices[0].name).to.be.equal("IDX_TEST");
expect(table!.indices[0].isUnique).to.be.true;
expect(table!.indices[0].columnNames.length).to.be.equal(2);
expect(table!.indices[0].columnNames).to.include.members(["FirstName", "LastName"]);
}
})));

View File

@ -1,18 +1,19 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
name: string;
constructor(name: string) {
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
}

View File

@ -1,18 +1,19 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
title: string;
constructor(title: string) {
constructor(id: number, title: string) {
this.id = id;
this.title = title;
}
}
}

View File

@ -1,18 +1,19 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class User {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
name: string;
constructor(name: string) {
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
}

View File

@ -17,11 +17,11 @@ describe("persistence > basic functionality", function() {
after(() => closeTestingConnections(connections));
it("should save an entity", () => Promise.all(connections.map(async connection => {
await connection.manager.save(new Post("Hello Post"));
await connection.manager.save(new Post(1, "Hello Post"));
})));
it("should remove an entity", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
await connection.manager.remove(post);
})));
@ -41,16 +41,16 @@ describe("persistence > basic functionality", function() {
it("should throw an exception if object literal is given instead of constructed entity because it cannot determine what to save", () => Promise.all(connections.map(async connection => {
await connection.manager.save({}).should.be.rejectedWith(`Cannot save, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.save([{}, {}]).should.be.rejectedWith(`Cannot save, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.save([new Post("Hello Post"), {}]).should.be.rejectedWith(`Cannot save, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.save([new Post(1, "Hello Post"), {}]).should.be.rejectedWith(`Cannot save, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.remove({}).should.be.rejectedWith(`Cannot remove, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.remove([{}, {}]).should.be.rejectedWith(`Cannot remove, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.remove([new Post("Hello Post"), {}]).should.be.rejectedWith(`Cannot remove, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
await connection.manager.remove([new Post(1, "Hello Post"), {}]).should.be.rejectedWith(`Cannot remove, given value must be instance of entity class, instead object literal is given. Or you must specify an entity target to method call.`);
})));
it("should be able to save and remove entities of different types", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const category = new Category("Hello Category");
const user = new User("Hello User");
const post = new Post(1, "Hello Post");
const category = new Category(1, "Hello Category");
const user = new User(1, "Hello User");
await connection.manager.save([post, category, user]);
await connection.manager.findOne(Post, 1).should.eventually.eql({ id: 1, title: "Hello Post" });

View File

@ -17,14 +17,11 @@ describe("persistence > cascades > example 1", () => {
it("should insert everything by cascades properly", () => Promise.all(connections.map(async connection => {
const photo = new Photo();
photo.id = 1;
const profile = new Profile();
profile.id = 1;
profile.photo = photo;
const user = new User();
user.id = 1;
user.profile = profile;
await connection.manager.save(user);

View File

@ -1,10 +1,10 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
@Entity()
export class Photo {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
}

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {User} from "./User";
import {Photo} from "./Photo";
import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
@ -8,7 +8,7 @@ import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn";
@Entity()
export class Profile {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
@OneToOne(type => User, user => user.profile, {

View File

@ -1,12 +1,12 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Profile} from "./Profile";
import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
@Entity()
export class User {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
@OneToOne(type => Profile, profile => profile.user, { cascade: ["insert"] })

View File

@ -18,23 +18,17 @@ describe("persistence > cascades > example 2", () => {
it("should insert everything by cascades properly", () => Promise.all(connections.map(async connection => {
const photo = new Photo();
photo.id = 1;
const user = new User();
user.id = 1;
const answer1 = new Answer();
answer1.id = 1;
answer1.photo = photo;
answer1.user = user;
const answer2 = new Answer();
answer2.id = 2;
answer2.photo = photo;
answer2.user = user;
const question = new Question();
question.id = 1;
question.answers = [answer1, answer2];
user.question = question;

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne";
import {Photo} from "./Photo";
import {User} from "./User";
@ -8,7 +8,7 @@ import {Question} from "./Question";
@Entity()
export class Answer {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(type => Question, question => question.answers, {

View File

@ -1,10 +1,10 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
@Entity()
export class Photo {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
}

View File

@ -1,12 +1,12 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Answer} from "./Answer";
import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany";
@Entity()
export class Question {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
@OneToMany(type => Answer, answer => answer.question, { cascade: ["insert"] })

View File

@ -1,12 +1,12 @@
import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Question} from "./Question";
import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne";
@Entity()
export class User {
@PrimaryColumn()
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(type => Question, {

View File

@ -8,7 +8,7 @@ import {Generated} from "../../../../../src/decorator/Generated";
@Entity()
export class Category {
@PrimaryColumn("int", {name: "theId"})
@PrimaryColumn({name: "theId"})
@Generated()
id: number;
@ -20,4 +20,4 @@ export class Category {
})
posts: Post[];
}
}

View File

@ -8,7 +8,7 @@ import {Generated} from "../../../../../src/decorator/Generated";
@Entity()
export class Post {
@PrimaryColumn("int", {name: "theId"})
@PrimaryColumn({name: "theId"})
@Generated()
id: number;
@ -20,4 +20,4 @@ export class Post {
})
category: Category;
}
}

View File

@ -1,5 +1,6 @@
import "reflect-metadata";
import {Connection} from "../../../../src/connection/Connection";
import {CockroachDriver} from "../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {PostIncrement} from "./entity/PostIncrement";
import {PostUuid} from "./entity/PostUuid";
@ -21,7 +22,9 @@ describe("persistence > entity updation", () => {
const post = new PostIncrement();
post.text = "Hello Post";
await connection.manager.save(post);
post.id.should.be.equal(1);
// CockroachDB does not use incremental ids
if (!(connection.driver instanceof CockroachDriver))
post.id.should.be.equal(1);
})));
it("should update generated uuid after saving", () => Promise.all(connections.map(async connection => {
@ -36,7 +39,6 @@ describe("persistence > entity updation", () => {
const post = new PostDefaultValues();
post.title = "Post #1";
await connection.manager.save(post);
post.id.should.be.equal(1);
post.title.should.be.equal("Post #1");
post.text.should.be.equal("hello post");
post.isActive.should.be.equal(true);
@ -49,7 +51,6 @@ describe("persistence > entity updation", () => {
const post = new PostSpecialColumns();
post.title = "Post #1";
await connection.manager.save(post);
post.id.should.be.equal(1);
post.title.should.be.equal("Post #1");
post.createDate.should.be.instanceof(Date);
post.updateDate.should.be.instanceof(Date);

View File

@ -48,7 +48,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser = await userRepository.findOne(1, {
const loadedUser = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -93,7 +93,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser1 = await userRepository.findOne(1, {
const loadedUser1 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -111,7 +111,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser2 = await userRepository.findOne(1, {
const loadedUser2 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -157,7 +157,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser1 = await userRepository.findOne(1, {
const loadedUser1 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -175,7 +175,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser2 = await userRepository.findOne(1, {
const loadedUser2 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -220,7 +220,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser1 = await userRepository.findOne(1, {
const loadedUser1 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }
@ -238,7 +238,7 @@ describe("persistence > many-to-many", function() {
await userRepository.save(newUser);
// load a post
const loadedUser2 = await userRepository.findOne(1, {
const loadedUser2 = await userRepository.findOne(newUser.id, {
join: {
alias: "user",
leftJoinAndSelect: { post: "user.post", categories: "post.categories" }

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
import {Post} from "./Post";
@ -7,7 +7,7 @@ import {Post} from "./Post";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -19,10 +19,11 @@ export class Category {
})
post?: Post|null|number;
constructor(name: string, post?: Post) {
constructor(id: number, name: string, post?: Post) {
this.id = id;
this.name = name;
if (post)
this.post = post;
}
}
}

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {Category} from "./Category";
import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
@ -7,7 +7,7 @@ import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -16,8 +16,9 @@ export class Post {
@OneToMany(type => Category, category => category.post)
categories: Category[];
constructor(title: string) {
constructor(id: number, title: string) {
this.id = id;
this.title = title;
}
}
}

View File

@ -17,10 +17,10 @@ describe("persistence > many-to-one bi-directional relation", function() {
after(() => closeTestingConnections(connections));
it("should save a category with a post attached", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -30,8 +30,8 @@ describe("persistence > many-to-one bi-directional relation", function() {
})));
it("should save a category and a new post by cascades", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const category = new Category("Hello Category");
const post = new Post(1, "Hello Post");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -41,11 +41,11 @@ describe("persistence > many-to-one bi-directional relation", function() {
})));
it("should update exist post by cascades when category is saved", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
post.title = "Updated post";
await connection.manager.save(category);
@ -67,11 +67,11 @@ describe("persistence > many-to-one bi-directional relation", function() {
})));
it("should NOT remove exist post by cascades when category is saved without a post (post is set to undefined)", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -94,11 +94,11 @@ describe("persistence > many-to-one bi-directional relation", function() {
})));
it("should unset exist post when its set to null", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -116,11 +116,11 @@ describe("persistence > many-to-one bi-directional relation", function() {
})));
it("should set category's post to NULL when post is removed from the database (database ON DELETE)", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -140,14 +140,14 @@ describe("persistence > many-to-one bi-directional relation", function() {
it("should work when relation id is directly set into relation (without related object)", () => Promise.all(connections.map(async connection => {
const post1 = new Post("Hello Post #1");
const post1 = new Post(1, "Hello Post #1");
await connection.manager.save(post1);
const post2 = new Post("Hello Post #2");
const post2 = new Post(2, "Hello Post #2");
await connection.manager.save(post2);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = 1;
await connection.manager.save(category);

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
import {Post} from "./Post";
@ -7,7 +7,7 @@ import {Post} from "./Post";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -19,10 +19,11 @@ export class Category {
})
post?: Post|null|number;
constructor(name: string, post?: Post) {
constructor(id: number, name: string, post?: Post) {
this.id = id;
this.name = name;
if (post)
this.post = post;
}
}
}

View File

@ -1,18 +1,19 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
title: string;
constructor(title: string) {
constructor(id: number, title: string) {
this.id = id;
this.title = title;
}
}
}

View File

@ -17,10 +17,10 @@ describe("persistence > many-to-one uni-directional relation", function() {
after(() => closeTestingConnections(connections));
it("should save a category with a post attached", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -30,8 +30,8 @@ describe("persistence > many-to-one uni-directional relation", function() {
})));
it("should save a category and a new post by cascades", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const category = new Category("Hello Category");
const post = new Post(1, "Hello Post");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -41,11 +41,11 @@ describe("persistence > many-to-one uni-directional relation", function() {
})));
it("should update exist post by cascades when category is saved", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
post.title = "Updated post";
await connection.manager.save(category);
@ -67,11 +67,11 @@ describe("persistence > many-to-one uni-directional relation", function() {
})));
it("should NOT remove exist post by cascades when category is saved without a post (post is set to undefined)", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -94,11 +94,11 @@ describe("persistence > many-to-one uni-directional relation", function() {
})));
it("should unset exist post when its set to null", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -116,11 +116,11 @@ describe("persistence > many-to-one uni-directional relation", function() {
})));
it("should set category's post to NULL when post is removed from the database (database ON DELETE)", () => Promise.all(connections.map(async connection => {
const post = new Post("Hello Post");
const post = new Post(1, "Hello Post");
await connection.manager.save(post);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = post;
await connection.manager.save(category);
@ -140,14 +140,14 @@ describe("persistence > many-to-one uni-directional relation", function() {
it("should work when relation id is directly set into relation (without related object)", () => Promise.all(connections.map(async connection => {
const post1 = new Post("Hello Post #1");
const post1 = new Post(1, "Hello Post #1");
await connection.manager.save(post1);
const post2 = new Post("Hello Post #2");
const post2 = new Post(2, "Hello Post #2");
await connection.manager.save(post2);
// update exist post from newly created category
const category = new Category("Hello Category");
const category = new Category(1, "Hello Category");
category.post = 1;
await connection.manager.save(category);

View File

@ -8,7 +8,7 @@ import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
@Entity()
export class Category {
@PrimaryColumn("int")
@PrimaryColumn()
categoryId: number;
@Column()
@ -17,4 +17,4 @@ export class Category {
@OneToMany(type => Post, post => post.category)
posts: Post[];
}
}

View File

@ -7,10 +7,10 @@ import {Category} from "./Category";
@Entity()
export class Post {
@PrimaryColumn("int")
@PrimaryColumn()
firstId: number;
@PrimaryColumn("int")
@PrimaryColumn()
secondId: number;
@Column()
@ -19,4 +19,4 @@ export class Post {
@ManyToOne(type => Category, category => category.posts)
category: Category;
}
}

View File

@ -15,7 +15,7 @@ describe("persistence > multi primary keys", () => {
describe("insert", function () {
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
it("should insert entity when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
const post1 = new Post();
post1.title = "Hello Post #1";
post1.firstId = 1;

View File

@ -7,10 +7,10 @@ import {Category} from "./Category";
@Entity()
export class Post {
@PrimaryColumn("int")
@PrimaryColumn()
firstId: number;
@PrimaryColumn("int")
@PrimaryColumn()
secondId: number;
@Column()
@ -19,4 +19,4 @@ export class Post {
@ManyToOne(type => Category, category => category.posts)
category: Category;
}
}

View File

@ -15,7 +15,7 @@ describe("persistence > multi primary keys", () => {
describe("insert", function () {
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
it("should insert entity when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
const post1 = new Post();
post1.title = "Hello Post #1";
post1.firstId = 1;

View File

@ -1,14 +1,14 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column({ type: String, default: "hello default value", nullable: true })
title?: string|null;
}
}

View File

@ -18,6 +18,7 @@ describe("persistence > null and default behaviour", () => {
// create category
const post = new Post();
post.id = 1;
post.title = "Category saved!";
await connection.manager.save(post);
@ -34,6 +35,7 @@ describe("persistence > null and default behaviour", () => {
// create category
const post = new Post();
post.id = 1;
await connection.manager.save(post);
const loadedPost = await connection.manager.findOne(Post, 1);
@ -49,6 +51,7 @@ describe("persistence > null and default behaviour", () => {
// create category
const post = new Post();
post.id = 1;
post.title = null;
await connection.manager.save(post);
@ -65,6 +68,7 @@ describe("persistence > null and default behaviour", () => {
// create category
const post = new Post();
post.id = 1;
post.title = "Category saved!";
await connection.manager.save(post);
@ -82,8 +86,8 @@ describe("persistence > null and default behaviour", () => {
it("should update to null when post.title is null", () => Promise.all(connections.map(async connection => {
// create category
const post = new Post();
post.id = 1;
post.title = "Category saved!";
await connection.manager.save(post);

View File

@ -1,5 +1,5 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Post} from "./Post";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
@ -7,7 +7,7 @@ import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -19,4 +19,4 @@ export class Category {
@ManyToMany(type => Post, post => post.categories)
posts: Post[];
}
}

View File

@ -1,6 +1,6 @@
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Category} from "./Category";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
import {JoinTable} from "../../../../../src/decorator/relations/JoinTable";
@ -9,7 +9,7 @@ import {Counters} from "./Counters";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -27,4 +27,4 @@ export class Post {
@JoinTable()
categories: Category[];
}
}

View File

@ -30,12 +30,14 @@ describe("persistence > partial persist", () => {
// save a new category
const newCategory = new Category();
newCategory.id = 1;
newCategory.name = "Animals";
newCategory.position = 999;
await categoryRepository.save(newCategory);
// save a new post
const newPost = new Post();
newPost.id = 1;
newPost.title = "All about animals";
newPost.description = "Description of the post about animals";
newPost.categories = [newCategory];

View File

@ -308,4 +308,4 @@ describe("query builder > cache", () => {
})));
});
});

View File

@ -1,12 +1,12 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Counters} from "./Counters";
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
@ -15,4 +15,4 @@ export class Photo {
@Column(type => Counters)
counters: Counters;
}
}

View File

@ -1,14 +1,14 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
import {Entity} from "../../../../../src/decorator/entity/Entity";
@Entity()
export class User {
@PrimaryGeneratedColumn()
@PrimaryColumn()
id: number;
@Column()
name: string;
}
}

View File

@ -17,6 +17,7 @@ describe("query builder > delete", () => {
it("should perform deletion correctly", () => Promise.all(connections.map(async connection => {
const user1 = new User();
user1.id = 1;
user1.name = "Alex Messer";
await connection.manager.save(user1);
@ -30,6 +31,7 @@ describe("query builder > delete", () => {
expect(loadedUser1).to.not.exist;
const user2 = new User();
user1.id = 2;
user2.name = "Alex Messer";
await connection.manager.save(user2);
@ -47,8 +49,9 @@ describe("query builder > delete", () => {
it("should be able to delete entities by embed criteria", () => Promise.all(connections.map(async connection => {
// save few photos
await connection.manager.save(Photo, { url: "1.jpg" });
await connection.manager.save(Photo, { id: 1, url: "1.jpg" });
await connection.manager.save(Photo, {
id: 2,
url: "2.jpg",
counters: {
likes: 2,
@ -56,7 +59,7 @@ describe("query builder > delete", () => {
comments: 1,
}
});
await connection.manager.save(Photo, { url: "3.jpg" });
await connection.manager.save(Photo, { id: 3, url: "3.jpg" });
// make sure photo with likes = 2 exist
const loadedPhoto1 = await connection.getRepository(Photo).findOne({ counters: { likes: 2 } });
@ -92,4 +95,4 @@ describe("query builder > delete", () => {
expect(loadedPhoto4).to.exist;
})));
});
});

View File

@ -1,5 +1,6 @@
import "reflect-metadata";
import {expect} from "chai";
import {CockroachDriver} from "../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Tag} from "./entity/Tag";
@ -177,7 +178,12 @@ describe("query builder > joins", () => {
.where("post.id = :id", { id: post.id })
.getRawOne();
expect(loadedRawPost!["categories_id"]).to.be.equal(1);
if (connection.driver instanceof CockroachDriver) {
expect(loadedRawPost!["categories_id"]).to.be.equal("1");
} else {
expect(loadedRawPost!["categories_id"]).to.be.equal(1);
}
})));
@ -823,4 +829,4 @@ describe("query builder > joins", () => {
})));
});
});
});

View File

@ -1,4 +1,5 @@
import "reflect-metadata";
import {CockroachDriver} from "../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {PostWithVersion} from "./entity/PostWithVersion";
@ -57,7 +58,7 @@ describe("query builder > locking", () => {
})));
it("should not throw error if pessimistic lock used with transaction", () => Promise.all(connections.map(async connection => {
if (connection.driver instanceof AbstractSqliteDriver)
if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver)
return;
return connection.manager.transaction(entityManager => {
@ -76,7 +77,7 @@ describe("query builder > locking", () => {
})));
it("should attach pessimistic read lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => {
if (connection.driver instanceof AbstractSqliteDriver)
if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver)
return;
const sql = connection.createQueryBuilder(PostWithVersion, "post")
@ -111,7 +112,7 @@ describe("query builder > locking", () => {
})));
it("should attach pessimistic write lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => {
if (connection.driver instanceof AbstractSqliteDriver)
if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver)
return;
const sql = connection.createQueryBuilder(PostWithVersion, "post")
@ -260,7 +261,7 @@ describe("query builder > locking", () => {
})));
it("should throw error if pessimistic locking not supported by given driver", () => Promise.all(connections.map(async connection => {
if (connection.driver instanceof AbstractSqliteDriver)
if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver)
return connection.manager.transaction(entityManager => {
return Promise.all([
entityManager.createQueryBuilder(PostWithVersion, "post")

View File

@ -109,4 +109,4 @@ describe("query builder > relation-id > many-to-one > embedded", () => {
})));
});
});

View File

@ -1,4 +1,5 @@
import "reflect-metadata";
import {CockroachDriver} from "../../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {User} from "./entity/User";
@ -264,11 +265,21 @@ describe("query builder > sub-query", () => {
.orderBy("post.id")
.getRawMany();
posts.should.be.eql([
{ id: 1, name: "Alex Messer" },
{ id: 2, name: "Alex Messer" },
{ id: 3, name: "Alex Messer" },
]);
// CockroachDB returns numeric data types as string
if (connection.driver instanceof CockroachDriver) {
posts.should.be.eql([
{ id: "1", name: "Alex Messer" },
{ id: "2", name: "Alex Messer" },
{ id: "3", name: "Alex Messer" },
]);
} else {
posts.should.be.eql([
{ id: 1, name: "Alex Messer" },
{ id: 2, name: "Alex Messer" },
{ id: 3, name: "Alex Messer" },
]);
}
})));
it("should execute sub query in selects (using provided sub query builder)", () => Promise.all(connections.map(async connection => {
@ -288,11 +299,21 @@ describe("query builder > sub-query", () => {
.orderBy("post.id")
.getRawMany();
posts.should.be.eql([
{ id: 1, name: "Alex Messer" },
{ id: 2, name: "Alex Messer" },
{ id: 3, name: "Alex Messer" },
]);
// CockroachDB returns numeric data types as string
if (connection.driver instanceof CockroachDriver) {
posts.should.be.eql([
{ id: "1", name: "Alex Messer" },
{ id: "2", name: "Alex Messer" },
{ id: "3", name: "Alex Messer" },
]);
} else {
posts.should.be.eql([
{ id: 1, name: "Alex Messer" },
{ id: 2, name: "Alex Messer" },
{ id: 3, name: "Alex Messer" },
]);
}
})));
it("should execute sub query in joins (using provided sub query builder)", () => Promise.all(connections.map(async connection => {
@ -371,4 +392,4 @@ describe("query builder > sub-query", () => {
]);
})));
});
});

View File

@ -36,7 +36,7 @@ describe("query runner > add column", () => {
column1.isPrimary = true;
// MySql and Sqlite does not supports autoincrement composite primary keys.
if (!(connection.driver instanceof MysqlDriver) && !(connection.driver instanceof AbstractSqliteDriver)) {
if (!(connection.driver instanceof MysqlDriver) && !(connection.driver instanceof AbstractSqliteDriver) && !(connection.driver instanceof CockroachDriver)) {
column1.isGenerated = true;
column1.generationStrategy = "increment";
}
@ -62,7 +62,7 @@ describe("query runner > add column", () => {
column1!.isPrimary.should.be.true;
// MySql and Sqlite does not supports autoincrement composite primary keys.
if (!(connection.driver instanceof MysqlDriver) && !(connection.driver instanceof AbstractSqliteDriver)) {
if (!(connection.driver instanceof MysqlDriver) && !(connection.driver instanceof AbstractSqliteDriver) && !(connection.driver instanceof CockroachDriver)) {
column1!.isGenerated.should.be.true;
column1!.generationStrategy!.should.be.equal("increment");
}

View File

@ -81,6 +81,9 @@ describe("query runner > change column", () => {
it("should correctly change column 'isGenerated' property and revert change", () => Promise.all(connections.map(async connection => {
if (connection.driver instanceof CockroachDriver)
return;
const queryRunner = connection.createQueryRunner();
let table = await queryRunner.getTable("post");
let idColumn = table!.findColumnByName("id")!;
@ -101,34 +104,31 @@ describe("query runner > change column", () => {
table!.findColumnByName("id")!.isGenerated.should.be.false;
expect(table!.findColumnByName("id")!.generationStrategy).to.be.undefined;
// CockroachDB does not allow changing primary key
if (!(connection.driver instanceof CockroachDriver)) {
table = await queryRunner.getTable("post");
idColumn = table!.findColumnByName("id")!;
changedIdColumn = idColumn.clone();
changedIdColumn.isPrimary = false;
await queryRunner.changeColumn(table!, idColumn, changedIdColumn);
table = await queryRunner.getTable("post");
idColumn = table!.findColumnByName("id")!;
changedIdColumn = idColumn.clone();
changedIdColumn.isPrimary = false;
await queryRunner.changeColumn(table!, idColumn, changedIdColumn);
// check case when both primary and generated properties set to true
table = await queryRunner.getTable("post");
idColumn = table!.findColumnByName("id")!;
changedIdColumn = idColumn.clone();
changedIdColumn.isPrimary = true;
changedIdColumn.isGenerated = true;
changedIdColumn.generationStrategy = "increment";
await queryRunner.changeColumn(table!, idColumn, changedIdColumn);
// check case when both primary and generated properties set to true
table = await queryRunner.getTable("post");
idColumn = table!.findColumnByName("id")!;
changedIdColumn = idColumn.clone();
changedIdColumn.isPrimary = true;
changedIdColumn.isGenerated = true;
changedIdColumn.generationStrategy = "increment";
await queryRunner.changeColumn(table!, idColumn, changedIdColumn);
table = await queryRunner.getTable("post");
table!.findColumnByName("id")!.isGenerated.should.be.true;
table!.findColumnByName("id")!.generationStrategy!.should.be.equal("increment");
table = await queryRunner.getTable("post");
table!.findColumnByName("id")!.isGenerated.should.be.true;
table!.findColumnByName("id")!.generationStrategy!.should.be.equal("increment");
await queryRunner.executeMemoryDownSql();
queryRunner.clearSqlMemory();
await queryRunner.executeMemoryDownSql();
queryRunner.clearSqlMemory();
table = await queryRunner.getTable("post");
table!.findColumnByName("id")!.isGenerated.should.be.false;
expect(table!.findColumnByName("id")!.generationStrategy).to.be.undefined;
}
table = await queryRunner.getTable("post");
table!.findColumnByName("id")!.isGenerated.should.be.false;
expect(table!.findColumnByName("id")!.generationStrategy).to.be.undefined;
await queryRunner.release();

View File

@ -10,7 +10,6 @@ describe("query runner > drop table", () => {
connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
});
});
beforeEach(() => reloadTestingDatabases(connections));

View File

@ -5,7 +5,7 @@ import { PrimaryColumn } from "../../../../../src/decorator/columns/PrimaryColum
@Entity()
export class PostBigInt {
@PrimaryColumn("int")
@PrimaryColumn()
id: number;
@Column()
@ -16,4 +16,4 @@ export class PostBigInt {
})
counter: string;
}
}

View File

@ -5,7 +5,7 @@ import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"
@Entity()
export class Post {
@PrimaryColumn("int")
@PrimaryColumn()
id: number;
@Column()
@ -17,4 +17,4 @@ export class Post {
@Column()
isNew: boolean = false;
}
}

View File

@ -2,4 +2,4 @@ export interface User {
id: number;
firstName: string;
secondName: string;
}
}

View File

@ -17,4 +17,4 @@
"nullable": false
}
}
}
}

View File

@ -5,7 +5,7 @@ import { PrimaryColumn } from "../../../../../src/decorator/columns/PrimaryColum
@Entity()
export class PostBigInt {
@PrimaryColumn("int")
@PrimaryColumn()
id: number;
@Column()
@ -16,4 +16,4 @@ export class PostBigInt {
})
counter: string;
}
}

View File

@ -1,4 +1,5 @@
import "reflect-metadata";
import {CockroachDriver} from "../../../src/driver/cockroachdb/CockroachDriver";
import {closeTestingConnections, createTestingConnections} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
@ -20,10 +21,18 @@ describe("github issues > #423 Cannot use Group as Table name && cannot autoSche
await queryRunner.release();
table!.should.exist;
table!.indices.length.should.be.equal(1);
table!.indices[0].name!.should.be.equal("Groups name");
table!.indices[0].columnNames[0].should.be.equal("name");
table!.indices[0].isUnique!.should.be.true;
// CockroachDB stores unique indices as UNIQUE constraints
if (connection.driver instanceof CockroachDriver) {
table!.uniques.length.should.be.equal(1);
table!.uniques[0].name!.should.be.equal("Groups name");
table!.uniques[0].columnNames[0].should.be.equal("name");
} else {
table!.indices.length.should.be.equal(1);
table!.indices[0].name!.should.be.equal("Groups name");
table!.indices[0].columnNames[0].should.be.equal("name");
table!.indices[0].isUnique!.should.be.true;
}
})));

View File

@ -25,9 +25,9 @@ export class Post {
@JoinColumn()
category: PostCategory;
@Column("integer")
@Column()
updatedColumns: number = 0;
@Column("integer")
@Column()
updatedRelations: number = 0;
}