Kutubxonasiz AppEngine db.Key Protobuf kodini qanday dekodlash mumkin

Shunday qilib, AppEngine ma'lumotlar bazasi kalitlaridan foydalanadigan har bir kishi uchun siz har bir ob'ektning global miqyosda noyob uuid-si asosan bir nechta asosiy ma'lumotlardan iborat base64-kodlangan qator ekanligini bilishingiz mumkin, xususan: ilova nomi, model nomi va uning id_yoki_name

Bu AppEngine-ga dunyoning istalgan ma'lumotlar bazasi ob'ektini chiroyli kichik, oddiy qator bilan topish imkonini beradi. Aslida, AppEngine-dagi barcha ReferenceProperty turlari ham xuddi shu bir xil base64-string sifatida saqlanadi, shuning uchun uni aloqador bo'lmagan ma'lumotlar bazasi dunyosida juda tez hal qilish mumkin. Bu ma'lumotlar omborining asosi bo'lganligi sababli, ushbu ma'lumotlarning barchasini iloji boricha kamroq baytlarga to'plash mantiqan to'g'ri keladi, shuning uchun tabiiy ravishda Google db.Key obyektini yuborish uchun satrga kodlash uchun Protokol Buferlaridan foydalanadi. sim.

Xo'sh, agar sizga ushbu jirkanch kalitlardan biri berilsa va siz entity.key().id_or_name() ga qarshi BigQuery ulanishini amalga oshirishingiz kerak bo'lsa nima bo'ladi? Yoki Javascript mijozingiz shunchaki model turini olishni xohlaydimi yoki u RPC-larsiz mijoz tomoni identifikatori va sizning JSON-da bunday qulay narsalar ketma-ketlashtirilmaganmi? Yoki bu baytlar qanday kodlanganiga qiziqsangiz-chi!

Ajablanarlisi shundaki, AppEngine butun stekni kompyuteringizda mahalliy ravishda ishga tushirish va hatto unitTest-ga yuklash uchun to'liq SDK-ni taqdim etadi, shunda biz har safar kalit ishga tushirilganda server tomonida nima sodir bo'lishini ko'rib chiqishimiz mumkin.

Shunday qilib, keling, bunga o'tamiz ...

Avval biz har qanday db.Key qatoridan boshlaymiz

Endi sehr entity_pb.py ichida chuqur sodir bo'la boshlaydi, u erda string buferini qayta ishlashni boshlaydi:

Tahlil qiluvchi baytning qaysi turi va qancha davom etishini ko'rish uchun baytni tekshirishdan so'ng darhol ma'lumotlarni oladi va keyin oldinga siljishda davom etadi. Bizning birinchi baytimiz "j" aslida umuman belgi emas (esda tutingki, 1 bayt 256 dona ma'lumotni ifodalashi mumkin, lekin bu baytlarning faqat kichik to'plami ASCII-da chop etilishi mumkin bo'lgan belgilardir. Butun satr aslida signalizatsiya aralashmasidir. baytlar, satrlar va o'zgaruvchan uzunlikdagi raqamlar ko'p hollarda ularning hex kodi yoki ord qiymati haqida o'ylash osonroq bo'ladi…j = '106'.

Ko'rib turganingizdek, bu kalitning ilova nomi qismini aniqlashga urinayotgan tahlilchi. Keling, avval o'zgaruvchan uzunlikdagi satr bo'lgan model turini olish uchun oldinga o'tamiz.

Bularning barchasi 0x12 sehrli baytda (18 ning 10 ta asosiy qiymati) sodir bo'ladi, bu erda biz hozir satrni tahlil qilmoqchi ekanligimizni bilamiz. Bundan keyingi keyingi bayt bizning satrimiz qancha bayt uzunligini bildiradi: ‘\n’ bu aslida python imkoni bo‘lganda ascii’da baytlarni ko‘rsatishga harakat qiladi. Ord(‘\n’) 0x0a yoki 10.

Bu "HelloWorld" filmidagi 10 ta belgi uchun 10 ta!

Endi bularning barchasi to'g'ridan-to'g'ri edi, to'g'rimi?

Endi ob'ekt identifikatorini olish biroz qiyinroq. Bu Int64 raqamlari xotiraga yoki katta so'rovga yuklangandan so'ng, lekin sim orqali ular biroz boshqacha kodlangan. Standart 8 bayt o'rniga raqam yanada kengroq kodlangan. Asosan bizda 7 bit ma'lumotlar bor va har bir baytda yakunlovchi 8-bit mavjud. Shunday qilib, 8-bit "1" bo'lsa, biz keyingi baytni buferdan tortib olishda davom etamiz va biz yo'lda kelgan barcha 7-bitlarni Int64-ga o'tkazish orqali birlashtiramiz.

Keling, misolni ko'rib chiqaylik

Keyingi 0x18 sehrli baytga duch kelamiz, bu 10 ta bazada 24 ga teng va endi biz quvurdan tushadigan o'zgaruvchan uzunlikdagi Int64 borligini bilamiz.

Imzosiz baytli erlarda, 8-bit bo'lgan "1" ga ega bo'lgan har qanday bayt ≥ 128 bo'lganligi sababli, biz undan kamroq raqamga tegmagunimizcha baytlarni o'qishni davom ettiramiz, bu ko'pincha ascii belgisidir, bu holda bu bayt '\x15' bo'ladi, bu NAK belgisidir (salbiy tan olish)

Va bu erda bayt massivining bit o'zgarishini amalga oshirishning haqiqiy tsikli:

Va voila! Bizda konsolda boshida chop etgan bir xil entity.key().id() mavjud.

Shunday qilib, davom eting va o'zingiz sinab ko'ring - istalgan kalit qatorini oling va hech qanday kutubxonani yuklamasdan python tarjimoningizni oching va buni ishga tushiring:

# in py2 
import base64
key = ‘agd0ZXN0YmVkchcLEgpIZWxsb1dvcmxkGIeXrevFivcVDA’
urlsafe = str(key)
mod = len(urlsafe) % 4
urlsafe += (‘=’*(4-mod)) if mod else ‘’
b = base64.b64decode(urlsafe.replace(‘-’, ‘+’).replace(‘_’, ‘/’))
id_bytes = b.split(‘\x18’)[-1][0:-1]
ba = bytearray(id_bytes)
length = ord(b.split(‘\x12’)[1][0])
db_type = b.split(‘\x12’)[1][1:length+1]
db_id = sum([(ba[i] & 127) << i*7 for i in xrange(len(ba))])
print ‘%s:%s’ % (db_type, db_id)

# Here's a function declaration that works for id_or_name in py2+3
def decode_key(key):
  # turn b64 string into some bytes
  urlsafe = str(key)
  mod = len(urlsafe) % 4
  urlsafe += ('='*(4-mod)) if mod else ''
  b = base64.b64decode(urlsafe.replace('-', '+').replace('_', '/'))

  # skip ahead to where the class name/id bytes are
  cls_and_id = b.split(b'\x0b\x12')[1]
  cls_len = cls_and_id[0] # ord() this in py2
  db_type = cls_and_id[1:cls_len + 1].decode()

  # figure out if this is id or name
  id_type = cls_and_id[cls_len + 1] # ord() this in py2
  assert id_type in [34, 24], "key fail id_type=%s" % id_type
  id_or_name = cls_and_id[cls_len + 2:]

  if id_type == 34: # name()
    name_len = id_or_name[0] # ord() this in py2
    db_id = id_or_name[1:name_len + 1].decode()
  else: # id()
    ba = bytearray(id_or_name[0:-1])
    db_id = sum([(ba[i] & 127) << i*7 for i in range(len(ba))])

  return db_type, db_id

Prolog:

Bu kaput ostida nima bo'layotganini tushunish uchun oddiygina nazariy mashq va men bu texnikani faqat disk raskadrovka yoki to'xtash uchun saqlab qo'ygan bo'lardim. Koʻp yillar davomida men obʼyektning boshqa obʼyekt bilan 1:1 nisbati boʻlganida men buni juda foydali deb bilgan boʻlsam-da, bunda lotin obʼyektning kalit_nomini kalitga aylantirish orqali ommaviy kalit_nomini qidirish tez boʻlishini xohlayman, men odatda ob'ekt kalitining short_key deb ataydigan narsadan foydalanishga harakat qiling. masalan

Bundan tashqari, mening namunaviy dizaynimda meros tuzilmasi mavjud bo'lsa, mijozlarga JSON-dagi id_or_name() o'rniga kalitlarni qaytarish kerak bo'ladi - bu erda ham men short_keys-dan foydalanishni boshladim. Barcha ob'ektlar bir xil AppEngine ilovasida ishlayotgan ekan, bu ko'p hollarda protobuf versiyasidan kamroq bo'ladi, shuningdek, o'qish va disk raskadrovka qilish ancha oson bo'ladi.

Masalan, foydalanuvchiga o'xshash ba'zi ob'ektning kanal ob'ektiga a'zoligini ifodalovchi ob'ektning key_name qilish orqali:

Men endi sekin kompozit so'rovsiz a'zolik ob'ektini topa olaman ... va men JSON javobimni tushuna olaman