INTEGRITY Документация

Хранилище Durable Object на основе SQLite

Durable Object Storage API позволяет Durable Objects для доступа к транзакционному и строго согласованному хранилищу. Хранилище, привязанное к Durable Object, доступно только его уникальному экземпляру и недоступно другим объектам.

Durable Object Storage API включает несколько методов: SQL, восстановление на момент времени (PITR), ключ-значение (KV) и alarm API. Доступные методы API зависят от бэкенда хранилища класса Durable Objects: либо SQLite или KV.

Методы 1 класс Durable Object на основе SQLite Класс Durable Object на основе KV
SQL API ✅ ❌
PITR API ✅ ❌
Синхронный KV API ✅ 2, 3 ❌
Асинхронный KV API ✅ 3 ✅
Alarms API ✅ ✅

Сноски

1 Каждый метод неявно оборачивается в транзакцию, поэтому его результаты атомарны и изолированы от всех остальных операций хранилища, даже при обращении к нескольким парам «ключ-значение».

2 методы KV API, такие как get(), put(), delete(), или list() хранят данные в скрытой таблице SQLite __cf_kv. Обратите внимание: эта таблица будет отображаться при перечислении всех таблиц, но доступ к её содержимому через SQL API получить не удастся.

3 Durable Objects на основе SQLite также используют синхронные методы API KV с помощью ctx.storage.kv, тогда как Durable Object на основе KV предоставляют только асинхронные методы KV API.

Доступ к хранилищу

Durable Objects получают доступ к Storage API через DurableObjectStorage интерфейс и доступен через DurableObjectState::storage свойство. К нему часто обращаются через this.ctx.storage на ctx параметр, передаваемый в конструктор Durable Object.

Следующий фрагмент кода показывает, как сохранять и получать данные с помощью Durable Object Storage API.

export class Counter extends DurableObject {
	constructor(ctx, env) {
		super(ctx, env);
	}

	async increment() {
		let value = (await this.ctx.storage.get("value")) || 0;
		value += 1;
		await this.ctx.storage.put("value", value);
		return value;
	}
}
export class Counter extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
  }

    async increment(): Promise<number> {
      let value: number = (await this.ctx.storage.get('value')) || 0;
      value += 1;
      await this.ctx.storage.put('value', value);
      return value;
    }

}
from workers import DurableObject

class Counter(DurableObject):
  def __init__(self, ctx, env):
    super().__init__(ctx, env)

  async def increment(self):
    value = (await self.ctx.storage.get('value')) or 0
    value += 1
    await self.ctx.storage.put('value', value)
    return value

JavaScript является однопоточным, событийно-ориентированным языком программирования. Это означает, что среды выполнения JavaScript по умолчанию допускают чередование запросов друг с другом, что может приводить к ошибкам параллелизма. Среда выполнения Durable Objects использует сочетание входные шлюзы и выходные шлюзы, чтобы избежать такого рода ошибок конкурентного доступа при выполнении операций с хранилищем. Подробнее в нашем запись в блоге ↗.

SQL API

SqlStorage интерфейс инкапсулирует методы изменения базы данных SQLite, встроенной в Durable Object. SqlStorage интерфейс доступен через sql свойство DurableObjectStorage в качестве класса.

Например, с помощью sql.exec() пользователь может создать таблицу и вставить строки.

import { DurableObject } from "cloudflare:workers";

export class MyDurableObject extends DurableObject {
  sql: SqlStorage;
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS artist(
        artistid    INTEGER PRIMARY KEY,
        artistname  TEXT
      );
      INSERT INTO artist (artistid, artistname) VALUES
        (123, 'Alice'),
        (456, 'Bob'),
        (789, 'Charlie');
    `);
  }
}
from workers import DurableObject

class MyDurableObject(DurableObject):
  def __init__(self, ctx, env):
    super().__init__(ctx, env)
    self.sql = ctx.storage.sql

    self.sql.exec("""
      CREATE TABLE IF NOT EXISTS artist(
        artistid    INTEGER PRIMARY KEY,
        artistname  TEXT
      );
      INSERT INTO artist (artistid, artistname) VALUES
        (123, 'Alice'),
        (456, 'Bob'),
        (789, 'Charlie');
    """)

Durable Objects поддерживают подмножество расширений SQLite для дополнительной функциональности, включая:

См. исходный код ↗ с полным списком поддерживаемых функций.

exec

exec(query: string, ...bindings: any[]): SqlStorageCursor

Параметры

Возвращает

Курсор (SqlStorageCursor) для итерации по результатам строк запроса в виде объектов. SqlStorageCursor является JavaScript Iterable ↗, который поддерживает итерацию с помощью for (let row of cursor). SqlStorageCursor также является JavaScript Iterator ↗, который поддерживает итерацию с помощью cursor.next().

SqlStorageCursor поддерживает следующие методы:

let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let rawResult = cursor.raw().next();

if (!rawResult.done) {
  console.log(rawResult.value); // prints [ 123, 'Alice' ]
} else {
  // query returned zero results
}

console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]
cursor = self.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;")
raw_result = cursor.raw().next()

if not raw_result.done:
  print(raw_result.value)  # prints [ 123, 'Alice' ]
else:
  # query returned zero results
  pass

print(cursor.toArray())  # prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

SqlStorageCursor имеет следующие свойства:

Примеры

SQL API примеры ниже используют следующую схему SQL:

import { DurableObject } from "cloudflare:workers";

export class MyDurableObject extends DurableObject {
  sql: SqlStorage
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`CREATE TABLE IF NOT EXISTS artist(
      artistid    INTEGER PRIMARY KEY,
      artistname  TEXT
    );INSERT INTO artist (artistid, artistname) VALUES
      (123, 'Alice'),
      (456, 'Bob'),
      (789, 'Charlie');`
    );
  }
}

Переберите результаты запроса как объекты строк:

  let cursor = this.sql.exec("SELECT * FROM artist;");

  for (let row of cursor) {
    // Iterate over row object and do something
  }

Преобразование результатов запроса в массив объектов строк:

  // Return array of row objects: [{"artistid":123,"artistname":"Alice"},{"artistid":456,"artistname":"Bob"},{"artistid":789,"artistname":"Charlie"}]
  let resultsArray1 = this.sql.exec("SELECT * FROM artist;").toArray();
  // OR
  let resultsArray2 = Array.from(this.sql.exec("SELECT * FROM artist;"));
  // OR
  let resultsArray3 = [...this.sql.exec("SELECT * FROM artist;")]; // JavaScript spread syntax

Преобразование результатов запроса в массив массивов значений строк:

  // Returns [[123,"Alice"],[456,"Bob"],[789,"Charlie"]]
  let cursor = this.sql.exec("SELECT * FROM artist;");
  let resultsArray = cursor.raw().toArray();

  // Returns ["artistid","artistname"]
  let columnNameArray = this.sql.exec("SELECT * FROM artist;").columnNames.toArray();

Получить объект первой строки результатов запроса:

  // Returns {"artistid":123,"artistname":"Alice"}
  let firstRow = this.sql.exec("SELECT * FROM artist ORDER BY artistname DESC;").toArray()[0];

Проверьте, что результат запроса содержит ровно одну строку:

  // returns error
  this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;").one();

  // returns { artistid: 123, artistname: 'Alice' }
  let oneRow = this.sql.exec("SELECT * FROM artist WHERE artistname = ?;", "Alice").one()

Поведение возвращённого курсора:

  let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
  let result = cursor.next();
  if (!result.done) {
    console.log(result.value); // prints { artistid: 123, artistname: 'Alice' }
  } else {
    // query returned zero results
  }

  let remainingRows = cursor.toArray();
  console.log(remainingRows); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

Возвращённый курсор и raw() итераторы проходят по одним и тем же результатам запроса:

  let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
  let result = cursor.raw().next();

  if (!result.done) {
    console.log(result.value); // prints [ 123, 'Alice' ]
  } else {
    // query returned zero results
  }

  console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

sql.exec().rowsRead():

  let cursor = this.sql.exec("SELECT * FROM artist;");
  cursor.next()
  console.log(cursor.rowsRead); // prints 1

  cursor.toArray(); // consumes remaining cursor
  console.log(cursor.rowsRead); // prints 3

databaseSize

databaseSize: number

Возвращает

Текущий размер базы данных SQLite в байтах.

let size = ctx.storage.sql.databaseSize;
size = ctx.storage.sql.databaseSize

PITR (Point In Time Recovery) API

Для Durable Objects на основе SQLite, доступны следующие методы API восстановления на определённый момент времени (PITR) для восстановления встроенной базы данных SQLite Durable Object на любой момент времени за последние 30 дней. Эти методы применяются ко всему содержимому базы данных SQLite, включая как хранимые SQL-данные объекта, так и хранимые данные ключ-значение с использованием put() API. PITR API не поддерживается при локальной разработке, так как журнал изменений данных не сохраняется локально.

PITR API представляет моменты времени с помощью «закладок» (bookmark). Закладка представляет собой в основном буквенно-числовую строку вида 0000007b-0000b26e-00001538-0c3e87bb37b3db5cc52eedb93cd3b96b. Закладки спроектированы так, чтобы их можно было сравнивать лексически: закладка, соответствующая более раннему моменту времени, оказывается меньше закладки, соответствующей более позднему моменту, при обычном сравнении строк.

getCurrentBookmark

ctx.storage.getCurrentBookmark(): Promise<string>

getBookmarkForTime

ctx.storage.getBookmarkForTime(timestamp: number | Date): Promise<string>

onNextSessionRestoreBookmark

ctx.storage.onNextSessionRestoreBookmark(bookmark: string): Promise<string>

Этот метод возвращает специальную закладку (bookmark), которая обозначает момент времени непосредственно перед восстановлением (хотя формально этот момент ещё находится в будущем). Поэтому после завершения восстановления его можно отменить, выполнив повторное восстановление до этой закладки.

const DAY_MS = 24*60*60*1000;
// restore to 2 days ago
let bookmark = ctx.storage.getBookmarkForTime(Date.now() - 2 * DAYS_MS);
ctx.storage.onNextSessionRestoreBookmark(bookmark);
from datetime import datetime, timedelta

now = datetime.now()
# restore to 2 days ago
bookmark = ctx.storage.getBookmarkForTime(now - timedelta(days=2))
ctx.storage.onNextSessionRestoreBookmark(bookmark)

Синхронный KV API

get

put

delete

list

Поддерживаемые параметры

Асинхронный KV API

get

Поддерживаемые параметры

put

удалить

Поддерживаемые параметры

список

Поддерживаемые параметры

Будильники

getAlarm

Поддерживаемые параметры

setAlarm

deleteAlarm

Поддерживаемые параметры

Прочее

deleteAll

transactionSync

transaction

sync

Свойства хранилища

sql

sql является доступным только для чтения свойством типа DurableObjectStorage инкапсулирующий SQL API.