diff --git a/lib/query/analytic.js b/lib/query/analytic.js index 982550208d..94cde5d8bf 100644 --- a/lib/query/analytic.js +++ b/lib/query/analytic.js @@ -18,7 +18,7 @@ class Analytic { this.grouping = 'columns'; } - partitionBy(column) { + partitionBy(column, direction) { assert( Array.isArray(column) || typeof column === 'string', `The argument to an analytic partitionBy function must be either a string @@ -28,12 +28,12 @@ class Analytic { if (Array.isArray(column)) { this.partitions = this.partitions.concat(column); } else { - this.partitions.push(column); + this.partitions.push({ column: column, order: direction }); } return this; } - orderBy(column) { + orderBy(column, direction) { assert( Array.isArray(column) || typeof column === 'string', `The argument to an analytic orderBy function must be either a string @@ -43,7 +43,7 @@ class Analytic { if (Array.isArray(column)) { this.order = this.order.concat(column); } else { - this.order.push(column); + this.order.push({ column: column, order: direction }); } return this; } diff --git a/lib/query/querybuilder.js b/lib/query/querybuilder.js index 70fceb6737..7b7e76ef1c 100644 --- a/lib/query/querybuilder.js +++ b/lib/query/querybuilder.js @@ -1247,15 +1247,18 @@ class Builder extends EventEmitter { typeof second === 'function' || second.isRawInstance || Array.isArray(second) || - typeof second === 'string', + typeof second === 'string' || + typeof second === 'object', `The second argument to an analytic function must be either a function, a raw, - an array of string or a single string.` + an array of string or object, an object or a single string.` ); if (third) { assert( - Array.isArray(third) || typeof third === 'string', - 'The third argument to an analytic function must be either a string or an array of string.' + Array.isArray(third) || + typeof third === 'string' || + typeof third === 'object', + 'The third argument to an analytic function must be either a string, an array of string or object or an object.' ); } @@ -1272,9 +1275,9 @@ class Builder extends EventEmitter { alias: alias, }; } else { - const order = typeof second === 'string' ? [second] : second; + const order = !Array.isArray(second) ? [second] : second; let partitions = third || []; - partitions = typeof partitions === 'string' ? [partitions] : partitions; + partitions = !Array.isArray(partitions) ? [partitions] : partitions; analytic = { grouping: 'columns', type: 'analytic', diff --git a/lib/query/querycompiler.js b/lib/query/querycompiler.js index 47f2e8ef04..e446b3b84d 100644 --- a/lib/query/querycompiler.js +++ b/lib/query/querycompiler.js @@ -1035,13 +1035,17 @@ class QueryCompiler { sql += 'partition by '; sql += map(stmt.partitions, function (partition) { - return self.formatter.columnize(partition); + if (isString(partition)) { + return self.formatter.columnize(partition); + } else return self.formatter.columnize(partition.column) + (partition.order ? ' ' + partition.order : ''); }).join(', ') + ' '; } sql += 'order by '; sql += map(stmt.order, function (order) { - return self.formatter.columnize(order); + if (isString(order)) { + return self.formatter.columnize(order); + } else return self.formatter.columnize(order.column) + (order.order ? ' ' + order.order : ''); }).join(', '); } diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js index e6e4598508..a240cc8292 100644 --- a/test/unit/query/builder.js +++ b/test/unit/query/builder.js @@ -7854,6 +7854,29 @@ describe('QueryBuilder', () => { ); }); + it('row_number with object', function () { + testsql( + qb() + .select('*') + .from('accounts') + .rowNumber(null, { column: 'email', order: 'asc' }, [ + { column: 'address', order: 'asc' }, + 'phone', + ]), + { + mssql: { + sql: 'select *, row_number() over (partition by [address] asc, [phone] order by [email] asc) from [accounts]', + }, + pg: { + sql: 'select *, row_number() over (partition by "address" asc, "phone" order by "email" asc) from "accounts"', + }, + oracledb: { + sql: 'select *, row_number() over (partition by "address" asc, "phone" order by "email" asc) from "accounts"', + }, + } + ); + }); + it('row_number with string', function () { testsql( qb().select('*').from('accounts').rowNumber(null, 'email', 'address'), diff --git a/types/index.d.ts b/types/index.d.ts index 222a144546..a33841a55e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1083,15 +1083,15 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( raw: Raw ): QueryBuilder; < TTable extends TableNames, TRecord2 = ResolveTableType & - ResolveTableType>, + ResolveTableType>, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TTable, clause: JoinCallback ): QueryBuilder; @@ -1099,7 +1099,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, clause: JoinCallback ): QueryBuilder; @@ -1107,7 +1107,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, columns: { [key: string]: string | number | boolean | Raw } ): QueryBuilder; @@ -1115,7 +1115,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, raw: Raw ): QueryBuilder; @@ -1127,7 +1127,7 @@ export declare namespace Knex { TRecord1 = ResolveTableType, TRecord2 = TRecord1 & ResolveTableType>, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TTable2, column1: `${TTable1}.${TKey1}`, column2: `${TTable2}.${TKey2}` @@ -1140,7 +1140,7 @@ export declare namespace Knex { TRecord1 = ResolveTableType, TRecord2 = TRecord1 & ResolveTableType>, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TTable2, column1: `${TTable2}.${TKey2}`, column2: `${TTable1}.${TKey1}` @@ -1149,7 +1149,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, column1: string, column2: string @@ -1158,7 +1158,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, column1: string, raw: Raw @@ -1171,7 +1171,7 @@ export declare namespace Knex { TRecord1 = ResolveTableType, TRecord2 = TRecord1 & ResolveTableType>, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TTable2, column1: `${TTable1}.${TKey1}`, operator: string, @@ -1185,7 +1185,7 @@ export declare namespace Knex { TRecord1 = ResolveTableType, TRecord2 = TRecord1 & ResolveTableType>, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TTable2, column1: `${TTable2}.${TKey2}`, operator: string, @@ -1195,7 +1195,7 @@ export declare namespace Knex { TJoinTargetRecord extends {} = any, TRecord2 extends {} = TRecord & TJoinTargetRecord, TResult2 = DeferredKeySelection.ReplaceBase - >( + >( tableName: TableDescriptor | AliasDict | QueryCallback, column1: string, operator: string, @@ -1267,7 +1267,7 @@ export declare namespace Knex { interface With extends WithRaw, - WithWrapped {} + WithWrapped {} interface WithRaw { (alias: string, raw: Raw | QueryBuilder): QueryBuilder; @@ -1415,7 +1415,7 @@ export declare namespace Knex { < TOptions extends { "as": string }, TResult2 = AggregationQueryResult - >( + >( columnName: Readonly>, options: Readonly ): QueryBuilder; @@ -1425,7 +1425,7 @@ export declare namespace Knex { < TAliases extends {} = Record, TResult2 = AggregationQueryResult - >(aliases: TAliases): QueryBuilder; + >(aliases: TAliases): QueryBuilder; >>( ...columnNames: ReadonlyArray> | Knex.Raw | string> ): QueryBuilder; @@ -1438,14 +1438,14 @@ export declare namespace Knex { TResult2 = AggregationQueryResult[TKey] }> - >( + >( columnName: Readonly, options: Readonly ): QueryBuilder; < TKey extends keyof ResolveTableType, TResult2 = AggregationQueryResult[TKey]>> - >( + >( ...columnNames: readonly TKey[] ): QueryBuilder; < @@ -1454,10 +1454,10 @@ export declare namespace Knex { // We have optional here because in most dialects aggregating by multiple keys simultaneously // causes rest of the keys to be dropped and only first to be considered [K in keyof TAliases]?: K extends keyof TRecord ? - TRecord[K] : - TValue + TRecord[K] : + TValue }> - >(aliases: TAliases): QueryBuilder; + >(aliases: TAliases): QueryBuilder; >>( ...columnNames: ReadonlyArray> | Knex.Raw | string> ): QueryBuilder; @@ -1467,20 +1467,20 @@ export declare namespace Knex { < TAlias extends string, TResult2 = AggregationQueryResult - >(alias: TAlias, raw: Raw | QueryCallback): QueryBuilder; + >(alias: TAlias, raw: Raw | QueryCallback): QueryBuilder; < TAlias extends string, TKey extends keyof ResolveTableType, TResult2 = AggregationQueryResult - >(alias: TAlias, orderBy: TKey | TKey[], partitionBy?: TKey | TKey[]): QueryBuilder< - TRecord, - TResult2 - >; + >(alias: TAlias, orderBy: TKey | TKey[] | { columnName: TKey, order?: 'asc' | 'desc' }, partitionBy?: TKey | TKey[] | { columnName: TKey, order?: 'asc' | 'desc' }): QueryBuilder< + TRecord, + TResult2 + >; } interface GroupBy extends RawQueryBuilder, - ColumnNameQueryBuilder {} + ColumnNameQueryBuilder {} interface OrderBy { (columnName: keyof TRecord | QueryBuilder, order?: 'asc' | 'desc'): QueryBuilder< @@ -1505,8 +1505,7 @@ export declare namespace Knex { } interface PartitionBy - extends RawQueryBuilder, - ColumnNameQueryBuilder {} + extends OrderBy {} interface Intersect { ( @@ -1566,7 +1565,7 @@ export declare namespace Knex { ResolveTableType, ColNameUT & string >[] - >( + >( ...columnNames: readonly ColNameUT[] ): QueryBuilder; @@ -1577,7 +1576,7 @@ export declare namespace Knex { ResolveTableType, ColNameUT & string >[] - >( + >( columnNames: readonly ColNameUT[] ): QueryBuilder; @@ -1589,7 +1588,7 @@ export declare namespace Knex { SafePartial, keyof TRecord & string >[] - >( + >( ...columnNames: readonly ColumnDescriptor[] ): QueryBuilder; @@ -1599,7 +1598,7 @@ export declare namespace Knex { SafePartial, keyof TRecord & string >[] - >( + >( columnNames: readonly ColumnDescriptor[] ): QueryBuilder; } @@ -1621,7 +1620,7 @@ export declare namespace Knex { interface Raw extends events.EventEmitter, - ChainableInterface> { + ChainableInterface> { timeout(ms: number, options?: {cancel?: boolean}): Raw; wrap(before: string, after: string): Raw; toSQL(): Sql; @@ -1638,27 +1637,27 @@ export declare namespace Knex { const RefMemberTag: unique symbol; interface Ref extends Raw { - // TypeScript can behave weirdly if type parameters are not - // actually used in the members of type. - // - // See: https://github.com/knex/knex/issues/3932 - // - // We simply need to propagate the type context so that we can extract - // them later, but we just add a "phantom" property so that typescript - // doesn't think that these parameters are unused - // - // Because unique symbol is used here, there is no way to actually - // access this at runtime - [RefMemberTag]: { - src: TSrc, - mapping: TMapping - }; - withSchema(schema: string): this; - as(alias: TAlias): Ref; + // TypeScript can behave weirdly if type parameters are not + // actually used in the members of type. + // + // See: https://github.com/knex/knex/issues/3932 + // + // We simply need to propagate the type context so that we can extract + // them later, but we just add a "phantom" property so that typescript + // doesn't think that these parameters are unused + // + // Because unique symbol is used here, there is no way to actually + // access this at runtime + [RefMemberTag]: { + src: TSrc, + mapping: TMapping + }; + withSchema(schema: string): this; + as(alias: TAlias): Ref; } interface RefBuilder { - (src: TSrc): Ref; + (src: TSrc): Ref; } interface BatchInsertBuilder extends Promise> { @@ -1671,18 +1670,18 @@ export declare namespace Knex { UnwrapArrayMember, ResolveTableType, TKey - >[] - >( - column: TKey + >[] + >( + column: TKey ): BatchInsertBuilder; returning< TKey extends StrKey>, TResult2 = DeferredKeySelection.SetSingle< DeferredKeySelection.Augment, ResolveTableType, TKey>, false - >[] - >( - columns: readonly TKey[] + >[] + >( + columns: readonly TKey[] ): BatchInsertBuilder; // if data with specific type passed, exclude this method returning[]>( @@ -1708,9 +1707,9 @@ export declare namespace Knex { interface QueryBuilder< TRecord extends {} = any, TResult = any - > + > extends QueryInterface, - ChainableInterface> { + ChainableInterface> { client: Client; or: QueryBuilder; not: QueryBuilder; @@ -2060,105 +2059,105 @@ export declare namespace Knex { | 'azure-active-directory-msi-app-service' | 'azure-active-directory-service-principal-secret'; -interface MsSqlDefaultAuthenticationConfig extends MsSqlConnectionConfigBase { - type?: 'default' | never; -} - -interface MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig - extends MsSqlConnectionConfigBase { - type: 'azure-active-directory-msi-app-service'; - /** - * If you user want to connect to an Azure app service using a specific client account - * they need to provide `clientId` asscoiate to their created idnetity. - * - * This is optional for retrieve token from azure web app service - */ - clientId?: string; - /** - * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken. - */ - msiEndpoint?: string; - /** - * A msi app service environment need to provide `msiSecret` for retriving the accesstoken. - */ - msiSecret?: string; -} - -interface MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig - extends MsSqlConnectionConfigBase { - type: 'azure-active-directory-msi-vm'; - /** - * If you user want to connect to an Azure app service using a specific client account - * they need to provide `clientId` asscoiate to their created idnetity. - * - * This is optional for retrieve token from azure web app service - */ - clientId?: string; - /** - * A user need to provide `msiEndpoint` for retriving the accesstoken. - */ - msiEndpoint?: string; -} - -interface MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig - extends MsSqlConnectionConfigBase { - type: 'azure-active-directory-access-token'; - /** - * A user-provided access token - */ - token: string; -} -interface MsSqlAzureActiveDirectoryPasswordAuthenticationConfig - extends MsSqlConnectionConfigBase { - type: 'azure-active-directory-password'; - /** - * Optional parameter for specific Azure tenant ID - */ - domain: string; - userName: string; - password: string; -} - -interface MsSqlAzureActiveDirectoryServicePrincipalSecretConfig - extends MsSqlConnectionConfigBase { - type: 'azure-active-directory-service-principal-secret'; - /** - * Application (`client`) ID from your registered Azure application - */ - clientId: string; - /** - * The created `client secret` for this registered Azure application - */ - clientSecret: string; - /** - * Directory (`tenant`) ID from your registered Azure application - */ - tenantId: string; -} + interface MsSqlDefaultAuthenticationConfig extends MsSqlConnectionConfigBase { + type?: 'default' | never; + } + + interface MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig + extends MsSqlConnectionConfigBase { + type: 'azure-active-directory-msi-app-service'; + /** + * If you user want to connect to an Azure app service using a specific client account + * they need to provide `clientId` asscoiate to their created idnetity. + * + * This is optional for retrieve token from azure web app service + */ + clientId?: string; + /** + * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken. + */ + msiEndpoint?: string; + /** + * A msi app service environment need to provide `msiSecret` for retriving the accesstoken. + */ + msiSecret?: string; + } + + interface MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig + extends MsSqlConnectionConfigBase { + type: 'azure-active-directory-msi-vm'; + /** + * If you user want to connect to an Azure app service using a specific client account + * they need to provide `clientId` asscoiate to their created idnetity. + * + * This is optional for retrieve token from azure web app service + */ + clientId?: string; + /** + * A user need to provide `msiEndpoint` for retriving the accesstoken. + */ + msiEndpoint?: string; + } + + interface MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig + extends MsSqlConnectionConfigBase { + type: 'azure-active-directory-access-token'; + /** + * A user-provided access token + */ + token: string; + } + interface MsSqlAzureActiveDirectoryPasswordAuthenticationConfig + extends MsSqlConnectionConfigBase { + type: 'azure-active-directory-password'; + /** + * Optional parameter for specific Azure tenant ID + */ + domain: string; + userName: string; + password: string; + } -interface MsSqlNtlmAuthenticationConfig extends MsSqlConnectionConfigBase { - type: 'ntlm'; - /** - * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login. - * - * This is necessary for forming a connection using ntlm type - */ - domain: string; - userName: string; - password: string; -} + interface MsSqlAzureActiveDirectoryServicePrincipalSecretConfig + extends MsSqlConnectionConfigBase { + type: 'azure-active-directory-service-principal-secret'; + /** + * Application (`client`) ID from your registered Azure application + */ + clientId: string; + /** + * The created `client secret` for this registered Azure application + */ + clientSecret: string; + /** + * Directory (`tenant`) ID from your registered Azure application + */ + tenantId: string; + } + + interface MsSqlNtlmAuthenticationConfig extends MsSqlConnectionConfigBase { + type: 'ntlm'; + /** + * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login. + * + * This is necessary for forming a connection using ntlm type + */ + domain: string; + userName: string; + password: string; + } -type MsSqlConnectionConfig = - | MsSqlDefaultAuthenticationConfig - | MsSqlNtlmAuthenticationConfig - | MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig - | MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig - | MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig - | MsSqlAzureActiveDirectoryPasswordAuthenticationConfig - | MsSqlAzureActiveDirectoryServicePrincipalSecretConfig; + type MsSqlConnectionConfig = + | MsSqlDefaultAuthenticationConfig + | MsSqlNtlmAuthenticationConfig + | MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig + | MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig + | MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig + | MsSqlAzureActiveDirectoryPasswordAuthenticationConfig + | MsSqlAzureActiveDirectoryServicePrincipalSecretConfig; // Config object for tedious: see http://tediousjs.github.io/tedious/api-connection.html -interface MsSqlConnectionConfigBase { + interface MsSqlConnectionConfigBase { type?: MsSqlAuthenticationTypeOptions; driver?: string; @@ -2367,14 +2366,14 @@ interface MsSqlConnectionConfigBase { } interface Migration { - up: (knex: Knex) => PromiseLike; - down?: (kenx: Knex) => PromiseLike; + up: (knex: Knex) => PromiseLike; + down?: (kenx: Knex) => PromiseLike; } interface MigrationSource { - getMigrations(loadExtensions: readonly string[]): Promise; - getMigrationName(migration: TMigrationSpec): string; - getMigration(migration: TMigrationSpec): Migration; + getMigrations(loadExtensions: readonly string[]): Promise; + getMigrationName(migration: TMigrationSpec): string; + getMigration(migration: TMigrationSpec): Migration; } interface MigratorConfig { @@ -2478,9 +2477,9 @@ interface MsSqlConnectionConfigBase { initializeDriver(): void; driver: any; poolDefaults(): { - min: number; - max: number; - propagateCreateError: boolean; + min: number; + max: number; + propagateCreateError: boolean; }; getPoolSettings(poolConfig: any): any; initializePool(config?: {}): void;