ソースを参照

create entity relationships for TransactionCompleted event

metmirr 4 年 前
コミット
e1df8948a5

+ 85 - 22
query-node/mappings/content-directory/entity-helper.ts

@@ -26,6 +26,7 @@ import {
   ContentDirectoryKnownClasses,
 } from './content-dir-consts'
 import {
+  ClassEntityMap,
   ICategory,
   IChannel,
   ICreateEntityOperation,
@@ -41,6 +42,7 @@ import {
   IVideoMediaEncoding,
   IWhereCond,
 } from '../types'
+import { getOrCreate } from './get-or-create'
 
 async function createBlockOrGetFromDatabase(db: DB, blockNumber: number): Promise<Block> {
   let b = await db.get(Block, { where: { block: blockNumber } })
@@ -52,8 +54,14 @@ async function createBlockOrGetFromDatabase(db: DB, blockNumber: number): Promis
   return b
 }
 
-async function createChannel({ db, block, id }: IDBBlockId, p: IChannel): Promise<void> {
-  // const { properties: p } = decode.channelEntity(event);
+async function createChannel(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: Map<String, IEntity[]>,
+  p: IChannel
+): Promise<Channel> {
+  const record = await db.get(Channel, { where: { id } })
+  if (record) return record
+
   const channel = new Channel()
 
   channel.version = block
@@ -64,13 +72,16 @@ async function createChannel({ db, block, id }: IDBBlockId, p: IChannel): Promis
   channel.isPublic = p.isPublic
   channel.coverPhotoUrl = p.coverPhotoURL
   channel.avatarPhotoUrl = p.avatarPhotoURL
-  channel.languageId = p.language
+  channel.language = await getOrCreate.language({ db, block, id }, classEntityMap, p.language)
   channel.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(channel)
+  return channel
 }
 
-async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Promise<void> {
-  // const p = decode.categoryEntity(event);
+async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Promise<Category> {
+  const record = await db.get(Category, { where: { id } })
+  if (record) return record
+
   const category = new Category()
 
   category.id = id
@@ -79,9 +90,13 @@ async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Prom
   category.version = block
   category.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(category)
+  return category
 }
 
-async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicense): Promise<void> {
+async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicense): Promise<KnownLicense> {
+  const record = await db.get(KnownLicense, { where: { id } })
+  if (record) return record
+
   const knownLicence = new KnownLicense()
 
   knownLicence.id = id
@@ -92,9 +107,16 @@ async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicens
   knownLicence.version = block
   knownLicence.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(knownLicence)
+  return knownLicence
 }
 
-async function createUserDefinedLicense({ db, block, id }: IDBBlockId, p: IUserDefinedLicense): Promise<void> {
+async function createUserDefinedLicense(
+  { db, block, id }: IDBBlockId,
+  p: IUserDefinedLicense
+): Promise<UserDefinedLicense> {
+  const record = await db.get(UserDefinedLicense, { where: { id } })
+  if (record) return record
+
   const userDefinedLicense = new UserDefinedLicense()
 
   userDefinedLicense.id = id
@@ -102,9 +124,16 @@ async function createUserDefinedLicense({ db, block, id }: IDBBlockId, p: IUserD
   userDefinedLicense.version = block
   userDefinedLicense.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(userDefinedLicense)
+  return userDefinedLicense
 }
 
-async function createJoystreamMediaLocation({ db, block, id }: IDBBlockId, p: IJoystreamMediaLocation): Promise<void> {
+async function createJoystreamMediaLocation(
+  { db, block, id }: IDBBlockId,
+  p: IJoystreamMediaLocation
+): Promise<JoystreamMediaLocation> {
+  const record = await db.get(JoystreamMediaLocation, { where: { id } })
+  if (record) return record
+
   const joyMediaLoc = new JoystreamMediaLocation()
 
   joyMediaLoc.id = id
@@ -112,9 +141,16 @@ async function createJoystreamMediaLocation({ db, block, id }: IDBBlockId, p: IJ
   joyMediaLoc.version = block
   joyMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(joyMediaLoc)
+  return joyMediaLoc
 }
 
-async function createHttpMediaLocation({ db, block, id }: IDBBlockId, p: IHttpMediaLocation): Promise<void> {
+async function createHttpMediaLocation(
+  { db, block, id }: IDBBlockId,
+  p: IHttpMediaLocation
+): Promise<HttpMediaLocation> {
+  const record = await db.get(HttpMediaLocation, { where: { id } })
+  if (record) return record
+
   const httpMediaLoc = new HttpMediaLocation()
 
   httpMediaLoc.id = id
@@ -123,48 +159,68 @@ async function createHttpMediaLocation({ db, block, id }: IDBBlockId, p: IHttpMe
   httpMediaLoc.version = block
   httpMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(httpMediaLoc)
+  return httpMediaLoc
 }
 
-async function createVideoMedia({ db, block, id }: IDBBlockId, p: IVideoMedia): Promise<void> {
+async function createVideoMedia(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: IVideoMedia
+): Promise<VideoMedia> {
   const videoMedia = new VideoMedia()
 
   videoMedia.id = id
-  videoMedia.encodingId = p.encoding
-  videoMedia.locationId = p.location
   videoMedia.pixelHeight = p.pixelHeight
   videoMedia.pixelWidth = p.pixelWidth
   videoMedia.size = p.size
   videoMedia.version = block
+  videoMedia.encoding = await getOrCreate.videoMediaEncoding({ db, block, id }, classEntityMap, p.encoding)
+  videoMedia.httpMediaLocation = await getOrCreate.httpMediaLocation({ db, block, id }, classEntityMap, p.location)
+  videoMedia.joystreamMediaLocation = await getOrCreate.joystreamMediaLocation(
+    { db, block, id },
+    classEntityMap,
+    p.location
+  )
   videoMedia.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save(videoMedia)
+  return videoMedia
 }
 
-async function createVideo({ db, block, id }: IDBBlockId, p: IVideo): Promise<void> {
+async function createVideo({ db, block, id }: IDBBlockId, classEntityMap: ClassEntityMap, p: IVideo): Promise<Video> {
+  const record = await db.get(Video, { where: { id } })
+  if (record) return record
+
   const video = new Video()
 
   video.id = id
   video.title = p.title
   video.description = p.description
-  video.categoryId = p.category
-  video.channelId = p.channel
   video.duration = p.duration
   video.hasMarketing = p.hasMarketing
   // TODO: needs to be handled correctly, from runtime CurationStatus is coming
   video.isCurated = p.isCurated || true
   video.isExplicit = p.isExplicit
   video.isPublic = p.isPublic
-  video.languageId = p.language
-  video.licenseId = p.license
-  video.videoMediaId = p.media
   video.publishedBeforeJoystream = p.publishedBeforeJoystream
   video.skippableIntroDuration = p.skippableIntroDuration
   video.thumbnailUrl = p.thumbnailURL
   video.version = block
+
+  video.language = await getOrCreate.language({ db, block, id }, classEntityMap, p.language)
+  video.knownLicense = await getOrCreate.knownLicense({ db, block, id }, classEntityMap, p.license)
+  video.userdefinedLicense = await getOrCreate.userDefinedLicense({ db, block, id }, classEntityMap, p.license)
+  video.category = await getOrCreate.category({ db, block, id }, classEntityMap, p.category)
+  video.channel = await getOrCreate.channel({ db, block, id }, classEntityMap, p.channel)
   video.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  video.media = await getOrCreate.videoMedia({ db, block, id }, classEntityMap, p.media)
   await db.save<Video>(video)
+  return video
 }
 
-async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Promise<void> {
+async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Promise<Language> {
+  const record = await db.get(Language, { where: { id } })
+  if (record) return record
+
   const language = new Language()
   language.id = id
   language.name = p.name
@@ -173,17 +229,24 @@ async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Prom
   language.happenedIn = await createBlockOrGetFromDatabase(db, block)
 
   await db.save<Language>(language)
+  return language
 }
 
-async function createVideoMediaEncoding({ db, block, id }: IDBBlockId, p: IVideoMediaEncoding): Promise<void> {
-  const encoding = new VideoMediaEncoding()
+async function createVideoMediaEncoding(
+  { db, block, id }: IDBBlockId,
+  p: IVideoMediaEncoding
+): Promise<VideoMediaEncoding> {
+  const record = await db.get(VideoMediaEncoding, { where: { id } })
+  if (record) return record
 
+  const encoding = new VideoMediaEncoding()
   encoding.id = id
   encoding.name = p.name
   encoding.version = block
   // happenedIn is not defined in the graphql schema!
-  // encoding.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  encoding.happenedIn = await createBlockOrGetFromDatabase(db, block)
   await db.save<VideoMediaEncoding>(encoding)
+  return encoding
 }
 
 async function batchCreateClassEntities(db: DB, block: number, operations: ICreateEntityOperation[]): Promise<void> {

+ 17 - 4
query-node/mappings/content-directory/entity.ts

@@ -63,6 +63,7 @@ import {
   IVideoMediaEncoding,
   IDBBlockId,
   IWhereCond,
+  IEntity,
 } from '../types'
 
 const debug = Debug('mappings:content-directory')
@@ -91,7 +92,11 @@ async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: Substrat
 
   switch (cls.name) {
     case ContentDirectoryKnownClasses.CHANNEL:
-      await createChannel(arg, decode.setProperties<IChannel>(event, channelPropertyNamesWithId))
+      await createChannel(
+        arg,
+        new Map<string, IEntity[]>(),
+        decode.setProperties<IChannel>(event, channelPropertyNamesWithId)
+      )
       break
 
     case ContentDirectoryKnownClasses.CATEGORY:
@@ -124,11 +129,19 @@ async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: Substrat
       break
 
     case ContentDirectoryKnownClasses.VIDEOMEDIA:
-      await createVideoMedia(arg, decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId))
+      await createVideoMedia(
+        arg,
+        new Map<string, IEntity[]>(),
+        decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId)
+      )
       break
 
     case ContentDirectoryKnownClasses.VIDEO:
-      await createVideo(arg, decode.setProperties<IVideo>(event, videoPropertyNamesWithId))
+      await createVideo(
+        arg,
+        new Map<string, IEntity[]>(),
+        decode.setProperties<IVideo>(event, videoPropertyNamesWithId)
+      )
       break
 
     case ContentDirectoryKnownClasses.LANGUAGE:
@@ -162,7 +175,7 @@ async function contentDirectory_EntityRemoved(db: DB, event: SubstrateEvent): Pr
 
   const cls = contentDirectoryClassNamesWithId.find((c) => c.classId === classEntity.classId)
   if (cls === undefined) {
-    console.log('Undefined class')
+    console.log('Unknown class')
     return
   }
 

+ 303 - 0
query-node/mappings/content-directory/get-or-create.ts

@@ -0,0 +1,303 @@
+import { Channel } from '../../generated/graphql-server/src/modules/channel/channel.model'
+import { Category } from '../../generated/graphql-server/src/modules/category/category.model'
+import { KnownLicense } from '../../generated/graphql-server/src/modules/known-license/known-license.model'
+import { UserDefinedLicense } from '../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
+import { JoystreamMediaLocation } from '../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
+import { HttpMediaLocation } from '../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
+import { VideoMedia } from '../../generated/graphql-server/src/modules/video-media/video-media.model'
+import { Language } from '../../generated/graphql-server/src/modules/language/language.model'
+import { VideoMediaEncoding } from '../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
+import { decode } from './decode'
+import {
+  CategoryPropertyNamesWithId,
+  channelPropertyNamesWithId,
+  httpMediaLocationPropertyNamesWithId,
+  joystreamMediaLocationPropertyNamesWithId,
+  knownLicensePropertyNamesWIthId,
+  languagePropertyNamesWIthId,
+  userDefinedLicensePropertyNamesWithId,
+  videoMediaEncodingPropertyNamesWithId,
+  videoPropertyNamesWithId,
+} from './content-dir-consts'
+import {
+  ClassEntityMap,
+  ICategory,
+  IChannel,
+  IDBBlockId,
+  IEntity,
+  IHttpMediaLocation,
+  IJoystreamMediaLocation,
+  IKnownLicense,
+  ILanguage,
+  IUserDefinedLicense,
+  IVideoMedia,
+  IVideoMediaEncoding,
+} from '../types'
+
+import {
+  createCategory,
+  createChannel,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
+  createKnownLicense,
+  createLanguage,
+  createUserDefinedLicense,
+  createVideoMedia,
+  createVideoMediaEncoding,
+} from './entity-helper'
+
+function findEntity(entityId: number, newlyCreatedEntities: IEntity[]): IEntity | undefined {
+  const entity = newlyCreatedEntities.find((e) => e.indexOf === entityId)
+  if (!entity) {
+    console.log(`Unknown entity id: ${entityId}`)
+    return
+  }
+  return entity
+}
+
+async function language(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<Language> {
+  let record = await db.get(Language, { where: { id: entityId.toString() } })
+  if (record) return record
+  const newlyCreatedEntities = classEntityMap.get('Language')
+
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown Language entity id`)
+
+    record = await createLanguage(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<ILanguage>(entity.properties, languagePropertyNamesWIthId)
+    )
+  }
+  if (!record) throw Error(`Language entity not found on the database`)
+  removeInsertedEntity('Language', entityId, classEntityMap)
+  return record
+}
+
+async function videoMediaEncoding(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<VideoMediaEncoding> {
+  let record = await db.get(VideoMediaEncoding, { where: { id: entityId.toString() } })
+  if (record) return record
+  const newlyCreatedEntities = classEntityMap.get('VideoMediaEncoding')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown VideoMediaEncoding entity id`)
+
+    record = await createVideoMediaEncoding(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<IVideoMediaEncoding>(entity.properties, videoMediaEncodingPropertyNamesWithId)
+    )
+  }
+
+  if (!record) throw Error(`VideoMediaEncoding entity not found on the database`)
+  removeInsertedEntity('VideoMediaEncoding', entityId, classEntityMap)
+  return record
+}
+
+async function videoMedia(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<VideoMedia> {
+  let record = await db.get(VideoMedia, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('VideoMedia')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown VideoMedia entity id`)
+
+    record = await createVideoMedia(
+      { db, block, id: entityId.toString() },
+      classEntityMap,
+      decode.setEntityPropertyValues<IVideoMedia>(entity.properties, videoPropertyNamesWithId)
+    )
+  }
+
+  if (!record) throw Error(`Video entity not found on the database`)
+  removeInsertedEntity('VideoMedia', entityId, classEntityMap)
+  return record
+}
+
+async function knownLicense(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<KnownLicense | undefined> {
+  let record = await db.get(KnownLicense, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('KnownLicense')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) {
+      console.log(`Unknown KnownLicense entity id`)
+      return
+    }
+
+    record = await createKnownLicense(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<IKnownLicense>(entity.properties, knownLicensePropertyNamesWIthId)
+    )
+  }
+
+  if (!record) throw Error(`KnownLicense entity not found on the database`)
+  removeInsertedEntity('KnownLicense', entityId, classEntityMap)
+  return record
+}
+async function userDefinedLicense(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<UserDefinedLicense | undefined> {
+  let record = await db.get(UserDefinedLicense, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('UserDefinedLicense')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) {
+      console.log(`Unknown UserDefinedLicense entity id`)
+      return
+    }
+    record = await createUserDefinedLicense(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<IUserDefinedLicense>(entity.properties, userDefinedLicensePropertyNamesWithId)
+    )
+  }
+  if (!record) {
+    console.log(`UserDefinedLicense entity not found on the database`)
+    return
+  }
+  removeInsertedEntity('UserDefinedLicense', entityId, classEntityMap)
+  return record
+}
+async function channel({ db, block }: IDBBlockId, classEntityMap: ClassEntityMap, entityId: number): Promise<Channel> {
+  let record = await db.get(Channel, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('Channel')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown Channel entity id`)
+
+    record = await createChannel(
+      { db, block, id: entityId.toString() },
+      classEntityMap,
+      decode.setEntityPropertyValues<IChannel>(entity.properties, channelPropertyNamesWithId)
+    )
+  }
+
+  if (!record) throw Error(`Channel entity not found on the database`)
+  removeInsertedEntity('Channel', entityId, classEntityMap)
+  return record
+}
+async function category(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<Category> {
+  let record = await db.get(Category, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('Category')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown Category entity id`)
+
+    record = await createCategory(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<ICategory>(entity.properties, CategoryPropertyNamesWithId)
+    )
+  }
+
+  if (!record) throw Error(`Category entity not found on the database`)
+  removeInsertedEntity('Category', entityId, classEntityMap)
+  return record
+}
+
+async function httpMediaLocation(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<HttpMediaLocation | undefined> {
+  let record = await db.get(HttpMediaLocation, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('HttpMediaLocation')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) {
+      console.log(`Unknown HttpMediaLocation entity id`)
+      return
+    }
+    record = await createHttpMediaLocation(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<IHttpMediaLocation>(entity.properties, httpMediaLocationPropertyNamesWithId)
+    )
+  }
+  if (!record) {
+    console.log(`HttpMediaLocation entity not found on the database`)
+    return
+  }
+  removeInsertedEntity('HttpMediaLocation', entityId, classEntityMap)
+  return record
+}
+
+async function joystreamMediaLocation(
+  { db, block }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  entityId: number
+): Promise<JoystreamMediaLocation | undefined> {
+  let record = await db.get(JoystreamMediaLocation, { where: { id: entityId.toString() } })
+  if (record) return record
+
+  const newlyCreatedEntities = classEntityMap.get('JoystreamMediaLocation')
+  if (newlyCreatedEntities) {
+    const entity = findEntity(entityId, newlyCreatedEntities)
+    if (!entity) throw Error(`Unknown JoystreamMediaLocation entity id`)
+
+    record = await createJoystreamMediaLocation(
+      { db, block, id: entityId.toString() },
+      decode.setEntityPropertyValues<IJoystreamMediaLocation>(
+        entity.properties,
+        joystreamMediaLocationPropertyNamesWithId
+      )
+    )
+  }
+
+  if (!record) {
+    console.log(`JoystreamMediaLocation entity not found on the database`)
+    return
+  }
+  removeInsertedEntity('JoystreamMediaLocation', entityId, classEntityMap)
+  return record
+}
+
+function removeInsertedEntity(key: string, insertedEntityId: number, classEntityMap: ClassEntityMap) {
+  const newlyCreatedEntities = classEntityMap.get(key)
+  // Remove the inserted entity from the list
+  classEntityMap.set(
+    key,
+    newlyCreatedEntities!.filter((e) => e.entityId !== insertedEntityId)
+  )
+}
+
+export const getOrCreate = {
+  language,
+  videoMediaEncoding,
+  videoMedia,
+  knownLicense,
+  userDefinedLicense,
+  channel,
+  category,
+  joystreamMediaLocation,
+  httpMediaLocation,
+}

+ 90 - 71
query-node/mappings/content-directory/transaction.ts

@@ -3,6 +3,7 @@ import Debug from 'debug'
 import { DB, SubstrateEvent } from '../../generated/indexer'
 import { decode } from './decode'
 import {
+  ClassEntityMap,
   ICategory,
   IChannel,
   ICreateEntityOperation,
@@ -100,80 +101,98 @@ async function batchAddSchemaSupportToEntity(
   entities: IEntity[],
   block: number
 ) {
-  // find the related entity ie. Channel, Video etc
-  for (const entity of entities) {
-    const { entityId, indexOf, properties } = entity
-
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const id = entityId ? entityId.toString() : indexOf!.toString()
+  const classEntityMap: ClassEntityMap = new Map<string, IEntity[]>()
 
+  for (const entity of entities) {
     const className = await getClassName(db, entity, createEntityOperations)
-    if (className === undefined) continue
-
-    const arg: IDBBlockId = { db, block, id }
-
-    switch (className) {
-      case ContentDirectoryKnownClasses.CATEGORY:
-        await createCategory(arg, decode.setEntityPropertyValues<ICategory>(properties, CategoryPropertyNamesWithId))
-        break
-
-      case ContentDirectoryKnownClasses.CHANNEL:
-        await createChannel(arg, decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId))
-        break
-
-      case ContentDirectoryKnownClasses.KNOWNLICENSE:
-        await createKnownLicense(
-          arg,
-          decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId)
-        )
-        break
-
-      case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
-        await createUserDefinedLicense(
-          arg,
-          decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId)
-        )
-        break
-
-      case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
-        await createJoystreamMediaLocation(
-          arg,
-          decode.setEntityPropertyValues<IJoystreamMediaLocation>(properties, joystreamMediaLocationPropertyNamesWithId)
-        )
-        break
-
-      case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
-        await createHttpMediaLocation(
-          arg,
-          decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId)
-        )
-        break
-
-      case ContentDirectoryKnownClasses.VIDEOMEDIA:
-        await createVideoMedia(
-          arg,
-          decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId)
-        )
-        break
-
-      case ContentDirectoryKnownClasses.VIDEO:
-        await createVideo(arg, decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId))
-        break
-
-      case ContentDirectoryKnownClasses.LANGUAGE:
-        await createLanguage(arg, decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId))
-        break
-
-      case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
-        await createVideoMediaEncoding(
-          arg,
-          decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId)
-        )
-        break
+    if (className !== undefined) {
+      const es = classEntityMap.get(className)
+      classEntityMap.set(className, es ? [...es, entity] : [entity])
+    }
+  }
 
-      default:
-        console.log(`Unknown class name: ${className}`)
-        break
+  // This is a copy of classEntityMap, we will use it to keep track of items.
+  // We will remove items from this list whenever we insert them into db
+  const doneList: ClassEntityMap = new Map(classEntityMap.entries())
+
+  for (const [className, entities] of classEntityMap) {
+    for (const entity of entities) {
+      const { entityId, indexOf, properties } = entity
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const id = entityId ? entityId.toString() : indexOf!.toString()
+      const arg: IDBBlockId = { db, block, id }
+
+      switch (className) {
+        case ContentDirectoryKnownClasses.CATEGORY:
+          await createCategory(arg, decode.setEntityPropertyValues<ICategory>(properties, CategoryPropertyNamesWithId))
+          break
+
+        case ContentDirectoryKnownClasses.CHANNEL:
+          await createChannel(
+            arg,
+            doneList,
+            decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId)
+          )
+          break
+
+        case ContentDirectoryKnownClasses.KNOWNLICENSE:
+          await createKnownLicense(
+            arg,
+            decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId)
+          )
+          break
+
+        case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+          await createUserDefinedLicense(
+            arg,
+            decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId)
+          )
+          break
+
+        case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+          await createJoystreamMediaLocation(
+            arg,
+            decode.setEntityPropertyValues<IJoystreamMediaLocation>(
+              properties,
+              joystreamMediaLocationPropertyNamesWithId
+            )
+          )
+          break
+
+        case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+          await createHttpMediaLocation(
+            arg,
+            decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId)
+          )
+          break
+
+        case ContentDirectoryKnownClasses.VIDEOMEDIA:
+          await createVideoMedia(
+            arg,
+            doneList,
+            decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId)
+          )
+          break
+
+        case ContentDirectoryKnownClasses.VIDEO:
+          await createVideo(arg, doneList, decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId))
+          break
+
+        case ContentDirectoryKnownClasses.LANGUAGE:
+          await createLanguage(arg, decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId))
+          break
+
+        case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+          await createVideoMediaEncoding(
+            arg,
+            decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId)
+          )
+          break
+
+        default:
+          console.log(`Unknown class name: ${className}`)
+          break
+      }
     }
   }
 }

+ 3 - 0
query-node/mappings/types.ts

@@ -166,5 +166,8 @@ export interface ICreateEntityOperation {
 export interface IDBBlockId {
   db: DB
   block: number
+  // Entity id
   id: string
 }
+
+export type ClassEntityMap = Map<string, IEntity[]>