Kropka (.) MongoDB w nazwie klucza

96

Wygląda na to, że mongo nie pozwala na wstawianie kluczy z kropką (.) Lub znakiem dolara ($), jednak kiedy zaimportowałem plik JSON zawierający kropkę za pomocą narzędzia mongoimport, działało dobrze. Kierowca narzeka na próbę wstawienia tego elementu.

Tak wygląda dokument w bazie danych:

{
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {
        "9.7x": [
            2007,
            2008,
            2009,
            2010
        ]
    }
}

Czy robię to wszystko źle i nie powinienem używać takich map skrótów z danymi zewnętrznymi (tj. Modelami), czy też mogę jakoś uciec przed kropką? Może myślę za dużo w stylu Javascript.

Michael Yagudaev
źródło
Warto obejrzeć, npmjs.com/package/mongo-escape
Sam Denty

Odpowiedzi:

87

MongoDB nie obsługuje kluczy z kropką, więc będziesz musiał wstępnie przetworzyć plik JSON, aby go usunąć / zamienić przed zaimportowaniem go, w przeciwnym razie będziesz musiał ustawić się na różnego rodzaju problemy.

Nie ma standardowego obejścia tego problemu, najlepsze podejście jest zbyt zależne od specyfiki sytuacji. Ale unikałbym jakiegokolwiek podejścia do kodowania / dekodowania klucza, jeśli to możliwe, ponieważ będziesz nadal płacić niedogodności związane z tym w nieskończoność, gdzie restrukturyzacja JSON prawdopodobnie byłaby jednorazowym kosztem.

JohnnyHK
źródło
1
Nie sądzę, że istnieje standardowy sposób, najlepsze podejście jest zbyt zależne od specyfiki sytuacji. Ale unikałbym jakiegokolwiek podejścia do kodowania / dekodowania klucza, jeśli to możliwe, ponieważ będziesz nadal płacić niedogodności związane z tym w nieskończoność, gdzie restrukturyzacja JSON prawdopodobnie byłaby jednorazowym kosztem.
JohnnyHK,
8
Znów wpadłem w tę sytuację. Wydaje się, że dzieje się tak nie tyle z nazwami kluczy aplikacji, które możemy kontrolować i często potrzebujemy zapytać, ale z danymi dostarczonymi przez użytkownika w zagnieżdżonych strukturach danych, których nie możemy kontrolować, ale (a) chcielibyśmy przechowywać w Mongo , (b) wiemy, w jakich konkretnych polach może się to zdarzyć (np. modelstutaj), oraz (c) nie musimy odpytywać ich według nazwy klucza w Mongo. Tak więc wzorzec, na którym się zdecydowałem, to JSON.stringifyto pole przy zapisywaniu i „JSON.parse” podczas pobierania.
prototyp
16
Jeśli musisz, możesz podać opcję {check_keys: false}, aby ominąć ten problem.
Tzury Bar Yochay
5
@TzuryBarYochay OMG, znalazłeś odpowiednik północno-zachodniego przejścia w MongoDB. Myślę, że to powinna być akceptowana odpowiedź.
prototyp
2
@emarel db.collection_foo.update ({this: "that"}, {$ set: {a: "b"}}, {check_keys: false})
Tzury Bar Yochay
23

Jak wspomniano w innych odpowiedziach, MongoDB nie zezwala na znaki $lub .jako klucze mapowania ze względu na ograniczenia dotyczące nazw pól . Jednak, jak wspomniano w sekcji Operator znaku dolara, uniknięcie tego ograniczenia nie zapobiega wstawianiu dokumentów z takimi kluczami, a jedynie zapobiega ich aktualizowaniu lub odpytywaniu.

Problem po prostu zastępując .z [dot]lub U+FF0E(jak wspomniano w innym miejscu na tej stronie) jest to, co się dzieje, gdy użytkownik chce legalnie przechowywania klucza [dot]lub U+FF0E?

Podejściem, które stosuje sterownik afMorphia Fantoma , polega na użyciu sekwencji ucieczki Unicode podobnych do tej w Javie, ale najpierw należy upewnić się, że znak ucieczki jest unikany. Zasadniczo dokonywane są następujące zamiany ciągów (*):

\  -->  \\
$  -->  \u0024
.  -->  \u002e

Odwrotna zamiana jest wykonywana, gdy klucze map są następnie odczytywane z MongoDB.

Lub w kodzie Fantoma :

Str encodeKey(Str key) {
    return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e")
}

Str decodeKey(Str key) {
    return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\")
}

Jedynym przypadkiem, w którym użytkownik musi być świadomy takich konwersji, jest tworzenie zapytań dla takich kluczy.

Biorąc pod uwagę, że przechowywanie dotted.property.namesw bazach danych do celów konfiguracyjnych jest powszechne, uważam, że takie podejście jest lepsze niż po prostu blokowanie wszystkich takich kluczy map.

(*) afMorphia faktycznie wykonuje pełne / poprawne reguły ucieczki unicode, jak wspomniano w składni ucieczki Unicode w Javie, ale opisana sekwencja zamiany działa równie dobrze.

Steve Eynon
źródło
Powinien //gzastąpić wszystkie wystąpienia, a nie tylko pierwsze. Również użycie odpowiedników o pełnej szerokości, jak w odpowiedzi Martina Konecnego, wydaje się być dobrym pomysłem. Na koniec do kodowania wystarczy jeden lewy ukośnik. key.replace(/\./g, '\uff0e').replace(/\$/g, '\uff04').replace(/\\/g, '\uff3c')
cw '
1
@cw '- kod jest w składni podobnej do Java, więc zamiana faktycznie zastępuje wszystkie wystąpienia, a do ucieczki ukośników odwrotnych wymagane są podwójne ukośniki odwrotne. I znowu, musisz wprowadzić jakąś formę ucieczki, aby upewnić się, że wszystkie sprawy są objęte. Ktoś może kiedyś potrzebować klucza U+FF04.
Steve Eynon
2
Jak się okazuje, Mongodb obsługuje kropki i dolary w swoich najnowszych wersjach. Zobacz: - stackoverflow.com/a/57106679/3515086
Abhidemon,
18

Dokumentacja Mongo sugeruje zastąpienie niedozwolonych znaków, takich jak $i .ich odpowiednikami w Unicode.

W takich sytuacjach klucze będą musiały zastąpić zarezerwowane $ i. postacie. Dowolny znak jest wystarczający, ale rozważ użycie odpowiedników Unicode o pełnej szerokości: U + FF04 (tj. „$”) i U + FF0E (tj. „.”).

Martin Konecny
źródło
74
To brzmi jak przepis na ogromne problemy z debugowaniem w przyszłości.
nikt
2
@AndrewMedico, @tamlyn - Myślę, że dokumenty oznaczają coś takiegodb.test.insert({"field\uff0ename": "test"})
P.Myer Nore
4
-1 A. To okropny pomysł - co jeśli ktoś faktycznie próbuje użyć tych znaków Unicode jako klucza? Wtedy masz cichy błąd, który zrobi, kto wie, co w twoim systemie. Nie używaj takich niejednoznacznych metod ucieczki. B. doktorzy mongo już tego nie mówią, prawdopodobnie dlatego, że ktoś zdał sobie sprawę, że to okropny pomysł
BT
7
@SergioTulentsev Mam ich, aby usunęli zalecenie :) github.com/mongodb/docs/commit/ ...
BT
2
@BT: cynk kapelusza, proszę pana :)
Sergio Tulentsev
15

Najnowsza stabilna wersja (v3.6.1) bazy danych MongoDB obsługuje teraz kropki (.) W kluczach lub nazwach pól.

Nazwy pól mogą teraz zawierać kropki (.) I dolar ($)

h4ck3d
źródło
10
Nawet jeśli serwer obsługuje to teraz, sterownik nadal sprawdza znaki $ i kropki w klawiszach i ich nie akceptuje. Dlatego Mongo tylko teoretycznie obsługuje kropki i znaki dolara. Praktycznie nie jest to jeszcze użyteczne :(
JMax
Może używasz starego lub niekompatybilnego klienta. Używałem tego na moich serwerach produkcyjnych bez żadnego wysiłku. Sprawdziłem klientów NodeJS i Java.
h4ck3d
Z Javą na pewno nie działa! Spróbuj mongoClient.getDatabase("mydb").getCollection("test").insertOne(new Document("value", new Document("key.with.dots", "value").append("$dollar", "value")));wykonać następujące polecenie: Błąd przy użyciu sterownika mongodb.3.6.3 i MongoDB 3.6.3.
JMax
1
Rzeczywiście, właśnie próbowałem z konfiguracją mongodb-4.1.1i pymongo-3.7.1. Mogę dodać dokumenty zawierające klucze z .robomongo, ale nie z pymongo, nadal się podnosi Żałuję , że InvalidDocument: key '1.1' must not contain '.'nie zostało to już naprawione ...
Nauka to bałagan
Próbowałem z serwerem mongodb 4.0.9 i sterownikiem java 3.10.2, ale nie akceptuje kropki w nazwie klucza. dziwne, że kiedy próbujesz użyć robomongo, działa ...
xyzt
12

Rozwiązanie, które właśnie zaimplementowałem, z którego jestem naprawdę zadowolony, polega na podzieleniu nazwy klucza i wartości na dwa oddzielne pola. W ten sposób mogę zachować postacie dokładnie takie same i nie martwić się żadnym z tych koszmarów związanych z analizą. Dokument wyglądałby następująco:

{
    ...
    keyName: "domain.com",
    keyValue: "unregistered",
    ...
}

Nadal możesz zapytać o to dość łatwo, po prostu wykonując a findna polach keyName i keyValue .

Więc zamiast:

 db.collection.find({"domain.com":"unregistered"})

który w rzeczywistości nie zadziała zgodnie z oczekiwaniami, uruchomisz:

db.collection.find({keyName:"domain.com", keyValue:"unregistered"})

i zwróci oczekiwany dokument.

Steve
źródło
Jak to zrobiłeś? Czy mógłbyś mi pomóc w tej samej sprawie.
profiler
Dodałem przykład zapytania. To pomaga?
Steve,
10

Możesz spróbować użyć skrótu w kluczu zamiast wartości, a następnie zapisać tę wartość w wartości JSON.

var crypto = require("crypto");   

function md5(value) {
    return crypto.createHash('md5').update( String(value) ).digest('hex');
}

var data = {
    "_id": {
        "$oid": "..."
    },
    "make": "saab",
    "models": {}
}

var version = "9.7x";

data.models[ md5(version) ] = {
    "version": version,
    "years" : [
        2007,
        2008,
        2009,
        2010
    ]
}

Następnie uzyskasz dostęp do modeli przy użyciu skrótu później.

var version = "9.7x";
collection.find( { _id : ...}, function(e, data ) {
    var models = data.models[ md5(version) ];
}
Henz
źródło
1
Podoba mi się to, czyste rozwiązanie z jednokierunkowym haszowaniem i bardzo podobne do tego, co działa pod maską.
Michael Yagudaev
3
Problem z używaniem skrótów jako kluczy polega na tym, że nie ma gwarancji, że są one unikalne i często powodują kolizje . Dodatkowo obliczanie kryptograficznego skrótu za każdym razem, gdy chcesz uzyskać dostęp do mapy, nie wydaje mi się najbardziej optymalnym rozwiązaniem.
Steve Eynon
2
Dlaczego jest to lepsze niż zastąpienie kropki specjalnym znakiem lub sekwencją?
B Seven
Konwersja łańcuchów do base64 jest znacznie lepsza.
Zen
8

Jest teraz obsługiwany

MongoDb 3.6 i nowsze obsługują zarówno kropki, jak i dolary w nazwach pól. Zobacz poniżej JIRA: https://jira.mongodb.org/browse/JAVA-2810

Aktualizacja Mongodb do wersji 3.6+ brzmi jak najlepszy sposób.

Abhidemon
źródło
To najlepsza odpowiedź. : +1
hello_abhishek
3
3.6 można je przechowywać tak, ale to nie jeszcze obsługiwane, może rzucać błędy kierowcy i może złamać zapytanie / aktualizacje: ograniczenia : „The MongoDB Query Language nie zawsze sensownie wyrazić zapytania nad dokumentami, których nazwy pola zawierają te znaki (patrz server- 30575). Do czasu dodania obsługi w języku zapytań stosowanie znaków $ i. W nazwach pól nie jest zalecane i nie jest obsługiwane przez oficjalne sterowniki MongoDB ”.
JeremyDouglass
4

Z dokumentów MongoDB „the”. znak nie może występować nigdzie w nazwie klucza ”. Wygląda na to, że będziesz musiał wymyślić schemat kodowania lub obejść się bez niego.

maerics
źródło
4

Musisz uciec od kluczy. Ponieważ wydaje się, że większość ludzi nie wie, jak prawidłowo uciec od ciągów, oto kroki:

  1. wybierz postać ucieczki (najlepiej wybrać postać, która jest rzadko używana). Na przykład. '~'
  2. Aby uciec, najpierw zastąp wszystkie wystąpienia znaku ucieczki jakąś sekwencją poprzedzoną znakiem ucieczki (np. '~' -> '~ t'), a następnie zamień dowolny znak lub sekwencję, której potrzebujesz, aby uciec, jakąś sekwencją poprzedzoną znakiem ucieczki . Na przykład. '.' -> „~ p”
  3. Aby cofnąć ucieczkę, najpierw usuń sekwencję sterującą ze wszystkich wystąpień drugiej sekwencji sterującej (np. „~ P” -> „.”), A następnie przekształć sekwencję znaków sterujących w pojedynczy znak sterujący (np. „~ S” -> „~ ')

Pamiętaj też, że mongo również nie pozwala, aby klawisze zaczynały się od '$', więc musisz tam zrobić coś podobnego

Oto kod, który to robi:

// returns an escaped mongo key
exports.escape = function(key) {
  return key.replace(/~/g, '~s')
            .replace(/\./g, '~p')
            .replace(/^\$/g, '~d')
}

// returns an unescaped mongo key
exports.unescape = function(escapedKey) {
  return escapedKey.replace(/^~d/g, '$')
                   .replace(/~p/g, '.')
                   .replace(/~s/g, '~')
}
BT
źródło
Ta ucieczka może nadal się zepsuć, jeśli masz ciągi takie jak „. ~ P.”. W tym przypadku ciągiem ucieczki będzie '~ p ~~ p ~ p'. Cofnięcie zmiany znaczenia da ci „. ~ ..”, który różni się od rzeczywistego ciągu.
jvc
1
@jvc Masz rację! Poprawiłem wyjaśnienie i przykładowe funkcje ucieczki. Daj mi znać, jeśli nadal są zepsute!
BT,
3

Późna odpowiedź, ale jeśli używasz Spring i Mongo, Spring może zarządzać konwersją za Ciebie za pomocą MappingMongoConverter. To rozwiązanie firmy JohnnyHK, ale obsługiwane przez Spring.

@Autowired
private MappingMongoConverter converter;

@PostConstruct
public void configureMongo() {
 converter.setMapKeyDotReplacement("xxx");
}

Jeśli zapisany plik Json to:

{ "axxxb" : "value" }

Do wiosny (MongoClient) będzie czytane jako:

{ "a.b" : "value" }
PomPom
źródło
wymagano komponentu bean typu „org.springframework.data.mongodb.core.convert.MappingMongoConverter”, którego nie można znaleźć.
Sathya Narayan C,
1

Używam następujących znaków ucieczki w JavaScript dla każdego klucza obiektu:

key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')

Podoba mi się to, że zastępuje tylko $na początku i nie używa znaków Unicode, które mogą być trudne w użyciu w konsoli. _jest dla mnie o wiele bardziej czytelny niż znak Unicode. Nie zastępuje również jednego zestawu znaków specjalnych ( $, .) innym (unicode). Ale właściwie ucieka z tradycyjnym \.

Mitar
źródło
3
A jeśli ktoś użyje znaku _ w którymkolwiek ze swoich kluczy, otrzymasz błędy.
BT
1

Nie jest idealny, ale zadziała w większości sytuacji: zastąp niedozwolone znaki czymś innym. Ponieważ jest w kluczach, te nowe znaki powinny być dość rzadkie.

/** This will replace \ with ⍀, ^$ with '₴' and dots with ⋅  to make the object compatible for mongoDB insert. 
Caveats:
    1. If you have any of ⍀, ₴ or ⋅ in your original documents, they will be converted to \$.upon decoding. 
    2. Recursive structures are always an issue. A cheap way to prevent a stack overflow is by limiting the number of levels. The default max level is 10.
 */
encodeMongoObj = function(o, level = 10) {
    var build = {}, key, newKey, value
    //if (typeof level === "undefined") level = 20     // default level if not provided
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = (level > 0) ? encodeMongoObj(value, level - 1) : null     // If this is an object, recurse if we can

        newKey = key.replace(/\\/g, '⍀').replace(/^\$/, '₴').replace(/\./g, '⋅')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

/** This will decode an object encoded with the above function. We assume the structure is not recursive since it should come from Mongodb */
decodeMongoObj = function(o) {
    var build = {}, key, newKey, value
    for (key in o) {
        value = o[key]
        if (typeof value === "object") value = decodeMongoObj(value)     // If this is an object, recurse
        newKey = key.replace(/⍀/g, '\\').replace(/^₴/, '$').replace(/⋅/g, '.')    // replace special chars prohibited in mongo keys
        build[newKey] = value
    }
    return build
}

Oto test:

var nastyObj = {
    "sub.obj" : {"$dollar\\backslash": "$\\.end$"}
}
nastyObj["$you.must.be.kidding"] = nastyObj     // make it recursive

var encoded = encodeMongoObj(nastyObj, 1)
console.log(encoded)
console.log( decodeMongoObj( encoded) )

a wyniki - zwróć uwagę, że wartości nie są modyfikowane:

{
  sub⋅obj: {
    ₴dollar⍀backslash: "$\\.end$"
  },
  ₴you⋅must⋅be⋅kidding: {
    sub⋅obj: null,
    ₴you⋅must⋅be⋅kidding: null
  }
}
[12:02:47.691] {
  "sub.obj": {
    $dollar\\backslash: "$\\.end$"
  },
  "$you.must.be.kidding": {
    "sub.obj": {},
    "$you.must.be.kidding": {}
  }
}
Nico
źródło
1

Jest jakiś brzydki sposób na zapytanie, które nie jest zalecane, aby używać go w aplikacji, a nie do celów debugowania (działa tylko na obiektach osadzonych):

db.getCollection('mycollection').aggregate([
    {$match: {mymapfield: {$type: "object" }}}, //filter objects with right field type
    {$project: {mymapfield: { $objectToArray: "$mymapfield" }}}, //"unwind" map to array of {k: key, v: value} objects
    {$match: {mymapfield: {k: "my.key.with.dot", v: "myvalue"}}} //query
])
sredni
źródło
1

Jak wspomniał inny użytkownik, kodowanie / dekodowanie może stać się problematyczne w przyszłości, więc prawdopodobnie łatwiej będzie zastąpić wszystkie klucze, które mają kropkę. Oto rekurencyjna funkcja, którą utworzyłem, aby zastąpić klucze znakiem „.” wystąpienia:

def mongo_jsonify(dictionary):
    new_dict = {}
    if type(dictionary) is dict:
        for k, v in dictionary.items():
            new_k = k.replace('.', '-')
            if type(v) is dict:
                new_dict[new_k] = mongo_jsonify(v)
            elif type(v) is list:
                new_dict[new_k] = [mongo_jsonify(i) for i in v]
            else:
                new_dict[new_k] = dictionary[k]
        return new_dict
    else:
        return dictionary

if __name__ == '__main__':
    with open('path_to_json', "r") as input_file:
        d = json.load(input_file)
    d = mongo_jsonify(d)
    pprint(d)

Możesz zmodyfikować ten kod, aby zastąpić '$', ponieważ jest to kolejny znak, na który mongo nie zezwala w kluczu.

Teddy Haley
źródło
0

W przypadku PHP podstawiam wartość HTML dla kropki. To jest"." .

Przechowuje w MongoDB w ten sposób:

  "validations" : {
     "4e25adbb1b0a55400e030000" : {
     "associate" : "true" 
    },
     "4e25adb11b0a55400e010000" : {
       "associate" : "true" 
     } 
   } 

i kod PHP ...

  $entry = array('associate' => $associate);         
  $data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry ));     
  $newstatus = $collection->update($key, $data, $options);      
JRForbes
źródło
0

Pary Lodash pozwolą Ci na zmianę

{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }

w

[ [ 'connect.sid',
's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]

za pomocą

var newObj = _.pairs(oldObj);
steampowered
źródło
0

Możesz go przechowywać tak, jak jest, a potem przekonwertować na ładny

Napisałem ten przykład na Livescript. Możesz użyć strony livescript.net, aby to ocenić

test =
  field:
    field1: 1
    field2: 2
    field3: 5
    nested:
      more: 1
      moresdafasdf: 23423
  field3: 3



get-plain = (json, parent)->
  | typeof! json is \Object => json |> obj-to-pairs |> map -> get-plain it.1, [parent,it.0].filter(-> it?).join(\.)
  | _ => key: parent, value: json

test |> get-plain |> flatten |> map (-> [it.key, it.value]) |> pairs-to-obj

Będzie produkować

{"field.field1":1,
 "field.field2":2,
 "field.field3":5,
 "field.nested.more":1,
 "field.nested.moresdafasdf":23423,
 "field3":3}

Andrey Stehno
źródło
0

Daję ci moją wskazówkę: możesz użyć JSON.stringify do zapisania obiektu / tablicy zawierającej nazwę klucza z kropkami, a następnie przeanalizuj ciąg do Object za pomocą JSON.parse, aby przetworzyć, gdy pobierzesz dane z bazy danych

Inne obejście: Zmień strukturę schematu, na przykład:

key : {
"keyName": "a.b"
"value": [Array]
}
Panie Cr
źródło
0

Najnowsza baza danych MongoDB obsługuje klawisze z kropką, ale sterownik java MongoDB nie obsługuje. Aby to działało w Javie, wyciągnąłem kod z repozytorium github sterownika java-mongo i dokonałem odpowiednich zmian w ich funkcji isValid Key, stworzyłem z niego nowy jar, używając go teraz.

ANDY MURAY
źródło
0

Zastąp kropkę ( .) lub dolara ( $) innymi znakami, które nigdy nie będą używane w prawdziwym dokumencie. I przywróć kropkę ( .) lub dolara ( $) podczas pobierania dokumentu. Strategia nie wpłynie na dane czytane przez użytkownika.

Możesz wybrać postać spośród wszystkich znaków .

Simin Jie
źródło
0

Dziwne jest to, że używając mongojs, mogę utworzyć dokument z kropką, jeśli sam ustawię _id, jednak nie mogę utworzyć dokumentu, gdy jest generowany _id:

Działa:

db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => {
    console.log(err, res);
});

Nie działa:

db.testcollection.save({"dot.ted": "value"}, (err, res) => {
    console.log(err, res);
});

Najpierw pomyślałem, że aktualizacja dokumentu za pomocą klawisza z kropką również zadziałała, ale identyfikuje kropkę jako podklucz!

Widząc, jak mongojs obsługuje kropkę (podklucz), upewnię się, że moje klucze nie zawierają kropki.

Sam
źródło
0

Jak wspomniał @JohnnyHK , usuń znaki interpunkcyjne lub „.” z kluczy, ponieważ spowoduje to znacznie większe problemy, gdy dane zaczną gromadzić się w większym zbiorze danych. Spowoduje to problemy, zwłaszcza w przypadku wywoływania operatorów agregujących, takich jak $ merge, które wymagają dostępu i porównania kluczy, co spowoduje błąd. Nauczyłem się tego na własnej skórze, proszę nie powtarzać tym, którzy zaczynają.

Yi Xiang Chong
źródło
-2

/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py

Znalazłem to w komunikatach o błędach. Jeśli używasz anaconda(znajdź odpowiedni plik, jeśli nie), po prostu zmień wartość z check_keys = Truena Falsew powyższym pliku. To zadziała!

Layang
źródło