Jak usunąć cały magazyn danych w Google App Engine?


122

Czy ktoś wie, jak usunąć cały magazyn danych w Google App Engine ?


2
db.delete (db.Query (keys_only = True)). Więcej szczegółów tutaj stackoverflow.com/a/10856555/290340 .
Evan Plaice

4
Jak wskazał poniżej @systempuntoout, GAE ma teraz administratora Datastore, który umożliwia między innymi zbiorcze usuwanie jednostek bez kodowania. Ta funkcja musi zostać tutaj ujawniona, a nie ukryta w trzecim komentarzu.
ralfoide

Administrator Datastore nie działa (strona ładuje ramkę iframe do nieistniejącego hosta), więc nadal musielibyśmy użyć metody db.delete.

Aby usunąć wszystkie dane z serwera deweloperskiego, wydaj następujące polecenie w wierszu polecenia cmd: /path/to/google_appengine/dev_appserver.py --clear_datastore yes myappname/ gdzie myappname to katalog zawierający plik app.yaml dla aplikacji .. musisz przejść do tej ścieżki katalogu .. kredyt: Steven Almeroth i Melllvar, aby uzyskać odpowiedź poniżej
gsinha

Odpowiedzi:


69

Jeśli mówisz o żywym magazynie danych , otwórz pulpit nawigacyjny swojej aplikacji (zaloguj się do appengine), a następnie datastore -> dataviewer, wybierz wszystkie wiersze tabeli, którą chcesz usunąć i naciśnij przycisk usuwania (będziesz musiał zrób to dla wszystkich swoich tabel). Możesz zrobić to samo programowo przez remote_api (ale nigdy go nie używałem).

Jeśli mówisz o deweloperskim magazynie danych , musisz po prostu usunąć następujący plik: „./WEB-INF/appengine-generated/local_db.bin” . Plik zostanie wygenerowany ponownie, gdy następnym razem uruchomisz serwer programistyczny i będziesz mieć czystą bazę danych.

Pamiętaj, aby później wyczyścić projekt.

To jedna z małych pułapek, które przydają się, gdy zaczynasz grać z silnikiem aplikacji Google. Przekonasz się, że utrwalasz obiekty w magazynie danych, a następnie zmieniasz model obiektów JDO dla twoich trwałych encji, kończąc na przestarzałych danych, które powodują awarię aplikacji w każdym miejscu.


16
W dev_appserver.py znajduje się parametr -c do usunięcia z magazynu danych deweloperskich.
svrist

1
@svrist Ale dotyczy to tylko silnika aplikacji Python. Czy ktoś wie, jak to zrobić na skróty w Javie? (W międzyczasie sugestia JohnIdol działa dobrze.)
mgiuca

2
Dzięki @John: Gdzie jest dokładna ścieżka w MAC OSX?
George Nguyen

3
Gdzie jest ścieżka w systemie Windows?
Shane Best

2
@ShaneBest ścieżka w systemie Windows to coś w rodzaju ./target/yourappid-1.0-SNAPSHOT/WEB-INF/appengine-generated/local_db.bin
morpheus

58

Najlepszym podejściem jest zdalna metoda API, zgodnie z sugestią Nicka, inżyniera App Engine z Google , więc zaufaj mu.

Nie jest to takie trudne, a najnowszy SDK 1.2.5 zapewnia plik remote_shell_api.py z półki. Więc przejdź do pobrania nowego SDK. Następnie postępuj zgodnie z instrukcjami:

  • podłącz zdalny serwer w linii poleceń: remote_shell_api.py yourapp /remote_api Powłoka zapyta o dane logowania, a jeśli zostanie autoryzowana, utworzy dla Ciebie powłokę Pythona. Musisz skonfigurować obsługę adresu URL dla / remote_api w pliku app.yaml

  • pobierz encje, które chcesz usunąć, kod wygląda mniej więcej tak:

    from models import Entry
    query = Entry.all(keys_only=True)
    entries =query.fetch(1000)
    db.delete(entries)
    \# This could bulk delete 1000 entities a time

Aktualizacja 2013-10-28 :

  • remote_shell_api.pyzostał zastąpiony przez remote_api_shell.py, z którym należy się połączyć remote_api_shell.py -s your_app_id.appspot.com, zgodnie z dokumentacją .

  • Dostępna jest nowa eksperymentalna funkcja Datastore Admin , po włączeniu jej w ustawieniach aplikacji można zbiorczo usuwać, a także tworzyć kopie zapasowe magazynu danych za pośrednictwem interfejsu internetowego.


17
Właściwie nie potrzebujesz pobierania. Wystarczy db.delete (Entry.all ()).
pobierz

4
Musisz to zrobić w 500 zestawach jednostek, inaczej otrzymasz: BadRequestError: nie można usunąć więcej niż 500 jednostek w jednym wywołaniu
marcc

1
Tylko do Twojej wiadomości, abyś mógł używać zdalnego interfejsu API, musisz go najpierw włączyć w swojej aplikacji za pomocą wbudowanych: - remote_api: w pliku YAML. Więcej informacji znajduje się na developers.google.com/appengine/articles/remote_api
Zaffiro

2
Przynajmniej dodaj „keys_only = True” podczas wywoływania Entry.all (). Nie ma potrzeby pobierania całego wpisu, jeśli nie musisz sprawdzać danych. W przeciwnym razie marnujesz cykle obliczeniowe.
Evan Plaice

1
+1 ... ale: od 2013, remote_shell_api.py nie istnieje. Bieżąca nazwa skryptu to remote_api_shell.py. Ponadto, jeśli używasz ndb (co jest tym, co większość ludzi robi obecnie), zalecany sposób użycia ndb.delete_multi (model.Entry.query (). Fetch (keys_only = True))
Uri

27

Najszybszy i skuteczny sposób obsłużyć luzem usunąć na magazyn danych jest użycie nowego odwzorowujący API ogłoszony na najnowszej Google I / O .

Jeśli wybranym językiem jest Python , wystarczy zarejestrować program mapujący w pliku mapreduce.yaml i zdefiniować taką funkcję:

from mapreduce import operation as op
def process(entity):
 yield op.db.Delete(entity)

W Javie powinieneś zajrzeć do tego artykułu, który sugeruje taką funkcję:

@Override
public void map(Key key, Entity value, Context context) {
    log.info("Adding key to deletion pool: " + key);
    DatastoreMutationPool mutationPool = this.getAppEngineContext(context)
            .getMutationPool();
    mutationPool.delete(value.getKey());
}

EDYCJA:
Od SDK 1.3.8 do tego celu służy funkcja administratora Datastore


27

Możesz wyczyścić magazyn danych serwera deweloperskiego po uruchomieniu serwera:

/path/to/dev_appserver.py --clear_datastore=yes myapp

Można też skrócić --clear_datastorez -c.


5
Nie jestem pewien, czy to ostatnia rzecz, ale rzeczywista składnia jest teraz /path/to/google_appengine/dev_appserver.py --clear_datastore yes myappname/(zwróć uwagę na „tak”)
Melllvar,

Jest to najbardziej przydatny sposób wielokrotnego usuwania magazynu danych podczas programowania. Ponieważ opcje szybko stają się przestarzałe, warto podkreślić, że ta flaga nadal obowiązuje w lipcu 2018 r. I działa na dev_appserver zainstalowanym przez interfejs CLI gcloud
Michael

W wersji 270.0.0 Google Cloud SDK „--clear_datastore = yes” nadal działa ze znakiem równości
franksands

15

Jeśli masz znaczną ilość danych, musisz użyć skryptu, aby je usunąć. Możesz jednak użyć remote_api, aby wyczyścić magazyn danych po stronie klienta w prosty sposób.


11

Proszę bardzo: przejdź do Datastore Admin, a następnie wybierz typ jednostki, którą chcesz usunąć, i kliknij Usuń. Mapreduce zajmie się usunięciem!


9

Istnieje kilka sposobów usuwania wpisów z Datastore App Engine:

wprowadź opis obrazu tutaj

  1. Najpierw zastanów się, czy naprawdę musisz usuwać wpisy. Jest to kosztowne i może być tańsze, aby ich nie usunąć.

  2. Możesz usunąć wszystkie wpisy ręcznie, korzystając z administratora Datastore.

  3. Możesz użyć Remote API i interaktywnie usuwać wpisy.

  4. Możesz usunąć wpisy programowo, używając kilku wierszy kodu.

  5. Możesz usuwać je zbiorczo za pomocą kolejek zadań i kursorów.

  6. Lub możesz użyć Mapreduce, aby uzyskać coś solidniejszego i bardziej wyszukanego.

Każda z tych metod została opisana w następującym wpisie na blogu: http://www.shiftedup.com/2015/03/28/how-to-bulk-delete-entries-in-app-engine-datastore

Mam nadzieję, że to pomoże!


6

Sposób konfiguracji zerowej polega na wysłaniu żądania HTTP z wykonaniem dowolnego kodu do usługi administratora, którą Twoja uruchomiona aplikacja ma już automatycznie:

import urllib
import urllib2

urllib2.urlopen('http://localhost:8080/_ah/admin/interactive/execute',
    data = urllib.urlencode({'code' : 'from google.appengine.ext import db\n' +
                                      'db.delete(db.Query())'}))

Działa to tylko na serwerze deweloperskim. Czy istnieje odpowiednik produkcji?
Gady,

3

Źródło

Mam to od http://code.google.com/appengine/articles/remote_api.html .

Utwórz konsolę interaktywną

Najpierw musisz zdefiniować interaktywną konsolę appenginge. Stwórz więc plik o nazwie appengine_console.py i wprowadź to:

#!/usr/bin/python
import code
import getpass
import sys

# These are for my OSX installation. Change it to match your google_appengine paths. sys.path.append("/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine")
sys.path.append("/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/yaml/lib")

from google.appengine.ext.remote_api import remote_api_stub
from google.appengine.ext import db

def auth_func():
  return raw_input('Username:'), getpass.getpass('Password:')

if len(sys.argv) < 2:
  print "Usage: %s app_id [host]" % (sys.argv[0],)
app_id = sys.argv[1]
if len(sys.argv) > 2:
  host = sys.argv[2]
else:
  host = '%s.appspot.com' % app_id

remote_api_stub.ConfigureRemoteDatastore(app_id, '/remote_api', auth_func, host)

code.interact('App Engine interactive console for %s' % (app_id,), None, locals())



Utwórz klasę bazową Mapper

Gdy to zrobisz, utwórz tę klasę Mapper. Właśnie utworzyłem nowy plik o nazwie utils.py i wrzuciłem to:

class Mapper(object):
  # Subclasses should replace this with a model class (eg, model.Person).
  KIND = None

  # Subclasses can replace this with a list of (property, value) tuples to filter by.
  FILTERS = []

  def map(self, entity):
    """Updates a single entity.

    Implementers should return a tuple containing two iterables (to_update, to_delete).
    """
    return ([], [])

  def get_query(self):
    """Returns a query over the specified kind, with any appropriate filters applied."""
    q = self.KIND.all()
    for prop, value in self.FILTERS:
      q.filter("%s =" % prop, value)
    q.order("__key__")
    return q

  def run(self, batch_size=100):
    """Executes the map procedure over all matching entities."""
    q = self.get_query()
    entities = q.fetch(batch_size)
    while entities:
      to_put = []
      to_delete = []
      for entity in entities:
        map_updates, map_deletes = self.map(entity)
        to_put.extend(map_updates)
        to_delete.extend(map_deletes)
      if to_put:
        db.put(to_put)
      if to_delete:
        db.delete(to_delete)
      q = self.get_query()
      q.filter("__key__ >", entities[-1].key())
      entities = q.fetch(batch_size)

Mapper ma być po prostu klasą abstrakcyjną, która pozwala na iterację po każdej encji danego rodzaju, czy to w celu wyodrębnienia ich danych, czy też zmodyfikowania ich i przechowywania zaktualizowanych jednostek z powrotem do magazynu danych.

Biegnij z tym!

Teraz uruchom swoją interaktywną konsolę appengine:

$python appengine_console.py <app_id_here>

To powinno uruchomić konsolę interaktywną. W nim utwórz podklasę Model:

from utils import Mapper
# import your model class here 
class MyModelDeleter(Mapper):
    KIND = <model_name_here>

    def map(self, entity):
        return ([], [entity])

I na koniec uruchom go (z interaktywnej konsoli): mapper = MyModelDeleter () mapper.run ()

Otóż ​​to!


3

Możesz to zrobić za pomocą interfejsu internetowego. Zaloguj się na swoje konto, nawiguj za pomocą linków po lewej stronie. W zarządzaniu magazynem danych masz opcje modyfikacji i usuwania danych. Użyj odpowiednich opcji.


3

Utworzyłem panel dodatków, którego można używać z wdrożonymi aplikacjami App Engine. Zawiera listę rodzajów, które są obecne w magazynie danych na liście rozwijanej. Możesz kliknąć przycisk, aby zaplanować „zadania”, które usuwają wszystkie elementy określonego rodzaju lub po prostu wszystko. Możesz go pobrać tutaj:
http://code.google.com/p/jobfeed/wiki/Nuke


3

W przypadku Pythona 1.3.8 zawiera wbudowanego eksperymentalnego administratora. Oni mówią : „umożliwiają następujące polecenie wbudowane w pliku app.yaml”

builtins:
- datastore_admin: on

„Usuwanie Datastore jest obecnie dostępne tylko w środowisku wykonawczym Python. Aplikacje Java mogą jednak nadal korzystać z tej funkcji, tworząc inną niż domyślną wersję aplikacji Python, która włącza Datastore Admin w app.yaml. Natywna obsługa języka Java zostanie uwzględniona w nadchodzącej wersji ”.


Dodanie konfiguracji w app.yaml spowodowało błąd. Zamiast tego możemy go włączyć na stronie „Ustawienia aplikacji” w sekcji „Administracja”. Jest przycisk, aby to włączyć
Sundeep

3

Otwórz „Datastore Admin” dla swojej aplikacji i włącz Administratora. Następnie wszystkie twoje podmioty zostaną wymienione z polami wyboru. Możesz po prostu wybrać niechciane wpisy i je usunąć.


3

To jest to, czego szukasz ...

db.delete(Entry.all(keys_only=True))

Wykonywanie zapytań zawierających tylko klucze jest znacznie szybsze niż pełne pobieranie, a Twój przydział będzie wymagał mniejszego działania, ponieważ zapytania zawierające tylko klucze są uważane za małe operacje.

Oto link do odpowiedzi Nicka Johnsona, opisującej to dalej.

Poniżej znajduje się kompleksowe rozwiązanie interfejsu API REST do obcinania tabeli ...

Skonfigurowałem REST API do obsługi transakcji bazy danych, w których trasy są bezpośrednio mapowane do odpowiedniego modelu / akcji. Można to wywołać, wprowadzając właściwy adres URL (example.com/inventory/truncate) i logując się.

Oto trasa:

Route('/inventory/truncate', DataHandler, defaults={'_model':'Inventory', '_action':'truncate'})

Oto program obsługi:

class DataHandler(webapp2.RequestHandler):
  @basic_auth
  def delete(self, **defaults):
    model = defaults.get('_model')
    action = defaults.get('_action')
    module = __import__('api.models', fromlist=[model])
    model_instance = getattr(module, model)()
    result = getattr(model_instance, action)()

Rozpoczyna się od dynamicznego załadowania modelu (tj. Inventory znalezionego w api.models), a następnie wywołuje poprawną metodę (Inventory.truncate ()) określoną w parametrze akcji.

@Basic_auth jest dekoratorem / opakowaniem, które zapewnia uwierzytelnianie wrażliwych operacji (np. POST / DELETE). Istnieje również dekorator OAuth jeśli się o bezpieczeństwo.

Wreszcie akcja nazywa się:

def truncate(self):
  db.delete(Inventory.all(keys_only=True))

Wygląda to jak magia, ale w rzeczywistości jest bardzo proste. Najlepsze jest to, że metoda delete () może zostać ponownie użyta do usunięcia jednego lub wielu wyników poprzez dodanie innej akcji do modelu.


3

Możesz usunąć wszystkie Datastore, usuwając wszystkie rodzaje jeden po drugim. z deską rozdzielczą Google Appengine. Postępuj zgodnie z tymi krokami.

  1. Zaloguj się na https://console.cloud.google.com/datastore/settings
  2. Kliknij Otwórz administratora Datastore . (Włącz ją, jeśli nie jest włączona).
  3. Wybierz wszystkie jednostki i naciśnij klawisz Delete (w tym kroku uruchom zadanie redukcji mapy w celu usunięcia wszystkich wybranych rodzajów).

Aby uzyskać więcej informacji, zobacz ten obraz http://storage.googleapis.com/bnifsc/Screenshot%20from%202015-01-31%2023%3A58%3A41.png


2

Jeśli masz dużo danych, korzystanie z interfejsu internetowego może być czasochłonne. Narzędzie App Engine Launcher umożliwia usunięcie wszystkiego za jednym razem za pomocą pola wyboru „Wyczyść magazyn danych przy uruchomieniu”. To narzędzie jest teraz dostępne zarówno dla systemu Windows, jak i Mac (platforma Python).


2

W przypadku serwera deweloperskiego zamiast uruchamiać serwer za pośrednictwem programu uruchamiającego silnik aplikacji Google, możesz uruchomić go z terminala, na przykład:

dev_appserver.py --port = [numer portu] --clear_datastore = tak [nazwa_aplikacji]

np. „czytnik” mojej aplikacji działa na porcie 15080. Po zmodyfikowaniu kodu i ponownym uruchomieniu serwera po prostu uruchamiam „dev_appserver.py --port = 15080 --clear_datastore = yes reader”.

To jest dobre dla mnie.



1

Często nie chcę usuwać całego magazynu danych, więc wyciągam czystą kopię /war/WEB-INF/local_db.bin z kontroli źródła. Może to tylko ja, ale wydaje się, że nawet po zatrzymaniu trybu deweloperskiego muszę fizycznie usunąć plik przed jego ściągnięciem. To jest w systemie Windows przy użyciu wtyczki subversion dla Eclipse.


0

Odmiana PHP:

import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.DatastoreServiceFactory;

define('DATASTORE_SERVICE', DatastoreServiceFactory::getDatastoreService());

function get_all($kind) {
    $query = new Query($kind);
    $prepared = DATASTORE_SERVICE->prepare($query);
    return $prepared->asIterable();
}

function delete_all($kind, $amount = 0) {
    if ($entities = get_all($kind)) {
        $r = $t = 0;
        $delete = array();
        foreach ($entities as $entity) {
            if ($r < 500) {
                $delete[] = $entity->getKey();
            } else {
                DATASTORE_SERVICE->delete($delete);
                $delete = array();
                $r = -1;
            }
            $r++; $t++;
            if ($amount && $amount < $t) break;
        }
        if ($delete) {
            DATASTORE_SERVICE->delete($delete);
        }
    }
}

Tak, zajmie to trochę czasu i 30 sekund. jest granicą. Myślę o umieszczeniu przykładowej aplikacji Ajax, aby zautomatyzować ponad 30 sekund.


To nawet nie jest poprawny plik php. import? Definiowanie stałej jako instancji obiektu?
Josh J

0
for amodel in db.Model.__subclasses__():
                dela=[]
                print amodel
                try:
                    m = amodel()
                    mq = m.all()
                    print mq.count()
                    for mw in mq:
                        dela.append(mw)
                    db.delete(dela)
            #~ print len(dela)

                except:
                    pass

0

Jeśli używasz ndb, metoda, która zadziałała w moim przypadku do czyszczenia magazynu danych:

ndb.delete_multi(ndb.Query(default_options=ndb.QueryOptions(keys_only=True)))

1
Myślę, że to nie zadziała. Appengine narzeka na Sorry, unexpected error: The kind "__Stat_Kind__" is reserved.to Wygląda na to, że appengine ma jakąś wewnętrzną jednostkę statystyczną, którą można ujawnić za pomocą tej metody (możliwy błąd na ich końcu?)
zniknął

0

W przypadku dowolnego magazynu danych znajdującego się w silniku aplikacji, a nie lokalnym, możesz użyć nowego interfejsu API Datastore . Oto wprowadzenie, jak zacząć .

Napisałem skrypt, który usuwa wszystkie niewbudowane encje. Interfejs API zmienia się dość szybko, więc dla odniesienia sklonowałem go w zatwierdzeniu 990ab5c7f2063e8147bcc56ee222836fd3d6e15b

from gcloud import datastore
from gcloud.datastore import SCOPE
from gcloud.datastore.connection import Connection
from gcloud.datastore import query

from oauth2client import client

def get_connection():
  client_email = 'XXXXXXXX@developer.gserviceaccount.com'
  private_key_string = open('/path/to/yourfile.p12', 'rb').read()

  svc_account_credentials = client.SignedJwtAssertionCredentials(
    service_account_name=client_email,
    private_key=private_key_string,
    scope=SCOPE)

  return Connection(credentials=svc_account_credentials)


def connect_to_dataset(dataset_id):
  connection = get_connection()
  datastore.set_default_connection(connection)
  datastore.set_default_dataset_id(dataset_id)

if __name__ == "__main__":
  connect_to_dataset(DATASET_NAME)
  gae_entity_query = query.Query()
  gae_entity_query.keys_only()
  for entity in gae_entity_query.fetch():
    if entity.kind[0] != '_':
      print entity.kind
      entity.key.delete()

0
  • kontynuując ideę svpino, mądrze jest ponowne użycie rekordów oznaczonych jako usuń. (jego pomysłem nie było usuwanie, ale oznaczanie jako „usunięte” nieużywanych rekordów). trochę pamięci podręcznej / memcache do obsługi kopii roboczej i zapisywania tylko różnic stanów (przed i po żądanym zadaniu) do magazynu danych sprawi, że będzie lepiej. w przypadku dużych zadań możliwe jest zapisanie fragmentów zmian itermediate do magazynu danych, aby uniknąć utraty danych w przypadku zniknięcia memcache. aby zabezpieczyć go przed utratą, można sprawdzić integralność / istnienie wyników w pamięci podręcznej i ponownie uruchomić zadanie (lub wymaganą część), aby powtórzyć brakujące obliczenia. gdy różnica danych jest zapisywana w magazynie danych, wymagane obliczenia są odrzucane w kolejce.

  • Innym pomysłem podobnym do zredukowanej mapy jest podzielenie rodzaju obiektu na kilka różnych rodzajów obiektów, więc zostanie on zebrany razem i widoczny jako pojedynczy rodzaj elementu dla użytkownika końcowego. wpisy są oznaczone tylko jako „usunięte”. kiedy liczba "usuniętych" wpisów na fragment przekracza pewien limit, "żywe" wpisy są rozdzielane między inne fragmenty, a ten fragment jest zamykany na zawsze, a następnie usuwany ręcznie z konsoli deweloperskiej (przypuszczenie po niższym koszcie) upd: wydaje się, że nie ma drop table w konsoli, usuwaj tylko rekord po rekordzie po zwykłej cenie.

  • możliwe jest usuwanie przez zapytanie przez fragmenty dużego zestawu rekordów bez awarii gae (przynajmniej działa lokalnie) z możliwością kontynuowania przy następnej próbie po upływie czasu:


    qdelete.getFetchPlan().setFetchSize(100);

    while (true)
    {
        long result = qdelete.deletePersistentAll(candidates);
        LOG.log(Level.INFO, String.format("deleted: %d", result));
        if (result <= 0)
            break;
    }
  • Czasami warto również utworzyć dodatkowe pole w tabeli podstawowej zamiast umieszczać kandydatów (powiązane rekordy) w oddzielnej tabeli. i tak, pole może być tablicą nieindeksowaną / serializowaną przy niewielkich kosztach obliczeniowych.

0

Dla wszystkich, którzy potrzebują szybkiego rozwiązania dla serwera deweloperskiego (stan na dzień pisania w lutym 2016 r.):

  1. Zatrzymaj serwer deweloperski.
  2. Usuń katalog docelowy .
  3. Odbuduj projekt.

Spowoduje to wyczyszczenie wszystkich danych z magazynu danych.


0

Byłem tak sfrustrowany istniejącymi rozwiązaniami do usuwania wszystkich danych w aktywnym magazynie danych , że stworzyłem małą aplikację GAE, która może usunąć całkiem sporo danych w ciągu 30 sekund.

Jak zainstalować itp .: https://github.com/xamde/xydra


0

Dla java

DatastoreService db = DatastoreServiceFactory.getDatastoreService();
List<Key> keys = new ArrayList<Key>();
for(Entity e : db.prepare(new Query().setKeysOnly()).asIterable())
    keys.add(e.getKey());
db.delete(keys);

Działa dobrze na serwerze deweloperskim


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.