/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder, TypeORMError } from 'typeorm'; import { DriverUtils } from 'typeorm/driver/DriverUtils.js'; import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js'; import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js'; import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js'; import { OrmUtils } from 'typeorm/util/OrmUtils.js'; import { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { MiAccessToken } from '@/models/AccessToken.js'; import { MiAd } from '@/models/Ad.js'; import { MiAnnouncement } from '@/models/Announcement.js'; import { MiAnnouncementRead } from '@/models/AnnouncementRead.js'; import { MiAntenna } from '@/models/Antenna.js'; import { MiApp } from '@/models/App.js'; import { MiAvatarDecoration } from '@/models/AvatarDecoration.js'; import { MiAuthSession } from '@/models/AuthSession.js'; import { MiBlocking } from '@/models/Blocking.js'; import { MiChannelFollowing } from '@/models/ChannelFollowing.js'; import { MiChannelFavorite } from '@/models/ChannelFavorite.js'; import { MiClip } from '@/models/Clip.js'; import { MiClipNote } from '@/models/ClipNote.js'; import { MiClipFavorite } from '@/models/ClipFavorite.js'; import { MiDriveFile } from '@/models/DriveFile.js'; import { MiDriveFolder } from '@/models/DriveFolder.js'; import { MiEmoji } from '@/models/Emoji.js'; import { MiFollowing } from '@/models/Following.js'; import { MiFollowRequest } from '@/models/FollowRequest.js'; import { MiGalleryLike } from '@/models/GalleryLike.js'; import { MiGalleryPost } from '@/models/GalleryPost.js'; import { MiHashtag } from '@/models/Hashtag.js'; import { MiInstance } from '@/models/Instance.js'; import { MiMeta } from '@/models/Meta.js'; import { MiModerationLog } from '@/models/ModerationLog.js'; import { MiMuting } from '@/models/Muting.js'; import { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { MiNote } from '@/models/Note.js'; import { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { MiNoteReaction } from '@/models/NoteReaction.js'; import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js'; import { MiNoteUnread } from '@/models/NoteUnread.js'; import { MiPage } from '@/models/Page.js'; import { MiPageLike } from '@/models/PageLike.js'; import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js'; import { MiPoll } from '@/models/Poll.js'; import { MiPollVote } from '@/models/PollVote.js'; import { MiPromoNote } from '@/models/PromoNote.js'; import { MiPromoRead } from '@/models/PromoRead.js'; import { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; import { MiRegistryItem } from '@/models/RegistryItem.js'; import { MiRelay } from '@/models/Relay.js'; import { MiSignin } from '@/models/Signin.js'; import { MiSwSubscription } from '@/models/SwSubscription.js'; import { MiUsedUsername } from '@/models/UsedUsername.js'; import { MiUser } from '@/models/User.js'; import { MiUserIp } from '@/models/UserIp.js'; import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserList } from '@/models/UserList.js'; import { MiUserListMembership } from '@/models/UserListMembership.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; import { MiUserPublickey } from '@/models/UserPublickey.js'; import { MiUserSecurityKey } from '@/models/UserSecurityKey.js'; import { MiUserMemo } from '@/models/UserMemo.js'; import { MiWebhook } from '@/models/Webhook.js'; import { MiChannel } from '@/models/Channel.js'; import { MiRetentionAggregation } from '@/models/RetentionAggregation.js'; import { MiRole } from '@/models/Role.js'; import { MiRoleAssignment } from '@/models/RoleAssignment.js'; import { MiFlash } from '@/models/Flash.js'; import { MiFlashLike } from '@/models/FlashLike.js'; import { MiUserListFavorite } from '@/models/UserListFavorite.js'; import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiReversiGame } from '@/models/ReversiGame.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; export interface MiRepository { createTableColumnNames(this: Repository & MiRepository, queryBuilder: InsertQueryBuilder): string[]; insertOne(this: Repository & MiRepository, entity: QueryDeepPartialEntity, findOptions?: Pick, 'relations'>): Promise; selectAliasColumnNames(this: Repository & MiRepository, queryBuilder: InsertQueryBuilder, builder: SelectQueryBuilder): void; } export const miRepository = { createTableColumnNames(queryBuilder) { // @ts-expect-error -- protected const insertedColumns = queryBuilder.getInsertedColumns(); if (insertedColumns.length) { return insertedColumns.map(column => column.databaseName); } if (!queryBuilder.expressionMap.mainAlias?.hasMetadata && !queryBuilder.expressionMap.insertColumns.length) { // @ts-expect-error -- protected const valueSets = queryBuilder.getValueSets(); if (valueSets.length === 1) { return Object.keys(valueSets[0]); } } return queryBuilder.expressionMap.insertColumns; }, async insertOne(entity, findOptions?) { const queryBuilder = this.createQueryBuilder().insert().values(entity); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const mainAlias = queryBuilder.expressionMap.mainAlias!; const name = mainAlias.name; mainAlias.name = 't'; const columnNames = this.createTableColumnNames(queryBuilder); queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2)); const builder = this.createQueryBuilder().addCommonTableExpression(queryBuilder, 'cte', { columnNames }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion builder.expressionMap.mainAlias!.tablePath = 'cte'; this.selectAliasColumnNames(queryBuilder, builder); if (findOptions) { builder.setFindOptions(findOptions); // @ts-expect-error -- protected builder.createJoinExpression = function(this: SelectQueryBuilder) { const joins = this.expressionMap.joinAttributes.map((joinAttr) => { /* eslint-disable @typescript-eslint/no-non-null-assertion */ const relation = joinAttr.relation!; const destinationTableName = joinAttr.tablePath; const destinationTableAlias = joinAttr.alias.name; let appendedCondition = joinAttr.condition ? ` AND (${joinAttr.condition})` : ''; const parentAlias = joinAttr.parentAlias; if (relation.isManyToOne || relation.isOneToOneOwner) { console.log(relation); const condition = relation.joinColumns.map((joinColumn) => `${destinationTableAlias}.${joinColumn.referencedColumn!.propertyPath}="${parentAlias}_${joinColumn.propertyPath}"`).join(' AND '); return ` ${joinAttr.direction} JOIN ${this.getTableName(destinationTableName)} ${this.escape(destinationTableAlias)}${ // @ts-expect-error -- private this.createTableLockExpression() } ON ${condition}${appendedCondition}`; } else if (relation.isOneToMany || relation.isOneToOneNotOwner) { const condition = relation.inverseRelation!.joinColumns.map((joinColumn) => { if (relation.inverseEntityMetadata.tableType === 'entity-child' && relation.inverseEntityMetadata.discriminatorColumn) { appendedCondition += ` AND ${destinationTableAlias}.${relation.inverseEntityMetadata.discriminatorColumn.databaseName}='${relation.inverseEntityMetadata.discriminatorValue}'`; } return `${destinationTableAlias}.${relation.inverseRelation!.propertyPath}.${joinColumn.referencedColumn!.propertyPath}="${parentAlias}_${joinColumn.propertyPath}"`; }).join(' AND '); if (!condition) { throw new TypeORMError(`Relation ${relation.entityMetadata.name}.${relation.propertyName} does not have join columns.`); } return ` ${joinAttr.direction} JOIN ${this.getTableName(destinationTableName)} ${this.escape(destinationTableAlias)}${ // @ts-expect-error -- private this.createTableLockExpression() } ON ${condition}${appendedCondition}`; } else { const junctionTableName = relation.junctionEntityMetadata!.tablePath; const junctionAlias = joinAttr.junctionAlias; let junctionCondition = '', destinationCondition = ''; if (relation.isOwning) { junctionCondition = relation.joinColumns.map((joinColumn) => `${junctionAlias}.${joinColumn.propertyPath}=${parentAlias}_${joinColumn.referencedColumn!.propertyPath}`).join(' AND '); destinationCondition = relation.inverseJoinColumns.map((joinColumn) => `${destinationTableAlias}.${joinColumn.referencedColumn!.propertyPath}="${junctionAlias}_${joinColumn.propertyPath}"`).join(' AND '); } else { junctionCondition = relation.inverseRelation!.inverseJoinColumns.map((joinColumn) => `${junctionAlias}.${joinColumn.propertyPath}="${parentAlias}_${joinColumn.referencedColumn!.propertyPath}"`).join(' AND '); destinationCondition = relation.inverseRelation!.joinColumns.map((joinColumn) => `${destinationTableAlias}.${joinColumn.referencedColumn!.propertyPath}="${junctionAlias}_${joinColumn.propertyPath}"`).join(' AND '); } return ` ${joinAttr.direction} JOIN ${this.getTableName(junctionTableName)} ${this.escape(junctionAlias)}${ // @ts-expect-error -- private this.createTableLockExpression() } ON ${junctionCondition} ${joinAttr.direction} JOIN ${this.getTableName(destinationTableName)} ${this.escape(destinationTableAlias)}${ // @ts-expect-error -- private this.createTableLockExpression() } ON ${destinationCondition}${appendedCondition}`; } /* eslint-enable @typescript-eslint/no-non-null-assertion */ }); return joins.join(' '); }; } const [query, parameters] = builder.getQueryAndParameters(); for (let i = 0; i < Math.ceil(query.length / 10000); i++) { console.log(query.slice(i * 10000, i * 10000 + 10000)); } console.log(parameters); const raw = await builder.execute(); mainAlias.name = name; const relationId = await new RelationIdLoader(builder.connection, this.queryRunner, builder.expressionMap.relationIdAttributes).load(raw); const relationCount = await new RelationCountLoader(builder.connection, this.queryRunner, builder.expressionMap.relationCountAttributes).load(raw); const result = new RawSqlResultsToEntityTransformer(queryBuilder.expressionMap, queryBuilder.connection.driver, relationId, relationCount, this.queryRunner).transform(raw, mainAlias); console.log(raw, relationId, relationCount, result, mainAlias.metadata.primaryColumns.map((column) => DriverUtils.buildAlias(queryBuilder.connection.driver, undefined, mainAlias.name, column.databaseName))); return result[0]; }, selectAliasColumnNames(queryBuilder, builder) { let selectOrAddSelect = (selection: string, selectionAliasName?: string) => { selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName); return builder.select(selection, selectionAliasName); }; for (const columnName of this.createTableColumnNames(queryBuilder)) { selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`); } }, } satisfies MiRepository; export { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAvatarDecoration, MiAuthSession, MiBlocking, MiChannelFollowing, MiChannelFavorite, MiClip, MiClipNote, MiClipFavorite, MiDriveFile, MiDriveFolder, MiEmoji, MiFollowing, MiFollowRequest, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiRenoteMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiChannel, MiRetentionAggregation, MiRole, MiRoleAssignment, MiFlash, MiFlashLike, MiUserMemo, MiBubbleGameRecord, MiReversiGame, }; export type AbuseUserReportsRepository = Repository & MiRepository; export type AccessTokensRepository = Repository & MiRepository; export type AdsRepository = Repository & MiRepository; export type AnnouncementsRepository = Repository & MiRepository; export type AnnouncementReadsRepository = Repository & MiRepository; export type AntennasRepository = Repository & MiRepository; export type AppsRepository = Repository & MiRepository; export type AvatarDecorationsRepository = Repository & MiRepository; export type AuthSessionsRepository = Repository & MiRepository; export type BlockingsRepository = Repository & MiRepository; export type ChannelFollowingsRepository = Repository & MiRepository; export type ChannelFavoritesRepository = Repository & MiRepository; export type ClipsRepository = Repository & MiRepository; export type ClipNotesRepository = Repository & MiRepository; export type ClipFavoritesRepository = Repository & MiRepository; export type DriveFilesRepository = Repository & MiRepository; export type DriveFoldersRepository = Repository & MiRepository; export type EmojisRepository = Repository & MiRepository; export type FollowingsRepository = Repository & MiRepository; export type FollowRequestsRepository = Repository & MiRepository; export type GalleryLikesRepository = Repository & MiRepository; export type GalleryPostsRepository = Repository & MiRepository; export type HashtagsRepository = Repository & MiRepository; export type InstancesRepository = Repository & MiRepository; export type MetasRepository = Repository & MiRepository; export type ModerationLogsRepository = Repository & MiRepository; export type MutingsRepository = Repository & MiRepository; export type RenoteMutingsRepository = Repository & MiRepository; export type NotesRepository = Repository & MiRepository; export type NoteFavoritesRepository = Repository & MiRepository; export type NoteReactionsRepository = Repository & MiRepository; export type NoteThreadMutingsRepository = Repository & MiRepository; export type NoteUnreadsRepository = Repository & MiRepository; export type PagesRepository = Repository & MiRepository; export type PageLikesRepository = Repository & MiRepository; export type PasswordResetRequestsRepository = Repository & MiRepository; export type PollsRepository = Repository & MiRepository; export type PollVotesRepository = Repository & MiRepository; export type PromoNotesRepository = Repository & MiRepository; export type PromoReadsRepository = Repository & MiRepository; export type RegistrationTicketsRepository = Repository & MiRepository; export type RegistryItemsRepository = Repository & MiRepository; export type RelaysRepository = Repository & MiRepository; export type SigninsRepository = Repository & MiRepository; export type SwSubscriptionsRepository = Repository & MiRepository; export type UsedUsernamesRepository = Repository & MiRepository; export type UsersRepository = Repository & MiRepository; export type UserIpsRepository = Repository & MiRepository; export type UserKeypairsRepository = Repository & MiRepository; export type UserListsRepository = Repository & MiRepository; export type UserListFavoritesRepository = Repository & MiRepository; export type UserListMembershipsRepository = Repository & MiRepository; export type UserNotePiningsRepository = Repository & MiRepository; export type UserPendingsRepository = Repository & MiRepository; export type UserProfilesRepository = Repository & MiRepository; export type UserPublickeysRepository = Repository & MiRepository; export type UserSecurityKeysRepository = Repository & MiRepository; export type WebhooksRepository = Repository & MiRepository; export type ChannelsRepository = Repository & MiRepository; export type RetentionAggregationsRepository = Repository & MiRepository; export type RolesRepository = Repository & MiRepository; export type RoleAssignmentsRepository = Repository & MiRepository; export type FlashsRepository = Repository & MiRepository; export type FlashLikesRepository = Repository & MiRepository; export type UserMemoRepository = Repository & MiRepository; export type BubbleGameRecordsRepository = Repository & MiRepository; export type ReversiGamesRepository = Repository & MiRepository;