Slide 61 de 74

SQL

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 main(): void {
	pooled := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 1, 1)
	if pooled 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')")

		found := hive.sql.run(db, findUser("ada"))
		if found is Result.Ok(users) {
			echo users
		}
		hive.sql.close(db)
	} else if pooled is Result.Error(err) {
		echo "não foi possível abrir o banco de dados: {err.message}"
	}
}
Uma coluna, e nenhuma linha
query insertUser(id: Int, name: Str): void {
	INSERT INTO users (id, name) VALUES ({id}, {name})
}

query userNames(): Str[dyn] {
	SELECT name FROM users ORDER BY name
}

query deleteUser(id: Int): void {
	DELETE FROM users WHERE id = {id}
}

proc main(): void {
	opened := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 1, 1)
	if opened is Result.Ok(db) {
		hive.sql.raw(db, "CREATE TABLE users (id INTEGER, name TEXT)")
		hive.sql.run(db, insertUser(1, "ada"))
		hive.sql.run(db, insertUser(2, "grace"))

		if hive.sql.run(db, userNames()) is Result.Ok(names) {
			echo names                      // um Str[dyn]: a coluna em si
		}
		if hive.sql.run(db, deleteUser(1)) is Result.Ok(rows) {
			echo "apagou {rows} linha"      // um Int: o que a instrução tocou
		}
		hive.sql.close(db)
	}
}
Toda coluna, como uma Table
query everything(): Table {
	SELECT * FROM users ORDER BY id
}

proc main(): void {
	opened := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 1, 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 VALUES (1, 'ada'), (2, 'grace')")
		echo hive.sql.run(db, everything())   // Ok([["id", "name"], ["1", "ada"], ["2", "grace"]])
		hive.sql.close(db)
	}
}

Uma query é um func cujo corpo é SQL e cujo tipo de retorno descreve suas linhas. Chamar uma não roda nada: ela responde com um hive.sql.Fragment<linhas> — o texto, e os valores vinculados ao lado — que pode ser vinculado, passado e guardado como qualquer valor, e hive.sql.run(connection, fragment) é o que o roda. Os valores são sempre vinculados como parâmetros, nunca emendados no texto, então nada que quem chama fornece pode mudar o que uma instrução significa: um {name} interpolado no corpo vira um marcador, ?, reescrito para $1, $2, … no PostgreSQL.

O tipo de retorno decide a forma do resultado:

  • um tipo de linha declarado, Row[dyn], volta como Result<Row[dyn], hive.sql.SqlError>, suas colunas casadas com os campos pelo nome — uma coluna escrita diferente precisa de um apelido, SELECT u.name AS author, e um tipo de linha só guarda escalares;
  • um único vetor escalar, Str[dyn] ou Int[dyn], volta como aquela coluna;
  • uma Table volta como uma linha de cabeçalho com os nomes das colunas, e depois toda linha como texto — o único resultado que SELECT * preenche, já que SELECT * contra um tipo de linha é erro de compilação: ele não diz nem quantas colunas voltam nem como se chamam;
  • void marca uma instrução, e volta como o número de linhas que ela tocou.

hive.sql.connect(driver, connString) e hive.sql.pool(driver, connString, maxOpen, maxIdle) abrem ambos um pool de conexões — pool só define os limites — respondendo com um Result<SqlConnection, SqlError>; guarde um pela vida do programa, e feche-o com close no final. Um driver é hive.sql.DatabaseDriver.SQLite(), .PostgreSQL() ou .Other(name); os dois primeiros vêm embutidos, e seus drivers são buscados uma vez, na primeira build que usa o módulo. SQL montado em tempo de execução passa por hive.sql.raw(connection, text), que também responde com uma Table. Um SqlError carrega um reason — "Connection", "Query", "Shape" para um número diferente de colunas, "Convert" para uma célula que não cabe no campo — e uma message.