Slide 63 de 74

A pegadinha do banco em memória

Um tipo de linha
type User { id: Int, name: Str }

query findUser(name: Str): User[dyn] {
	SELECT id, name FROM users WHERE name = {name}
}

proc lookUp(db: hive.sql.SqlConnection, name: Str): Str {
	if hive.sql.run(db, findUser(name)) is Result.Ok(rows) {
		if rows bounds 0 {
			return rows[0].name
		}
	}
	return "?"
}

proc main(): void {
	// Um ":memory:" simples daria a cada uma destas oito conexões um banco
	// privado e vazio, só dela. O cache compartilhado é o que faz todas
	// elas serem o mesmo banco.
	opened := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 8, 1)
	if opened is Result.Ok(db) {
		hive.sql.raw(db, "CREATE TABLE users (id INTEGER, name TEXT)")
		hive.sql.raw(db, "INSERT INTO users (id, name) VALUES (1, 'ada'), (2, 'grace')")

		echo await [lookUp(db, "ada"), lookUp(db, "grace")]
		hive.sql.close(db)
	}
}

Um banco SQLite :memory: simples pertence à conexão, não ao processo: cada conexão ganha um banco privado e vazio que some quando ela fecha. connect e pool abrem ambos um pool de conexões, então isso tem um canto afiado. Uma query por vez funciona; várias ao mesmo tempo não — o pool abre mais conexões, cada uma cai num banco próprio, e as queries que foram para as novas voltam com "no such table". Um programa que passa em todo teste com uma query por vez começa a falhar no momento em que as requisições se sobrepõem, que é exatamente o que acontece atrás de um servidor HTTP.

Há duas saídas, e uma delas é a resposta de verdade. pool(…, 1, 1) limita o pool a uma conexão só, o que funciona mas faz toda query do programa passar por ela uma de cada vez. "file::memory:?cache=shared" é a que se deve usar: as conexões compartilham um único banco em memória, então o pool pode ser tão largo quanto o trabalho. Nada disso é regra do Hive — é do SQLite — mas vale saber antes que a produção ensine.