import { Alias } from "./Alias" import { ObjectLiteral } from "../common/ObjectLiteral" import { OrderByCondition } from "../find-options/OrderByCondition" import { JoinAttribute } from "./JoinAttribute" import { QueryBuilder } from "./QueryBuilder" import { QueryBuilderCteOptions } from "./QueryBuilderCte" import { RelationIdAttribute } from "./relation-id/RelationIdAttribute" import { RelationCountAttribute } from "./relation-count/RelationCountAttribute" import { DataSource } from "../data-source/DataSource" import { EntityMetadata } from "../metadata/EntityMetadata" import { SelectQuery } from "./SelectQuery" import { ColumnMetadata } from "../metadata/ColumnMetadata" import { RelationMetadata } from "../metadata/RelationMetadata" import { SelectQueryBuilderOption } from "./SelectQueryBuilderOption" import { TypeORMError } from "../error" import { WhereClause } from "./WhereClause" import { UpsertType } from "../driver/types/UpsertType" import { CockroachConnectionOptions } from "../driver/cockroachdb/CockroachConnectionOptions" /** * Contains all properties of the QueryBuilder that needs to be build a final query. */ export class QueryExpressionMap { // ------------------------------------------------------------------------- // Public Properties // ------------------------------------------------------------------------- /** * Strategy to load relations. */ relationLoadStrategy: "join" | "query" = "join" /** * Indicates if QueryBuilder used to select entities and not a raw results. */ queryEntity: boolean = false /** * Main alias is a main selection object selected by QueryBuilder. */ mainAlias?: Alias /** * All aliases (including main alias) used in the query. */ aliases: Alias[] = [] /** * Represents query type. QueryBuilder is able to build SELECT, UPDATE and DELETE queries. */ queryType: | "select" | "update" | "delete" | "insert" | "relation" | "soft-delete" | "restore" = "select" /** * Data needs to be SELECT-ed. */ selects: SelectQuery[] = [] /** * Max execution time in millisecond. */ maxExecutionTime: number = 0 /** * Whether SELECT is DISTINCT. */ selectDistinct: boolean = false /** * SELECT DISTINCT ON query (postgres). */ selectDistinctOn: string[] = [] /** * FROM-s to be selected. */ // froms: { target: string, alias: string }[] = []; /** * If update query was used, it needs "update set" - properties which will be updated by this query. * If insert query was used, it needs "insert set" - values that needs to be inserted. */ valuesSet?: ObjectLiteral | ObjectLiteral[] /** * Optional returning (or output) clause for insert, update or delete queries. */ returning: string | string[] /** * Extra returning columns to be added to the returning statement if driver supports it. */ extraReturningColumns: ColumnMetadata[] = [] /** * Optional on conflict statement used in insertion query in postgres. */ onConflict: string = "" /** * Optional on ignore statement used in insertion query in databases. */ onIgnore: boolean = false /** * Optional on update statement used in insertion query in databases. */ onUpdate: { conflict?: string | string[] columns?: string[] overwrite?: string[] skipUpdateIfNoValuesChanged?: boolean indexPredicate?: string upsertType?: UpsertType } /** * JOIN queries. */ joinAttributes: JoinAttribute[] = [] /** * RelationId queries. */ relationIdAttributes: RelationIdAttribute[] = [] /** * Relation count queries. */ relationCountAttributes: RelationCountAttribute[] = [] /** * WHERE queries. */ wheres: WhereClause[] = [] /** * HAVING queries. */ havings: { type: "simple" | "and" | "or"; condition: string }[] = [] /** * ORDER BY queries. */ orderBys: OrderByCondition = {} /** * GROUP BY queries. */ groupBys: string[] = [] /** * LIMIT query. */ limit?: number /** * OFFSET query. */ offset?: number /** * Number of rows to skip of result using pagination. */ skip?: number /** * Number of rows to take using pagination. */ take?: number /** * Use certain index for the query. * * SELECT * FROM table_name USE INDEX (col1_index, col2_index) WHERE col1=1 AND col2=2 AND col3=3; */ useIndex?: string /** * Locking mode. */ lockMode?: | "optimistic" | "pessimistic_read" | "pessimistic_write" | "dirty_read" /* "pessimistic_partial_write" and "pessimistic_write_or_fail" are deprecated and will be removed in a future version. Use onLocked instead. */ | "pessimistic_partial_write" | "pessimistic_write_or_fail" | "for_no_key_update" | "for_key_share" /** * Current version of the entity, used for locking. */ lockVersion?: number | Date /** * Tables to be specified in the "FOR UPDATE OF" clause, referred by their alias */ lockTables?: string[] /** * Modify behavior when encountering locked rows. NOWAIT or SKIP LOCKED */ onLocked?: "nowait" | "skip_locked" /** * Indicates if soft-deleted rows should be included in entity result. * By default the soft-deleted rows are not included. */ withDeleted: boolean = false /** * Parameters used to be escaped in final query. */ parameters: ObjectLiteral = {} /** * Indicates if alias, table names and column names will be escaped by driver, or not. * * todo: rename to isQuotingDisabled, also think if it should be named "escaping" */ disableEscaping: boolean = true /** * Indicates if virtual columns should be included in entity result. * * todo: what to do with it? is it properly used? what about persistence? */ enableRelationIdValues: boolean = false /** * Extra where condition appended to the end of original where conditions with AND keyword. * Original condition will be wrapped into brackets. */ extraAppendedAndWhereCondition: string = "" /** * Indicates if query builder creates a subquery. */ subQuery: boolean = false /** * Indicates if property names are prefixed with alias names during property replacement. * By default this is enabled, however we need this because aliases are not supported in UPDATE and DELETE queries, * but user can use them in WHERE expressions. */ aliasNamePrefixingEnabled: boolean = true /** * Indicates if query result cache is enabled or not. */ cache: boolean = false /** * Time in milliseconds in which cache will expire. * If not set then global caching time will be used. */ cacheDuration: number /** * Cache id. * Used to identifier your cache queries. */ cacheId: string /** * Options that define QueryBuilder behaviour. */ options: SelectQueryBuilderOption[] = [] /** * Property path of relation to work with. * Used in relational query builder. */ relationPropertyPath: string /** * Entity (target) which relations will be updated. */ of: any | any[] /** * List of columns where data should be inserted. * Used in INSERT query. */ insertColumns: string[] = [] /** * Used if user wants to update or delete a specific entities. */ whereEntities: ObjectLiteral[] = [] /** * Indicates if entity must be updated after insertion / updation. * This may produce extra query or use RETURNING / OUTPUT statement (depend on database). */ updateEntity: boolean = true /** * Indicates if listeners and subscribers must be called before and after query execution. */ callListeners: boolean = true /** * Indicates if query must be wrapped into transaction. */ useTransaction: boolean = false /** * Indicates if query should be time travel query * https://www.cockroachlabs.com/docs/stable/as-of-system-time.html */ timeTravel?: boolean | string /** * Extra parameters. * * @deprecated Use standard parameters instead */ nativeParameters: ObjectLiteral = {} /** * Query Comment to include extra information for debugging or other purposes. */ comment?: string /** * Items from an entity that have been locally generated & are recorded here for later use. * Examples include the UUID generation when the database does not natively support it. * These are included in the entity index order. */ locallyGenerated: { [key: number]: ObjectLiteral } = {} commonTableExpressions: { queryBuilder: QueryBuilder | string alias: string options: QueryBuilderCteOptions }[] = [] // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(protected connection: DataSource) { if (connection.options.relationLoadStrategy) { this.relationLoadStrategy = connection.options.relationLoadStrategy } this.timeTravel = (connection.options as CockroachConnectionOptions) ?.timeTravelQueries || false } // ------------------------------------------------------------------------- // Accessors // ------------------------------------------------------------------------- /** * Get all ORDER BY queries - if order by is specified by user then it uses them, * otherwise it uses default entity order by if it was set. */ get allOrderBys() { if ( !Object.keys(this.orderBys).length && this.mainAlias!.hasMetadata && this.options.indexOf("disable-global-order") === -1 ) { const entityOrderBy = this.mainAlias!.metadata.orderBy || {} return Object.keys(entityOrderBy).reduce((orderBy, key) => { orderBy[this.mainAlias!.name + "." + key] = entityOrderBy[key] return orderBy }, {} as OrderByCondition) } return this.orderBys } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Creates a main alias and adds it to the current expression map. */ setMainAlias(alias: Alias): Alias { // if main alias is already set then remove it from the array // if (this.mainAlias) // this.aliases.splice(this.aliases.indexOf(this.mainAlias)); // set new main alias this.mainAlias = alias return alias } /** * Creates a new alias and adds it to the current expression map. */ createAlias(options: { type: "from" | "select" | "join" | "other" name?: string target?: Function | string tablePath?: string subQuery?: string metadata?: EntityMetadata }): Alias { let aliasName = options.name if (!aliasName && options.tablePath) aliasName = options.tablePath if (!aliasName && typeof options.target === "function") aliasName = options.target.name if (!aliasName && typeof options.target === "string") aliasName = options.target const alias = new Alias() alias.type = options.type if (aliasName) alias.name = aliasName if (options.metadata) alias.metadata = options.metadata if (options.target && !alias.hasMetadata) alias.metadata = this.connection.getMetadata(options.target) if (options.tablePath) alias.tablePath = options.tablePath if (options.subQuery) alias.subQuery = options.subQuery this.aliases.push(alias) return alias } /** * Finds alias with the given name. * If alias was not found it throw an exception. */ findAliasByName(aliasName: string): Alias { const alias = this.aliases.find((alias) => alias.name === aliasName) if (!alias) throw new TypeORMError( `"${aliasName}" alias was not found. Maybe you forgot to join it?`, ) return alias } findColumnByAliasExpression( aliasExpression: string, ): ColumnMetadata | undefined { const [aliasName, propertyPath] = aliasExpression.split(".") const alias = this.findAliasByName(aliasName) return alias.metadata.findColumnWithPropertyName(propertyPath) } /** * Gets relation metadata of the relation this query builder works with. * * todo: add proper exceptions */ get relationMetadata(): RelationMetadata { if (!this.mainAlias) throw new TypeORMError(`Entity to work with is not specified!`) // todo: better message const relationMetadata = this.mainAlias.metadata.findRelationWithPropertyPath( this.relationPropertyPath, ) if (!relationMetadata) throw new TypeORMError( `Relation ${this.relationPropertyPath} was not found in entity ${this.mainAlias.name}`, ) // todo: better message return relationMetadata } /** * Copies all properties of the current QueryExpressionMap into a new one. * Useful when QueryBuilder needs to create a copy of itself. */ clone(): QueryExpressionMap { const map = new QueryExpressionMap(this.connection) map.queryType = this.queryType map.selects = this.selects.map((select) => select) map.maxExecutionTime = this.maxExecutionTime map.selectDistinct = this.selectDistinct map.selectDistinctOn = this.selectDistinctOn this.aliases.forEach((alias) => map.aliases.push(new Alias(alias))) map.relationLoadStrategy = this.relationLoadStrategy map.mainAlias = this.mainAlias map.valuesSet = this.valuesSet map.returning = this.returning map.onConflict = this.onConflict map.onIgnore = this.onIgnore map.onUpdate = this.onUpdate map.joinAttributes = this.joinAttributes.map( (join) => new JoinAttribute(this.connection, this, join), ) map.relationIdAttributes = this.relationIdAttributes.map( (relationId) => new RelationIdAttribute(this, relationId), ) map.relationCountAttributes = this.relationCountAttributes.map( (relationCount) => new RelationCountAttribute(this, relationCount), ) map.wheres = this.wheres.map((where) => ({ ...where })) map.havings = this.havings.map((having) => ({ ...having })) map.orderBys = Object.assign({}, this.orderBys) map.groupBys = this.groupBys.map((groupBy) => groupBy) map.limit = this.limit map.offset = this.offset map.skip = this.skip map.take = this.take map.lockMode = this.lockMode map.onLocked = this.onLocked map.lockVersion = this.lockVersion map.lockTables = this.lockTables map.withDeleted = this.withDeleted map.parameters = Object.assign({}, this.parameters) map.disableEscaping = this.disableEscaping map.enableRelationIdValues = this.enableRelationIdValues map.extraAppendedAndWhereCondition = this.extraAppendedAndWhereCondition map.subQuery = this.subQuery map.aliasNamePrefixingEnabled = this.aliasNamePrefixingEnabled map.cache = this.cache map.cacheId = this.cacheId map.cacheDuration = this.cacheDuration map.relationPropertyPath = this.relationPropertyPath map.of = this.of map.insertColumns = this.insertColumns map.whereEntities = this.whereEntities map.updateEntity = this.updateEntity map.callListeners = this.callListeners map.useTransaction = this.useTransaction map.timeTravel = this.timeTravel map.nativeParameters = Object.assign({}, this.nativeParameters) map.comment = this.comment map.commonTableExpressions = this.commonTableExpressions.map( (cteOptions) => ({ alias: cteOptions.alias, queryBuilder: typeof cteOptions.queryBuilder === "string" ? cteOptions.queryBuilder : cteOptions.queryBuilder.clone(), options: cteOptions.options, }), ) return map } }